mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] extract external agent import picker renderer (#27065)
## Why The external-agent import picker is easier to review when its rendering refactor lands separately from new state and interaction behavior. This layer is intended to be behavior-neutral. ## What changed - extract external-agent migration rendering into a dedicated `render` module - preserve existing behavior while separating presentation from interaction logic - establish a smaller foundation for the import picker UX in the next PR ## Validation - `just test -p codex-tui external_agent_config_migration` (10 passed) ## Stack 1. [#27064](https://github.com/openai/codex/pull/27064): remove the startup migration flow 2. [#27065](https://github.com/openai/codex/pull/27065): extract the picker renderer 3. [#27070](https://github.com/openai/codex/pull/27070): add the external-agent import picker UX 4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow through `/import` **This PR is stack item 2.** Draft while the lower stack dependency is reviewed.
This commit is contained in:
committed by
GitHub
Unverified
parent
ccf1a18518
commit
72667f4f41
@@ -1,9 +1,5 @@
|
||||
use crate::diff_render::display_path_for;
|
||||
use crate::key_hint;
|
||||
use crate::line_truncation::truncate_line_with_ellipsis_if_overflow;
|
||||
use crate::render::Insets;
|
||||
use crate::render::RectExt as _;
|
||||
use crate::selection_list::selection_option_row_with_dim;
|
||||
use crate::style::accent_style;
|
||||
use crate::tui::FrameRequester;
|
||||
use crate::tui::Tui;
|
||||
@@ -15,18 +11,14 @@ use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Constraint;
|
||||
use ratatui::layout::Layout;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Stylize as _;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::Clear;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use ratatui::widgets::Wrap;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
mod render;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ExternalAgentConfigMigrationOutcome {
|
||||
Proceed(Vec<ExternalAgentConfigMigrationItem>),
|
||||
@@ -633,124 +625,6 @@ impl ExternalAgentConfigMigrationScreen {
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &ExternalAgentConfigMigrationScreen {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Clear.render(area, buf);
|
||||
|
||||
let inner_area = area.inset(Insets::vh(/*v*/ 1, /*h*/ 2));
|
||||
let error_height = u16::from(self.error.is_some());
|
||||
let fixed_height = 1u16 + 2u16 + error_height + 1u16 + 4u16 + 1u16;
|
||||
let list_height =
|
||||
self.render_line_count()
|
||||
.max(1)
|
||||
.min(inner_area.height.saturating_sub(fixed_height) as usize) as u16;
|
||||
let [
|
||||
header_area,
|
||||
intro_area,
|
||||
error_area,
|
||||
list_area,
|
||||
list_gap_area,
|
||||
actions_area,
|
||||
footer_area,
|
||||
_spacer_area,
|
||||
] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(error_height),
|
||||
Constraint::Length(list_height),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(4),
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(inner_area);
|
||||
|
||||
let heading = Line::from(vec!["> ".into(), "External agent config detected".bold()]);
|
||||
heading.render(header_area, buf);
|
||||
|
||||
Paragraph::new(vec![
|
||||
Line::from("We found settings from another agent that you can add to this project."),
|
||||
Line::from("Select what to import"),
|
||||
])
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(intro_area, buf);
|
||||
|
||||
if let Some(error) = &self.error {
|
||||
Paragraph::new(error.clone().red().to_string())
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(error_area, buf);
|
||||
}
|
||||
|
||||
self.render_items(list_area, buf);
|
||||
Clear.render(list_gap_area, buf);
|
||||
|
||||
let [
|
||||
actions_intro_area,
|
||||
proceed_area,
|
||||
skip_area,
|
||||
skip_forever_area,
|
||||
] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.areas(actions_area);
|
||||
let actions_intro = format!(
|
||||
"Selected {} of {} item(s).",
|
||||
self.selected_count(),
|
||||
self.items.len()
|
||||
);
|
||||
Paragraph::new(actions_intro)
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(actions_intro_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 0,
|
||||
ActionMenuOption::Proceed.label().to_string(),
|
||||
self.focus == FocusArea::Actions
|
||||
&& self.highlighted_action == ActionMenuOption::Proceed,
|
||||
/*dim*/ self.focus != FocusArea::Actions || !self.proceed_enabled(),
|
||||
)
|
||||
.render(proceed_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 1,
|
||||
ActionMenuOption::Skip.label().to_string(),
|
||||
self.focus == FocusArea::Actions && self.highlighted_action == ActionMenuOption::Skip,
|
||||
/*dim*/ self.focus != FocusArea::Actions,
|
||||
)
|
||||
.render(skip_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 2,
|
||||
ActionMenuOption::SkipForever.label().to_string(),
|
||||
self.focus == FocusArea::Actions
|
||||
&& self.highlighted_action == ActionMenuOption::SkipForever,
|
||||
/*dim*/ self.focus != FocusArea::Actions,
|
||||
)
|
||||
.render(skip_forever_area, buf);
|
||||
|
||||
Line::from(vec![
|
||||
"Use ".dim(),
|
||||
key_hint::plain(KeyCode::Up).into(),
|
||||
"/".dim(),
|
||||
key_hint::plain(KeyCode::Down).into(),
|
||||
" to move, ".dim(),
|
||||
key_hint::plain(KeyCode::Char(' ')).into(),
|
||||
" to toggle, ".dim(),
|
||||
"1".cyan(),
|
||||
"/".dim(),
|
||||
"2".cyan(),
|
||||
"/".dim(),
|
||||
"3".cyan(),
|
||||
" to choose, ".dim(),
|
||||
"a".cyan(),
|
||||
"/".dim(),
|
||||
"n".cyan(),
|
||||
" for all/none".dim(),
|
||||
])
|
||||
.render(footer_area, buf);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ctrl_exit_combo(key_event: KeyEvent) -> bool {
|
||||
key_event.modifiers.contains(KeyModifiers::CONTROL)
|
||||
&& matches!(key_event.code, KeyCode::Char('c') | KeyCode::Char('d'))
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
use super::ActionMenuOption;
|
||||
use super::ExternalAgentConfigMigrationScreen;
|
||||
use super::FocusArea;
|
||||
use crate::key_hint;
|
||||
use crate::render::Insets;
|
||||
use crate::render::RectExt as _;
|
||||
use crate::selection_list::selection_option_row_with_dim;
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Constraint;
|
||||
use ratatui::layout::Layout;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::prelude::Stylize as _;
|
||||
use ratatui::text::Line;
|
||||
use ratatui::widgets::Clear;
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::widgets::Widget;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
use ratatui::widgets::Wrap;
|
||||
|
||||
impl WidgetRef for &ExternalAgentConfigMigrationScreen {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
Clear.render(area, buf);
|
||||
|
||||
let inner_area = area.inset(Insets::vh(/*v*/ 1, /*h*/ 2));
|
||||
let error_height = u16::from(self.error.is_some());
|
||||
let fixed_height = 1u16 + 2u16 + error_height + 1u16 + 4u16 + 1u16;
|
||||
let list_height =
|
||||
self.render_line_count()
|
||||
.max(1)
|
||||
.min(inner_area.height.saturating_sub(fixed_height) as usize) as u16;
|
||||
let [
|
||||
header_area,
|
||||
intro_area,
|
||||
error_area,
|
||||
list_area,
|
||||
list_gap_area,
|
||||
actions_area,
|
||||
footer_area,
|
||||
_spacer_area,
|
||||
] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(2),
|
||||
Constraint::Length(error_height),
|
||||
Constraint::Length(list_height),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(4),
|
||||
Constraint::Length(1),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.areas(inner_area);
|
||||
|
||||
let heading = Line::from(vec!["> ".into(), "External agent config detected".bold()]);
|
||||
heading.render(header_area, buf);
|
||||
|
||||
Paragraph::new(vec![
|
||||
Line::from("We found settings from another agent that you can add to this project."),
|
||||
Line::from("Select what to import"),
|
||||
])
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(intro_area, buf);
|
||||
|
||||
if let Some(error) = &self.error {
|
||||
Paragraph::new(error.clone().red().to_string())
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(error_area, buf);
|
||||
}
|
||||
|
||||
self.render_items(list_area, buf);
|
||||
Clear.render(list_gap_area, buf);
|
||||
|
||||
let [
|
||||
actions_intro_area,
|
||||
proceed_area,
|
||||
skip_area,
|
||||
skip_forever_area,
|
||||
] = Layout::vertical([
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.areas(actions_area);
|
||||
let actions_intro = format!(
|
||||
"Selected {} of {} item(s).",
|
||||
self.selected_count(),
|
||||
self.items.len()
|
||||
);
|
||||
Paragraph::new(actions_intro)
|
||||
.wrap(Wrap { trim: false })
|
||||
.render(actions_intro_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 0,
|
||||
ActionMenuOption::Proceed.label().to_string(),
|
||||
self.focus == FocusArea::Actions
|
||||
&& self.highlighted_action == ActionMenuOption::Proceed,
|
||||
/*dim*/ self.focus != FocusArea::Actions || !self.proceed_enabled(),
|
||||
)
|
||||
.render(proceed_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 1,
|
||||
ActionMenuOption::Skip.label().to_string(),
|
||||
self.focus == FocusArea::Actions && self.highlighted_action == ActionMenuOption::Skip,
|
||||
/*dim*/ self.focus != FocusArea::Actions,
|
||||
)
|
||||
.render(skip_area, buf);
|
||||
selection_option_row_with_dim(
|
||||
/*index*/ 2,
|
||||
ActionMenuOption::SkipForever.label().to_string(),
|
||||
self.focus == FocusArea::Actions
|
||||
&& self.highlighted_action == ActionMenuOption::SkipForever,
|
||||
/*dim*/ self.focus != FocusArea::Actions,
|
||||
)
|
||||
.render(skip_forever_area, buf);
|
||||
|
||||
Line::from(vec![
|
||||
"Use ".dim(),
|
||||
key_hint::plain(KeyCode::Up).into(),
|
||||
"/".dim(),
|
||||
key_hint::plain(KeyCode::Down).into(),
|
||||
" to move, ".dim(),
|
||||
key_hint::plain(KeyCode::Char(' ')).into(),
|
||||
" to toggle, ".dim(),
|
||||
"1".cyan(),
|
||||
"/".dim(),
|
||||
"2".cyan(),
|
||||
"/".dim(),
|
||||
"3".cyan(),
|
||||
" to choose, ".dim(),
|
||||
"a".cyan(),
|
||||
"/".dim(),
|
||||
"n".cyan(),
|
||||
" for all/none".dim(),
|
||||
])
|
||||
.render(footer_area, buf);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user