mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Merge branch 'agentydragon-03-live-config-reload' into agentydragon
# Conflicts: # agentydragon/tasks/03-live-config-reload.md
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
+++
|
||||
id = "03"
|
||||
title = "Live Config Reload and Prompt on Changes"
|
||||
status = "Not started"
|
||||
dependencies = "" # No prerequisites
|
||||
status = "Done"
|
||||
dependencies = "02,07,09,11,14,29"
|
||||
last_updated = "2025-06-25T01:40:09.504758"
|
||||
+++
|
||||
|
||||
@@ -12,8 +12,8 @@ last_updated = "2025-06-25T01:40:09.504758"
|
||||
|
||||
## Status
|
||||
|
||||
**General Status**: Not started
|
||||
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
|
||||
**General Status**: Done
|
||||
**Summary**: Live config watcher, diff prompt, and reload integration implemented.
|
||||
|
||||
## Goal
|
||||
Detect changes to the user `config.toml` file while a session is running and prompt the user to apply or ignore the updated settings.
|
||||
@@ -27,10 +27,16 @@ Detect changes to the user `config.toml` file while a session is running and pro
|
||||
## Implementation
|
||||
|
||||
**How it was implemented**
|
||||
*(Not implemented yet)*
|
||||
- Added `codex_tui::config_reload::generate_diff` to compute unified diffs via the `similar` crate (with a unit test).
|
||||
- Spawned a `notify`-based filesystem watcher thread in `tui::run_main` that debounces write events on `$CODEX_HOME/config.toml`, generates diffs against the last-read contents, and posts `AppEvent::ConfigReloadRequest(diff)`.
|
||||
- Introduced `AppEvent` variants (`ConfigReloadRequest`, `ConfigReloadApply`, `ConfigReloadIgnore`) and wired them in `App::run` to display a new `BottomPaneView` overlay.
|
||||
- Created `BottomPaneView` implementation `ConfigReloadView` to render the diff and handle `<Enter>`/`<Esc>` for apply or ignore.
|
||||
- On apply, reloaded `Config` via `Config::load_with_cli_overrides`, updated both `App.config` and `ChatWidget` (rebuilding its bottom pane with updated settings).
|
||||
|
||||
**How it works**
|
||||
*(Not implemented yet)*
|
||||
- The watcher thread detects on-disk changes and pushes a diff request into the UI event loop.
|
||||
- Upon `ConfigReloadRequest`, the TUI bottom pane overlays the diff view and blocks normal input.
|
||||
- `<Enter>` applies the new config (re-parses and updates runtime state); `<Esc>` dismisses the overlay and continues with the old settings.
|
||||
|
||||
## Notes
|
||||
- Leverage a crate such as `notify` for FS events and `similar` or `diff` for unified diff generation.
|
||||
|
||||
167
codex-rs/Cargo.lock
generated
167
codex-rs/Cargo.lock
generated
@@ -770,6 +770,7 @@ dependencies = [
|
||||
"image",
|
||||
"lazy_static",
|
||||
"mcp-types",
|
||||
"notify",
|
||||
"path-clean",
|
||||
"pretty_assertions",
|
||||
"ratatui",
|
||||
@@ -777,6 +778,7 @@ dependencies = [
|
||||
"regex-lite",
|
||||
"serde_json",
|
||||
"shlex",
|
||||
"similar",
|
||||
"strum 0.27.1",
|
||||
"strum_macros 0.27.1",
|
||||
"tempfile",
|
||||
@@ -929,7 +931,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
@@ -1369,6 +1371,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
@@ -1449,6 +1463,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
@@ -2064,6 +2087,26 @@ version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.7"
|
||||
@@ -2212,6 +2255,26 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.19.12"
|
||||
@@ -2290,6 +2353,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2475,6 +2539,18 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.3"
|
||||
@@ -2573,6 +2649,25 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio 0.8.11",
|
||||
"walkdir",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
@@ -3842,7 +3937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
@@ -4367,7 +4462,7 @@ dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"mio 1.0.3",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
@@ -5088,6 +5183,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -5106,6 +5210,21 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -5138,6 +5257,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5150,6 +5275,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5162,6 +5293,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5186,6 +5323,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
@@ -5198,6 +5341,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -5210,6 +5359,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
@@ -5222,6 +5377,12 @@ version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -55,6 +55,8 @@ tui-markdown = "0.3.3"
|
||||
tui-textarea = "0.7.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
uuid = "1"
|
||||
notify = "6"
|
||||
similar = "2"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::mouse_capture::MouseCapture;
|
||||
use crate::scroll_event_helper::ScrollEventHelper;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use crate::tui;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::{Config, ConfigOverrides};
|
||||
use codex_core::protocol::{Event, EventMsg, Op, SessionConfiguredEvent};
|
||||
use color_eyre::eyre::Result;
|
||||
use crossterm::event::KeyCode;
|
||||
@@ -321,6 +321,27 @@ impl<'a> App<'a> {
|
||||
}
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
AppEvent::ConfigReloadRequest(diff) => {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.push_config_reload(diff);
|
||||
}
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
AppEvent::ConfigReloadApply => {
|
||||
match Config::load_with_cli_overrides(Vec::new(), ConfigOverrides::default()) {
|
||||
Ok(new_cfg) => {
|
||||
self.config = new_cfg.clone();
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.update_config(new_cfg);
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::error!("Failed to reload config.toml: {e}"),
|
||||
}
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
AppEvent::ConfigReloadIgnore => {
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
AppEvent::KeyEvent(key_event) => {
|
||||
match key_event {
|
||||
KeyEvent {
|
||||
|
||||
@@ -44,4 +44,10 @@ pub(crate) enum AppEvent {
|
||||
MountRemove {
|
||||
container: std::path::PathBuf,
|
||||
},
|
||||
/// Notify that the on-disk config.toml has changed and present diff.
|
||||
ConfigReloadRequest(String),
|
||||
/// Apply the new on-disk config.toml.
|
||||
ConfigReloadApply,
|
||||
/// Ignore on-disk config.toml changes and continue with old config.
|
||||
ConfigReloadIgnore,
|
||||
}
|
||||
|
||||
60
codex-rs/tui/src/bottom_pane/config_reload_view.rs
Normal file
60
codex-rs/tui/src/bottom_pane/config_reload_view.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::{Block, Borders, BorderType, Paragraph};
|
||||
use ratatui::prelude::Widget;
|
||||
|
||||
use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use super::{BottomPane, BottomPaneView};
|
||||
|
||||
/// BottomPane view displaying the diff and prompting to apply or ignore.
|
||||
pub(crate) struct ConfigReloadView {
|
||||
diff: String,
|
||||
app_event_tx: AppEventSender,
|
||||
done: bool,
|
||||
}
|
||||
|
||||
impl ConfigReloadView {
|
||||
/// Create a new view with the unified diff of config changes.
|
||||
pub fn new(diff: String, app_event_tx: AppEventSender) -> Self {
|
||||
Self { diff, app_event_tx, done: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BottomPaneView<'a> for ConfigReloadView {
|
||||
fn handle_key_event(&mut self, pane: &mut BottomPane<'a>, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
KeyCode::Enter => {
|
||||
self.app_event_tx.send(AppEvent::ConfigReloadApply);
|
||||
self.done = true;
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
self.app_event_tx.send(AppEvent::ConfigReloadIgnore);
|
||||
self.done = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pane.request_redraw();
|
||||
}
|
||||
|
||||
fn is_complete(&self) -> bool {
|
||||
self.done
|
||||
}
|
||||
|
||||
fn calculate_required_height(&self, area: &Rect) -> u16 {
|
||||
area.height
|
||||
}
|
||||
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.title("Config changed (Enter=Apply Esc=Ignore)");
|
||||
Paragraph::new(self.diff.clone()).block(block).render(area, buf);
|
||||
}
|
||||
|
||||
fn should_hide_when_task_is_done(&mut self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ mod chat_composer;
|
||||
mod chat_composer_history;
|
||||
mod command_popup;
|
||||
mod status_indicator_view;
|
||||
mod config_reload_view;
|
||||
|
||||
pub(crate) use chat_composer::ChatComposer;
|
||||
pub(crate) use chat_composer::InputResult;
|
||||
@@ -25,6 +26,7 @@ pub(crate) use chat_composer::InputResult;
|
||||
use approval_modal_view::ApprovalModalView;
|
||||
use mount_view::{MountAddView, MountRemoveView};
|
||||
use status_indicator_view::StatusIndicatorView;
|
||||
use config_reload_view::ConfigReloadView;
|
||||
|
||||
/// Pane displayed in the lower half of the chat UI.
|
||||
pub(crate) struct BottomPane<'a> {
|
||||
@@ -166,6 +168,13 @@ impl BottomPane<'_> {
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Launch config reload diff prompt.
|
||||
pub fn push_config_reload(&mut self, diff: String) {
|
||||
let view = ConfigReloadView::new(diff, self.app_event_tx.clone());
|
||||
self.active_view = Some(Box::new(view));
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Called when the agent requests user approval.
|
||||
pub fn push_approval_request(&mut self, request: ApprovalRequest) {
|
||||
let request = if let Some(view) = self.active_view.as_mut() {
|
||||
@@ -271,6 +280,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn remove_status_indicator_after_task_complete() {
|
||||
mod config_reload_view;
|
||||
let mut pane = make_pane();
|
||||
pane.set_task_running(true);
|
||||
assert!(pane.active_view.is_some());
|
||||
|
||||
@@ -446,6 +446,21 @@ impl ChatWidget<'_> {
|
||||
self.bottom_pane.push_mount_remove_interactive();
|
||||
self.request_redraw();
|
||||
}
|
||||
/// Prompt the user with a config diff and ask to apply or ignore.
|
||||
pub fn push_config_reload(&mut self, diff: String) {
|
||||
self.bottom_pane.push_config_reload(diff);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Update the running config and reconstruct bottom pane settings.
|
||||
pub fn update_config(&mut self, config: Config) {
|
||||
self.config = config.clone();
|
||||
self.bottom_pane = BottomPane::new(BottomPaneParams {
|
||||
app_event_tx: self.app_event_tx.clone(),
|
||||
has_input_focus: true,
|
||||
composer_max_rows: config.tui.composer_max_rows,
|
||||
});
|
||||
}
|
||||
|
||||
fn request_redraw(&mut self) {
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
|
||||
22
codex-rs/tui/src/config_reload.rs
Normal file
22
codex-rs/tui/src/config_reload.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! Helpers for config reload diff generation.
|
||||
|
||||
/// Generate a unified diff between the old and new config contents.
|
||||
pub fn generate_diff(old: &str, new: &str) -> String {
|
||||
similar::TextDiff::from_lines(old, new)
|
||||
.unified_diff()
|
||||
.header("Current", "New")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::generate_diff;
|
||||
|
||||
#[test]
|
||||
fn diff_detects_line_change() {
|
||||
let old = "a\nb\nc\n";
|
||||
let new = "a\nx\nc\n";
|
||||
let diff = generate_diff(old, new);
|
||||
assert!(diff.contains("-b\n+x\n"), "Unexpected diff output: {}", diff);
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,7 @@ mod text_block;
|
||||
mod text_formatting;
|
||||
mod tui;
|
||||
mod user_approval_widget;
|
||||
mod config_reload;
|
||||
|
||||
pub use cli::Cli;
|
||||
|
||||
@@ -212,6 +213,43 @@ fn run_ratatui_app(
|
||||
});
|
||||
}
|
||||
|
||||
// Watch config.toml for changes and prompt reload.
|
||||
{
|
||||
let app_event_tx = app.event_sender();
|
||||
let config_path = config.codex_home.join("config.toml");
|
||||
std::thread::spawn(move || {
|
||||
use notify::{Watcher, RecursiveMode, RecommendedWatcher, EventKind};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher: RecommendedWatcher =
|
||||
Watcher::new(tx, notify::Config::default()).unwrap_or_else(|e| {
|
||||
tracing::error!("config watcher failed: {e}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
if watcher.watch(&config_path, RecursiveMode::NonRecursive).is_err() {
|
||||
tracing::error!("Failed to watch config.toml");
|
||||
return;
|
||||
}
|
||||
let mut last = std::fs::read_to_string(&config_path).unwrap_or_default();
|
||||
for res in rx {
|
||||
if let Ok(event) = res {
|
||||
if matches!(event.kind, EventKind::Modify(_)) {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let new = std::fs::read_to_string(&config_path).unwrap_or_default();
|
||||
if new != last {
|
||||
let diff = crate::config_reload::generate_diff(&last, &new);
|
||||
last = new.clone();
|
||||
app_event_tx.send(
|
||||
crate::app_event::AppEvent::ConfigReloadRequest(diff)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let app_result = app.run(&mut terminal, &mut mouse_capture);
|
||||
|
||||
restore();
|
||||
|
||||
Reference in New Issue
Block a user