code cleaning

This commit is contained in:
pap
2025-08-01 19:56:32 +01:00
parent c9b80cd456
commit c03e8fc860
6 changed files with 46 additions and 54 deletions

View File

@@ -5,7 +5,6 @@ use crate::file_search::FileSearchManager;
use crate::get_git_diff::get_git_diff;
use crate::git_warning_screen::GitWarningOutcome;
use crate::git_warning_screen::GitWarningScreen;
use crate::slash_command::Command;
use crate::tui;
use codex_core::config::Config;
use codex_core::protocol::Event;
@@ -29,6 +28,8 @@ use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;
use crate::slash_command::SlashCommand;
/// Time window for debouncing redraw requests.
const REDRAW_DEBOUNCE: Duration = Duration::from_millis(10);
@@ -340,7 +341,7 @@ impl App<'_> {
AppState::GitWarning { .. } => {}
},
AppEvent::DispatchCommand(command) => match command {
Command::New => {
SlashCommand::New => {
let new_widget = Box::new(ChatWidget::new(
self.config.clone(),
self.app_event_tx.clone(),
@@ -351,16 +352,16 @@ impl App<'_> {
self.app_state = AppState::Chat { widget: new_widget };
self.app_event_tx.send(AppEvent::RequestRedraw);
}
Command::Compact => {
SlashCommand::Compact => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.clear_token_usage();
self.app_event_tx.send(AppEvent::CodexOp(Op::Compact));
}
}
Command::Quit => {
SlashCommand::Quit => {
break;
}
Command::Diff => {
SlashCommand::Diff => {
let (is_git_repo, diff_text) = match get_git_diff() {
Ok(v) => v,
Err(e) => {
@@ -382,7 +383,7 @@ impl App<'_> {
}
}
#[cfg(debug_assertions)]
Command::TestApproval => {
SlashCommand::TestApproval => {
use std::collections::HashMap;
use codex_core::protocol::ApplyPatchApprovalRequestEvent;

View File

@@ -3,7 +3,7 @@ use codex_file_search::FileMatch;
use crossterm::event::KeyEvent;
use ratatui::text::Line;
use crate::slash_command::Command;
use crate::slash_command::SlashCommand;
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
@@ -33,7 +33,7 @@ pub(crate) enum AppEvent {
/// Dispatch a recognized slash command from the UI (composer) to the app
/// layer so it can be handled centrally.
DispatchCommand(Command),
DispatchCommand(SlashCommand),
/// Kick off an asynchronous file search for the given query (text after
/// the `@`). Previous searches may be cancelled by the app layer so there

View File

@@ -19,7 +19,6 @@ use tui_textarea::TextArea;
use super::chat_composer_history::ChatComposerHistory;
use super::command_popup::CommandPopup;
use super::file_search_popup::FileSearchPopup;
use crate::slash_command::Command;
use crate::app_event::AppEvent;
use crate::app_event_sender::AppEventSender;
@@ -56,7 +55,7 @@ pub(crate) struct ChatComposer<'a> {
/// Popup state at most one can be visible at any time.
enum ActivePopup {
None,
Slash(CommandPopup<Command>),
Slash(CommandPopup),
File(FileSearchPopup),
}

View File

@@ -12,38 +12,24 @@ use ratatui::widgets::Table;
use ratatui::widgets::Widget;
use ratatui::widgets::WidgetRef;
use crate::slash_command::Command;
use crate::slash_command::SlashCommand;
use crate::slash_command::built_in_slash_commands;
pub trait CommandInfo: Copy {
fn command(&self) -> &'static str;
fn description(&self) -> &'static str;
}
impl CommandInfo for Command {
fn command(&self) -> &'static str {
Command::command(*self)
}
fn description(&self) -> &'static str {
Command::description(*self)
}
}
const MAX_POPUP_ROWS: usize = 5;
/// Ideally this is enough to show the longest command name.
const FIRST_COLUMN_WIDTH: u16 = 20;
use ratatui::style::Modifier;
pub(crate) struct CommandPopup<C: CommandInfo> {
pub(crate) struct CommandPopup {
prefix: char,
command_filter: String,
all_commands: Vec<(&'static str, C)>,
all_commands: Vec<(&'static str, SlashCommand)>,
selected_idx: Option<usize>,
}
impl<C: CommandInfo> CommandPopup<C> {
pub(crate) fn new(prefix: char, all_commands: Vec<(&'static str, C)>) -> Self {
impl CommandPopup {
pub(crate) fn new(prefix: char, all_commands: Vec<(&'static str, SlashCommand)>) -> Self {
Self {
prefix,
command_filter: String::new(),
@@ -53,17 +39,25 @@ impl<C: CommandInfo> CommandPopup<C> {
}
/// Update the filter string based on the current composer text. The text
/// passed in is expected to start with this popup's prefix (e.g. '/' or '@').
/// Everything after the prefix up to the first ASCII whitespace becomes
/// the active filter that is used to narrow down the list of available commands.
/// passed in is expected to start with a leading '/'. Everything after the
/// *first* '/' on the *first* line becomes the active filter that is used
/// to narrow down the list of available commands.
pub(crate) fn on_composer_text_change(&mut self, text: String) {
let first_line = text.lines().next().unwrap_or("");
if first_line.starts_with(self.prefix) {
let stripped = &first_line[self.prefix.len_utf8()..];
if let Some(stripped) = first_line.strip_prefix('/') {
// Extract the *first* token (sequence of non-whitespace
// characters) after the slash so that `/clear something` still
// shows the help for `/clear`.
let token = stripped.trim_start();
let cmd_token = token.split_whitespace().next().unwrap_or("");
// Update the filter keeping the original case (commands are all
// lower-case for now but this may change in the future).
self.command_filter = cmd_token.to_string();
} else {
// The composer no longer starts with '/'. Reset the filter so the
// popup shows the *full* command list if it is still displayed
// for some reason.
self.command_filter.clear();
}
@@ -84,7 +78,7 @@ impl<C: CommandInfo> CommandPopup<C> {
/// Return the list of commands that match the current filter. Matching is
/// performed using a *prefix* comparison on the command name.
fn filtered_commands(&self) -> Vec<&C> {
fn filtered_commands(&self) -> Vec<&SlashCommand> {
self.all_commands
.iter()
.filter_map(|(_name, cmd)| {
@@ -98,7 +92,7 @@ impl<C: CommandInfo> CommandPopup<C> {
None
}
})
.collect::<Vec<&C>>()
.collect::<Vec<&SlashCommand>>()
}
/// Move the selection cursor one step up.
@@ -138,24 +132,25 @@ impl<C: CommandInfo> CommandPopup<C> {
}
/// Return currently selected command, if any.
pub(crate) fn selected_command(&self) -> Option<&C> {
pub(crate) fn selected_command(&self) -> Option<&SlashCommand> {
let matches = self.filtered_commands();
self.selected_idx.and_then(|idx| matches.get(idx).copied())
}
}
impl CommandPopup<Command> {
impl CommandPopup {
pub(crate) fn slash() -> Self {
CommandPopup::new('/', built_in_slash_commands())
}
}
impl<C: CommandInfo> WidgetRef for CommandPopup<C> {
impl WidgetRef for CommandPopup {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
let matches = self.filtered_commands();
let mut rows: Vec<Row> = Vec::new();
let visible_matches: Vec<&C> = matches.into_iter().take(MAX_POPUP_ROWS).collect();
let visible_matches: Vec<&SlashCommand> =
matches.into_iter().take(MAX_POPUP_ROWS).collect();
if visible_matches.is_empty() {
rows.push(Row::new(vec![

View File

@@ -532,11 +532,6 @@ impl ChatWidget<'_> {
&self.token_usage
}
// pub(crate) fn add_background_event(&mut self, msg: String) {
// self.add_to_history(HistoryCell::new_background_event(msg));
// self.request_redraw();
// }
pub(crate) fn clear_token_usage(&mut self) {
self.token_usage = TokenUsage::default();
self.bottom_pane

View File

@@ -9,7 +9,7 @@ use strum_macros::IntoStaticStr;
Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, EnumIter, AsRefStr, IntoStaticStr,
)]
#[strum(serialize_all = "kebab-case")]
pub enum Command {
pub enum SlashCommand {
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
// more frequently used commands should be listed first.
New,
@@ -20,16 +20,18 @@ pub enum Command {
TestApproval,
}
impl Command {
impl SlashCommand {
/// User-visible description shown in the popup.
pub fn description(self) -> &'static str {
match self {
Command::New => "Start a new chat.",
Command::Compact => "Compact the chat history.",
Command::Quit => "Exit the application.",
Command::Diff => "Show git diff of the working directory (including untracked files)",
SlashCommand::New => "Start a new chat.",
SlashCommand::Compact => "Compact the chat history.",
SlashCommand::Quit => "Exit the application.",
SlashCommand::Diff => {
"Show git diff of the working directory (including untracked files)"
}
#[cfg(debug_assertions)]
Command::TestApproval => "Test approval request",
SlashCommand::TestApproval => "Test approval request",
}
}
@@ -41,6 +43,6 @@ impl Command {
}
/// Return all built-in commands in a Vec paired with their command string.
pub fn built_in_slash_commands() -> Vec<(&'static str, Command)> {
Command::iter().map(|c| (c.command(), c)).collect()
pub fn built_in_slash_commands() -> Vec<(&'static str, SlashCommand)> {
SlashCommand::iter().map(|c| (c.command(), c)).collect()
}