@@ -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")
@@ -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()