mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 17:09:13 -10:00
fix(tui): diff gutters number one side, context keeps its indent, commands keep their lines
All three were seen in the same real DeepSeek session that found the
approval-before-refusal bug (CW/artifacts/readme-shot-20261018/).
Diff gutter: format_line_numbers printed both the old and the new line
number on every row, so a removed line showed a new-side number and an
added line an old-side number. The patch card for the slugkit fix read
" 9 9 - words = ..." and " 10 9 + lower = ...", which
looks like line 9 exists on both sides. A '+' row now leaves the old
column blank and a '-' row leaves the new column blank; context rows
keep both. The existing context-numbering assertion is unchanged.
Diff context indentation: every row's marker was removed with
trim_start_matches, which strips all leading copies of the character. On
a context row (' ' prefix) that removed the code's own indentation, so
unchanged lines rendered flush left while the -/+ rows beside them kept
theirs; on a '-' or '+' row it also ate content that began with the same
character (a removed "- item" Markdown line lost its dash). All seven
sites now drop exactly the one marker byte (&raw[1..]; is_added,
is_deleted and the ' ' check guarantee it is there).
Shell card command: render_command_mode wrapped the whole command with
wrap_text, which keeps '\n' inside a chunk, and the span renderer then
dropped it, so "echo first-line\necho second-line" displayed as
"command: echo first-lineecho second-line". It now wraps each line
separately, matching how the approval card already shows the command.
Also credits @Lstarsky0 for #7022 in both changelogs.
Checks:
- cargo test -p codewhale-tui --lib -- tools::shell core::engine
tui::history diff approval: 1500 passed; 0 failed; 5 ignored (same run
as the refusal commit, before the indentation change)
- after the indentation change: cargo test -p codewhale-tui --lib -- diff
patch file_mutation: 493 passed; 0 failed
- clippy with CI's flags: clean on the tree before the &raw[1..] change;
after it, only cargo build and the diff tests were run
Verified in a real TUI with a scripted mock edit (phase5 in
CW/artifacts/cw7087-20261018/mock/server.py; captures diff-after-1.txt and
diff-after4-1.txt in CW/artifacts/approval-card-20261018/):
4 4 def slug(text):
5 5 """Join words with dashes."""
6 - words = text.split()
6 + lower = text.lower()
7 + words = lower.split()
7 8 return '-'.join(words)
and the refused two-line mock command shows "command: echo first-line"
with "echo second-line" on the next row (multiline-after4-1.txt).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
1 parent
89d1407efd
commit
afe5f9b30f
4 files changed
+31
-9
No files matched your search
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- `/statusline` shows its last two items, Workspace and Git branch, without
|
||||
scrolling. The picker had not counted its padding rows. Thanks @Lstarsky0
|
||||
(#7022).
|
||||
- Diffs in the TUI number each line only on its own side: a removed line
|
||||
shows its old line number and an added line its new one. Unchanged
|
||||
context lines keep their indentation instead of starting at the left
|
||||
edge.
|
||||
- A shell card shows a multiline command one line per row instead of running
|
||||
the lines together.
|
||||
- A shell card whose command is waiting on your approval now says
|
||||
"waiting for approval" with a still mark, instead of "running (Ns)" with
|
||||
a spinner. Its elapsed time starts when you approve and the command
|
||||
|
||||
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- `/statusline` shows its last two items, Workspace and Git branch, without
|
||||
scrolling. The picker had not counted its padding rows. Thanks @Lstarsky0
|
||||
(#7022).
|
||||
- Diffs in the TUI number each line only on its own side: a removed line
|
||||
shows its old line number and an added line its new one. Unchanged
|
||||
context lines keep their indentation instead of starting at the left
|
||||
edge.
|
||||
- A shell card shows a multiline command one line per row instead of running
|
||||
the lines together.
|
||||
- A shell card whose command is waiting on your approval now says
|
||||
"waiting for approval" with a still mark, instead of "running (Ns)" with
|
||||
a spinner. Its elapsed time starts when you approve and the command
|
||||
|
||||
@@ -100,7 +100,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
}
|
||||
|
||||
if is_added(raw) {
|
||||
let content = raw.trim_start_matches('+');
|
||||
let content = &raw[1..];
|
||||
collector.extend(render_diff_line(
|
||||
content,
|
||||
width,
|
||||
@@ -120,7 +120,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
// A deleted run followed by an added run of the same length is a
|
||||
// set of replaced lines: emphasise the words that changed within
|
||||
// each pair. Any other shape renders line by line as before.
|
||||
let mut removed = vec![raw.trim_start_matches('-')];
|
||||
let mut removed = vec![&raw[1..]];
|
||||
let mut added: Vec<&str> = Vec::new();
|
||||
let mut removed_markers: Vec<(usize, &str)> = Vec::new();
|
||||
let mut added_markers: Vec<(usize, &str)> = Vec::new();
|
||||
@@ -135,7 +135,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
&& let Some(next) = lines.next_if(|next| is_deleted(next))
|
||||
{
|
||||
if removed.len() < INTRALINE_MAX_RUN {
|
||||
removed.push(next.trim_start_matches('-'));
|
||||
removed.push(&next[1..]);
|
||||
} else {
|
||||
flush_plain_run(
|
||||
&mut collector,
|
||||
@@ -148,7 +148,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
);
|
||||
render_plain_diff_line(
|
||||
&mut collector,
|
||||
next.trim_start_matches('-'),
|
||||
&next[1..],
|
||||
width,
|
||||
&mut old_line,
|
||||
&mut new_line,
|
||||
@@ -158,7 +158,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
}
|
||||
} else if let Some(next) = lines.next_if(|next| is_added(next)) {
|
||||
if added.len() < INTRALINE_MAX_RUN {
|
||||
added.push(next.trim_start_matches('+'));
|
||||
added.push(&next[1..]);
|
||||
} else {
|
||||
flush_plain_run(
|
||||
&mut collector,
|
||||
@@ -180,7 +180,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
);
|
||||
render_plain_diff_line(
|
||||
&mut collector,
|
||||
next.trim_start_matches('+'),
|
||||
&next[1..],
|
||||
width,
|
||||
&mut old_line,
|
||||
&mut new_line,
|
||||
@@ -252,7 +252,7 @@ pub fn render_diff_body_bounded(diff: &str, width: u16, max_rows: usize) -> Boun
|
||||
}
|
||||
|
||||
if raw.starts_with(' ') {
|
||||
let content = raw.trim_start_matches(' ');
|
||||
let content = &raw[1..];
|
||||
collector.extend(render_diff_line(
|
||||
content,
|
||||
width,
|
||||
@@ -712,9 +712,11 @@ fn render_diff_line(
|
||||
|
||||
fn format_line_numbers(old_line: Option<usize>, new_line: Option<usize>, marker: char) -> String {
|
||||
let old = old_line
|
||||
.filter(|_| marker != '+')
|
||||
.map(|value| format!("{value:>LINE_NUMBER_WIDTH$}"))
|
||||
.unwrap_or_else(|| " ".repeat(LINE_NUMBER_WIDTH));
|
||||
let new = new_line
|
||||
.filter(|_| marker != '-')
|
||||
.map(|value| format!("{value:>LINE_NUMBER_WIDTH$}"))
|
||||
.unwrap_or_else(|| " ".repeat(LINE_NUMBER_WIDTH));
|
||||
format!("{old} {new} {marker} ")
|
||||
|
||||
@@ -2396,8 +2396,10 @@ fn render_command_mode(command: &str, width: u16, mode: RenderMode) -> Vec<Line<
|
||||
RenderMode::Live => TOOL_COMMAND_LINE_LIMIT,
|
||||
RenderMode::Transcript => usize::MAX,
|
||||
};
|
||||
for (count, chunk) in wrap_text(command, width.saturating_sub(4).max(1) as usize)
|
||||
.into_iter()
|
||||
let wrap_width = width.saturating_sub(4).max(1) as usize;
|
||||
for (count, chunk) in command
|
||||
.lines()
|
||||
.flat_map(|line| wrap_text(line, wrap_width))
|
||||
.enumerate()
|
||||
{
|
||||
if count >= cap {
|
||||
|
||||
Reference in new issue
Block a user