mirror of
https://github.com/openai/codex.git
synced 2026-09-16 12:13:30 +00:00
Merge 217de68af6 into sapling-pr-archive-bolinfest
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
We provide Codex CLI as a standalone, native executable to ensure a zero-dependency install.
|
||||
|
||||
w00t!
|
||||
|
||||
## Installing Codex
|
||||
|
||||
Today, the easiest way to install Codex is via `npm`, though we plan to publish Codex to other package managers soon.
|
||||
|
||||
@@ -250,6 +250,30 @@ impl<'a> App<'a> {
|
||||
SlashCommand::Quit => {
|
||||
break;
|
||||
}
|
||||
SlashCommand::Diff => {
|
||||
use crate::get_git_diff::get_git_diff;
|
||||
|
||||
let (is_repo, diff_text) = match get_git_diff() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let msg = format!("Failed to compute diff: {e}");
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.add_background_event(msg);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let text = if is_repo {
|
||||
diff_text
|
||||
} else {
|
||||
"`/diff` — _not inside a git repository_".to_string()
|
||||
};
|
||||
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.add_background_event(text);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,6 +384,14 @@ impl ChatWidget<'_> {
|
||||
self.app_event_tx.send(AppEvent::Redraw);
|
||||
}
|
||||
|
||||
/// Inject a background event into the conversation history. This is used
|
||||
/// for displaying informational messages that originate from the UI
|
||||
/// itself (e.g. the `/diff` command) rather than from the backend agent.
|
||||
pub(crate) fn add_background_event(&mut self, message: String) {
|
||||
self.conversation_history.add_background_event(message);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
pub(crate) fn handle_scroll_delta(&mut self, scroll_delta: i32) {
|
||||
// If the user is trying to scroll exactly one line, we let them, but
|
||||
// otherwise we assume they are trying to scroll in larger increments.
|
||||
|
||||
114
codex-rs/tui/src/get_git_diff.rs
Normal file
114
codex-rs/tui/src/get_git_diff.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! Utility to compute the current Git diff for the working directory.
|
||||
//!
|
||||
//! The implementation mirrors the behaviour of the TypeScript version in
|
||||
//! `codex-cli`: it returns the diff for tracked changes as well as any
|
||||
//! untracked files. When the current directory is not inside a Git
|
||||
//! repository, the function returns `Ok((false, String::new()))`.
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
/// Return value of [`get_git_diff`].
|
||||
///
|
||||
/// * `bool` – Whether the current working directory is inside a Git repo.
|
||||
/// * `String` – The concatenated diff (may be empty).
|
||||
pub(crate) fn get_git_diff() -> io::Result<(bool, String)> {
|
||||
// First check if we are inside a Git repository.
|
||||
if !inside_git_repo()? {
|
||||
return Ok((false, String::new()));
|
||||
}
|
||||
|
||||
// 1. Diff for tracked files.
|
||||
let tracked_diff = run_git_capture_diff(&["diff", "--color"])?;
|
||||
|
||||
// 2. Determine untracked files.
|
||||
let untracked_output = run_git_capture_stdout(&["ls-files", "--others", "--exclude-standard"])?;
|
||||
|
||||
let mut untracked_diff = String::new();
|
||||
let null_device: &Path = if cfg!(windows) {
|
||||
Path::new("NUL")
|
||||
} else {
|
||||
Path::new("/dev/null")
|
||||
};
|
||||
|
||||
for file in untracked_output
|
||||
.split('\n')
|
||||
.map(str::trim)
|
||||
.filter(|s| !s.is_empty())
|
||||
{
|
||||
// Use `git diff --no-index` to generate a diff against the null device.
|
||||
let args = [
|
||||
"diff",
|
||||
"--color",
|
||||
"--no-index",
|
||||
"--",
|
||||
null_device.to_str().unwrap_or("/dev/null"),
|
||||
file,
|
||||
];
|
||||
|
||||
match run_git_capture_diff(&args) {
|
||||
Ok(diff) => untracked_diff.push_str(&diff),
|
||||
// If the file disappeared between ls-files and diff we ignore the error.
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((true, format!("{}{}", tracked_diff, untracked_diff)))
|
||||
}
|
||||
|
||||
/// Helper that executes `git` with the given `args` and returns `stdout` as a
|
||||
/// UTF-8 string. Any non-zero exit status is considered an *error*.
|
||||
fn run_git_capture_stdout(args: &[&str]) -> io::Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
} else {
|
||||
Err(io::Error::other(format!(
|
||||
"git {:?} failed with status {}",
|
||||
args, output.status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`run_git_capture_stdout`] but treats exit status 1 as success and
|
||||
/// returns stdout. Git returns 1 for diffs when differences are present.
|
||||
fn run_git_capture_diff(args: &[&str]) -> io::Result<String> {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.output()?;
|
||||
|
||||
if output.status.success() || output.status.code() == Some(1) {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).into_owned())
|
||||
} else {
|
||||
Err(io::Error::other(format!(
|
||||
"git {:?} failed with status {}",
|
||||
args, output.status
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if the current directory is inside a Git repository.
|
||||
fn inside_git_repo() -> io::Result<bool> {
|
||||
let status = Command::new("git")
|
||||
.args(["rev-parse", "--is-inside-work-tree"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status();
|
||||
|
||||
match status {
|
||||
Ok(s) if s.success() => Ok(true),
|
||||
Ok(_) => Ok(false),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false), // git not installed
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
@@ -453,7 +453,14 @@ impl HistoryCell {
|
||||
pub(crate) fn new_background_event(message: String) -> Self {
|
||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||
lines.push(Line::from("event".dim()));
|
||||
lines.extend(message.lines().map(|l| Line::from(l.to_string()).dim()));
|
||||
|
||||
for raw in message.lines() {
|
||||
// Parse ANSI color sequences so they render correctly in Ratatui.
|
||||
// We preserve any colors encoded in the input; additionally mark
|
||||
// the text as dim to distinguish background events from regular
|
||||
// conversation.
|
||||
lines.push(ansi_escape_line(raw).dim());
|
||||
}
|
||||
lines.push(Line::from(""));
|
||||
HistoryCell::BackgroundEvent {
|
||||
view: TextBlock::new(lines),
|
||||
|
||||
@@ -29,6 +29,7 @@ mod citation_regex;
|
||||
mod cli;
|
||||
mod conversation_history_widget;
|
||||
mod exec_command;
|
||||
mod get_git_diff;
|
||||
mod git_warning_screen;
|
||||
mod history_cell;
|
||||
mod log_layer;
|
||||
|
||||
@@ -15,6 +15,8 @@ pub enum SlashCommand {
|
||||
New,
|
||||
ToggleMouseMode,
|
||||
Quit,
|
||||
/// Show git diff of the working directory.
|
||||
Diff,
|
||||
}
|
||||
|
||||
impl SlashCommand {
|
||||
@@ -26,6 +28,9 @@ impl SlashCommand {
|
||||
"Toggle mouse mode (enable for scrolling, disable for text selection)"
|
||||
}
|
||||
SlashCommand::Quit => "Exit the application.",
|
||||
SlashCommand::Diff => {
|
||||
"Show git diff of the working directory (including untracked files)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user