@@ -8,7 +8,7 @@ use collections::HashSet;
use fs::Fs;
use fuzzy::StringMatchCandidate;
use gpui::{
- BackgroundExecutor, Context, DismissEvent, Entity, Subscription, Task, Window, prelude::*,
+ App, BackgroundExecutor, Context, DismissEvent, Entity, Subscription, Task, Window, prelude::*,
};
use ordered_float::OrderedFloat;
use picker::popover_menu::PickerPopoverMenu;
@@ -67,6 +67,113 @@ impl ConfigOptionsView {
}
}
+ pub fn toggle_category_picker(
+ &mut self,
+ category: acp::SessionConfigOptionCategory,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> bool {
+ let Some(config_id) = self.first_config_option_id(category) else {
+ return false;
+ };
+
+ let Some(selector) = self.selector_for_config_id(&config_id, cx) else {
+ return false;
+ };
+
+ selector.update(cx, |selector, cx| {
+ selector.toggle_picker(window, cx);
+ });
+
+ true
+ }
+
+ pub fn cycle_category_option(
+ &mut self,
+ category: acp::SessionConfigOptionCategory,
+ favorites_only: bool,
+ cx: &mut Context<Self>,
+ ) -> bool {
+ let Some(config_id) = self.first_config_option_id(category) else {
+ return false;
+ };
+
+ let Some(next_value) = self.next_value_for_config(&config_id, favorites_only, cx) else {
+ return false;
+ };
+
+ let task = self
+ .config_options
+ .set_config_option(config_id, next_value, cx);
+
+ cx.spawn(async move |_, _| {
+ if let Err(err) = task.await {
+ log::error!("Failed to set config option: {:?}", err);
+ }
+ })
+ .detach();
+
+ true
+ }
+
+ fn first_config_option_id(
+ &self,
+ category: acp::SessionConfigOptionCategory,
+ ) -> Option<acp::SessionConfigId> {
+ self.config_options
+ .config_options()
+ .into_iter()
+ .find(|option| option.category.as_ref() == Some(&category))
+ .map(|option| option.id)
+ }
+
+ fn selector_for_config_id(
+ &self,
+ config_id: &acp::SessionConfigId,
+ cx: &App,
+ ) -> Option<Entity<ConfigOptionSelector>> {
+ self.selectors
+ .iter()
+ .find(|selector| selector.read(cx).config_id() == config_id)
+ .cloned()
+ }
+
+ fn next_value_for_config(
+ &self,
+ config_id: &acp::SessionConfigId,
+ favorites_only: bool,
+ cx: &mut Context<Self>,
+ ) -> Option<acp::SessionConfigValueId> {
+ let mut options = extract_options(&self.config_options, config_id);
+ if options.is_empty() {
+ return None;
+ }
+
+ if favorites_only {
+ let favorites = self
+ .agent_server
+ .favorite_config_option_value_ids(config_id, cx);
+ options.retain(|option| favorites.contains(&option.value));
+ if options.is_empty() {
+ return None;
+ }
+ }
+
+ let current_value = get_current_value(&self.config_options, config_id);
+ let current_index = current_value
+ .as_ref()
+ .and_then(|current| options.iter().position(|option| &option.value == current))
+ .unwrap_or(usize::MAX);
+
+ let next_index = if current_index == usize::MAX {
+ 0
+ } else {
+ (current_index + 1) % options.len()
+ };
+
+ Some(options[next_index].value.clone())
+ }
+
fn config_option_ids(
config_options: &Rc<dyn AgentSessionConfigOptions>,
) -> Vec<acp::SessionConfigId> {
@@ -206,6 +313,14 @@ impl ConfigOptionSelector {
.find(|opt| opt.id == self.config_id)
}
+ fn config_id(&self) -> &acp::SessionConfigId {
+ &self.config_id
+ }
+
+ fn toggle_picker(&self, window: &mut Window, cx: &mut Context<Self>) {
+ self.picker_handle.toggle(window, cx);
+ }
+
fn current_value_name(&self) -> String {
let Some(option) = self.current_option() else {
return "Unknown".to_string();
@@ -6953,6 +6953,19 @@ impl Render for AcpThreadView {
cx.notify();
}))
.on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
+ if let Some(config_options_view) = this.config_options_view.as_ref() {
+ let handled = config_options_view.update(cx, |view, cx| {
+ view.toggle_category_picker(
+ acp::SessionConfigOptionCategory::Mode,
+ window,
+ cx,
+ )
+ });
+ if handled {
+ return;
+ }
+ }
+
if let Some(profile_selector) = this.profile_selector.as_ref() {
profile_selector.read(cx).menu_handle().toggle(window, cx);
} else if let Some(mode_selector) = this.mode_selector() {
@@ -6960,6 +6973,19 @@ impl Render for AcpThreadView {
}
}))
.on_action(cx.listener(|this, _: &CycleModeSelector, window, cx| {
+ if let Some(config_options_view) = this.config_options_view.as_ref() {
+ let handled = config_options_view.update(cx, |view, cx| {
+ view.cycle_category_option(
+ acp::SessionConfigOptionCategory::Mode,
+ false,
+ cx,
+ )
+ });
+ if handled {
+ return;
+ }
+ }
+
if let Some(profile_selector) = this.profile_selector.as_ref() {
profile_selector.update(cx, |profile_selector, cx| {
profile_selector.cycle_profile(cx);
@@ -6971,12 +6997,38 @@ impl Render for AcpThreadView {
}
}))
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
+ if let Some(config_options_view) = this.config_options_view.as_ref() {
+ let handled = config_options_view.update(cx, |view, cx| {
+ view.toggle_category_picker(
+ acp::SessionConfigOptionCategory::Model,
+ window,
+ cx,
+ )
+ });
+ if handled {
+ return;
+ }
+ }
+
if let Some(model_selector) = this.model_selector.as_ref() {
model_selector
.update(cx, |model_selector, cx| model_selector.toggle(window, cx));
}
}))
.on_action(cx.listener(|this, _: &CycleFavoriteModels, window, cx| {
+ if let Some(config_options_view) = this.config_options_view.as_ref() {
+ let handled = config_options_view.update(cx, |view, cx| {
+ view.cycle_category_option(
+ acp::SessionConfigOptionCategory::Model,
+ true,
+ cx,
+ )
+ });
+ if handled {
+ return;
+ }
+ }
+
if let Some(model_selector) = this.model_selector.as_ref() {
model_selector.update(cx, |model_selector, cx| {
model_selector.cycle_favorite_models(window, cx);