agent_ui: Make input fields in Bedrock settings keyboard navigable (#42916)

Closes https://github.com/zed-industries/zed/issues/36587

This PR enables jumping from one input to the other, in the Bedrock
settings section, with tab.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal
2025-11-17 19:13:15 -03:00
committed by GitHub
parent 5f356d04ff
commit 98a83b47e6
3 changed files with 87 additions and 15 deletions
+59 -15
View File
@@ -24,7 +24,10 @@ use bedrock::{
use collections::{BTreeMap, HashMap};
use credentials_provider::CredentialsProvider;
use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
use gpui::{AnyView, App, AsyncApp, Context, Entity, FontWeight, Subscription, Task};
use gpui::{
AnyView, App, AsyncApp, Context, Entity, FocusHandle, FontWeight, Subscription, Task, Window,
actions,
};
use gpui_tokio::Tokio;
use http_client::HttpClient;
use language_model::{
@@ -47,6 +50,8 @@ use util::ResultExt;
use crate::AllLanguageModelSettings;
actions!(bedrock, [Tab, TabPrev]);
const PROVIDER_ID: LanguageModelProviderId = LanguageModelProviderId::new("amazon-bedrock");
const PROVIDER_NAME: LanguageModelProviderName = LanguageModelProviderName::new("Amazon Bedrock");
@@ -1012,6 +1017,7 @@ struct ConfigurationView {
region_editor: Entity<InputField>,
state: Entity<State>,
load_credentials_task: Option<Task<()>>,
focus_handle: FocusHandle,
}
impl ConfigurationView {
@@ -1022,11 +1028,41 @@ impl ConfigurationView {
const PLACEHOLDER_REGION: &'static str = "us-east-1";
fn new(state: Entity<State>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
cx.observe(&state, |_, _, cx| {
cx.notify();
})
.detach();
let access_key_id_editor = cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
.label("Access Key ID")
.tab_index(0)
.tab_stop(true)
});
let secret_access_key_editor = cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
.label("Secret Access Key")
.tab_index(1)
.tab_stop(true)
});
let session_token_editor = cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
.label("Session Token (Optional)")
.tab_index(2)
.tab_stop(true)
});
let region_editor = cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_REGION)
.label("Region")
.tab_index(3)
.tab_stop(true)
});
let load_credentials_task = Some(cx.spawn({
let state = state.clone();
async move |this, cx| {
@@ -1046,22 +1082,13 @@ impl ConfigurationView {
}));
Self {
access_key_id_editor: cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_ACCESS_KEY_ID_TEXT)
.label("Access Key ID")
}),
secret_access_key_editor: cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_SECRET_ACCESS_KEY_TEXT)
.label("Secret Access Key")
}),
session_token_editor: cx.new(|cx| {
InputField::new(window, cx, Self::PLACEHOLDER_SESSION_TOKEN_TEXT)
.label("Session Token (Optional)")
}),
region_editor: cx
.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_REGION).label("Region")),
access_key_id_editor,
secret_access_key_editor,
session_token_editor,
region_editor,
state,
load_credentials_task,
focus_handle,
}
}
@@ -1141,6 +1168,19 @@ impl ConfigurationView {
fn should_render_editor(&self, cx: &Context<Self>) -> bool {
self.state.read(cx).is_authenticated()
}
fn on_tab(&mut self, _: &menu::SelectNext, window: &mut Window, _: &mut Context<Self>) {
window.focus_next();
}
fn on_tab_prev(
&mut self,
_: &menu::SelectPrevious,
window: &mut Window,
_: &mut Context<Self>,
) {
window.focus_prev();
}
}
impl Render for ConfigurationView {
@@ -1190,6 +1230,9 @@ impl Render for ConfigurationView {
v_flex()
.size_full()
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_tab))
.on_action(cx.listener(Self::on_tab_prev))
.on_action(cx.listener(ConfigurationView::save_credentials))
.child(Label::new("To use Zed's agent with Bedrock, you can set a custom authentication strategy through the settings.json, or use static credentials."))
.child(Label::new("But, to access models on AWS, you need to:").mt_1())
@@ -1234,6 +1277,7 @@ impl ConfigurationView {
fn render_static_credentials_ui(&self) -> impl IntoElement {
v_flex()
.my_2()
.tab_group()
.gap_1p5()
.child(
Label::new("Static Keys")
+27
View File
@@ -37,6 +37,10 @@ pub struct InputField {
disabled: bool,
/// The minimum width of for the input
min_width: Length,
/// The tab index for keyboard navigation order.
tab_index: Option<isize>,
/// Whether this field is a tab stop (can be focused via Tab key).
tab_stop: bool,
}
impl Focusable for InputField {
@@ -63,6 +67,8 @@ impl InputField {
start_icon: None,
disabled: false,
min_width: px(192.).into(),
tab_index: None,
tab_stop: true,
}
}
@@ -86,6 +92,16 @@ impl InputField {
self
}
pub fn tab_index(mut self, index: isize) -> Self {
self.tab_index = Some(index);
self
}
pub fn tab_stop(mut self, tab_stop: bool) -> Self {
self.tab_stop = tab_stop;
self
}
pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
self.disabled = disabled;
self.editor
@@ -151,6 +167,16 @@ impl Render for InputField {
..Default::default()
};
let focus_handle = self.editor.focus_handle(cx);
let configured_handle = if let Some(tab_index) = self.tab_index {
focus_handle.tab_index(tab_index).tab_stop(self.tab_stop)
} else if !self.tab_stop {
focus_handle.tab_stop(false)
} else {
focus_handle
};
v_flex()
.id(self.placeholder.clone())
.w_full()
@@ -168,6 +194,7 @@ impl Render for InputField {
})
.child(
h_flex()
.track_focus(&configured_handle)
.min_w(self.min_width)
.min_h_8()
.w_full()
+1
View File
@@ -4702,6 +4702,7 @@ mod tests {
"assistant",
"assistant2",
"auto_update",
"bedrock",
"branches",
"buffer_search",
"channel_modal",