Compare commits

...

4 Commits

Author SHA1 Message Date
jimmyfraiture
984e931582 More 2 2025-09-22 13:57:35 +01:00
jimmyfraiture
def129c482 More 2025-09-22 13:54:50 +01:00
jimmyfraiture
aa8b4bed98 V3 2025-09-22 13:49:11 +01:00
jimmyfraiture
3b526789b7 chore: clippy-2 2025-09-22 13:21:21 +01:00
81 changed files with 672 additions and 608 deletions

View File

@@ -37,6 +37,38 @@ expect_used = "deny"
redundant_clone = "deny"
uninlined_format_args = "deny"
unwrap_used = "deny"
manual_clamp = "deny"
manual_filter = "deny"
manual_find = "deny"
manual_flatten = "deny"
manual_map = "deny"
manual_memcpy = "deny"
manual_non_exhaustive = "deny"
manual_ok_or = "deny"
manual_range_contains = "deny"
manual_retain = "deny"
manual_strip = "deny"
manual_try_fold = "deny"
manual_unwrap_or = "deny"
redundant_closure = "deny"
redundant_closure_for_method_calls = "deny"
redundant_pattern_matching = "deny"
redundant_static_lifetimes = "deny"
redundant_else = "deny"
needless_borrow = "deny"
needless_borrowed_reference = "deny"
needless_collect = "deny"
needless_option_as_deref = "deny"
needless_update = "deny"
needless_pass_by_value = "deny"
needless_late_init = "deny"
needless_question_mark = "deny"
trivially_copy_pass_by_ref = "deny"
identity_op = "deny"
unnecessary_lazy_evaluations = "deny"
unnecessary_sort_by = "deny"
unnecessary_filter_map = "deny"
unnecessary_to_owned = "deny"
[profile.release]
lto = "fat"

View File

@@ -650,19 +650,22 @@ fn derive_new_contents_from_chunks(
let mut original_lines: Vec<String> = original_contents
.split('\n')
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect();
// Drop the trailing empty element that results from the final newline so
// that line counts match the behaviour of standard `diff`.
if original_lines.last().is_some_and(|s| s.is_empty()) {
if original_lines
.last()
.is_some_and(std::string::String::is_empty)
{
original_lines.pop();
}
let replacements = compute_replacements(&original_lines, path, chunks)?;
let new_lines = apply_replacements(original_lines, &replacements);
let mut new_lines = new_lines;
if !new_lines.last().is_some_and(|s| s.is_empty()) {
if !new_lines.last().is_some_and(std::string::String::is_empty) {
new_lines.push(String::new());
}
let new_contents = new_lines.join("\n");
@@ -706,7 +709,10 @@ fn compute_replacements(
if chunk.old_lines.is_empty() {
// Pure addition (no old lines). We'll add them at the end or just
// before the final empty line if one exists.
let insertion_idx = if original_lines.last().is_some_and(|s| s.is_empty()) {
let insertion_idx = if original_lines
.last()
.is_some_and(std::string::String::is_empty)
{
original_lines.len() - 1
} else {
original_lines.len()
@@ -732,11 +738,11 @@ fn compute_replacements(
let mut new_slice: &[String] = &chunk.new_lines;
if found.is_none() && pattern.last().is_some_and(|s| s.is_empty()) {
if found.is_none() && pattern.last().is_some_and(std::string::String::is_empty) {
// Retry without the trailing empty line which represents the final
// newline in the file.
pattern = &pattern[..pattern.len() - 1];
if new_slice.last().is_some_and(|s| s.is_empty()) {
if new_slice.last().is_some_and(std::string::String::is_empty) {
new_slice = &new_slice[..new_slice.len() - 1];
}
@@ -856,7 +862,7 @@ mod tests {
}
fn strs_to_strings(strs: &[&str]) -> Vec<String> {
strs.iter().map(|s| s.to_string()).collect()
strs.iter().map(std::string::ToString::to_string).collect()
}
// Test helpers to reduce repetition when building bash -lc heredoc scripts

View File

@@ -112,6 +112,7 @@ pub fn parse_patch(patch: &str) -> Result<ApplyPatchArgs, ParseError> {
parse_patch_text(patch, mode)
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum ParseMode {
/// Parse the patch text argument as is.
Strict,

View File

@@ -114,7 +114,10 @@ mod tests {
use super::seek_sequence;
fn to_vec(strings: &[&str]) -> Vec<String> {
strings.iter().map(|s| s.to_string()).collect()
strings
.iter()
.map(std::string::ToString::to_string)
.collect()
}
#[test]

View File

@@ -54,7 +54,9 @@ where
let argv1 = args.next().unwrap_or_default();
if argv1 == CODEX_APPLY_PATCH_ARG1 {
let patch_arg = args.next().and_then(|s| s.to_str().map(|s| s.to_owned()));
let patch_arg = args
.next()
.and_then(|s| s.to_str().map(std::borrow::ToOwned::to_owned));
let exit_code = match patch_arg {
Some(patch_arg) => {
let mut stdout = std::io::stdout();

View File

@@ -23,7 +23,7 @@ pub async fn login_with_chatgpt(codex_home: PathBuf) -> std::io::Result<()> {
}
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides);
let config = load_config_or_exit(&cli_config_overrides);
match login_with_chatgpt(config.codex_home).await {
Ok(_) => {
@@ -41,7 +41,7 @@ pub async fn run_login_with_api_key(
cli_config_overrides: CliConfigOverrides,
api_key: String,
) -> ! {
let config = load_config_or_exit(cli_config_overrides);
let config = load_config_or_exit(&cli_config_overrides);
match login_with_api_key(&config.codex_home, &api_key) {
Ok(_) => {
@@ -56,7 +56,7 @@ pub async fn run_login_with_api_key(
}
pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides);
let config = load_config_or_exit(&cli_config_overrides);
match CodexAuth::from_codex_home(&config.codex_home) {
Ok(Some(auth)) => match auth.mode {
@@ -87,7 +87,7 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! {
}
pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides);
let config = load_config_or_exit(&cli_config_overrides);
match logout(&config.codex_home) {
Ok(true) => {
@@ -105,7 +105,7 @@ pub async fn run_logout(cli_config_overrides: CliConfigOverrides) -> ! {
}
}
fn load_config_or_exit(cli_config_overrides: CliConfigOverrides) -> Config {
fn load_config_or_exit(cli_config_overrides: &CliConfigOverrides) -> Config {
let cli_overrides = match cli_config_overrides.parse_overrides() {
Ok(v) => v,
Err(e) => {

View File

@@ -243,7 +243,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
proto::run_main(proto_cli).await?;
}
Some(Subcommand::Completion(completion_cli)) => {
print_completion(completion_cli);
print_completion(&completion_cli);
}
Some(Subcommand::Debug(debug_args)) => match debug_args.cmd {
DebugCommand::Seatbelt(mut seatbelt_cli) => {
@@ -363,7 +363,7 @@ fn merge_resume_cli_flags(interactive: &mut TuiCli, resume_cli: TuiCli) {
.extend(resume_cli.config_overrides.raw_overrides);
}
fn print_completion(cmd: CompletionCommand) {
fn print_completion(cmd: &CompletionCommand) {
let mut app = MultitoolCli::command();
let name = "codex";
generate(cmd.shell, &mut app, name, &mut std::io::stdout());

View File

@@ -99,10 +99,10 @@ impl McpCli {
codex_mcp_server::run_main(codex_linux_sandbox_exe, config_overrides).await?;
}
McpSubcommand::List(args) => {
run_list(&config_overrides, args)?;
run_list(&config_overrides, &args)?;
}
McpSubcommand::Get(args) => {
run_get(&config_overrides, args)?;
run_get(&config_overrides, &args)?;
}
McpSubcommand::Add(args) => {
run_add(&config_overrides, args)?;
@@ -188,7 +188,7 @@ fn run_remove(config_overrides: &CliConfigOverrides, remove_args: RemoveArgs) ->
Ok(())
}
fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> Result<()> {
fn run_list(config_overrides: &CliConfigOverrides, list_args: &ListArgs) -> Result<()> {
let overrides = config_overrides.parse_overrides().map_err(|e| anyhow!(e))?;
let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default())
.context("failed to load configuration")?;
@@ -285,7 +285,7 @@ fn run_list(config_overrides: &CliConfigOverrides, list_args: ListArgs) -> Resul
Ok(())
}
fn run_get(config_overrides: &CliConfigOverrides, get_args: GetArgs) -> Result<()> {
fn run_get(config_overrides: &CliConfigOverrides, get_args: &GetArgs) -> Result<()> {
let overrides = config_overrides.parse_overrides().map_err(|e| anyhow!(e))?;
let config = Config::load_with_cli_overrides(overrides, ConfigOverrides::default())
.context("failed to load configuration")?;

View File

@@ -392,7 +392,7 @@ mod tests {
async fn roundtrip_auth_dot_json() {
let codex_home = tempdir().unwrap();
let _ = write_auth_file(
AuthFileParams {
&AuthFileParams {
openai_api_key: None,
chatgpt_plan_type: "pro".to_string(),
},
@@ -438,7 +438,7 @@ mod tests {
async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
let codex_home = tempdir().unwrap();
let fake_jwt = write_auth_file(
AuthFileParams {
&AuthFileParams {
openai_api_key: None,
chatgpt_plan_type: "pro".to_string(),
},
@@ -519,7 +519,7 @@ mod tests {
chatgpt_plan_type: String,
}
fn write_auth_file(params: AuthFileParams, codex_home: &Path) -> std::io::Result<String> {
fn write_auth_file(params: &AuthFileParams, codex_home: &Path) -> std::io::Result<String> {
let auth_file = get_auth_file(codex_home);
// Create a minimal valid JWT for the id_token field.
#[derive(Serialize)]
@@ -536,7 +536,7 @@ mod tests {
"email_verified": true,
"https://api.openai.com/auth": {
"chatgpt_account_id": "bc3618e3-489d-4d49-9362-1561dc53ba53",
"chatgpt_plan_type": params.chatgpt_plan_type,
"chatgpt_plan_type": params.chatgpt_plan_type.clone(),
"chatgpt_user_id": "user-12345",
"user_id": "user-12345",
}
@@ -548,7 +548,7 @@ mod tests {
let fake_jwt = format!("{header_b64}.{payload_b64}.{signature_b64}");
let auth_json_data = json!({
"OPENAI_API_KEY": params.openai_api_key,
"OPENAI_API_KEY": params.openai_api_key.clone(),
"tokens": {
"id_token": fake_jwt,
"access_token": "test-access-token",

View File

@@ -74,7 +74,7 @@ pub fn try_parse_word_only_commands_sequence(tree: &Tree, src: &str) -> Option<V
}
// Walk uses a stack (LIFO), so re-sort by position to restore source order.
command_nodes.sort_by_key(|node| node.start_byte());
command_nodes.sort_by_key(tree_sitter::Node::start_byte);
let mut commands = Vec::new();
for node in command_nodes {

View File

@@ -462,7 +462,7 @@ async fn process_chat_sse<S>(
if let Some(reasoning_val) = choice.get("delta").and_then(|d| d.get("reasoning")) {
let mut maybe_text = reasoning_val
.as_str()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.filter(|s| !s.is_empty());
if maybe_text.is_none() && reasoning_val.is_object() {
@@ -706,9 +706,8 @@ where
return Poll::Ready(Some(Ok(ResponseEvent::OutputItemDone(
item,
))));
} else {
continue;
}
continue;
}
}
}
@@ -792,9 +791,8 @@ where
if matches!(this.mode, AggregateMode::Streaming) {
// In streaming mode, also forward the delta immediately.
return Poll::Ready(Some(Ok(ResponseEvent::OutputTextDelta(delta))));
} else {
continue;
}
continue;
}
Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta(delta)))) => {
// Always accumulate reasoning deltas so we can emit a final Reasoning item at Completed.
@@ -802,9 +800,8 @@ where
if matches!(this.mode, AggregateMode::Streaming) {
// In streaming mode, also forward the delta immediately.
return Poll::Ready(Some(Ok(ResponseEvent::ReasoningContentDelta(delta))));
} else {
continue;
}
continue;
}
Poll::Ready(Some(Ok(ResponseEvent::ReasoningSummaryDelta(_)))) => {
continue;

View File

@@ -335,9 +335,10 @@ impl ModelClient {
// Prefer the plan_type provided in the error message if present
// because it's more up to date than the one encoded in the auth
// token.
let plan_type = error
.plan_type
.or_else(|| auth.as_ref().and_then(|a| a.get_plan_type()));
let plan_type = error.plan_type.or_else(|| {
auth.as_ref()
.and_then(super::auth::CodexAuth::get_plan_type)
});
let resets_in_seconds = error.resets_in_seconds;
return Err(CodexErr::UsageLimitReached(UsageLimitReachedError {
plan_type,

View File

@@ -306,9 +306,8 @@ pub(crate) struct TurnContext {
}
impl TurnContext {
fn resolve_path(&self, path: Option<String>) -> PathBuf {
path.as_ref()
.map(PathBuf::from)
fn resolve_path(&self, path: Option<&str>) -> PathBuf {
path.map(PathBuf::from)
.map_or_else(|| self.cwd.clone(), |p| self.cwd.join(p))
}
}
@@ -1038,7 +1037,7 @@ impl Session {
/// Spawn the configured notifier (if any) with the given JSON payload as
/// the last argument. Failures are logged but otherwise ignored so that
/// notification issues do not interfere with the main workflow.
fn maybe_notify(&self, notification: UserNotification) {
fn maybe_notify(&self, notification: &UserNotification) {
let Some(notify_command) = &self.notify else {
return;
};
@@ -1047,7 +1046,7 @@ impl Session {
return;
}
let Ok(json) = serde_json::to_string(&notification) else {
let Ok(json) = serde_json::to_string(notification) else {
error!("failed to serialise notification payload");
return;
};
@@ -1104,14 +1103,14 @@ pub(crate) struct AgentTask {
impl AgentTask {
fn spawn(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_context: &Arc<TurnContext>,
sub_id: String,
input: Vec<InputItem>,
) -> Self {
let handle = {
let sess = sess.clone();
let sub_id = sub_id.clone();
let tc = Arc::clone(&turn_context);
let tc = Arc::clone(turn_context);
tokio::spawn(async move { run_task(sess, tc, sub_id, input).await }).abort_handle()
};
Self {
@@ -1124,14 +1123,14 @@ impl AgentTask {
fn review(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_context: &Arc<TurnContext>,
sub_id: String,
input: Vec<InputItem>,
) -> Self {
let handle = {
let sess = sess.clone();
let sub_id = sub_id.clone();
let tc = Arc::clone(&turn_context);
let tc = Arc::clone(turn_context);
tokio::spawn(async move { run_task(sess, tc, sub_id, input).await }).abort_handle()
};
Self {
@@ -1144,7 +1143,7 @@ impl AgentTask {
fn compact(
sess: Arc<Session>,
turn_context: Arc<TurnContext>,
turn_context: &Arc<TurnContext>,
sub_id: String,
input: Vec<InputItem>,
compact_instructions: String,
@@ -1152,7 +1151,7 @@ impl AgentTask {
let handle = {
let sess = sess.clone();
let sub_id = sub_id.clone();
let tc = Arc::clone(&turn_context);
let tc = Arc::clone(turn_context);
tokio::spawn(async move {
compact::run_compact_task(sess, tc, sub_id, input, compact_instructions).await
})
@@ -1291,8 +1290,7 @@ async fn submission_loop(
// attempt to inject input into current task
if let Err(items) = sess.inject_input(items).await {
// no current task, spawn a new one
let task =
AgentTask::spawn(sess.clone(), Arc::clone(&turn_context), sub.id, items);
let task = AgentTask::spawn(sess.clone(), &turn_context, sub.id, items);
sess.set_task(task).await;
}
}
@@ -1368,8 +1366,7 @@ async fn submission_loop(
turn_context = Arc::new(fresh_turn_context);
// no current task, spawn a new one with the perturn context
let task =
AgentTask::spawn(sess.clone(), Arc::clone(&turn_context), sub.id, items);
let task = AgentTask::spawn(sess.clone(), &turn_context, sub.id, items);
sess.set_task(task).await;
}
}
@@ -1618,7 +1615,7 @@ async fn spawn_review_thread(
// Clone sub_id for the upcoming announcement before moving it into the task.
let sub_id_for_event = sub_id.clone();
let task = AgentTask::review(sess.clone(), tc.clone(), sub_id, input);
let task = AgentTask::review(sess.clone(), &tc, sub_id, input);
sess.set_task(task).await;
// Announce entering review mode so UIs can switch modes.
@@ -1748,7 +1745,7 @@ async fn run_task(
.unwrap_or(i64::MAX);
let total_usage_tokens = total_token_usage
.as_ref()
.map(|usage| usage.tokens_in_context_window());
.map(codex_protocol::protocol::TokenUsage::tokens_in_context_window);
let token_limit_reached = total_usage_tokens
.map(|tokens| (tokens as i64) >= limit)
.unwrap_or(false);
@@ -1884,7 +1881,7 @@ async fn run_task(
last_agent_message = get_last_assistant_message_from_turn(
&items_to_record_in_conversation_history,
);
sess.maybe_notify(UserNotification::AgentTurnComplete {
sess.maybe_notify(&UserNotification::AgentTurnComplete {
turn_id: sub_id.clone(),
input_messages: turn_input_messages,
last_assistant_message: last_agent_message.clone(),
@@ -2254,7 +2251,7 @@ async fn handle_response_item(
turn_diff_tracker,
sub_id.to_string(),
name,
arguments,
&arguments,
call_id,
)
.await,
@@ -2427,7 +2424,7 @@ async fn handle_function_call(
turn_diff_tracker: &mut TurnDiffTracker,
sub_id: String,
name: String,
arguments: String,
arguments: &str,
call_id: String,
) -> ResponseInputItem {
match name.as_str() {
@@ -2458,7 +2455,7 @@ async fn handle_function_call(
timeout_ms: Option<u64>,
}
let args = match serde_json::from_str::<UnifiedExecArgs>(&arguments) {
let args = match serde_json::from_str::<UnifiedExecArgs>(arguments) {
Ok(args) => args,
Err(err) => {
return ResponseInputItem::FunctionCallOutput {
@@ -2485,7 +2482,7 @@ async fn handle_function_call(
struct SeeImageArgs {
path: String,
}
let args = match serde_json::from_str::<SeeImageArgs>(&arguments) {
let args = match serde_json::from_str::<SeeImageArgs>(arguments) {
Ok(a) => a,
Err(e) => {
return ResponseInputItem::FunctionCallOutput {
@@ -2497,7 +2494,7 @@ async fn handle_function_call(
};
}
};
let abs = turn_context.resolve_path(Some(args.path));
let abs = turn_context.resolve_path(Some(&args.path));
let output = match sess
.inject_input(vec![InputItem::LocalImage { path: abs }])
.await
@@ -2514,7 +2511,7 @@ async fn handle_function_call(
ResponseInputItem::FunctionCallOutput { call_id, output }
}
"apply_patch" => {
let args = match serde_json::from_str::<ApplyPatchToolArgs>(&arguments) {
let args = match serde_json::from_str::<ApplyPatchToolArgs>(arguments) {
Ok(a) => a,
Err(e) => {
return ResponseInputItem::FunctionCallOutput {
@@ -2547,7 +2544,7 @@ async fn handle_function_call(
"update_plan" => handle_update_plan(sess, arguments, sub_id, call_id).await,
EXEC_COMMAND_TOOL_NAME => {
// TODO(mbolin): Sandbox check.
let exec_params = match serde_json::from_str::<ExecCommandParams>(&arguments) {
let exec_params = match serde_json::from_str::<ExecCommandParams>(arguments) {
Ok(params) => params,
Err(e) => {
return ResponseInputItem::FunctionCallOutput {
@@ -2570,7 +2567,7 @@ async fn handle_function_call(
}
}
WRITE_STDIN_TOOL_NAME => {
let write_stdin_params = match serde_json::from_str::<WriteStdinParams>(&arguments) {
let write_stdin_params = match serde_json::from_str::<WriteStdinParams>(arguments) {
Ok(params) => params,
Err(e) => {
return ResponseInputItem::FunctionCallOutput {
@@ -2599,7 +2596,13 @@ async fn handle_function_call(
// TODO(mbolin): Determine appropriate timeout for tool call.
let timeout = None;
handle_mcp_tool_call(
sess, &sub_id, call_id, server, tool_name, arguments, timeout,
sess,
&sub_id,
call_id,
server,
tool_name,
arguments.to_string(),
timeout,
)
.await
}
@@ -2673,7 +2676,7 @@ async fn handle_custom_tool_call(
fn to_exec_params(params: ShellToolCallParams, turn_context: &TurnContext) -> ExecParams {
ExecParams {
command: params.command,
cwd: turn_context.resolve_path(params.workdir.clone()),
cwd: turn_context.resolve_path(params.workdir.as_deref()),
timeout_ms: params.timeout_ms,
env: create_env(&turn_context.shell_environment_policy),
with_escalated_permissions: params.with_escalated_permissions,
@@ -2682,12 +2685,12 @@ fn to_exec_params(params: ShellToolCallParams, turn_context: &TurnContext) -> Ex
}
fn parse_container_exec_arguments(
arguments: String,
arguments: &str,
turn_context: &TurnContext,
call_id: &str,
) -> Result<ExecParams, Box<ResponseInputItem>> {
// parse command
match serde_json::from_str::<ShellToolCallParams>(&arguments) {
match serde_json::from_str::<ShellToolCallParams>(arguments) {
Ok(shell_tool_call_params) => Ok(to_exec_params(shell_tool_call_params, turn_context)),
Err(e) => {
// allow model to re-sample

View File

@@ -44,7 +44,7 @@ pub(super) async fn spawn_compact_task(
) {
let task = AgentTask::compact(
sess.clone(),
turn_context,
&turn_context,
sub_id,
input,
SUMMARIZATION_PROMPT.to_string(),
@@ -159,16 +159,15 @@ async fn run_compact_task_inner(
.await;
tokio::time::sleep(delay).await;
continue;
} else {
let event = Event {
id: sub_id.clone(),
msg: EventMsg::Error(ErrorEvent {
message: e.to_string(),
}),
};
sess.send_event(event).await;
return;
}
let event = Event {
id: sub_id.clone(),
msg: EventMsg::Error(ErrorEvent {
message: e.to_string(),
}),
};
sess.send_event(event).await;
return;
}
}
}

View File

@@ -136,7 +136,7 @@ async fn persist_overrides_with_behavior(
} else {
doc.get("profile")
.and_then(|i| i.as_str())
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
};
let mut mutated = false;

View File

@@ -148,7 +148,7 @@ impl ConversationManager {
) -> CodexResult<NewConversation> {
// Compute the prefix up to the cut point.
let history = RolloutRecorder::get_rollout_history(&path).await?;
let history = truncate_before_nth_user_message(history, nth_user_message);
let history = truncate_before_nth_user_message(&history, nth_user_message);
// Spawn a new conversation with the computed initial history.
let auth_manager = self.auth_manager.clone();
@@ -163,7 +163,7 @@ impl ConversationManager {
/// Return a prefix of `items` obtained by cutting strictly before the nth user message
/// (0-based) and all items that follow it.
fn truncate_before_nth_user_message(history: InitialHistory, n: usize) -> InitialHistory {
fn truncate_before_nth_user_message(history: &InitialHistory, n: usize) -> InitialHistory {
// Work directly on rollout items, and cut the vector at the nth user message input.
let items: Vec<RolloutItem> = history.get_rollout_items();
@@ -253,7 +253,7 @@ mod tests {
.cloned()
.map(RolloutItem::ResponseItem)
.collect();
let truncated = truncate_before_nth_user_message(InitialHistory::Forked(initial), 1);
let truncated = truncate_before_nth_user_message(&InitialHistory::Forked(initial), 1);
let got_items = truncated.get_rollout_items();
let expected_items = vec![
RolloutItem::ResponseItem(items[0].clone()),
@@ -270,7 +270,7 @@ mod tests {
.cloned()
.map(RolloutItem::ResponseItem)
.collect();
let truncated2 = truncate_before_nth_user_message(InitialHistory::Forked(initial2), 2);
let truncated2 = truncate_before_nth_user_message(&InitialHistory::Forked(initial2), 2);
assert!(matches!(truncated2, InitialHistory::New));
}
@@ -289,7 +289,7 @@ mod tests {
.map(RolloutItem::ResponseItem)
.collect();
let truncated = truncate_before_nth_user_message(InitialHistory::Forked(rollout_items), 1);
let truncated = truncate_before_nth_user_message(&InitialHistory::Forked(rollout_items), 1);
let got_items = truncated.get_rollout_items();
let expected: Vec<RolloutItem> = vec![

View File

@@ -52,7 +52,7 @@ pub async fn discover_prompts_in_excluding(
let Some(name) = path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
else {
continue;
};

View File

@@ -200,7 +200,7 @@ async fn get_git_remotes(cwd: &Path) -> Option<Vec<String>> {
let mut remotes: Vec<String> = String::from_utf8(output.stdout)
.ok()?
.lines()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect();
if let Some(pos) = remotes.iter().position(|r| r == "origin") {
let origin = remotes.remove(pos);
@@ -477,7 +477,7 @@ async fn diff_against_sha(cwd: &Path, sha: &GitSha) -> Option<String> {
let untracked: Vec<String> = String::from_utf8(untracked_output.stdout)
.ok()?
.lines()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.filter(|s| !s.is_empty())
.collect();

View File

@@ -162,7 +162,7 @@ mod tests {
use super::*;
fn vec_str(args: &[&str]) -> Vec<String> {
args.iter().map(|s| s.to_string()).collect()
args.iter().map(std::string::ToString::to_string).collect()
}
#[test]

View File

@@ -403,7 +403,7 @@ fn sanitize_json_schema(value: &mut JsonValue) {
let mut ty = map
.get("type")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
.map(std::string::ToString::to_string);
// If type is an array (union), pick first supported; else leave to inference
if ty.is_none()

View File

@@ -40,7 +40,7 @@ impl From<ParsedCommand> for codex_protocol::parse_command::ParsedCommand {
}
fn shlex_join(tokens: &[String]) -> String {
shlex_try_join(tokens.iter().map(|s| s.as_str()))
shlex_try_join(tokens.iter().map(std::string::String::as_str))
.unwrap_or_else(|_| "<command included NUL byte>".to_string())
}
@@ -74,14 +74,18 @@ mod tests {
use super::*;
fn shlex_split_safe(s: &str) -> Vec<String> {
shlex_split(s).unwrap_or_else(|| s.split_whitespace().map(|s| s.to_string()).collect())
shlex_split(s).unwrap_or_else(|| {
s.split_whitespace()
.map(std::string::ToString::to_string)
.collect()
})
}
fn vec_str(args: &[&str]) -> Vec<String> {
args.iter().map(|s| s.to_string()).collect()
args.iter().map(std::string::ToString::to_string).collect()
}
fn assert_parsed(args: &[String], expected: Vec<ParsedCommand>) {
fn assert_parsed(args: &[String], expected: &[ParsedCommand]) {
let out = parse_command(args);
assert_eq!(out, expected);
}
@@ -90,7 +94,7 @@ mod tests {
fn git_status_is_unknown() {
assert_parsed(
&vec_str(&["git", "status"]),
vec![ParsedCommand::Unknown {
&[ParsedCommand::Unknown {
cmd: "git status".to_string(),
}],
);
@@ -101,7 +105,7 @@ mod tests {
let inner = "git status | wc -l";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Unknown {
&[ParsedCommand::Unknown {
cmd: "git status".to_string(),
}],
);
@@ -112,7 +116,7 @@ mod tests {
let inner = "echo foo > bar";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Unknown {
&[ParsedCommand::Unknown {
cmd: "echo foo > bar".to_string(),
}],
);
@@ -124,7 +128,7 @@ mod tests {
"rg --version && node -v && pnpm -v && rg --files | wc -l && rg --files | head -n 40";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![
&[
// Expect commands in left-to-right execution order
ParsedCommand::Search {
cmd: "rg --version".to_string(),
@@ -154,7 +158,7 @@ mod tests {
let inner = "rg -n \"navigate-to-route\" -S";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg -n navigate-to-route -S".to_string(),
query: Some("navigate-to-route".to_string()),
path: None,
@@ -168,7 +172,7 @@ mod tests {
let inner = "rg -n \"BUG|FIXME|TODO|XXX|HACK\" -S | head -n 200";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![
&[
ParsedCommand::Search {
cmd: "rg -n 'BUG|FIXME|TODO|XXX|HACK' -S".to_string(),
query: Some("BUG|FIXME|TODO|XXX|HACK".to_string()),
@@ -186,7 +190,7 @@ mod tests {
let inner = "rg --files webview/src | sed -n";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files webview/src".to_string(),
query: None,
path: Some("webview".to_string()),
@@ -199,7 +203,7 @@ mod tests {
let inner = "rg --files | head -n 50";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![
&[
ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
@@ -217,7 +221,7 @@ mod tests {
let inner = "cat webview/README.md";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "README.md".to_string(),
}],
@@ -228,7 +232,7 @@ mod tests {
fn cd_then_cat_is_single_read() {
assert_parsed(
&shlex_split_safe("cd foo && cat foo.txt"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "cat foo.txt".to_string(),
name: "foo.txt".to_string(),
}],
@@ -240,7 +244,7 @@ mod tests {
// Ensure a leading `cd` inside bash -lc is dropped when followed by another command.
assert_parsed(
&shlex_split_safe("bash -lc 'cd foo && bar'"),
vec![ParsedCommand::Unknown {
&[ParsedCommand::Unknown {
cmd: "bar".to_string(),
}],
);
@@ -250,7 +254,7 @@ mod tests {
fn bash_cd_then_cat_is_read() {
assert_parsed(
&shlex_split_safe("bash -lc 'cd foo && cat foo.txt'"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "cat foo.txt".to_string(),
name: "foo.txt".to_string(),
}],
@@ -262,7 +266,7 @@ mod tests {
let inner = "ls -la | sed -n '1,120p'";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::ListFiles {
&[ParsedCommand::ListFiles {
cmd: "ls -la".to_string(),
path: None,
}],
@@ -274,7 +278,7 @@ mod tests {
let inner = "head -n 50 Cargo.toml";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "Cargo.toml".to_string(),
}],
@@ -286,7 +290,7 @@ mod tests {
let inner = "cat tui/Cargo.toml | sed -n '1,200p'";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "Cargo.toml".to_string(),
}],
@@ -298,7 +302,7 @@ mod tests {
let inner = "tail -n +522 README.md";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "README.md".to_string(),
}],
@@ -322,7 +326,7 @@ mod tests {
fn supports_npm_run_build_is_unknown() {
assert_parsed(
&vec_str(&["npm", "run", "build"]),
vec![ParsedCommand::Unknown {
&[ParsedCommand::Unknown {
cmd: "npm run build".to_string(),
}],
);
@@ -332,7 +336,7 @@ mod tests {
fn supports_grep_recursive_current_dir() {
assert_parsed(
&vec_str(&["grep", "-R", "CODEX_SANDBOX_ENV_VAR", "-n", "."]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "grep -R CODEX_SANDBOX_ENV_VAR -n .".to_string(),
query: Some("CODEX_SANDBOX_ENV_VAR".to_string()),
path: Some(".".to_string()),
@@ -350,7 +354,7 @@ mod tests {
"-n",
"core/src/spawn.rs",
]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "grep -R CODEX_SANDBOX_ENV_VAR -n core/src/spawn.rs".to_string(),
query: Some("CODEX_SANDBOX_ENV_VAR".to_string()),
path: Some("spawn.rs".to_string()),
@@ -364,7 +368,7 @@ mod tests {
// Previously, grep queries were passed through short_display_path, which is incorrect.
assert_parsed(
&shlex_split_safe("grep -R src/main.rs -n ."),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "grep -R src/main.rs -n .".to_string(),
query: Some("src/main.rs".to_string()),
path: Some(".".to_string()),
@@ -376,7 +380,7 @@ mod tests {
fn supports_grep_weird_backtick_in_query() {
assert_parsed(
&shlex_split_safe("grep -R COD`EX_SANDBOX -n"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "grep -R 'COD`EX_SANDBOX' -n".to_string(),
query: Some("COD`EX_SANDBOX".to_string()),
path: None,
@@ -388,7 +392,7 @@ mod tests {
fn supports_cd_and_rg_files() {
assert_parsed(
&shlex_split_safe("cd codex-rs && rg --files"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -480,7 +484,7 @@ mod tests {
let inner = "nl -ba core/src/parse_command.rs | sed -n '1200,1720p'";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "parse_command.rs".to_string(),
}],
@@ -492,7 +496,7 @@ mod tests {
let inner = "sed -n '2000,2200p' tui/src/history_cell.rs";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: inner.to_string(),
name: "history_cell.rs".to_string(),
}],
@@ -505,7 +509,7 @@ mod tests {
r#"printf "\n===== ansi-escape/Cargo.toml =====\n"; cat -- ansi-escape/Cargo.toml"#;
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "cat -- ansi-escape/Cargo.toml".to_string(),
name: "Cargo.toml".to_string(),
}],
@@ -518,7 +522,7 @@ mod tests {
let inner = "yes | rg --files";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -534,7 +538,7 @@ mod tests {
);
assert_parsed(
&args,
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "sed -n '260,640p' exec/src/event_processor_with_human_output.rs".to_string(),
name: "event_processor_with_human_output.rs".to_string(),
}],
@@ -545,7 +549,7 @@ mod tests {
fn preserves_rg_with_spaces() {
assert_parsed(
&shlex_split_safe("yes | rg -n 'foo bar' -S"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg -n 'foo bar' -S".to_string(),
query: Some("foo bar".to_string()),
path: None,
@@ -557,7 +561,7 @@ mod tests {
fn ls_with_glob() {
assert_parsed(
&shlex_split_safe("ls -I '*.test.js'"),
vec![ParsedCommand::ListFiles {
&[ParsedCommand::ListFiles {
cmd: "ls -I '*.test.js'".to_string(),
path: None,
}],
@@ -568,7 +572,7 @@ mod tests {
fn trim_on_semicolon() {
assert_parsed(
&shlex_split_safe("rg foo ; echo done"),
vec![
&[
ParsedCommand::Search {
cmd: "rg foo".to_string(),
query: Some("foo".to_string()),
@@ -586,7 +590,7 @@ mod tests {
// Ensure we split commands on the logical OR operator as well.
assert_parsed(
&shlex_split_safe("rg foo || echo done"),
vec![
&[
ParsedCommand::Search {
cmd: "rg foo".to_string(),
query: Some("foo".to_string()),
@@ -641,7 +645,7 @@ mod tests {
},
];
assert_parsed(&args, expected);
assert_parsed(&args, &expected);
}
#[test]
@@ -649,7 +653,7 @@ mod tests {
// `true` should be dropped from parsed sequences
assert_parsed(
&shlex_split_safe("true && rg --files"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -658,7 +662,7 @@ mod tests {
assert_parsed(
&shlex_split_safe("rg --files && true"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -671,7 +675,7 @@ mod tests {
let inner = "true && rg --files";
assert_parsed(
&vec_str(&["bash", "-lc", inner]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -681,7 +685,7 @@ mod tests {
let inner2 = "rg --files || true";
assert_parsed(
&vec_str(&["bash", "-lc", inner2]),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -693,7 +697,7 @@ mod tests {
fn shorten_path_on_windows() {
assert_parsed(
&shlex_split_safe(r#"cat "pkg\src\main.rs""#),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: r#"cat "pkg\\src\\main.rs""#.to_string(),
name: "main.rs".to_string(),
}],
@@ -704,7 +708,7 @@ mod tests {
fn head_with_no_space() {
assert_parsed(
&shlex_split_safe("bash -lc 'head -n50 Cargo.toml'"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "head -n50 Cargo.toml".to_string(),
name: "Cargo.toml".to_string(),
}],
@@ -717,7 +721,7 @@ mod tests {
let inner = "rg --files | head -n 1";
assert_parsed(
&shlex_split_safe(inner),
vec![
&[
ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
@@ -734,7 +738,7 @@ mod tests {
fn tail_with_no_space() {
assert_parsed(
&shlex_split_safe("bash -lc 'tail -n+10 README.md'"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "tail -n+10 README.md".to_string(),
name: "README.md".to_string(),
}],
@@ -745,7 +749,7 @@ mod tests {
fn grep_with_query_and_path() {
assert_parsed(
&shlex_split_safe("grep -R TODO src"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "grep -R TODO src".to_string(),
query: Some("TODO".to_string()),
path: Some("src".to_string()),
@@ -757,7 +761,7 @@ mod tests {
fn rg_with_equals_style_flags() {
assert_parsed(
&shlex_split_safe("rg --colors=never -n foo src"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg '--colors=never' -n foo src".to_string(),
query: Some("foo".to_string()),
path: Some("src".to_string()),
@@ -770,7 +774,7 @@ mod tests {
// cat -- <file> should be treated as a read of that file
assert_parsed(
&shlex_split_safe("cat -- ./-strange-file-name"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "cat -- ./-strange-file-name".to_string(),
name: "-strange-file-name".to_string(),
}],
@@ -779,7 +783,7 @@ mod tests {
// sed -n <range> <file> should be treated as a read of <file>
assert_parsed(
&shlex_split_safe("sed -n '12,20p' Cargo.toml"),
vec![ParsedCommand::Read {
&[ParsedCommand::Read {
cmd: "sed -n '12,20p' Cargo.toml".to_string(),
name: "Cargo.toml".to_string(),
}],
@@ -791,7 +795,7 @@ mod tests {
// When an `nl` stage has only flags, it should be dropped from the summary
assert_parsed(
&shlex_split_safe("rg --files | nl -ba"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "rg --files".to_string(),
query: None,
path: None,
@@ -803,7 +807,7 @@ mod tests {
fn ls_with_time_style_and_path() {
assert_parsed(
&shlex_split_safe("ls --time-style=long-iso ./dist"),
vec![ParsedCommand::ListFiles {
&[ParsedCommand::ListFiles {
cmd: "ls '--time-style=long-iso' ./dist".to_string(),
// short_display_path drops "dist" and shows "." as the last useful segment
path: Some(".".to_string()),
@@ -815,7 +819,7 @@ mod tests {
fn fd_file_finder_variants() {
assert_parsed(
&shlex_split_safe("fd -t f src/"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "fd -t f src/".to_string(),
query: None,
path: Some("src".to_string()),
@@ -825,7 +829,7 @@ mod tests {
// fd with query and path should capture both
assert_parsed(
&shlex_split_safe("fd main src"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "fd main src".to_string(),
query: Some("main".to_string()),
path: Some("src".to_string()),
@@ -837,7 +841,7 @@ mod tests {
fn find_basic_name_filter() {
assert_parsed(
&shlex_split_safe("find . -name '*.rs'"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "find . -name '*.rs'".to_string(),
query: Some("*.rs".to_string()),
path: Some(".".to_string()),
@@ -849,7 +853,7 @@ mod tests {
fn find_type_only_path() {
assert_parsed(
&shlex_split_safe("find src -type f"),
vec![ParsedCommand::Search {
&[ParsedCommand::Search {
cmd: "find src -type f".to_string(),
query: None,
path: Some("src".to_string()),
@@ -894,16 +898,16 @@ fn simplify_once(commands: &[ParsedCommand]) -> Option<Vec<ParsedCommand>> {
// echo ... && ...rest => ...rest
if let ParsedCommand::Unknown { cmd } = &commands[0]
&& shlex_split(cmd).is_some_and(|t| t.first().map(|s| s.as_str()) == Some("echo"))
&& shlex_split(cmd)
.is_some_and(|t| t.first().map(std::string::String::as_str) == Some("echo"))
{
return Some(commands[1..].to_vec());
}
// cd foo && [any command] => [any command] (keep non-cd when a cd is followed by something)
if let Some(idx) = commands.iter().position(|pc| match pc {
ParsedCommand::Unknown { cmd } => {
shlex_split(cmd).is_some_and(|t| t.first().map(|s| s.as_str()) == Some("cd"))
}
ParsedCommand::Unknown { cmd } => shlex_split(cmd)
.is_some_and(|t| t.first().map(std::string::String::as_str) == Some("cd")),
_ => false,
}) && commands.len() > idx + 1
{
@@ -1035,7 +1039,7 @@ fn short_display_path(path: &str) -> String {
});
parts
.next()
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_else(|| trimmed.to_string())
}
@@ -1190,8 +1194,8 @@ fn parse_bash_lc_commands(original: &[String]) -> Option<Vec<ParsedCommand>> {
if had_connectors {
let has_pipe = script_tokens.iter().any(|t| t == "|");
let has_sed_n = script_tokens.windows(2).any(|w| {
w.first().map(|s| s.as_str()) == Some("sed")
&& w.get(1).map(|s| s.as_str()) == Some("-n")
w.first().map(std::string::String::as_str) == Some("sed")
&& w.get(1).map(std::string::String::as_str) == Some("-n")
});
if has_pipe && has_sed_n {
ParsedCommand::Read {
@@ -1271,7 +1275,8 @@ fn is_small_formatting_command(tokens: &[String]) -> bool {
// Keep `sed -n <range> file` (treated as a file read elsewhere);
// otherwise consider it a formatting helper in a pipeline.
tokens.len() < 4
|| !(tokens[1] == "-n" && is_valid_sed_n_arg(tokens.get(2).map(|s| s.as_str())))
|| !(tokens[1] == "-n"
&& is_valid_sed_n_arg(tokens.get(2).map(std::string::String::as_str)))
}
_ => false,
}
@@ -1318,7 +1323,10 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
(None, non_flags.first().map(|s| short_display_path(s)))
} else {
(
non_flags.first().cloned().map(|s| s.to_string()),
non_flags
.first()
.cloned()
.map(std::string::ToString::to_string),
non_flags.get(1).map(|s| short_display_path(s)),
)
};
@@ -1353,7 +1361,10 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
.collect();
// Do not shorten the query: grep patterns may legitimately contain slashes
// and should be preserved verbatim. Only paths should be shortened.
let query = non_flags.first().cloned().map(|s| s.to_string());
let query = non_flags
.first()
.cloned()
.map(std::string::ToString::to_string);
let path = non_flags.get(1).map(|s| short_display_path(s));
ParsedCommand::Search {
cmd: shlex_join(main_cmd),
@@ -1363,11 +1374,12 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
}
Some((head, tail)) if head == "cat" => {
// Support both `cat <file>` and `cat -- <file>` forms.
let effective_tail: &[String] = if tail.first().map(|s| s.as_str()) == Some("--") {
&tail[1..]
} else {
tail
};
let effective_tail: &[String] =
if tail.first().map(std::string::String::as_str) == Some("--") {
&tail[1..]
} else {
tail
};
if effective_tail.len() == 1 {
let name = short_display_path(&effective_tail[0]);
ParsedCommand::Read {
@@ -1479,7 +1491,7 @@ fn summarize_main_tokens(main_cmd: &[String]) -> ParsedCommand {
if head == "sed"
&& tail.len() >= 3
&& tail[0] == "-n"
&& is_valid_sed_n_arg(tail.get(1).map(|s| s.as_str())) =>
&& is_valid_sed_n_arg(tail.get(1).map(std::string::String::as_str)) =>
{
if let Some(path) = tail.get(2) {
let name = short_display_path(path);

View File

@@ -65,7 +65,7 @@ At most one step can be in_progress at a time.
/// than forcing it to come up and document a plan (TBD how that affects performance).
pub(crate) async fn handle_update_plan(
session: &Session,
arguments: String,
arguments: &str,
sub_id: String,
call_id: String,
) -> ResponseInputItem {
@@ -91,10 +91,10 @@ pub(crate) async fn handle_update_plan(
}
fn parse_update_plan_arguments(
arguments: String,
arguments: &str,
call_id: &str,
) -> Result<UpdatePlanArgs, Box<ResponseInputItem>> {
match serde_json::from_str::<UpdatePlanArgs>(&arguments) {
match serde_json::from_str::<UpdatePlanArgs>(arguments) {
Ok(args) => Ok(args),
Err(e) => {
let output = ResponseInputItem::FunctionCallOutput {

View File

@@ -372,7 +372,7 @@ pub async fn find_conversation_path_by_id_str(
&root,
exclude,
threads,
cancel,
&cancel,
compute_indices,
)
.map_err(|e| io::Error::other(format!("file search failed: {e}")))?;

View File

@@ -73,7 +73,8 @@ impl Shell {
return Some(command);
}
let joined = shlex::try_join(command.iter().map(|s| s.as_str())).ok();
let joined =
shlex::try_join(command.iter().map(std::string::String::as_str)).ok();
return joined.map(|arg| {
vec![
ps.exe.clone(),
@@ -111,7 +112,7 @@ fn format_shell_invocation_with_rc(
rc_path: &str,
) -> Option<Vec<String>> {
let joined = strip_bash_lc(command)
.or_else(|| shlex::try_join(command.iter().map(|s| s.as_str())).ok())?;
.or_else(|| shlex::try_join(command.iter().map(std::string::String::as_str)).ok())?;
let rc_command = if std::path::Path::new(rc_path).exists() {
format!("source {rc_path} && ({joined})")
@@ -326,8 +327,9 @@ mod tests {
bashrc_path: bashrc_path.to_str().unwrap().to_string(),
});
let actual_cmd = shell
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
let actual_cmd = shell.format_default_shell_invocation(
input.iter().map(std::string::ToString::to_string).collect(),
);
let expected_cmd = expected_cmd
.iter()
.map(|s| s.replace("BASHRC_PATH", bashrc_path.to_str().unwrap()))
@@ -433,8 +435,9 @@ mod macos_tests {
zshrc_path: zshrc_path.to_str().unwrap().to_string(),
});
let actual_cmd = shell
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
let actual_cmd = shell.format_default_shell_invocation(
input.iter().map(std::string::ToString::to_string).collect(),
);
let expected_cmd = expected_cmd
.iter()
.map(|s| s.replace("ZSHRC_PATH", zshrc_path.to_str().unwrap()))
@@ -558,11 +561,17 @@ mod tests_windows {
];
for (shell, input, expected_cmd) in cases {
let actual_cmd = shell
.format_default_shell_invocation(input.iter().map(|s| s.to_string()).collect());
let actual_cmd = shell.format_default_shell_invocation(
input.iter().map(std::string::ToString::to_string).collect(),
);
assert_eq!(
actual_cmd,
Some(expected_cmd.iter().map(|s| s.to_string()).collect())
Some(
expected_cmd
.iter()
.map(std::string::ToString::to_string)
.collect()
)
);
}
}

View File

@@ -17,56 +17,56 @@ fn is_valid_header_value_char(c: char) -> bool {
c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' || c == '/'
}
fn sanitize_header_value(value: String) -> String {
fn sanitize_header_value(value: &str) -> String {
value.replace(|c| !is_valid_header_value_char(c), "_")
}
fn detect_terminal() -> String {
sanitize_header_value(
if let Ok(tp) = std::env::var("TERM_PROGRAM")
&& !tp.trim().is_empty()
{
let ver = std::env::var("TERM_PROGRAM_VERSION").ok();
match ver {
Some(v) if !v.trim().is_empty() => format!("{tp}/{v}"),
_ => tp,
}
} else if let Ok(v) = std::env::var("WEZTERM_VERSION") {
if !v.trim().is_empty() {
format!("WezTerm/{v}")
} else {
"WezTerm".to_string()
}
} else if std::env::var("KITTY_WINDOW_ID").is_ok()
|| std::env::var("TERM")
.map(|t| t.contains("kitty"))
.unwrap_or(false)
{
"kitty".to_string()
} else if std::env::var("ALACRITTY_SOCKET").is_ok()
|| std::env::var("TERM")
.map(|t| t == "alacritty")
.unwrap_or(false)
{
"Alacritty".to_string()
} else if let Ok(v) = std::env::var("KONSOLE_VERSION") {
if !v.trim().is_empty() {
format!("Konsole/{v}")
} else {
"Konsole".to_string()
}
} else if std::env::var("GNOME_TERMINAL_SCREEN").is_ok() {
return "gnome-terminal".to_string();
} else if let Ok(v) = std::env::var("VTE_VERSION") {
if !v.trim().is_empty() {
format!("VTE/{v}")
} else {
"VTE".to_string()
}
} else if std::env::var("WT_SESSION").is_ok() {
return "WindowsTerminal".to_string();
let raw = if let Ok(tp) = std::env::var("TERM_PROGRAM")
&& !tp.trim().is_empty()
{
let ver = std::env::var("TERM_PROGRAM_VERSION").ok();
match ver {
Some(v) if !v.trim().is_empty() => format!("{tp}/{v}"),
_ => tp,
}
} else if let Ok(v) = std::env::var("WEZTERM_VERSION") {
if !v.trim().is_empty() {
format!("WezTerm/{v}")
} else {
std::env::var("TERM").unwrap_or_else(|_| "unknown".to_string())
},
)
"WezTerm".to_string()
}
} else if std::env::var("KITTY_WINDOW_ID").is_ok()
|| std::env::var("TERM")
.map(|t| t.contains("kitty"))
.unwrap_or(false)
{
"kitty".to_string()
} else if std::env::var("ALACRITTY_SOCKET").is_ok()
|| std::env::var("TERM")
.map(|t| t == "alacritty")
.unwrap_or(false)
{
"Alacritty".to_string()
} else if let Ok(v) = std::env::var("KONSOLE_VERSION") {
if !v.trim().is_empty() {
format!("Konsole/{v}")
} else {
"Konsole".to_string()
}
} else if std::env::var("GNOME_TERMINAL_SCREEN").is_ok() {
return sanitize_header_value("gnome-terminal");
} else if let Ok(v) = std::env::var("VTE_VERSION") {
if !v.trim().is_empty() {
format!("VTE/{v}")
} else {
"VTE".to_string()
}
} else if std::env::var("WT_SESSION").is_ok() {
return sanitize_header_value("WindowsTerminal");
} else {
std::env::var("TERM").unwrap_or_else(|_| "unknown".to_string())
};
sanitize_header_value(&raw)
}

View File

@@ -65,7 +65,7 @@ impl TurnDiffTracker {
let baseline_file_info = if path.exists() {
let mode = file_mode_for_path(path);
let mode_val = mode.unwrap_or(FileMode::Regular);
let content = blob_bytes(path, &mode_val).unwrap_or_default();
let content = blob_bytes(path, mode_val).unwrap_or_default();
let oid = if mode == Some(FileMode::Symlink) {
format!("{:x}", git_blob_sha1_hex_bytes(&content))
} else {
@@ -266,7 +266,7 @@ impl TurnDiffTracker {
};
let current_mode = file_mode_for_path(&current_external_path).unwrap_or(FileMode::Regular);
let right_bytes = blob_bytes(&current_external_path, &current_mode);
let right_bytes = blob_bytes(&current_external_path, current_mode);
// Compute displays with &mut self before borrowing any baseline content.
let left_display = self.relative_to_git_root_str(&baseline_external_path);
@@ -388,7 +388,7 @@ enum FileMode {
}
impl FileMode {
fn as_str(&self) -> &'static str {
fn as_str(self) -> &'static str {
match self {
FileMode::Regular => "100644",
#[cfg(unix)]
@@ -427,9 +427,9 @@ fn file_mode_for_path(_path: &Path) -> Option<FileMode> {
Some(FileMode::Regular)
}
fn blob_bytes(path: &Path, mode: &FileMode) -> Option<Vec<u8>> {
fn blob_bytes(path: &Path, mode: FileMode) -> Option<Vec<u8>> {
if path.exists() {
let contents = if *mode == FileMode::Symlink {
let contents = if mode == FileMode::Symlink {
symlink_blob_bytes(path)
.ok_or_else(|| anyhow!("failed to read symlink target for {}", path.display()))
} else {

View File

@@ -29,7 +29,7 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
assert_eq!(sandbox_type, SandboxType::MacosSeatbelt);
let params = ExecParams {
command: cmd.iter().map(|s| s.to_string()).collect(),
command: cmd.iter().map(std::string::ToString::to_string).collect(),
cwd: tmp.path().to_path_buf(),
timeout_ms: Some(1000),
env: HashMap::new(),

View File

@@ -17,7 +17,7 @@ use codex_core::protocol::ExecCommandOutputDeltaEvent;
use codex_core::protocol::ExecOutputStream;
use codex_core::protocol::SandboxPolicy;
fn collect_stdout_events(rx: Receiver<Event>) -> Vec<u8> {
fn collect_stdout_events(rx: &Receiver<Event>) -> Vec<u8> {
let mut out = Vec::new();
while let Ok(ev) = rx.try_recv() {
if let EventMsg::ExecCommandOutputDelta(ExecCommandOutputDeltaEvent {
@@ -79,7 +79,7 @@ async fn test_exec_stdout_stream_events_echo() {
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout.text, "hello-world\n");
let streamed = collect_stdout_events(rx);
let streamed = collect_stdout_events(&rx);
// We should have received at least the same contents (possibly in one chunk)
assert_eq!(String::from_utf8_lossy(&streamed), "hello-world\n");
}

View File

@@ -24,7 +24,7 @@ use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
fn text_user_input(text: String) -> serde_json::Value {
fn text_user_input(text: &str) -> serde_json::Value {
serde_json::json!({
"type": "message",
"role": "user",
@@ -685,13 +685,13 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() {
let shell = default_user_shell().await;
let expected_ui_text =
"<user_instructions>\n\nbe consistent and helpful\n\n</user_instructions>";
let expected_ui_msg = text_user_input(expected_ui_text.to_string());
let expected_ui_msg = text_user_input(expected_ui_text);
let expected_env_msg_1 = text_user_input(default_env_context_str(
let expected_env_msg_1 = text_user_input(&default_env_context_str(
&cwd.path().to_string_lossy(),
&shell,
));
let expected_user_message_1 = text_user_input("hello 1".to_string());
let expected_user_message_1 = text_user_input("hello 1");
let expected_input_1 = serde_json::Value::Array(vec![
expected_ui_msg.clone(),
@@ -700,7 +700,7 @@ async fn send_user_turn_with_no_changes_does_not_send_environment_context() {
]);
assert_eq!(body1["input"], expected_input_1);
let expected_user_message_2 = text_user_input("hello 2".to_string());
let expected_user_message_2 = text_user_input("hello 2");
let expected_input_2 = serde_json::Value::Array(vec![
expected_ui_msg,
expected_env_msg_1,
@@ -802,8 +802,8 @@ async fn send_user_turn_with_changes_sends_environment_context() {
"content": [ { "type": "input_text", "text": expected_ui_text } ]
});
let expected_env_text_1 = default_env_context_str(&default_cwd.to_string_lossy(), &shell);
let expected_env_msg_1 = text_user_input(expected_env_text_1);
let expected_user_message_1 = text_user_input("hello 1".to_string());
let expected_env_msg_1 = text_user_input(&expected_env_text_1);
let expected_user_message_1 = text_user_input("hello 1");
let expected_input_1 = serde_json::Value::Array(vec![
expected_ui_msg.clone(),
expected_env_msg_1.clone(),
@@ -811,7 +811,7 @@ async fn send_user_turn_with_changes_sends_environment_context() {
]);
assert_eq!(body1["input"], expected_input_1);
let expected_env_msg_2 = text_user_input(format!(
let expected_env_msg_2 = text_user_input(&format!(
r#"<environment_context>
<cwd>{}</cwd>
<approval_policy>never</approval_policy>
@@ -820,7 +820,7 @@ async fn send_user_turn_with_changes_sends_environment_context() {
</environment_context>"#,
default_cwd.to_string_lossy()
));
let expected_user_message_2 = text_user_input("hello 2".to_string());
let expected_user_message_2 = text_user_input("hello 2");
let expected_input_2 = serde_json::Value::Array(vec![
expected_ui_msg,
expected_env_msg_1,

View File

@@ -573,7 +573,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
}
fn escape_command(command: &[String]) -> String {
try_join(command.iter().map(|s| s.as_str())).unwrap_or_else(|_| command.join(" "))
try_join(command.iter().map(std::string::String::as_str)).unwrap_or_else(|_| command.join(" "))
}
fn format_file_change(change: &FileChange) -> &'static str {

View File

@@ -45,7 +45,7 @@ fn find_session_file_containing_marker(
&& payload.get("type").and_then(|t| t.as_str()) == Some("message")
&& payload
.get("content")
.map(|c| c.to_string())
.map(std::string::ToString::to_string)
.unwrap_or_default()
.contains(marker)
{

View File

@@ -89,15 +89,14 @@ pub fn resolve_observed_args_with_patterns(
program: program.to_string(),
matcher: pattern,
});
} else {
for positional_arg in vararg {
let matched_arg = MatchedArg::new(
positional_arg.index,
pattern.arg_type(),
&positional_arg.value.clone(),
)?;
matched_args.push(matched_arg);
}
}
for positional_arg in vararg {
let matched_arg = MatchedArg::new(
positional_arg.index,
pattern.arg_type(),
&positional_arg.value.clone(),
)?;
matched_args.push(matched_arg);
}
}
ArgMatcherCardinality::ZeroOrMore => {

View File

@@ -108,7 +108,7 @@ fn ensure_absolute_path(path: &str, cwd: &Option<OsString>) -> Result<PathBuf> {
file.absolutize()
};
result
.map(|path| path.into_owned())
.map(std::borrow::Cow::into_owned)
.map_err(|error| CannotCanonicalizePath {
file: path.to_string(),
error: error.kind(),

View File

@@ -10,6 +10,7 @@ use codex_execpolicy::get_default_policy;
use serde::Deserialize;
use serde::Serialize;
use serde::de;
use starlark::Error as StarlarkError;
use std::path::PathBuf;
use std::str::FromStr;
@@ -71,13 +72,13 @@ fn main() -> Result<()> {
}
None => get_default_policy(),
};
let policy = policy.map_err(|err| err.into_anyhow())?;
let policy = policy.map_err(StarlarkError::into_anyhow)?;
let exec = match args.command {
Command::Check { command } => match command.split_first() {
Some((first, rest)) => ExecArg {
program: first.to_string(),
args: rest.iter().map(|s| s.to_string()).collect(),
args: rest.to_vec(),
},
None => {
eprintln!("no command provided");
@@ -161,6 +162,6 @@ impl FromStr for ExecArg {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).map_err(|e| e.into())
serde_json::from_str(s).map_err(Into::into)
}
}

View File

@@ -22,7 +22,7 @@ impl Policy {
pub fn new(
programs: MultiMap<String, ProgramSpec>,
forbidden_program_regexes: Vec<ForbiddenProgramRegex>,
forbidden_substrings: Vec<String>,
forbidden_substrings: &[String],
) -> std::result::Result<Self, RegexError> {
let forbidden_substrings_pattern = if forbidden_substrings.is_empty() {
None

View File

@@ -97,7 +97,7 @@ impl PolicyBuilder {
let programs = self.programs.into_inner();
let forbidden_program_regexes = self.forbidden_program_regexes.into_inner();
let forbidden_substrings = self.forbidden_substrings.into_inner();
Policy::new(programs, forbidden_program_regexes, forbidden_substrings)
Policy::new(programs, forbidden_program_regexes, &forbidden_substrings)
}
fn add_program_spec(&self, program_spec: ProgramSpec) {

View File

@@ -169,7 +169,7 @@ impl ProgramSpec {
let mut options = self
.required_options
.difference(&matched_opt_names)
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<_>>();
options.sort();
return Err(Error::MissingRequiredOptions {

View File

@@ -103,7 +103,7 @@ pub async fn run_main<T: Reporter>(
&search_directory,
exclude,
threads,
cancel_flag,
&cancel_flag,
compute_indices,
)?;
let match_count = matches.len();
@@ -127,7 +127,7 @@ pub fn run(
search_directory: &Path,
exclude: Vec<String>,
threads: NonZero<usize>,
cancel_flag: Arc<AtomicBool>,
cancel_flag: &Arc<AtomicBool>,
compute_indices: bool,
) -> anyhow::Result<FileSearchResults> {
let pattern = create_pattern(pattern_text);
@@ -183,7 +183,7 @@ pub fn run(
const CHECK_INTERVAL: usize = 1024;
let mut processed = 0;
let cancel = cancel_flag.clone();
let cancel = Arc::clone(cancel_flag);
Box::new(move |entry| {
if let Some(path) = get_file_path(&entry, search_directory) {

View File

@@ -36,12 +36,12 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
}
if !sandbox_policy.has_full_disk_write_access() {
let writable_roots = sandbox_policy
let writable_roots: Vec<PathBuf> = sandbox_policy
.get_writable_roots_with_cwd(cwd)
.into_iter()
.map(|writable_root| writable_root.root)
.collect();
install_filesystem_landlock_rules_on_current_thread(writable_roots)?;
install_filesystem_landlock_rules_on_current_thread(&writable_roots)?;
}
// TODO(ragona): Add appropriate restrictions if
@@ -56,7 +56,7 @@ pub(crate) fn apply_sandbox_policy_to_current_thread(
///
/// # Errors
/// Returns [`CodexErr::Sandbox`] variants when the ruleset fails to apply.
fn install_filesystem_landlock_rules_on_current_thread(writable_roots: Vec<PathBuf>) -> Result<()> {
fn install_filesystem_landlock_rules_on_current_thread(writable_roots: &[PathBuf]) -> Result<()> {
let abi = ABI::V5;
let access_rw = AccessFs::from_all(abi);
let access_ro = AccessFs::from_read(abi);
@@ -70,7 +70,7 @@ fn install_filesystem_landlock_rules_on_current_thread(writable_roots: Vec<PathB
.set_no_new_privs(true);
if !writable_roots.is_empty() {
ruleset = ruleset.add_rules(landlock::path_beneath_rules(&writable_roots, access_rw))?;
ruleset = ruleset.add_rules(landlock::path_beneath_rules(writable_roots, access_rw))?;
}
let status = ruleset.restrict_self()?;

View File

@@ -38,7 +38,7 @@ async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) {
let cwd = std::env::current_dir().expect("cwd should exist");
let sandbox_cwd = cwd.clone();
let params = ExecParams {
command: cmd.iter().map(|elm| elm.to_string()).collect(),
command: cmd.iter().map(std::string::ToString::to_string).collect(),
cwd,
timeout_ms: Some(timeout_ms),
env: create_env_from_core_vars(),
@@ -138,7 +138,7 @@ async fn assert_network_blocked(cmd: &[&str]) {
let cwd = std::env::current_dir().expect("cwd should exist");
let sandbox_cwd = cwd.clone();
let params = ExecParams {
command: cmd.iter().map(|s| s.to_string()).collect(),
command: cmd.iter().map(std::string::ToString::to_string).collect(),
cwd,
// Give the tool a generous 2-second timeout so even slow DNS timeouts
// do not stall the suite.

View File

@@ -496,11 +496,11 @@ fn compose_success_url(port: u16, issuer: &str, id_token: &str, access_token: &s
.unwrap_or("");
let completed_onboarding = token_claims
.get("completed_platform_onboarding")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let is_org_owner = token_claims
.get("is_org_owner")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let needs_setup = (!completed_onboarding) && is_org_owner;
let plan_type = access_claims

View File

@@ -57,8 +57,8 @@ pub(crate) async fn handle_exec_approval_request(
event_id: String,
call_id: String,
) {
let escaped_command =
shlex::try_join(command.iter().map(|s| s.as_str())).unwrap_or_else(|_| command.join(" "));
let escaped_command = shlex::try_join(command.iter().map(std::string::String::as_str))
.unwrap_or_else(|_| command.join(" "));
let message = format!(
"Allow Codex to run `{escaped_command}` in `{cwd}`?",
cwd = cwd.to_string_lossy()

View File

@@ -111,7 +111,7 @@ pub async fn run_main(
JSONRPCMessage::Request(r) => processor.process_request(r).await,
JSONRPCMessage::Response(r) => processor.process_response(r).await,
JSONRPCMessage::Notification(n) => processor.process_notification(n).await,
JSONRPCMessage::Error(e) => processor.process_error(e),
JSONRPCMessage::Error(e) => processor.process_error(&e),
}
}

View File

@@ -106,25 +106,25 @@ impl MessageProcessor {
self.handle_ping(request_id, params).await;
}
McpClientRequest::ListResourcesRequest(params) => {
self.handle_list_resources(params);
self.handle_list_resources(&params);
}
McpClientRequest::ListResourceTemplatesRequest(params) => {
self.handle_list_resource_templates(params);
self.handle_list_resource_templates(&params);
}
McpClientRequest::ReadResourceRequest(params) => {
self.handle_read_resource(params);
self.handle_read_resource(&params);
}
McpClientRequest::SubscribeRequest(params) => {
self.handle_subscribe(params);
self.handle_subscribe(&params);
}
McpClientRequest::UnsubscribeRequest(params) => {
self.handle_unsubscribe(params);
self.handle_unsubscribe(&params);
}
McpClientRequest::ListPromptsRequest(params) => {
self.handle_list_prompts(params);
self.handle_list_prompts(&params);
}
McpClientRequest::GetPromptRequest(params) => {
self.handle_get_prompt(params);
self.handle_get_prompt(&params);
}
McpClientRequest::ListToolsRequest(params) => {
self.handle_list_tools(request_id, params).await;
@@ -133,10 +133,10 @@ impl MessageProcessor {
self.handle_call_tool(request_id, params).await;
}
McpClientRequest::SetLevelRequest(params) => {
self.handle_set_level(params);
self.handle_set_level(&params);
}
McpClientRequest::CompleteRequest(params) => {
self.handle_complete(params);
self.handle_complete(&params);
}
}
}
@@ -165,28 +165,28 @@ impl MessageProcessor {
self.handle_cancelled_notification(params).await;
}
ServerNotification::ProgressNotification(params) => {
self.handle_progress_notification(params);
self.handle_progress_notification(&params);
}
ServerNotification::ResourceListChangedNotification(params) => {
self.handle_resource_list_changed(params);
self.handle_resource_list_changed(&params);
}
ServerNotification::ResourceUpdatedNotification(params) => {
self.handle_resource_updated(params);
self.handle_resource_updated(&params);
}
ServerNotification::PromptListChangedNotification(params) => {
self.handle_prompt_list_changed(params);
self.handle_prompt_list_changed(&params);
}
ServerNotification::ToolListChangedNotification(params) => {
self.handle_tool_list_changed(params);
self.handle_tool_list_changed(&params);
}
ServerNotification::LoggingMessageNotification(params) => {
self.handle_logging_message(params);
self.handle_logging_message(&params);
}
}
}
/// Handle an error object received from the peer.
pub(crate) fn process_error(&mut self, err: JSONRPCError) {
pub(crate) fn process_error(&mut self, err: &JSONRPCError) {
tracing::error!("<- error: {:?}", err);
}
@@ -264,50 +264,49 @@ impl MessageProcessor {
fn handle_list_resources(
&self,
params: <mcp_types::ListResourcesRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::ListResourcesRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("resources/list -> params: {:?}", params);
}
fn handle_list_resource_templates(
&self,
params:
<mcp_types::ListResourceTemplatesRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::ListResourceTemplatesRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("resources/templates/list -> params: {:?}", params);
}
fn handle_read_resource(
&self,
params: <mcp_types::ReadResourceRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::ReadResourceRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("resources/read -> params: {:?}", params);
}
fn handle_subscribe(
&self,
params: <mcp_types::SubscribeRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::SubscribeRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("resources/subscribe -> params: {:?}", params);
}
fn handle_unsubscribe(
&self,
params: <mcp_types::UnsubscribeRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::UnsubscribeRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("resources/unsubscribe -> params: {:?}", params);
}
fn handle_list_prompts(
&self,
params: <mcp_types::ListPromptsRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::ListPromptsRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("prompts/list -> params: {:?}", params);
}
fn handle_get_prompt(
&self,
params: <mcp_types::GetPromptRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::GetPromptRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("prompts/get -> params: {:?}", params);
}
@@ -550,14 +549,14 @@ impl MessageProcessor {
fn handle_set_level(
&self,
params: <mcp_types::SetLevelRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::SetLevelRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("logging/setLevel -> params: {:?}", params);
}
fn handle_complete(
&self,
params: <mcp_types::CompleteRequest as mcp_types::ModelContextProtocolRequest>::Params,
params: &<mcp_types::CompleteRequest as mcp_types::ModelContextProtocolRequest>::Params,
) {
tracing::info!("completion/complete -> params: {:?}", params);
}
@@ -623,14 +622,14 @@ impl MessageProcessor {
fn handle_progress_notification(
&self,
params: <mcp_types::ProgressNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::ProgressNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!("notifications/progress -> params: {:?}", params);
}
fn handle_resource_list_changed(
&self,
params: <mcp_types::ResourceListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::ResourceListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!(
"notifications/resources/list_changed -> params: {:?}",
@@ -640,28 +639,28 @@ impl MessageProcessor {
fn handle_resource_updated(
&self,
params: <mcp_types::ResourceUpdatedNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::ResourceUpdatedNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!("notifications/resources/updated -> params: {:?}", params);
}
fn handle_prompt_list_changed(
&self,
params: <mcp_types::PromptListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::PromptListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!("notifications/prompts/list_changed -> params: {:?}", params);
}
fn handle_tool_list_changed(
&self,
params: <mcp_types::ToolListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::ToolListChangedNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!("notifications/tools/list_changed -> params: {:?}", params);
}
fn handle_logging_message(
&self,
params: <mcp_types::LoggingMessageNotification as mcp_types::ModelContextProtocolNotification>::Params,
params: &<mcp_types::LoggingMessageNotification as mcp_types::ModelContextProtocolNotification>::Params,
) {
tracing::info!("notifications/message -> params: {:?}", params);
}

View File

@@ -172,7 +172,7 @@ fn create_expected_elicitation_request(
) -> anyhow::Result<JSONRPCRequest> {
let expected_message = format!(
"Allow Codex to run `{}` in `{}`?",
shlex::try_join(command.iter().map(|s| s.as_ref()))?,
shlex::try_join(command.iter().map(std::convert::AsRef::as_ref))?,
workdir.to_string_lossy()
);
Ok(JSONRPCRequest {

View File

@@ -68,7 +68,7 @@ async fn shell_command_interruption() -> anyhow::Result<()> {
"call_sleep",
)?])
.await;
create_config_toml(&codex_home, server.uri())?;
create_config_toml(&codex_home, &server.uri())?;
// Start MCP server and initialize.
let mut mcp = McpProcess::new(&codex_home).await?;
@@ -140,7 +140,7 @@ async fn shell_command_interruption() -> anyhow::Result<()> {
// Helpers
// ---------------------------------------------------------------------------
fn create_config_toml(codex_home: &Path, server_uri: String) -> std::io::Result<()> {
fn create_config_toml(codex_home: &Path, server_uri: &str) -> std::io::Result<()> {
let config_toml = codex_home.join("config.toml");
std::fs::write(
config_toml,

View File

@@ -117,7 +117,7 @@ impl OllamaClient {
.map(|arr| {
arr.iter()
.filter_map(|v| v.get("name").and_then(|n| n.as_str()))
.map(|s| s.to_string())
.map(std::string::ToString::to_string)
.collect::<Vec<_>>()
})
.unwrap_or_default();

View File

@@ -16,8 +16,8 @@ pub(crate) fn pull_events_from_value(value: &JsonValue) -> Vec<PullEvent> {
.and_then(|d| d.as_str())
.unwrap_or("")
.to_string();
let total = value.get("total").and_then(|t| t.as_u64());
let completed = value.get("completed").and_then(|t| t.as_u64());
let total = value.get("total").and_then(serde_json::Value::as_u64);
let completed = value.get("completed").and_then(serde_json::Value::as_u64);
if total.is_some() || completed.is_some() {
events.push(PullEvent::ChunkProgress {
digest,

View File

@@ -133,8 +133,9 @@ fn generate_index_ts(out_dir: &Path) -> Result<PathBuf> {
entries.push(format!("export type {{ {name} }} from \"./{name}\";\n"));
}
let mut content =
String::with_capacity(HEADER.len() + entries.iter().map(|s| s.len()).sum::<usize>());
let mut content = String::with_capacity(
HEADER.len() + entries.iter().map(std::string::String::len).sum::<usize>(),
);
content.push_str(HEADER);
for line in &entries {
content.push_str(line);

View File

@@ -188,10 +188,8 @@ impl App {
}
TuiEvent::Draw => {
self.chat_widget.maybe_post_pending_notification(tui);
if self
.chat_widget
.handle_paste_burst_tick(tui.frame_requester())
{
let frame_requester = tui.frame_requester();
if self.chat_widget.handle_paste_burst_tick(&frame_requester) {
return Ok(true);
}
tui.draw(
@@ -300,11 +298,11 @@ impl App {
}
AppEvent::StartFileSearch(query) => {
if !query.is_empty() {
self.file_search.on_user_query(query);
self.file_search.on_user_query(&query);
}
}
AppEvent::FileSearchResult { query, matches } => {
self.chat_widget.apply_file_search_result(query, matches);
self.chat_widget.apply_file_search_result(&query, matches);
}
AppEvent::UpdateReasoningEffort(effort) => {
self.on_update_reasoning_effort(effort);
@@ -338,12 +336,12 @@ impl App {
"failed to persist model selection"
);
if let Some(profile) = profile {
self.chat_widget.add_error_message(format!(
"Failed to save model for profile `{profile}`: {err}"
));
let message =
format!("Failed to save model for profile `{profile}`: {err}");
self.chat_widget.add_error_message(&message);
} else {
self.chat_widget
.add_error_message(format!("Failed to save default model: {err}"));
let message = format!("Failed to save default model: {err}");
self.chat_widget.add_error_message(&message);
}
}
}

View File

@@ -45,7 +45,7 @@ impl App {
kind: KeyEventKind::Press | KeyEventKind::Repeat,
..
}) => {
self.overlay_step_backtrack(tui, event)?;
self.overlay_step_backtrack(tui, &event)?;
Ok(true)
}
TuiEvent::Key(KeyEvent {
@@ -58,7 +58,7 @@ impl App {
}
// Catchall: forward any other events to the overlay widget.
_ => {
self.overlay_forward_event(tui, event)?;
self.overlay_forward_event(tui, &event)?;
Ok(true)
}
}
@@ -73,7 +73,7 @@ impl App {
Ok(true)
} else {
// Not in backtrack mode: forward events to the overlay widget.
self.overlay_forward_event(tui, event)?;
self.overlay_forward_event(tui, &event)?;
Ok(true)
}
}
@@ -202,7 +202,7 @@ impl App {
}
/// Forward any event to the overlay and close it if done.
fn overlay_forward_event(&mut self, tui: &mut tui::Tui, event: TuiEvent) -> Result<()> {
fn overlay_forward_event(&mut self, tui: &mut tui::Tui, event: &TuiEvent) -> Result<()> {
if let Some(overlay) = &mut self.overlay {
overlay.handle_event(tui, event)?;
if overlay.is_done() {
@@ -233,7 +233,7 @@ impl App {
}
/// Handle Esc in overlay backtrack preview: step selection if armed, else forward.
fn overlay_step_backtrack(&mut self, tui: &mut tui::Tui, event: TuiEvent) -> Result<()> {
fn overlay_step_backtrack(&mut self, tui: &mut tui::Tui, event: &TuiEvent) -> Result<()> {
if self.backtrack.base_id.is_some() {
self.step_backtrack_and_highlight(tui);
} else {
@@ -344,7 +344,7 @@ impl App {
self.trim_transcript_for_backtrack(nth_user_message);
self.render_transcript_once(tui);
if !prefill.is_empty() {
self.chat_widget.set_composer_text(prefill.to_string());
self.chat_widget.set_composer_text(prefill);
}
tui.frame_requester().schedule_frame();
}

View File

@@ -162,7 +162,7 @@ impl ChatComposer {
textarea_rect.width = textarea_rect.width.saturating_sub(LIVE_PREFIX_COLS);
textarea_rect.x = textarea_rect.x.saturating_add(LIVE_PREFIX_COLS);
let state = self.textarea_state.borrow();
self.textarea.cursor_pos_with_state(textarea_rect, &state)
self.textarea.cursor_pos_with_state(textarea_rect, *state)
}
/// Returns true if the composer currently contains no user input.
@@ -206,7 +206,7 @@ impl ChatComposer {
let placeholder = format!("[Pasted Content {char_count} chars]");
self.textarea.insert_element(&placeholder);
self.pending_pastes.push((placeholder, pasted));
} else if char_count > 1 && self.handle_paste_image_path(pasted.clone()) {
} else if char_count > 1 && self.handle_paste_image_path(&pasted) {
self.textarea.insert_str(" ");
} else {
self.textarea.insert_str(&pasted);
@@ -224,8 +224,8 @@ impl ChatComposer {
true
}
pub fn handle_paste_image_path(&mut self, pasted: String) -> bool {
let Some(path_buf) = normalize_pasted_path(&pasted) else {
pub fn handle_paste_image_path(&mut self, pasted: &str) -> bool {
let Some(path_buf) = normalize_pasted_path(pasted) else {
return false;
};
@@ -252,12 +252,12 @@ impl ChatComposer {
}
/// Replace the entire composer content with `text` and reset cursor.
pub(crate) fn set_text_content(&mut self, text: String) {
pub(crate) fn set_text_content(&mut self, text: &str) {
// Clear any existing content, placeholders, and attachments first.
self.textarea.set_text("");
self.pending_pastes.clear();
self.attached_images.clear();
self.textarea.set_text(&text);
self.textarea.set_text(text);
self.textarea.set_cursor(0);
self.sync_command_popup();
self.sync_file_search_popup();
@@ -297,19 +297,19 @@ impl ChatComposer {
}
/// Integrate results from an asynchronous file search.
pub(crate) fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
pub(crate) fn on_file_search_result(&mut self, query: &str, matches: Vec<FileMatch>) {
// Only apply if user is still editing a token starting with `query`.
let current_opt = Self::current_at_token(&self.textarea);
let Some(current_token) = current_opt else {
return;
};
if !current_token.starts_with(&query) {
if !current_token.starts_with(query) {
return;
}
if let ActivePopup::File(popup) = &mut self.active_popup {
popup.set_matches(&query, matches);
popup.set_matches(query, matches);
}
}
@@ -381,7 +381,7 @@ impl ChatComposer {
// Ensure popup filtering/selection reflects the latest composer text
// before applying completion.
let first_line = self.textarea.text().lines().next().unwrap_or("");
popup.on_composer_text_change(first_line.to_string());
popup.on_composer_text_change(first_line);
if let Some(sel) = popup.selected_item() {
match sel {
CommandItem::Builtin(cmd) => {
@@ -420,9 +420,9 @@ impl ChatComposer {
self.textarea.set_text("");
// Capture any needed data from popup before clearing it.
let prompt_content = match sel {
CommandItem::UserPrompt(idx) => {
popup.prompt_content(idx).map(|s| s.to_string())
}
CommandItem::UserPrompt(idx) => popup
.prompt_content(idx)
.map(std::string::ToString::to_string),
_ => None,
};
// Hide popup since an action has been dispatched.
@@ -550,7 +550,7 @@ impl ChatComposer {
let format_label = match Path::new(&sel_path)
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_ascii_lowercase())
.map(str::to_ascii_lowercase)
{
Some(ext) if ext == "png" => "PNG",
Some(ext) if ext == "jpg" || ext == "jpeg" => "JPEG",
@@ -617,7 +617,7 @@ impl ChatComposer {
text[safe_cursor..]
.chars()
.next()
.map(|c| c.is_whitespace())
.map(char::is_whitespace)
.unwrap_or(false)
} else {
false
@@ -645,7 +645,7 @@ impl ChatComposer {
let ws_len_right: usize = after_cursor
.chars()
.take_while(|c| c.is_whitespace())
.map(|c| c.len_utf8())
.map(char::len_utf8)
.sum();
let start_right = safe_cursor + ws_len_right;
let end_right_rel = text[start_right..]
@@ -910,7 +910,8 @@ impl ChatComposer {
if !grab.grabbed.is_empty() {
self.textarea.replace_range(grab.start_byte..safe_cur, "");
}
self.paste_burst.begin_with_retro_grabbed(grab.grabbed, now);
self.paste_burst
.begin_with_retro_grabbed(&grab.grabbed, now);
self.paste_burst.append_char_to_buffer(ch, now);
return (InputResult::None, true);
}
@@ -1092,9 +1093,8 @@ impl ChatComposer {
.nth(occ_before)
{
break 'out Some((remove_idx, ph.clone()));
} else {
break 'out Some((i, ph.clone()));
}
break 'out Some((i, ph.clone()));
}
None
};
@@ -1150,7 +1150,7 @@ impl ChatComposer {
match &mut self.active_popup {
ActivePopup::Command(popup) => {
if input_starts_with_slash {
popup.on_composer_text_change(first_line.to_string());
popup.on_composer_text_change(first_line);
} else {
self.active_popup = ActivePopup::None;
}
@@ -1158,7 +1158,7 @@ impl ChatComposer {
_ => {
if input_starts_with_slash {
let mut command_popup = CommandPopup::new(self.custom_prompts.clone());
command_popup.on_composer_text_change(first_line.to_string());
command_popup.on_composer_text_change(first_line);
self.active_popup = ActivePopup::Command(command_popup);
}
}

View File

@@ -65,7 +65,7 @@ impl CommandPopup {
/// 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) {
pub(crate) fn on_composer_text_change(&mut self, text: &str) {
let first_line = text.lines().next().unwrap_or("");
if let Some(stripped) = first_line.strip_prefix('/') {
@@ -224,7 +224,7 @@ mod tests {
let mut popup = CommandPopup::new(Vec::new());
// Simulate the composer line starting with '/in' so the popup filters
// matching commands by prefix.
popup.on_composer_text_change("/in".to_string());
popup.on_composer_text_change("/in");
// Access the filtered list via the selected command and ensure that
// one of the matches is the new "init" command.
@@ -242,7 +242,7 @@ mod tests {
#[test]
fn selecting_init_by_exact_match() {
let mut popup = CommandPopup::new(Vec::new());
popup.on_composer_text_change("/init".to_string());
popup.on_composer_text_change("/init");
// When an exact match exists, the selected command should be that
// command by default.
@@ -257,7 +257,7 @@ mod tests {
#[test]
fn model_is_first_suggestion_for_mo() {
let mut popup = CommandPopup::new(Vec::new());
popup.on_composer_text_change("/mo".to_string());
popup.on_composer_text_change("/mo");
let matches = popup.filtered_items();
match matches.first() {
Some(CommandItem::Builtin(cmd)) => assert_eq!(cmd.command(), "model"),
@@ -287,7 +287,9 @@ mod tests {
let mut prompt_names: Vec<String> = items
.into_iter()
.filter_map(|it| match it {
CommandItem::UserPrompt(i) => popup.prompt_name(i).map(|s| s.to_string()),
CommandItem::UserPrompt(i) => {
popup.prompt_name(i).map(std::string::ToString::to_string)
}
_ => None,
})
.collect();

View File

@@ -237,7 +237,7 @@ impl BottomPaneView for CustomPromptView {
height: text_area_height,
};
let state = self.textarea_state.borrow();
self.textarea.cursor_pos_with_state(textarea_rect, &state)
self.textarea.cursor_pos_with_state(textarea_rect, *state)
}
}

View File

@@ -201,7 +201,7 @@ impl BottomPane {
return if self.composer_is_empty() {
CancellationEvent::NotHandled
} else {
self.set_composer_text(String::new());
self.set_composer_text("");
self.show_ctrl_c_quit_hint();
CancellationEvent::Handled
};
@@ -250,7 +250,7 @@ impl BottomPane {
}
/// Replace the composer text with `text`.
pub(crate) fn set_composer_text(&mut self, text: String) {
pub(crate) fn set_composer_text(&mut self, text: &str) {
self.composer.set_text_content(text);
self.request_redraw();
}
@@ -452,7 +452,7 @@ impl BottomPane {
}
}
pub(crate) fn on_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
pub(crate) fn on_file_search_result(&mut self, query: &str, matches: Vec<FileMatch>) {
self.composer.on_file_search_result(query, matches);
self.request_redraw();
}

View File

@@ -149,9 +149,9 @@ impl PasteBurst {
}
/// Begin buffering with retroactively grabbed text.
pub fn begin_with_retro_grabbed(&mut self, grabbed: String, now: Instant) {
pub fn begin_with_retro_grabbed(&mut self, grabbed: &str, now: Instant) {
if !grabbed.is_empty() {
self.buffer.push_str(&grabbed);
self.buffer.push_str(grabbed);
}
self.active = true;
self.burst_window_until = Some(now + PASTE_ENTER_SUPPRESS_WINDOW);
@@ -183,10 +183,10 @@ impl PasteBurst {
let start_byte = retro_start_index(before, retro_chars);
let grabbed = before[start_byte..].to_string();
let looks_pastey =
grabbed.chars().any(|c| c.is_whitespace()) || grabbed.chars().count() >= 16;
grabbed.chars().any(char::is_whitespace) || grabbed.chars().count() >= 16;
if looks_pastey {
// Note: caller is responsible for removing this slice from UI text.
self.begin_with_retro_grabbed(grabbed.clone(), now);
self.begin_with_retro_grabbed(&grabbed, now);
Some(RetroGrab {
start_byte,
grabbed,

View File

@@ -132,11 +132,11 @@ impl TextArea {
#[cfg_attr(not(test), allow(dead_code))]
pub fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> {
self.cursor_pos_with_state(area, &TextAreaState::default())
self.cursor_pos_with_state(area, TextAreaState::default())
}
/// Compute the on-screen cursor position taking scrolling into account.
pub fn cursor_pos_with_state(&self, area: Rect, state: &TextAreaState) -> Option<(u16, u16)> {
pub fn cursor_pos_with_state(&self, area: Rect, state: TextAreaState) -> Option<(u16, u16)> {
let lines = self.wrapped_lines(area.width);
let effective_scroll = self.effective_scroll(area.height, &lines, state.scroll);
let i = Self::wrapped_line_index_by_start(&lines, self.cursor_pos)?;
@@ -1408,7 +1408,7 @@ mod tests {
let mut state = TextAreaState::default();
let small_area = Rect::new(0, 0, 6, 1);
// First call: cursor not visible -> effective scroll ensures it is
let (_x, y) = t.cursor_pos_with_state(small_area, &state).unwrap();
let (_x, y) = t.cursor_pos_with_state(small_area, state).unwrap();
assert_eq!(y, 0);
// Render with state to update actual scroll value
@@ -1429,7 +1429,7 @@ mod tests {
// effective scroll is 0 and the cursor position matches cursor_pos.
let bad_state = TextAreaState { scroll: 999 };
let (x1, y1) = t.cursor_pos(area).unwrap();
let (x2, y2) = t.cursor_pos_with_state(area, &bad_state).unwrap();
let (x2, y2) = t.cursor_pos_with_state(area, bad_state).unwrap();
assert_eq!((x2, y2), (x1, y1));
// Case 2: Cursor below the current window — y should be clamped to the
@@ -1442,7 +1442,7 @@ mod tests {
t.set_cursor(t.text().len().saturating_sub(2));
let small_area = Rect::new(0, 0, wrap_width, 2);
let state = TextAreaState { scroll: 0 };
let (_x, y) = t.cursor_pos_with_state(small_area, &state).unwrap();
let (_x, y) = t.cursor_pos_with_state(small_area, state).unwrap();
assert_eq!(y, small_area.y + small_area.height - 1);
// Case 3: Cursor above the current window — y should be top row (0)
@@ -1456,7 +1456,7 @@ mod tests {
let state = TextAreaState {
scroll: lines.saturating_mul(2),
};
let (_x, y) = t.cursor_pos_with_state(area, &state).unwrap();
let (_x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!(y, area.y);
}
@@ -1480,7 +1480,7 @@ mod tests {
// With state and small height, cursor should be visible at row 0, col 0
let small_area = Rect::new(0, 0, 4, 1);
let state = TextAreaState::default();
let (x, y) = t.cursor_pos_with_state(small_area, &state).unwrap();
let (x, y) = t.cursor_pos_with_state(small_area, state).unwrap();
assert_eq!((x, y), (0, 0));
// Place cursor in the middle of the second wrapped line ("efgh"), at 'g'
@@ -1510,35 +1510,35 @@ mod tests {
// Start at beginning
t.set_cursor(0);
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x, y) = t.cursor_pos_with_state(area, &state).unwrap();
let (x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x, y), (0, 0));
// Move down to second visual line; should be at bottom row (row 1) within 2-line viewport
t.move_cursor_down();
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x, y) = t.cursor_pos_with_state(area, &state).unwrap();
let (x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x, y), (0, 1));
// Move down to third visual line; viewport scrolls and keeps cursor on bottom row
t.move_cursor_down();
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x, y) = t.cursor_pos_with_state(area, &state).unwrap();
let (x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x, y), (0, 1));
// Move up to second visual line; with current scroll, it appears on top row
t.move_cursor_up();
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x, y) = t.cursor_pos_with_state(area, &state).unwrap();
let (x, y) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x, y), (0, 0));
// Column preservation across moves: set to col 2 on first line, move down
t.set_cursor(2);
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x0, y0) = t.cursor_pos_with_state(area, &state).unwrap();
let (x0, y0) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x0, y0), (2, 0));
t.move_cursor_down();
ratatui::widgets::StatefulWidgetRef::render_ref(&(&t), area, &mut buf, &mut state);
let (x1, y1) = t.cursor_pos_with_state(area, &state).unwrap();
let (x1, y1) = t.cursor_pos_with_state(area, state).unwrap();
assert_eq!((x1, y1), (2, 1));
}
@@ -1796,7 +1796,7 @@ mod tests {
// cursor_pos_with_state: always within viewport rows
let (_x, _y) = ta
.cursor_pos_with_state(area, &state)
.cursor_pos_with_state(area, state)
.unwrap_or((area.x, area.y));
// Stateful render should not panic, and updates scroll

View File

@@ -248,22 +248,22 @@ impl ChatWidget {
}
}
fn on_agent_message(&mut self, message: String) {
fn on_agent_message(&mut self, message: &str) {
let sink = AppEventHistorySink(self.app_event_tx.clone());
let finished = self.stream.apply_final_answer(&message, &sink);
let finished = self.stream.apply_final_answer(message, &sink);
self.handle_if_stream_finished(finished);
self.request_redraw();
}
fn on_agent_message_delta(&mut self, delta: String) {
fn on_agent_message_delta(&mut self, delta: &str) {
self.handle_streaming_delta(delta);
}
fn on_agent_reasoning_delta(&mut self, delta: String) {
fn on_agent_reasoning_delta(&mut self, delta: &str) {
// For reasoning deltas, do not stream to history. Accumulate the
// current reasoning block and extract the first bold element
// (between **/**) as the chunk header. Show this header as status.
self.reasoning_buffer.push_str(&delta);
self.reasoning_buffer.push_str(delta);
if let Some(header) = extract_first_bold(&self.reasoning_buffer) {
// Update the shimmer header to the extracted reasoning chunk header.
@@ -279,7 +279,7 @@ impl ChatWidget {
self.full_reasoning_buffer.push_str(&self.reasoning_buffer);
if !self.full_reasoning_buffer.is_empty() {
let cell = history_cell::new_reasoning_summary_block(
self.full_reasoning_buffer.clone(),
&self.full_reasoning_buffer,
&self.config,
);
self.add_boxed_history(cell);
@@ -340,7 +340,7 @@ impl ChatWidget {
self.rate_limit_snapshot = Some(snapshot);
if !warnings.is_empty() {
for warning in warnings {
self.add_to_history(history_cell::new_warning_event(warning));
self.add_to_history(history_cell::new_warning_event(&warning));
}
self.request_redraw();
}
@@ -356,7 +356,7 @@ impl ChatWidget {
self.stream.clear_all();
}
fn on_error(&mut self, message: String) {
fn on_error(&mut self, message: &str) {
self.finalize_turn();
self.add_to_history(history_cell::new_error_event(message));
self.request_redraw();
@@ -368,13 +368,13 @@ impl ChatWidget {
/// Handle a turn aborted due to user interrupt (Esc).
/// When there are queued user messages, restore them into the composer
/// separated by newlines rather than autosubmitting the next one.
fn on_interrupted_turn(&mut self, reason: TurnAbortReason) {
fn on_interrupted_turn(&mut self, reason: &TurnAbortReason) {
// Finalize, log a gentle prompt, and clear running state.
self.finalize_turn();
if reason != TurnAbortReason::ReviewEnded {
if *reason != TurnAbortReason::ReviewEnded {
self.add_to_history(history_cell::new_error_event(
"Conversation interrupted - tell the model what to do differently".to_owned(),
"Conversation interrupted - tell the model what to do differently",
));
}
@@ -386,7 +386,7 @@ impl ChatWidget {
.map(|m| m.text.clone())
.collect::<Vec<_>>()
.join("\n");
self.bottom_pane.set_composer_text(combined);
self.bottom_pane.set_composer_text(&combined);
// Clear the queue and update the status indicator list.
self.queued_user_messages.clear();
self.refresh_queued_user_messages();
@@ -450,7 +450,7 @@ impl ChatWidget {
fn on_exec_command_end(&mut self, ev: ExecCommandEndEvent) {
let ev2 = ev.clone();
self.defer_or_handle(|q| q.push_exec_end(ev), |s| s.handle_exec_end_now(ev2));
self.defer_or_handle(|q| q.push_exec_end(ev), |s| s.handle_exec_end_now(&ev2));
}
fn on_mcp_tool_call_begin(&mut self, ev: McpToolCallBeginEvent) {
@@ -467,7 +467,7 @@ impl ChatWidget {
self.flush_answer_stream_with_separator();
}
fn on_web_search_end(&mut self, ev: WebSearchEndEvent) {
fn on_web_search_end(&mut self, ev: &WebSearchEndEvent) {
self.flush_answer_stream_with_separator();
self.add_to_history(history_cell::new_web_search_call(format!(
"Searched: {}",
@@ -492,11 +492,11 @@ impl ChatWidget {
self.app_event_tx.send(AppEvent::ExitRequest);
}
fn on_turn_diff(&mut self, unified_diff: String) {
fn on_turn_diff(&mut self, unified_diff: &str) {
debug!("TurnDiffEvent: {unified_diff}");
}
fn on_background_event(&mut self, message: String) {
fn on_background_event(&mut self, message: &str) {
debug!("BackgroundEvent: {message}");
}
@@ -551,16 +551,16 @@ impl ChatWidget {
}
#[inline]
fn handle_streaming_delta(&mut self, delta: String) {
fn handle_streaming_delta(&mut self, delta: &str) {
// Before streaming agent content, flush any active exec cell group.
self.flush_active_exec_cell();
let sink = AppEventHistorySink(self.app_event_tx.clone());
self.stream.begin(&sink);
self.stream.push_and_maybe_commit(&delta, &sink);
self.stream.push_and_maybe_commit(delta, &sink);
self.request_redraw();
}
pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {
pub(crate) fn handle_exec_end_now(&mut self, ev: &ExecCommandEndEvent) {
let running = self.running_commands.remove(&ev.call_id);
let (command, parsed) = match running {
Some(rc) => (rc.command, rc.parsed_cmd),
@@ -608,7 +608,7 @@ impl ChatWidget {
self.flush_answer_stream_with_separator();
// Emit the proposed command into history (like proposed patches)
self.add_to_history(history_cell::new_proposed_command(&ev.command));
let command = shlex::try_join(ev.command.iter().map(|s| s.as_str()))
let command = shlex::try_join(ev.command.iter().map(std::string::String::as_str))
.unwrap_or_else(|_| ev.command.join(" "));
self.notify(Notification::ExecApprovalRequested { command });
@@ -881,7 +881,7 @@ impl ChatWidget {
} if !self.queued_user_messages.is_empty() => {
// Prefer the most recently queued item.
if let Some(user_message) = self.queued_user_messages.pop_back() {
self.bottom_pane.set_composer_text(user_message.text);
self.bottom_pane.set_composer_text(&user_message.text);
self.refresh_queued_user_messages();
self.request_redraw();
}
@@ -931,7 +931,7 @@ impl ChatWidget {
"'/{}' is disabled while a task is in progress.",
cmd.command()
);
self.add_to_history(history_cell::new_error_event(message));
self.add_to_history(history_cell::new_error_event(&message));
self.request_redraw();
return;
}
@@ -1040,7 +1040,7 @@ impl ChatWidget {
}
// Returns true if caller should skip rendering this frame (a future frame is scheduled).
pub(crate) fn handle_paste_burst_tick(&mut self, frame_requester: FrameRequester) -> bool {
pub(crate) fn handle_paste_burst_tick(&mut self, frame_requester: &FrameRequester) -> bool {
if self.bottom_pane.flush_paste_burst_if_due() {
// A paste just flushed; request an immediate redraw and skip this frame.
self.request_redraw();
@@ -1150,17 +1150,19 @@ impl ChatWidget {
match msg {
EventMsg::SessionConfigured(e) => self.on_session_configured(e),
EventMsg::AgentMessage(AgentMessageEvent { message }) => self.on_agent_message(message),
EventMsg::AgentMessage(AgentMessageEvent { message }) => {
self.on_agent_message(&message)
}
EventMsg::AgentMessageDelta(AgentMessageDeltaEvent { delta }) => {
self.on_agent_message_delta(delta)
self.on_agent_message_delta(&delta)
}
EventMsg::AgentReasoningDelta(AgentReasoningDeltaEvent { delta })
| EventMsg::AgentReasoningRawContentDelta(AgentReasoningRawContentDeltaEvent {
delta,
}) => self.on_agent_reasoning_delta(delta),
}) => self.on_agent_reasoning_delta(&delta),
EventMsg::AgentReasoning(AgentReasoningEvent { .. }) => self.on_agent_reasoning_final(),
EventMsg::AgentReasoningRawContent(AgentReasoningRawContentEvent { text }) => {
self.on_agent_reasoning_delta(text);
self.on_agent_reasoning_delta(&text);
self.on_agent_reasoning_final()
}
EventMsg::AgentReasoningSectionBreak(_) => self.on_reasoning_section_break(),
@@ -1172,18 +1174,15 @@ impl ChatWidget {
self.set_token_info(ev.info);
self.on_rate_limit_snapshot(ev.rate_limits);
}
EventMsg::Error(ErrorEvent { message }) => self.on_error(message),
EventMsg::TurnAborted(ev) => match ev.reason {
TurnAbortReason::Interrupted => {
self.on_interrupted_turn(ev.reason);
EventMsg::Error(ErrorEvent { message }) => self.on_error(&message),
EventMsg::TurnAborted(ev) => {
let reason = ev.reason;
if reason == TurnAbortReason::Replaced {
self.on_error("Turn aborted: replaced by a new task")
} else {
self.on_interrupted_turn(&reason);
}
TurnAbortReason::Replaced => {
self.on_error("Turn aborted: replaced by a new task".to_owned())
}
TurnAbortReason::ReviewEnded => {
self.on_interrupted_turn(ev.reason);
}
},
}
EventMsg::PlanUpdate(update) => self.on_plan_update(update),
EventMsg::ExecApprovalRequest(ev) => {
// For replayed events, synthesize an empty id (these should not occur).
@@ -1200,19 +1199,19 @@ impl ChatWidget {
EventMsg::McpToolCallBegin(ev) => self.on_mcp_tool_call_begin(ev),
EventMsg::McpToolCallEnd(ev) => self.on_mcp_tool_call_end(ev),
EventMsg::WebSearchBegin(ev) => self.on_web_search_begin(ev),
EventMsg::WebSearchEnd(ev) => self.on_web_search_end(ev),
EventMsg::WebSearchEnd(ev) => self.on_web_search_end(&ev),
EventMsg::GetHistoryEntryResponse(ev) => self.on_get_history_entry_response(ev),
EventMsg::McpListToolsResponse(ev) => self.on_list_mcp_tools(ev),
EventMsg::McpListToolsResponse(ev) => self.on_list_mcp_tools(&ev),
EventMsg::ListCustomPromptsResponse(ev) => self.on_list_custom_prompts(ev),
EventMsg::ShutdownComplete => self.on_shutdown_complete(),
EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => self.on_turn_diff(unified_diff),
EventMsg::TurnDiff(TurnDiffEvent { unified_diff }) => self.on_turn_diff(&unified_diff),
EventMsg::BackgroundEvent(BackgroundEventEvent { message }) => {
self.on_background_event(message)
self.on_background_event(&message)
}
EventMsg::StreamError(StreamErrorEvent { message }) => self.on_stream_error(message),
EventMsg::UserMessage(ev) => {
if from_replay {
self.on_user_message_event(ev);
self.on_user_message_event(&ev);
}
}
EventMsg::ConversationPath(ev) => {
@@ -1220,16 +1219,19 @@ impl ChatWidget {
.send(crate::app_event::AppEvent::ConversationHistory(ev));
}
EventMsg::EnteredReviewMode(review_request) => {
self.on_entered_review_mode(review_request)
self.on_entered_review_mode(&review_request)
}
EventMsg::ExitedReviewMode(review) => self.on_exited_review_mode(review),
}
}
fn on_entered_review_mode(&mut self, review: ReviewRequest) {
fn on_entered_review_mode(&mut self, review: &ReviewRequest) {
// Enter review mode and emit a concise banner
self.is_review_mode = true;
let banner = format!(">> Code review started: {} <<", review.user_facing_hint);
let banner = format!(
">> Code review started: {} <<",
review.user_facing_hint.as_str()
);
self.add_to_history(history_cell::new_review_status_line(banner));
self.request_redraw();
}
@@ -1246,7 +1248,7 @@ impl ChatWidget {
if explanation.is_empty() {
tracing::error!("Reviewer failed to output a response.");
self.add_to_history(history_cell::new_error_event(
"Reviewer failed to output a response.".to_owned(),
"Reviewer failed to output a response.",
));
} else {
// Show explanation when there are no structured findings.
@@ -1275,8 +1277,8 @@ impl ChatWidget {
self.request_redraw();
}
fn on_user_message_event(&mut self, event: UserMessageEvent) {
match event.kind {
fn on_user_message_event(&mut self, event: &UserMessageEvent) {
match event.kind.as_ref() {
Some(InputMessageKind::EnvironmentContext)
| Some(InputMessageKind::UserInstructions) => {
// Skip XMLwrapped context blocks in the transcript.
@@ -1503,7 +1505,7 @@ impl ChatWidget {
self.request_redraw();
}
pub(crate) fn add_error_message(&mut self, message: String) {
pub(crate) fn add_error_message(&mut self, message: &str) {
self.add_to_history(history_cell::new_error_event(message));
self.request_redraw();
}
@@ -1517,7 +1519,7 @@ impl ChatWidget {
}
/// Forward file-search results to the bottom pane.
pub(crate) fn apply_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
pub(crate) fn apply_file_search_result(&mut self, query: &str, matches: Vec<FileMatch>) {
self.bottom_pane.on_file_search_result(query, matches);
}
@@ -1552,7 +1554,7 @@ impl ChatWidget {
}
/// Replace the composer content with the provided text and reset cursor.
pub(crate) fn set_composer_text(&mut self, text: String) {
pub(crate) fn set_composer_text(&mut self, text: &str) {
self.bottom_pane.set_composer_text(text);
}
@@ -1572,8 +1574,8 @@ impl ChatWidget {
}
}
fn on_list_mcp_tools(&mut self, ev: McpListToolsResponseEvent) {
self.add_to_history(history_cell::new_mcp_tools_output(&self.config, ev.tools));
fn on_list_mcp_tools(&mut self, ev: &McpListToolsResponseEvent) {
self.add_to_history(history_cell::new_mcp_tools_output(&self.config, &ev.tools));
}
fn on_list_custom_prompts(&mut self, ev: ListCustomPromptsResponseEvent) {
@@ -1900,9 +1902,8 @@ fn extract_first_bold(s: &str) -> Option<String> {
let trimmed = inner.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
} else {
return None;
}
return None;
}
j += 1;
}

View File

@@ -79,7 +79,7 @@ impl InterruptManager {
chat.handle_apply_patch_approval_now(id, ev)
}
QueuedInterrupt::ExecBegin(ev) => chat.handle_exec_begin_now(ev),
QueuedInterrupt::ExecEnd(ev) => chat.handle_exec_end_now(ev),
QueuedInterrupt::ExecEnd(ev) => chat.handle_exec_end_now(&ev),
QueuedInterrupt::McpBegin(ev) => chat.handle_mcp_begin_now(ev),
QueuedInterrupt::McpEnd(ev) => chat.handle_mcp_end_now(ev),
QueuedInterrupt::PatchEnd(ev) => chat.handle_patch_apply_end_now(ev),

View File

@@ -451,9 +451,9 @@ fn sample_rate_limit_snapshot(
}
}
fn capture_limits_snapshot(snapshot: Option<RateLimitSnapshotEvent>) -> String {
fn capture_limits_snapshot(snapshot: Option<&RateLimitSnapshotEvent>) -> String {
let lines = match snapshot {
Some(ref snapshot) => history_cell::new_limits_output(snapshot).display_lines(80),
Some(snapshot) => history_cell::new_limits_output(snapshot).display_lines(80),
None => history_cell::new_limits_unavailable().display_lines(80),
};
styled_lines_to_string(&lines)
@@ -467,25 +467,25 @@ fn limits_placeholder() {
#[test]
fn limits_snapshot_basic() {
let visual = capture_limits_snapshot(Some(sample_rate_limit_snapshot(30.0, 60.0, 40.0)));
let visual = capture_limits_snapshot(Some(&sample_rate_limit_snapshot(30.0, 60.0, 40.0)));
assert_snapshot!(visual);
}
#[test]
fn limits_snapshot_hourly_remaining() {
let visual = capture_limits_snapshot(Some(sample_rate_limit_snapshot(0.0, 20.0, 10.0)));
let visual = capture_limits_snapshot(Some(&sample_rate_limit_snapshot(0.0, 20.0, 10.0)));
assert_snapshot!(visual);
}
#[test]
fn limits_snapshot_mixed_usage() {
let visual = capture_limits_snapshot(Some(sample_rate_limit_snapshot(20.0, 20.0, 10.0)));
let visual = capture_limits_snapshot(Some(&sample_rate_limit_snapshot(20.0, 20.0, 10.0)));
assert_snapshot!(visual);
}
#[test]
fn limits_snapshot_weekly_heavy() {
let visual = capture_limits_snapshot(Some(sample_rate_limit_snapshot(98.0, 0.0, 10.0)));
let visual = capture_limits_snapshot(Some(&sample_rate_limit_snapshot(98.0, 0.0, 10.0)));
assert_snapshot!(visual);
}
@@ -1170,7 +1170,10 @@ async fn binary_size_transcript_snapshot() {
call_id: e.call_id.clone(),
command: e.command,
cwd: e.cwd,
parsed_cmd: parsed_cmd.into_iter().map(|c| c.into()).collect(),
parsed_cmd: parsed_cmd
.into_iter()
.map(std::convert::Into::into)
.collect(),
}),
}
}
@@ -1191,7 +1194,7 @@ async fn binary_size_transcript_snapshot() {
crate::insert_history::insert_history_lines_to_writer(
&mut terminal,
&mut ansi,
lines,
&lines,
);
}
}
@@ -1216,7 +1219,7 @@ async fn binary_size_transcript_snapshot() {
crate::insert_history::insert_history_lines_to_writer(
&mut terminal,
&mut ansi,
lines,
&lines,
);
}
}
@@ -1247,7 +1250,7 @@ async fn binary_size_transcript_snapshot() {
// Trim trailing spaces to match plain text fixture
lines.push(s.trim_end().to_string());
}
while lines.last().is_some_and(|l| l.is_empty()) {
while lines.last().is_some_and(std::string::String::is_empty) {
lines.pop();
}
// Consider content only after the last session banner marker. Skip the transient
@@ -2181,7 +2184,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() {
}),
});
chat.bottom_pane
.set_composer_text("Summarize recent commits".to_string());
.set_composer_text("Summarize recent commits");
chat.handle_codex_event(Event {
id: "t1".into(),
msg: EventMsg::AgentMessage(AgentMessageEvent { message: "Im going to search the repo for where “Change Approved” is rendered to update that view.".into() }),
@@ -2202,7 +2205,7 @@ fn chatwidget_exec_and_status_layout_vt100_snapshot() {
// 1) Apply any pending history insertions by emitting ANSI to a buffer via insert_history_lines_to_writer
let mut ansi: Vec<u8> = Vec::new();
for lines in drain_insert_history(&mut rx) {
crate::insert_history::insert_history_lines_to_writer(&mut term, &mut ansi, lines);
crate::insert_history::insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
}
// 2) Render the ChatWidget UI into an off-screen buffer using WidgetRef directly
@@ -2318,7 +2321,7 @@ printf 'fenced within fenced\n'
if let AppEvent::InsertHistoryCell(cell) = app_ev {
let lines = cell.display_lines(width);
crate::insert_history::insert_history_lines_to_writer(
&mut term, &mut ansi, lines,
&mut term, &mut ansi, &lines,
);
inserted_any = true;
}
@@ -2337,7 +2340,7 @@ printf 'fenced within fenced\n'
}),
});
for lines in drain_insert_history(&mut rx) {
crate::insert_history::insert_history_lines_to_writer(&mut term, &mut ansi, lines);
crate::insert_history::insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
}
let mut parser = vt100::Parser::new(height, width, 0);

View File

@@ -198,7 +198,7 @@ pub fn pasted_image_format(path: &Path) -> EncodedImageFormat {
match path
.extension()
.and_then(|e| e.to_str())
.map(|s| s.to_ascii_lowercase())
.map(str::to_ascii_lowercase)
.as_deref()
{
Some("png") => EncodedImageFormat::Png,

View File

@@ -16,6 +16,7 @@ use codex_core::protocol::FileChange;
const SPACES_AFTER_LINE_NUMBER: usize = 6;
// Internal representation for diff line rendering
#[derive(Clone, Copy)]
enum DiffLineType {
Insert,
Delete,
@@ -80,6 +81,7 @@ fn collect_rows(changes: &HashMap<PathBuf, FileChange>) -> Vec<Row> {
rows
}
#[derive(Clone, Copy)]
enum HeaderKind {
ProposedChange,
Edited,
@@ -283,7 +285,7 @@ fn calculate_add_remove_from_diff(diff: &str) -> (usize, usize) {
patch
.hunks()
.iter()
.flat_map(|h| h.lines())
.flat_map(diffy::Hunk::lines)
.fold((0, 0), |(a, d), l| match l {
diffy::Line::Insert(_) => (a + 1, d),
diffy::Line::Delete(_) => (a, d + 1),

View File

@@ -5,7 +5,7 @@ use dirs::home_dir;
use shlex::try_join;
pub(crate) fn escape_command(command: &[String]) -> String {
try_join(command.iter().map(|s| s.as_str())).unwrap_or_else(|_| command.join(" "))
try_join(command.iter().map(std::string::String::as_str)).unwrap_or_else(|_| command.join(" "))
}
pub(crate) fn strip_bash_lc_and_escape(command: &[String]) -> String {

View File

@@ -79,7 +79,7 @@ impl FileSearchManager {
}
/// Call whenever the user edits the `@` token.
pub fn on_user_query(&self, query: String) {
pub fn on_user_query(&self, query: &str) {
{
#[expect(clippy::unwrap_used)]
let mut st = self.state.lock().unwrap();
@@ -90,7 +90,7 @@ impl FileSearchManager {
// Update latest query.
st.latest_query.clear();
st.latest_query.push_str(&query);
st.latest_query.push_str(query);
// If there is an in-flight search that is definitely obsolete,
// cancel it now.
@@ -170,7 +170,7 @@ impl FileSearchManager {
&search_dir,
Vec::new(),
NUM_FILE_SEARCH_THREADS,
cancellation_token.clone(),
&cancellation_token,
compute_indices,
)
.map(|res| res.matches)

View File

@@ -66,7 +66,7 @@ pub(crate) struct CommandOutput {
pub(crate) formatted_output: String,
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub(crate) enum PatchEventType {
ApprovalRequest,
ApplyBegin { auto_approved: bool },
@@ -270,12 +270,7 @@ pub(crate) struct PatchHistoryCell {
impl HistoryCell for PatchHistoryCell {
fn display_lines(&self, width: u16) -> Vec<Line<'static>> {
create_diff_summary(
&self.changes,
self.event_type.clone(),
&self.cwd,
width as usize,
)
create_diff_summary(&self.changes, self.event_type, &self.cwd, width as usize)
}
}
@@ -390,23 +385,19 @@ impl ExecCell {
.iter()
.all(|c| matches!(c, ParsedCommand::Read { .. }))
{
let names: Vec<String> = call
.parsed
.iter()
.map(|c| match c {
ParsedCommand::Read { name, .. } => name.clone(),
_ => unreachable!(),
})
.unique()
.collect();
vec![(
"Read",
itertools::Itertools::intersperse(
names.into_iter().map(|n| n.into()),
", ".dim(),
)
.collect(),
)]
let spans = itertools::Itertools::intersperse(
call.parsed
.iter()
.filter_map(|c| match c {
ParsedCommand::Read { name, .. } => Some(name.clone()),
_ => None,
})
.unique()
.map(std::convert::Into::into),
", ".dim(),
)
.collect();
vec![("Read", spans)]
} else {
let mut lines = Vec::new();
for p in call.parsed {
@@ -449,7 +440,13 @@ impl ExecCell {
push_owned_lines(&wrapped, &mut out_indented);
}
}
out.extend(prefix_lines(out_indented, "".dim(), " ".into()));
let initial_prefix = "".dim();
let subsequent_prefix = " ".into();
out.extend(prefix_lines(
out_indented,
&initial_prefix,
&subsequent_prefix,
));
out
}
@@ -501,16 +498,12 @@ impl ExecCell {
if let Some(output) = call.output.as_ref()
&& output.exit_code != 0
{
let out = output_lines(
Some(output),
OutputLinesParams {
only_err: false,
include_angle_pipe: false,
include_prefix: false,
},
)
.into_iter()
.join("\n");
let params = OutputLinesParams {
only_err: false,
include_angle_pipe: false,
include_prefix: false,
};
let out = output_lines(Some(output), &params).into_iter().join("\n");
if !out.trim().is_empty() {
// Wrap the output.
for line in out.lines() {
@@ -519,7 +512,13 @@ impl ExecCell {
}
}
}
lines.extend(prefix_lines(body_lines, "".dim(), " ".into()));
let initial_prefix = "".dim();
let subsequent_prefix = " ".into();
lines.extend(prefix_lines(
body_lines,
&initial_prefix,
&subsequent_prefix,
));
lines
}
}
@@ -1114,7 +1113,7 @@ pub(crate) fn new_limits_unavailable() -> PlainHistoryCell {
}
#[allow(clippy::disallowed_methods)]
pub(crate) fn new_warning_event(message: String) -> PlainHistoryCell {
pub(crate) fn new_warning_event(message: &str) -> PlainHistoryCell {
PlainHistoryCell {
lines: vec![vec![format!("{message}").yellow()].into()],
}
@@ -1309,7 +1308,7 @@ pub(crate) fn empty_mcp_output() -> PlainHistoryCell {
/// Render MCP tools grouped by connection using the fully-qualified tool names.
pub(crate) fn new_mcp_tools_output(
config: &Config,
tools: std::collections::HashMap<String, mcp_types::Tool>,
tools: &std::collections::HashMap<String, mcp_types::Tool>,
) -> PlainHistoryCell {
let mut lines: Vec<Line<'static>> = vec![
"/mcp".magenta().into(),
@@ -1362,7 +1361,7 @@ pub(crate) fn new_info_event(message: String, hint: Option<String>) -> PlainHist
PlainHistoryCell { lines }
}
pub(crate) fn new_error_event(message: String) -> PlainHistoryCell {
pub(crate) fn new_error_event(message: &str) -> PlainHistoryCell {
// Use a hair space (U+200A) to create a subtle, near-invisible separation
// before the text. VS16 is intentionally omitted to keep spacing tighter
// in terminals like Ghostty.
@@ -1412,7 +1411,9 @@ impl HistoryCell for PlanUpdateCell {
.into_iter()
.map(|s| s.to_string().set_style(step_style).into())
.collect();
prefix_lines(step_text, box_str.into(), " ".into())
let box_span: Span<'static> = box_str.into();
let indent_span: Span<'static> = " ".into();
prefix_lines(step_text, &box_span, &indent_span)
};
let mut lines: Vec<Line<'static>> = vec![];
@@ -1435,7 +1436,13 @@ impl HistoryCell for PlanUpdateCell {
indented_lines.extend(render_step(status, step));
}
}
lines.extend(prefix_lines(indented_lines, "".into(), " ".into()));
let initial_prefix: Span<'static> = " ".into();
let subsequent_prefix: Span<'static> = " ".into();
lines.extend(prefix_lines(
indented_lines,
&initial_prefix,
&subsequent_prefix,
));
lines
}
@@ -1463,6 +1470,11 @@ pub(crate) fn new_patch_apply_failure(stderr: String) -> PlainHistoryCell {
lines.push(Line::from("✘ Failed to apply patch".magenta().bold()));
if !stderr.trim().is_empty() {
let params = OutputLinesParams {
only_err: true,
include_angle_pipe: true,
include_prefix: true,
};
lines.extend(output_lines(
Some(&CommandOutput {
exit_code: 1,
@@ -1470,11 +1482,7 @@ pub(crate) fn new_patch_apply_failure(stderr: String) -> PlainHistoryCell {
stderr,
formatted_output: String::new(),
}),
OutputLinesParams {
only_err: true,
include_angle_pipe: true,
include_prefix: true,
},
&params,
));
}
@@ -1495,25 +1503,25 @@ pub(crate) fn new_proposed_command(command: &[String]) -> PlainHistoryCell {
let subsequent_prefix: Span<'static> = " ".into();
lines.extend(prefix_lines(
highlighted_lines,
initial_prefix,
subsequent_prefix,
&initial_prefix,
&subsequent_prefix,
));
PlainHistoryCell { lines }
}
pub(crate) fn new_reasoning_block(
full_reasoning_buffer: String,
full_reasoning_buffer: &str,
config: &Config,
) -> TranscriptOnlyHistoryCell {
let mut lines: Vec<Line<'static>> = Vec::new();
lines.push(Line::from("thinking".magenta().italic()));
append_markdown(&full_reasoning_buffer, &mut lines, config);
append_markdown(full_reasoning_buffer, &mut lines, config);
TranscriptOnlyHistoryCell { lines }
}
pub(crate) fn new_reasoning_summary_block(
full_reasoning_buffer: String,
full_reasoning_buffer: &str,
config: &Config,
) -> Box<dyn HistoryCell> {
if config.model_family.reasoning_summary_format == ReasoningSummaryFormat::Experimental {
@@ -1523,19 +1531,19 @@ pub(crate) fn new_reasoning_summary_block(
// reasoning summary
//
// So we need to strip header from reasoning summary
let full_reasoning_buffer = full_reasoning_buffer.trim();
if let Some(open) = full_reasoning_buffer.find("**") {
let after_open = &full_reasoning_buffer[(open + 2)..];
let trimmed = full_reasoning_buffer.trim();
if let Some(open) = trimmed.find("**") {
let after_open = &trimmed[(open + 2)..];
if let Some(close) = after_open.find("**") {
let after_close_idx = open + 2 + close + 2;
// if we don't have anything beyond `after_close_idx`
// then we don't have a summary to inject into history
if after_close_idx < full_reasoning_buffer.len() {
let header_buffer = full_reasoning_buffer[..after_close_idx].to_string();
if after_close_idx < trimmed.len() {
let header_buffer = trimmed[..after_close_idx].to_string();
let mut header_lines = Vec::new();
append_markdown(&header_buffer, &mut header_lines, config);
let summary_buffer = full_reasoning_buffer[after_close_idx..].to_string();
let summary_buffer = trimmed[after_close_idx..].to_string();
let mut summary_lines = Vec::new();
append_markdown(&summary_buffer, &mut summary_lines, config);
@@ -1553,12 +1561,12 @@ struct OutputLinesParams {
include_prefix: bool,
}
fn output_lines(output: Option<&CommandOutput>, params: OutputLinesParams) -> Vec<Line<'static>> {
fn output_lines(output: Option<&CommandOutput>, params: &OutputLinesParams) -> Vec<Line<'static>> {
let OutputLinesParams {
only_err,
include_angle_pipe,
include_prefix,
} = params;
} = *params;
let CommandOutput {
exit_code,
stdout,
@@ -2173,7 +2181,7 @@ mod tests {
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
let cell = new_reasoning_summary_block(
"**High level reasoning**\n\nDetailed reasoning goes here.".to_string(),
"**High level reasoning**\n\nDetailed reasoning goes here.",
&config,
);
@@ -2192,8 +2200,7 @@ mod tests {
let mut config = test_config();
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
let cell =
new_reasoning_summary_block("Detailed reasoning goes here.".to_string(), &config);
let cell = new_reasoning_summary_block("Detailed reasoning goes here.", &config);
let rendered = render_transcript(cell.as_ref());
assert_eq!(rendered, vec!["thinking", "Detailed reasoning goes here."]);
@@ -2204,10 +2211,7 @@ mod tests {
let mut config = test_config();
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
let cell = new_reasoning_summary_block(
"**High level reasoning without closing".to_string(),
&config,
);
let cell = new_reasoning_summary_block("**High level reasoning without closing", &config);
let rendered = render_transcript(cell.as_ref());
assert_eq!(
@@ -2221,10 +2225,7 @@ mod tests {
let mut config = test_config();
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
let cell = new_reasoning_summary_block(
"**High level reasoning without closing**".to_string(),
&config,
);
let cell = new_reasoning_summary_block("**High level reasoning without closing**", &config);
let rendered = render_transcript(cell.as_ref());
assert_eq!(
@@ -2232,10 +2233,8 @@ mod tests {
vec!["thinking", "High level reasoning without closing"]
);
let cell = new_reasoning_summary_block(
"**High level reasoning without closing**\n\n ".to_string(),
&config,
);
let cell =
new_reasoning_summary_block("**High level reasoning without closing**\n\n ", &config);
let rendered = render_transcript(cell.as_ref());
assert_eq!(
@@ -2250,7 +2249,7 @@ mod tests {
config.model_family.reasoning_summary_format = ReasoningSummaryFormat::Experimental;
let cell = new_reasoning_summary_block(
"**High level plan**\n\nWe should fix the bug next.".to_string(),
"**High level plan**\n\nWe should fix the bug next.",
&config,
);

View File

@@ -22,7 +22,7 @@ use ratatui::text::Span;
/// Insert `lines` above the viewport using the terminal's backend writer
/// (avoids direct stdout references).
pub(crate) fn insert_history_lines(terminal: &mut tui::Terminal, lines: Vec<Line>) {
pub(crate) fn insert_history_lines(terminal: &mut tui::Terminal, lines: &[Line]) {
let mut out = std::io::stdout();
insert_history_lines_to_writer(terminal, &mut out, lines);
}
@@ -32,7 +32,7 @@ pub(crate) fn insert_history_lines(terminal: &mut tui::Terminal, lines: Vec<Line
pub fn insert_history_lines_to_writer<B, W>(
terminal: &mut crate::custom_terminal::Terminal<B>,
writer: &mut W,
lines: Vec<Line>,
lines: &[Line],
) where
B: ratatui::backend::Backend,
W: Write,
@@ -43,7 +43,7 @@ pub fn insert_history_lines_to_writer<B, W>(
// Pre-wrap lines using word-aware wrapping so terminal scrollback sees the same
// formatting as the TUI. This avoids character-level hard wrapping by the terminal.
let wrapped = word_wrap_lines_borrowed(&lines, area.width.max(1) as usize);
let wrapped = word_wrap_lines_borrowed(lines, area.width.max(1) as usize);
let wrapped_lines = wrapped.len() as u16;
let cursor_top = if area.bottom() < screen_size.height {
// If the viewport is not at the bottom of the screen, scroll it down to make room.
@@ -322,7 +322,8 @@ mod tests {
let mut line: Line<'static> = Line::from(vec!["> ".into(), "Hello world".into()]);
line = line.style(Color::Green);
let mut ansi: Vec<u8> = Vec::new();
insert_history_lines_to_writer(&mut term, &mut ansi, vec![line]);
let lines = vec![line];
insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
// Parse ANSI using vt100 and assert at least one non-default fg color appears
let mut parser = Parser::new(height, width, 0);
@@ -365,7 +366,8 @@ mod tests {
line = line.style(Color::Green);
let mut ansi: Vec<u8> = Vec::new();
insert_history_lines_to_writer(&mut term, &mut ansi, vec![line]);
let lines = vec![line];
insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
// Parse and inspect the final screen buffer.
let mut parser = Parser::new(height, width, 0);
@@ -430,7 +432,8 @@ mod tests {
]);
let mut ansi: Vec<u8> = Vec::new();
insert_history_lines_to_writer(&mut term, &mut ansi, vec![line]);
let lines = vec![line];
insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
let mut parser = Parser::new(height, width, 0);
parser.process(&ansi);
@@ -489,7 +492,7 @@ mod tests {
term.set_viewport_area(viewport);
let mut ansi: Vec<u8> = Vec::new();
insert_history_lines_to_writer(&mut term, &mut ansi, lines);
insert_history_lines_to_writer(&mut term, &mut ansi, &lines);
let mut parser = Parser::new(height, width, 0);
parser.process(&ansi);

View File

@@ -170,14 +170,13 @@ impl RowBuilder {
if suffix.is_empty() {
// Fits entirely; keep in buffer (do not push yet) so we can append more later.
break;
} else {
// Emit wrapped prefix as a non-explicit row and continue with the remainder.
self.rows.push(Row {
text: prefix,
explicit_break: false,
});
self.current_line = suffix.to_string();
}
// Emit wrapped prefix as a non-explicit row and continue with the remainder.
self.rows.push(Row {
text: prefix,
explicit_break: false,
});
self.current_line = suffix.to_string();
}
}
}

View File

@@ -52,7 +52,7 @@ pub(crate) fn render_markdown_text_with_citations(
let parser = Parser::new_ext(input, options);
let mut w = Writer::new(
parser,
scheme.map(|s| s.to_string()),
scheme.map(std::string::ToString::to_string),
Some(cwd.to_path_buf()),
);
w.run();
@@ -108,7 +108,7 @@ where
match event {
Event::Start(tag) => self.start_tag(tag),
Event::End(tag) => self.end_tag(tag),
Event::Text(text) => self.text(text),
Event::Text(text) => self.text(&text),
Event::Code(code) => self.code(code),
Event::SoftBreak => self.soft_break(),
Event::HardBreak => self.hard_break(),
@@ -119,8 +119,8 @@ where
self.push_line(Line::from("———"));
self.needs_newline = true;
}
Event::Html(html) => self.html(html, false),
Event::InlineHtml(html) => self.html(html, true),
Event::Html(html) => self.html(&html, false),
Event::InlineHtml(html) => self.html(&html, true),
Event::FootnoteReference(_) => {}
Event::TaskListMarker(_) => {}
}
@@ -236,7 +236,7 @@ where
self.needs_newline = true;
}
fn text(&mut self, text: CowStr<'a>) {
fn text(&mut self, text: &CowStr<'a>) {
if self.pending_marker_line {
self.push_line(Line::default());
}
@@ -287,7 +287,7 @@ where
self.push_span(span);
}
fn html(&mut self, html: CowStr<'a>, inline: bool) {
fn html(&mut self, html: &CowStr<'a>, inline: bool) {
self.pending_marker_line = false;
for (i, line) in html.lines().enumerate() {
if self.needs_newline {
@@ -329,7 +329,7 @@ where
let is_ordered = self
.list_indices
.last()
.map(|index| index.is_some())
.map(std::option::Option::is_some)
.unwrap_or(false);
let width = depth * 4 - 3;
let marker = if let Some(last_index) = self.list_indices.last_mut() {

View File

@@ -310,10 +310,8 @@ mod tests {
let long = "> This is a very long quoted line that should wrap across multiple columns to verify style preservation.";
let out = super::simulate_stream_markdown_for_tests(&[long, "\n"], true, &cfg);
// Wrap to a narrow width to force multiple output lines.
let wrapped = crate::wrapping::word_wrap_lines(
out.iter().collect::<Vec<_>>(),
crate::wrapping::RtOptions::new(24),
);
let wrapped =
crate::wrapping::word_wrap_lines(out.iter(), crate::wrapping::RtOptions::new(24));
// Filter out purely blank lines
let non_blank: Vec<_> = wrapped
.into_iter()

View File

@@ -98,7 +98,7 @@ impl WidgetRef for &ModelUpgradePopup {
let mut lines: Vec<Line> = Vec::new();
if show_animation {
let frame = self.animation.current_frame();
lines.extend(frame.lines().map(|l| l.into()));
lines.extend(frame.lines().map(std::convert::Into::into));
// Spacer between animation and text content.
lines.push("".into());
}

View File

@@ -118,7 +118,7 @@ impl KeyboardHandler for AuthModeWidget {
}
fn handle_paste(&mut self, pasted: String) {
let _ = self.handle_api_key_entry_paste(pasted);
let _ = self.handle_api_key_entry_paste(&pasted);
}
}
@@ -403,7 +403,7 @@ impl AuthModeWidget {
true
}
fn handle_api_key_entry_paste(&mut self, pasted: String) -> bool {
fn handle_api_key_entry_paste(&mut self, pasted: &str) -> bool {
let trimmed = pasted.trim();
if trimmed.is_empty() {
return false;

View File

@@ -62,7 +62,7 @@ impl WidgetRef for &WelcomeWidget {
let frame = self.animation.current_frame();
// let frame_line_count = frame.lines().count();
// lines.reserve(frame_line_count + 2);
lines.extend(frame.lines().map(|l| l.into()));
lines.extend(frame.lines().map(std::convert::Into::into));
lines.push("".into());
}
lines.push(Line::from(vec![

View File

@@ -35,7 +35,7 @@ impl Overlay {
Self::Static(StaticOverlay::with_title(lines, title))
}
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: TuiEvent) -> Result<()> {
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: &TuiEvent) -> Result<()> {
match self {
Overlay::Transcript(o) => o.handle_event(tui, event),
Overlay::Static(o) => o.handle_event(tui, event),
@@ -382,7 +382,7 @@ impl TranscriptOverlay {
let cell_lines = if Some(idx) == highlight_cell {
cell.transcript_lines()
.into_iter()
.map(|l| l.reversed())
.map(ratatui::prelude::Stylize::reversed)
.collect()
} else {
cell.transcript_lines()
@@ -440,7 +440,7 @@ impl TranscriptOverlay {
}
impl TranscriptOverlay {
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: TuiEvent) -> Result<()> {
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: &TuiEvent) -> Result<()> {
match event {
TuiEvent::Key(key_event) => match key_event {
KeyEvent {
@@ -463,7 +463,7 @@ impl TranscriptOverlay {
self.is_done = true;
Ok(())
}
other => self.view.handle_key_event(tui, other),
other => self.view.handle_key_event(tui, *other),
},
TuiEvent::Draw => {
tui.draw(u16::MAX, |frame| {
@@ -510,7 +510,7 @@ impl StaticOverlay {
}
impl StaticOverlay {
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: TuiEvent) -> Result<()> {
pub(crate) fn handle_event(&mut self, tui: &mut tui::Tui, event: &TuiEvent) -> Result<()> {
match event {
TuiEvent::Key(key_event) => match key_event {
KeyEvent {
@@ -527,7 +527,7 @@ impl StaticOverlay {
self.is_done = true;
Ok(())
}
other => self.view.handle_key_event(tui, other),
other => self.view.handle_key_event(tui, *other),
},
TuiEvent::Draw => {
tui.draw(u16::MAX, |frame| {

View File

@@ -39,8 +39,8 @@ pub fn is_blank_line_spaces_only(line: &Line<'_>) -> bool {
/// `subsequent_prefix` for following lines. Returns a new Vec of owned lines.
pub fn prefix_lines(
lines: Vec<Line<'static>>,
initial_prefix: Span<'static>,
subsequent_prefix: Span<'static>,
initial_prefix: &Span<'static>,
subsequent_prefix: &Span<'static>,
) -> Vec<Line<'static>> {
lines
.into_iter()

View File

@@ -45,7 +45,7 @@ impl SessionLogger {
Ok(())
}
fn write_json_line(&self, value: serde_json::Value) {
fn write_json_line(&self, value: &serde_json::Value) {
let Some(mutex) = self.file.get() else {
return;
};
@@ -53,7 +53,7 @@ impl SessionLogger {
Ok(g) => g,
Err(poisoned) => poisoned.into_inner(),
};
match serde_json::to_string(&value) {
match serde_json::to_string(value) {
Ok(serialized) => {
if let Err(e) = guard.write_all(serialized.as_bytes()) {
tracing::warn!("session log write error: {}", e);
@@ -119,7 +119,7 @@ pub(crate) fn maybe_init(config: &Config) {
"model_provider_id": config.model_provider_id,
"model_provider_name": config.model_provider.name,
});
LOGGER.write_json_line(header);
LOGGER.write_json_line(&header);
}
pub(crate) fn log_inbound_app_event(event: &AppEvent) {
@@ -138,7 +138,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
"dir": "to_tui",
"kind": "new_session",
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
AppEvent::InsertHistoryCell(cell) => {
let value = json!({
@@ -147,7 +147,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
"kind": "insert_history_cell",
"lines": cell.transcript_lines().len(),
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
AppEvent::StartFileSearch(query) => {
let value = json!({
@@ -156,7 +156,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
"kind": "file_search_start",
"query": query,
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
AppEvent::FileSearchResult { query, matches } => {
let value = json!({
@@ -166,7 +166,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
"query": query,
"matches": matches.len(),
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
// Noise or control flow record variant only
other => {
@@ -176,7 +176,7 @@ pub(crate) fn log_inbound_app_event(event: &AppEvent) {
"kind": "app_event",
"variant": format!("{other:?}").split('(').next().unwrap_or("app_event"),
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
}
}
@@ -197,7 +197,7 @@ pub(crate) fn log_session_end() {
"dir": "meta",
"kind": "session_end",
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}
fn write_record<T>(dir: &str, kind: &str, obj: &T)
@@ -210,5 +210,5 @@ where
"kind": kind,
"payload": obj,
});
LOGGER.write_json_line(value);
LOGGER.write_json_line(&value);
}

View File

@@ -416,10 +416,10 @@ impl Tui {
}
#[cfg(unix)]
fn apply_prepared_resume_action(&mut self, prepared: PreparedResumeAction) -> Result<()> {
fn apply_prepared_resume_action(&mut self, prepared: &PreparedResumeAction) -> Result<()> {
match prepared {
PreparedResumeAction::RealignViewport(area) => {
self.terminal.set_viewport_area(area);
self.terminal.set_viewport_area(*area);
}
PreparedResumeAction::RestoreAltScreen => {
execute!(self.terminal.backend_mut(), EnterAlternateScreen)?;
@@ -511,7 +511,7 @@ impl Tui {
#[cfg(unix)]
{
if let Some(prepared) = prepared_resume.take() {
self.apply_prepared_resume_action(prepared)?;
self.apply_prepared_resume_action(&prepared)?;
}
}
let terminal = &mut self.terminal;
@@ -536,10 +536,7 @@ impl Tui {
terminal.set_viewport_area(area);
}
if !self.pending_history_lines.is_empty() {
crate::insert_history::insert_history_lines(
terminal,
self.pending_history_lines.clone(),
);
crate::insert_history::insert_history_lines(terminal, &self.pending_history_lines);
self.pending_history_lines.clear();
}
// Update the y position for suspending so Ctrl-Z can place the cursor correctly.

View File

@@ -219,10 +219,10 @@ impl UserApprovalWidget {
}
fn send_decision(&mut self, decision: ReviewDecision) {
self.send_decision_with_feedback(decision, String::new())
self.send_decision_with_feedback(decision, "")
}
fn send_decision_with_feedback(&mut self, decision: ReviewDecision, feedback: String) {
fn send_decision_with_feedback(&mut self, decision: ReviewDecision, feedback: &str) {
match &self.approval_request {
ApprovalRequest::Exec { command, .. } => {
let full_cmd = strip_bash_lc_and_escape(command);

View File

@@ -511,7 +511,7 @@ mod tests {
.subsequent_indent(Line::from(" "));
let lines = [Line::from("hello world"), Line::from("foo bar baz")];
let out = word_wrap_lines_borrowed(lines.iter().collect::<Vec<_>>(), opts);
let out = word_wrap_lines_borrowed(lines.iter(), opts);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert!(rendered.first().unwrap().starts_with("- "));
@@ -523,7 +523,7 @@ mod tests {
#[test]
fn wrap_lines_borrowed_without_indents_is_concat_of_single_wraps() {
let lines = [Line::from("hello"), Line::from("world!")];
let out = word_wrap_lines_borrowed(lines.iter().collect::<Vec<_>>(), 10);
let out = word_wrap_lines_borrowed(lines.iter(), 10);
let rendered: Vec<String> = out.iter().map(concat_line).collect();
assert_eq!(rendered, vec!["hello", "world!"]);
}
@@ -543,7 +543,10 @@ mod tests {
let lines = [line];
// Force small width to exercise wrapping at spaces.
let wrapped = word_wrap_lines_borrowed(&lines, 40);
let joined: String = wrapped.iter().map(|l| l.to_string()).join("\n");
let joined: String = wrapped
.iter()
.map(std::string::ToString::to_string)
.join("\n");
assert_eq!(
joined,
r#"Years passed, and Willowmere thrived

View File

@@ -41,7 +41,7 @@ impl TestScenario {
}
}
fn run_insert(&mut self, lines: Vec<Line<'static>>) -> Vec<u8> {
fn run_insert(&mut self, lines: &[Line<'static>]) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(&mut self.term, &mut buf, lines);
buf
@@ -79,7 +79,7 @@ fn basic_insertion_no_wrap() {
let mut scenario = TestScenario::new(20, 6, area);
let lines = vec!["first".into(), "second".into()];
let buf = scenario.run_insert(lines);
let buf = scenario.run_insert(&lines);
let rows = scenario.screen_rows_from_bytes(&buf);
assert_contains!(rows, String::from("first"));
assert_contains!(rows, String::from("second"));
@@ -101,7 +101,7 @@ fn long_token_wraps() {
let long = "A".repeat(45); // > 2 lines at width 20
let lines = vec![long.clone().into()];
let buf = scenario.run_insert(lines);
let buf = scenario.run_insert(&lines);
let mut parser = vt100::Parser::new(6, 20, 0);
parser.process(&buf);
let screen = parser.screen();
@@ -133,7 +133,7 @@ fn emoji_and_cjk() {
let text = String::from("😀😀😀😀😀 你好世界");
let lines = vec![text.clone().into()];
let buf = scenario.run_insert(lines);
let buf = scenario.run_insert(&lines);
let rows = scenario.screen_rows_from_bytes(&buf);
let reconstructed: String = rows.join("").chars().filter(|c| *c != ' ').collect();
for ch in text.chars().filter(|c| !c.is_whitespace()) {
@@ -150,7 +150,8 @@ fn mixed_ansi_spans() {
let mut scenario = TestScenario::new(20, 6, area);
let line = vec!["red".red(), "+plain".into()].into();
let buf = scenario.run_insert(vec![line]);
let lines = vec![line];
let buf = scenario.run_insert(&lines);
let rows = scenario.screen_rows_from_bytes(&buf);
assert_contains!(rows, String::from("red+plain"));
}
@@ -161,7 +162,7 @@ fn cursor_restoration() {
let mut scenario = TestScenario::new(20, 6, area);
let lines = vec!["x".into()];
let buf = scenario.run_insert(lines);
let buf = scenario.run_insert(&lines);
let s = String::from_utf8_lossy(&buf);
// CUP to 1;1 (ANSI: ESC[1;1H)
assert!(
@@ -182,7 +183,8 @@ fn word_wrap_no_mid_word_split() {
let mut scenario = TestScenario::new(40, 10, area);
let sample = "Years passed, and Willowmere thrived in peace and friendship. Miras herb garden flourished with both ordinary and enchanted plants, and travelers spoke of the kindness of the woman who tended them.";
let buf = scenario.run_insert(vec![sample.into()]);
let lines = vec![sample.into()];
let buf = scenario.run_insert(&lines);
let rows = scenario.screen_rows_from_bytes(&buf);
let joined = rows.join("\n");
assert!(
@@ -198,7 +200,8 @@ fn em_dash_and_space_word_wrap() {
let mut scenario = TestScenario::new(40, 10, area);
let sample = "Mara found an old key on the shore. Curious, she opened a tarnished box half-buried in sand—and inside lay a single, glowing seed.";
let buf = scenario.run_insert(vec![sample.into()]);
let lines = vec![sample.into()];
let buf = scenario.run_insert(&lines);
let rows = scenario.screen_rows_from_bytes(&buf);
let joined = rows.join("\n");
assert!(
@@ -214,7 +217,7 @@ fn pre_scroll_region_down() {
let mut scenario = TestScenario::new(20, 6, area);
let lines = vec!["first".into(), "second".into()];
let buf = scenario.run_insert(lines);
let buf = scenario.run_insert(&lines);
let s = String::from_utf8_lossy(&buf);
// Expect we limited scroll region to [top+1 .. screen_height] => [4 .. 6] (1-based)
assert!(

View File

@@ -27,7 +27,7 @@ fn live_001_commit_on_overflow() {
let lines: Vec<Line<'static>> = commit_rows.into_iter().map(|r| r.text.into()).collect();
let mut buf: Vec<u8> = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(&mut term, &mut buf, lines);
codex_tui::insert_history::insert_history_lines_to_writer(&mut term, &mut buf, &lines);
let mut parser = vt100::Parser::new(6, 20, 0);
parser.process(&buf);
@@ -80,7 +80,7 @@ fn live_002_pre_scroll_and_commit() {
let lines: Vec<Line<'static>> = commit_rows.into_iter().map(|r| r.text.into()).collect();
let mut buf: Vec<u8> = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(&mut term, &mut buf, lines);
codex_tui::insert_history::insert_history_lines_to_writer(&mut term, &mut buf, &lines);
let s = String::from_utf8_lossy(&buf);
// Expect a SetScrollRegion to [area.top()+1 .. screen_height] and a cursor move to top of that region.

View File

@@ -19,19 +19,13 @@ fn stream_commit_trickle_no_duplication() {
// Step 1: commit first row
let mut out1 = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(
&mut t,
&mut out1,
vec!["one".into()],
);
let lines1 = vec!["one".into()];
codex_tui::insert_history::insert_history_lines_to_writer(&mut t, &mut out1, &lines1);
// Step 2: later commit next row
let mut out2 = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(
&mut t,
&mut out2,
vec!["two".into()],
);
let lines2 = vec!["two".into()];
codex_tui::insert_history::insert_history_lines_to_writer(&mut t, &mut out2, &lines2);
let combined = [out1, out2].concat();
let s = String::from_utf8_lossy(&combined);
@@ -58,11 +52,8 @@ fn live_ring_rows_not_inserted_into_history() {
// Commit two rows to history.
let mut buf = Vec::new();
codex_tui::insert_history::insert_history_lines_to_writer(
&mut t,
&mut buf,
vec!["one".into(), "two".into()],
);
let lines = vec!["one".into(), "two".into()];
codex_tui::insert_history::insert_history_lines_to_writer(&mut t, &mut buf, &lines);
// The live ring might display tail+head rows like ["two", "three"],
// but only committed rows should be present in the history ANSI stream.