mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
66e13efd9c
Problem: The TUI still depended on `codex-core` directly in a number of places, and we had no enforcement from keeping this problem from getting worse. Solution: Route TUI core access through `codex-app-server-client::legacy_core`, add CI enforcement for that boundary, and re-export this legacy bridge inside the TUI as `crate::legacy_core` so the remaining call sites stay readable. There is no functional change in this PR — just changes to import targets. Over time, we can whittle away at the remaining symbols in this legacy namespace with the eventual goal of removing them all. In the meantime, this linter rule will prevent us from inadvertently importing new symbols from core.
226 lines
7.1 KiB
Rust
226 lines
7.1 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use crate::legacy_core::config::set_project_trust_level;
|
|
use codex_git_utils::resolve_root_git_project_for_trust;
|
|
use codex_protocol::config_types::TrustLevel;
|
|
use crossterm::event::KeyCode;
|
|
use crossterm::event::KeyEvent;
|
|
use crossterm::event::KeyEventKind;
|
|
use ratatui::buffer::Buffer;
|
|
use ratatui::layout::Rect;
|
|
use ratatui::style::Stylize;
|
|
use ratatui::text::Line;
|
|
use ratatui::widgets::Paragraph;
|
|
use ratatui::widgets::WidgetRef;
|
|
use ratatui::widgets::Wrap;
|
|
|
|
use crate::key_hint;
|
|
use crate::onboarding::onboarding_screen::KeyboardHandler;
|
|
use crate::onboarding::onboarding_screen::StepStateProvider;
|
|
use crate::render::Insets;
|
|
use crate::render::renderable::ColumnRenderable;
|
|
use crate::render::renderable::Renderable;
|
|
use crate::render::renderable::RenderableExt as _;
|
|
use crate::selection_list::selection_option_row;
|
|
|
|
use super::onboarding_screen::StepState;
|
|
pub(crate) struct TrustDirectoryWidget {
|
|
pub codex_home: PathBuf,
|
|
pub cwd: PathBuf,
|
|
pub show_windows_create_sandbox_hint: bool,
|
|
pub should_quit: bool,
|
|
pub selection: Option<TrustDirectorySelection>,
|
|
pub highlighted: TrustDirectorySelection,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum TrustDirectorySelection {
|
|
Trust,
|
|
Quit,
|
|
}
|
|
|
|
impl WidgetRef for &TrustDirectoryWidget {
|
|
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
|
let mut column = ColumnRenderable::new();
|
|
|
|
column.push(Line::from(vec![
|
|
"> ".into(),
|
|
"You are in ".bold(),
|
|
self.cwd.to_string_lossy().to_string().into(),
|
|
]));
|
|
column.push("");
|
|
|
|
column.push(
|
|
Paragraph::new(
|
|
"Do you trust the contents of this directory? Working with untrusted contents comes with higher risk of prompt injection.".to_string(),
|
|
)
|
|
.wrap(Wrap { trim: true })
|
|
.inset(Insets::tlbr(/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0)),
|
|
);
|
|
column.push("");
|
|
|
|
let options: Vec<(&str, TrustDirectorySelection)> = vec![
|
|
("Yes, continue", TrustDirectorySelection::Trust),
|
|
("No, quit", TrustDirectorySelection::Quit),
|
|
];
|
|
|
|
for (idx, (text, selection)) in options.iter().enumerate() {
|
|
column.push(selection_option_row(
|
|
idx,
|
|
text.to_string(),
|
|
self.highlighted == *selection,
|
|
));
|
|
}
|
|
|
|
column.push("");
|
|
|
|
if let Some(error) = &self.error {
|
|
column.push(
|
|
Paragraph::new(error.to_string())
|
|
.red()
|
|
.wrap(Wrap { trim: true })
|
|
.inset(Insets::tlbr(
|
|
/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0,
|
|
)),
|
|
);
|
|
column.push("");
|
|
}
|
|
|
|
column.push(
|
|
Line::from(vec![
|
|
"Press ".dim(),
|
|
key_hint::plain(KeyCode::Enter).into(),
|
|
if self.show_windows_create_sandbox_hint {
|
|
" to continue and create a sandbox...".dim()
|
|
} else {
|
|
" to continue".dim()
|
|
},
|
|
])
|
|
.inset(Insets::tlbr(
|
|
/*top*/ 0, /*left*/ 2, /*bottom*/ 0, /*right*/ 0,
|
|
)),
|
|
);
|
|
|
|
column.render(area, buf);
|
|
}
|
|
}
|
|
|
|
impl KeyboardHandler for TrustDirectoryWidget {
|
|
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
|
if key_event.kind == KeyEventKind::Release {
|
|
return;
|
|
}
|
|
|
|
match key_event.code {
|
|
KeyCode::Up | KeyCode::Char('k') => {
|
|
self.highlighted = TrustDirectorySelection::Trust;
|
|
}
|
|
KeyCode::Down | KeyCode::Char('j') => {
|
|
self.highlighted = TrustDirectorySelection::Quit;
|
|
}
|
|
KeyCode::Char('1') | KeyCode::Char('y') => self.handle_trust(),
|
|
KeyCode::Char('2') | KeyCode::Char('n') => self.handle_quit(),
|
|
KeyCode::Enter => match self.highlighted {
|
|
TrustDirectorySelection::Trust => self.handle_trust(),
|
|
TrustDirectorySelection::Quit => self.handle_quit(),
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl StepStateProvider for TrustDirectoryWidget {
|
|
fn get_step_state(&self) -> StepState {
|
|
if self.selection.is_some() || self.should_quit {
|
|
StepState::Complete
|
|
} else {
|
|
StepState::InProgress
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TrustDirectoryWidget {
|
|
fn handle_trust(&mut self) {
|
|
let target =
|
|
resolve_root_git_project_for_trust(&self.cwd).unwrap_or_else(|| self.cwd.clone());
|
|
if let Err(e) = set_project_trust_level(&self.codex_home, &target, TrustLevel::Trusted) {
|
|
tracing::error!("Failed to set project trusted: {e:?}");
|
|
self.error = Some(format!("Failed to set trust for {}: {e}", target.display()));
|
|
}
|
|
|
|
self.selection = Some(TrustDirectorySelection::Trust);
|
|
}
|
|
|
|
fn handle_quit(&mut self) {
|
|
self.highlighted = TrustDirectorySelection::Quit;
|
|
self.should_quit = true;
|
|
}
|
|
|
|
pub fn should_quit(&self) -> bool {
|
|
self.should_quit
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::test_backend::VT100Backend;
|
|
|
|
use super::*;
|
|
use crossterm::event::KeyCode;
|
|
use crossterm::event::KeyEvent;
|
|
use crossterm::event::KeyEventKind;
|
|
use crossterm::event::KeyModifiers;
|
|
use pretty_assertions::assert_eq;
|
|
use ratatui::Terminal;
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn release_event_does_not_change_selection() {
|
|
let codex_home = TempDir::new().expect("temp home");
|
|
let mut widget = TrustDirectoryWidget {
|
|
codex_home: codex_home.path().to_path_buf(),
|
|
cwd: PathBuf::from("."),
|
|
show_windows_create_sandbox_hint: false,
|
|
should_quit: false,
|
|
selection: None,
|
|
highlighted: TrustDirectorySelection::Quit,
|
|
error: None,
|
|
};
|
|
|
|
let release = KeyEvent {
|
|
kind: KeyEventKind::Release,
|
|
..KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)
|
|
};
|
|
widget.handle_key_event(release);
|
|
assert_eq!(widget.selection, None);
|
|
|
|
let press = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
|
|
widget.handle_key_event(press);
|
|
assert!(widget.should_quit);
|
|
}
|
|
|
|
#[test]
|
|
fn renders_snapshot_for_git_repo() {
|
|
let codex_home = TempDir::new().expect("temp home");
|
|
let widget = TrustDirectoryWidget {
|
|
codex_home: codex_home.path().to_path_buf(),
|
|
cwd: PathBuf::from("/workspace/project"),
|
|
show_windows_create_sandbox_hint: false,
|
|
should_quit: false,
|
|
selection: None,
|
|
highlighted: TrustDirectorySelection::Trust,
|
|
error: None,
|
|
};
|
|
|
|
let mut terminal =
|
|
Terminal::new(VT100Backend::new(/*width*/ 70, /*height*/ 14)).expect("terminal");
|
|
terminal
|
|
.draw(|f| (&widget).render_ref(f.area(), f.buffer_mut()))
|
|
.expect("draw");
|
|
|
|
insta::assert_snapshot!(terminal.backend());
|
|
}
|
|
}
|