Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions codex-rs/tui/src/app/background_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use super::*;
use codex_app_server_protocol::MarketplaceAddParams;
use codex_app_server_protocol::MarketplaceAddResponse;
use codex_app_server_protocol::MarketplaceRemoveParams;
use codex_app_server_protocol::MarketplaceRemoveResponse;
use codex_utils_absolute_path::AbsolutePathBuf;

impl App {
Expand Down Expand Up @@ -130,6 +132,30 @@ impl App {
});
}

pub(super) fn fetch_marketplace_remove(
&mut self,
app_server: &AppServerSession,
cwd: PathBuf,
marketplace_name: String,
marketplace_display_name: String,
) {
let request_handle = app_server.request_handle();
let app_event_tx = self.app_event_tx.clone();
tokio::spawn(async move {
let cwd_for_event = cwd.clone();
let marketplace_name_for_event = marketplace_name.clone();
let result = fetch_marketplace_remove(request_handle, marketplace_name)
.await
.map_err(|err| format!("Failed to remove marketplace: {err}"));
app_event_tx.send(AppEvent::MarketplaceRemoveLoaded {
cwd: cwd_for_event,
marketplace_name: marketplace_name_for_event,
marketplace_display_name,
result,
});
});
}

pub(super) fn fetch_plugin_install(
&mut self,
app_server: &AppServerSession,
Expand Down Expand Up @@ -582,6 +608,19 @@ fn marketplace_add_source_for_request(cwd: &std::path::Path, source: String) ->
source
}

pub(super) async fn fetch_marketplace_remove(
request_handle: AppServerRequestHandle,
marketplace_name: String,
) -> Result<MarketplaceRemoveResponse> {
let request_id = RequestId::String(format!("marketplace-remove-{}", Uuid::new_v4()));
request_handle
.request_typed(ClientRequest::MarketplaceRemove {
request_id,
params: MarketplaceRemoveParams { marketplace_name },
})
.await
.wrap_err("marketplace/remove failed in TUI")
}
pub(super) async fn fetch_plugin_install(
request_handle: AppServerRequestHandle,
marketplace_path: AbsolutePathBuf,
Expand Down
53 changes: 53 additions & 0 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,21 @@ impl App {
AppEvent::OpenMarketplaceAddLoading { source } => {
self.chat_widget.open_marketplace_add_loading_popup(&source);
}
AppEvent::OpenMarketplaceRemoveConfirm {
marketplace_name,
marketplace_display_name,
} => {
self.chat_widget.open_marketplace_remove_confirmation(
marketplace_name,
marketplace_display_name,
);
}
AppEvent::OpenMarketplaceRemoveLoading {
marketplace_display_name,
} => {
self.chat_widget
.open_marketplace_remove_loading_popup(&marketplace_display_name);
}
AppEvent::OpenPluginDetailLoading {
plugin_display_name,
} => {
Expand Down Expand Up @@ -423,6 +438,44 @@ impl App {
self.chat_widget
.on_marketplace_add_loaded(cwd.clone(), source, result);
if add_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path() {
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
tracing::warn!(error = %err, "failed to refresh config after marketplace add");
}
self.fetch_plugins_list(app_server, cwd);
Comment thread
canvrno-oai marked this conversation as resolved.
}
}
AppEvent::FetchMarketplaceRemove {
cwd,
marketplace_name,
marketplace_display_name,
} => {
self.fetch_marketplace_remove(
app_server,
cwd,
marketplace_name,
marketplace_display_name,
);
}
AppEvent::MarketplaceRemoveLoaded {
cwd,
marketplace_name,
marketplace_display_name,
result,
} => {
let remove_succeeded = result.is_ok();
self.chat_widget.on_marketplace_remove_loaded(
cwd.clone(),
marketplace_name,
marketplace_display_name,
result,
);
if remove_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
{
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
tracing::warn!(error = %err, "failed to refresh config after marketplace remove");
}
self.chat_widget.refresh_plugin_mentions();
self.chat_widget.submit_op(AppCommand::reload_user_config());
self.fetch_plugins_list(app_server, cwd);
Comment thread
canvrno-oai marked this conversation as resolved.
}
}
Expand Down
27 changes: 27 additions & 0 deletions codex-rs/tui/src/app_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use codex_app_server_protocol::AddCreditsNudgeCreditType;
use codex_app_server_protocol::AddCreditsNudgeEmailStatus;
use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::MarketplaceAddResponse;
use codex_app_server_protocol::MarketplaceRemoveResponse;
use codex_app_server_protocol::McpServerStatus;
use codex_app_server_protocol::McpServerStatusDetail;
use codex_app_server_protocol::PluginInstallResponse;
Expand Down Expand Up @@ -309,6 +310,32 @@ pub(crate) enum AppEvent {
result: Result<MarketplaceAddResponse, String>,
},

/// Open the confirmation prompt for removing a marketplace.
OpenMarketplaceRemoveConfirm {
marketplace_name: String,
marketplace_display_name: String,
},

/// Replace the plugins popup with a marketplace-remove loading state.
OpenMarketplaceRemoveLoading {
marketplace_display_name: String,
},

/// Remove a marketplace by name.
FetchMarketplaceRemove {
cwd: PathBuf,
marketplace_name: String,
marketplace_display_name: String,
},

/// Result of removing a marketplace.
MarketplaceRemoveLoaded {
cwd: PathBuf,
marketplace_name: String,
marketplace_display_name: String,
result: Result<MarketplaceRemoveResponse, String>,
},

/// Replace the plugins popup with a plugin-detail loading state.
OpenPluginDetailLoading {
plugin_display_name: String,
Expand Down
26 changes: 22 additions & 4 deletions codex-rs/tui/src/bottom_pane/list_selection_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ pub(crate) struct SelectionViewParams {
pub subtitle: Option<String>,
pub footer_note: Option<Line<'static>>,
pub footer_hint: Option<Line<'static>>,
pub tab_footer_hints: Vec<(String, Line<'static>)>,
pub items: Vec<SelectionItem>,
pub tabs: Vec<SelectionTab>,
pub initial_tab_id: Option<String>,
Expand Down Expand Up @@ -209,6 +210,7 @@ impl Default for SelectionViewParams {
subtitle: None,
footer_note: None,
footer_hint: None,
tab_footer_hints: Vec::new(),
items: Vec::new(),
tabs: Vec::new(),
initial_tab_id: None,
Expand Down Expand Up @@ -239,6 +241,7 @@ pub(crate) struct ListSelectionView {
view_id: Option<&'static str>,
footer_note: Option<Line<'static>>,
footer_hint: Option<Line<'static>>,
tab_footer_hints: Vec<(String, Line<'static>)>,
items: Vec<SelectionItem>,
tabs: Vec<SelectionTab>,
active_tab_idx: Option<usize>,
Expand Down Expand Up @@ -309,6 +312,7 @@ impl ListSelectionView {
view_id: params.view_id,
footer_note: params.footer_note,
footer_hint: params.footer_hint,
tab_footer_hints: params.tab_footer_hints,
items: params.items,
tabs: params.tabs,
active_tab_idx,
Expand Down Expand Up @@ -377,6 +381,16 @@ impl ListSelectionView {
.unwrap_or(self.header.as_ref())
}

fn active_footer_hint(&self) -> Option<&Line<'static>> {
self.active_tab_id()
.and_then(|active_tab_id| {
self.tab_footer_hints
.iter()
.find_map(|(tab_id, hint)| (tab_id.as_str() == active_tab_id).then_some(hint))
})
.or(self.footer_hint.as_ref())
}

fn active_tab_id(&self) -> Option<&str> {
self.active_tab_idx
.and_then(|idx| self.tabs.get(idx))
Expand Down Expand Up @@ -1001,7 +1015,7 @@ impl Renderable for ListSelectionView {
let note_lines = wrap_styled_line(note, note_width);
height = height.saturating_add(note_lines.len() as u16);
}
if self.footer_hint.is_some() {
if self.active_footer_hint().is_some() {
height = height.saturating_add(1);
}
height
Expand All @@ -1018,7 +1032,7 @@ impl Renderable for ListSelectionView {
.as_ref()
.map(|note| wrap_styled_line(note, note_width));
let note_height = note_lines.as_ref().map_or(0, |lines| lines.len() as u16);
let footer_rows = note_height + u16::from(self.footer_hint.is_some());
let footer_rows = note_height + u16::from(self.active_footer_hint().is_some());
let [content_area, footer_area] =
Layout::vertical([Constraint::Fill(1), Constraint::Length(footer_rows)]).areas(area);

Expand Down Expand Up @@ -1196,7 +1210,11 @@ impl Renderable for ListSelectionView {
if footer_area.height > 0 {
let [note_area, hint_area] = Layout::vertical([
Constraint::Length(note_height),
Constraint::Length(if self.footer_hint.is_some() { 1 } else { 0 }),
Constraint::Length(if self.active_footer_hint().is_some() {
1
} else {
0
}),
])
.areas(footer_area);

Expand All @@ -1221,7 +1239,7 @@ impl Renderable for ListSelectionView {
}
}

if let Some(hint) = &self.footer_hint {
if let Some(hint) = self.active_footer_hint() {
let hint_area = Rect {
x: hint_area.x + 2,
y: hint_area.y,
Expand Down
5 changes: 5 additions & 0 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5478,6 +5478,7 @@ impl ChatWidget {
..
} if modifiers.contains(KeyModifiers::CONTROL) && c.eq_ignore_ascii_case(&'c')
)
&& !key_hint::ctrl(KeyCode::Char('r')).is_press(key_event)
{
self.bottom_pane.handle_key_event(key_event);
if self.bottom_pane.no_modal_or_popup_active() {
Expand Down Expand Up @@ -5595,6 +5596,10 @@ impl ChatWidget {
return;
}

if self.handle_plugins_popup_key_event(key_event) {
return;
}

match key_event {
KeyEvent {
code: KeyCode::BackTab,
Expand Down
Loading
Loading