Skip to content

Commit 03e47a4

Browse files
KJ7LNWEric Wheeler
authored andcommitted
feat: compress terminal output with backspace characters (RooCodeInc#2907)
Follow-up to RooCodeInc#2562 adding support for backspace character compression. Optimizes terminal output by handling backspace characters similar to carriage returns, improving readability of progress spinners and other terminal output that uses backspace for animation. - Added processBackspaces function using efficient indexOf approach - Added comprehensive test suite for backspace handling - Integrated with terminal output compression pipeline Signed-off-by: Eric Wheeler <[email protected]> Co-authored-by: Eric Wheeler <[email protected]>
1 parent aae0a6c commit 03e47a4

File tree

3 files changed

+116
-3
lines changed

3 files changed

+116
-3
lines changed

src/integrations/misc/__tests__/extract-text.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
truncateOutput,
66
applyRunLengthEncoding,
77
processCarriageReturns,
8+
processBackspaces,
89
} from "../extract-text"
910

1011
describe("addLineNumbers", () => {
@@ -189,6 +190,69 @@ describe("truncateOutput", () => {
189190
expect(truncateOutput("single line", 10)).toBe("single line")
190191
})
191192

193+
describe("processBackspaces", () => {
194+
it("should handle basic backspace deletion", () => {
195+
const input = "abc\b\bxy"
196+
const expected = "axy"
197+
expect(processBackspaces(input)).toBe(expected)
198+
})
199+
200+
it("should handle backspaces at start of input", () => {
201+
const input = "\b\babc"
202+
const expected = "abc"
203+
expect(processBackspaces(input)).toBe(expected)
204+
})
205+
206+
it("should handle backspaces with newlines", () => {
207+
const input = "abc\b\n123\b\b"
208+
const expected = "ab\n1"
209+
expect(processBackspaces(input)).toBe(expected)
210+
})
211+
212+
it("should handle consecutive backspaces", () => {
213+
const input = "abcdef\b\b\b\bxy"
214+
const expected = "abxy"
215+
expect(processBackspaces(input)).toBe(expected)
216+
})
217+
218+
it("should handle backspaces at end of input", () => {
219+
const input = "abc\b\b"
220+
const expected = "a"
221+
expect(processBackspaces(input)).toBe(expected)
222+
})
223+
224+
it("should handle mixed backspaces and content", () => {
225+
const input = "abc\bx\byz\b\b123"
226+
const expected = "ab123"
227+
expect(processBackspaces(input)).toBe(expected)
228+
})
229+
230+
it("should handle multiple groups of consecutive backspaces", () => {
231+
const input = "abc\b\bdef\b\b\bghi\b\b\b\bjkl"
232+
const expected = "jkl"
233+
expect(processBackspaces(input)).toBe(expected)
234+
})
235+
236+
it("should handle backspaces with empty content between them", () => {
237+
const input = "abc\b\b\b\b\b\bdef"
238+
const expected = "def"
239+
expect(processBackspaces(input)).toBe(expected)
240+
})
241+
242+
it("should handle complex mixed content with backspaces", () => {
243+
const input = "Loading[\b\b\b\b\b\b\b\bProgress[\b\b\b\b\b\b\b\b\bStatus: \b\b\b\b\b\b\b\bDone!"
244+
// Technically terminal displays "Done!s: [" but we assume \b is destructive as an optimization
245+
const expected = "Done!"
246+
expect(processBackspaces(input)).toBe(expected)
247+
})
248+
249+
it("should handle backspaces with special characters", () => {
250+
const input = "abc😀\b\bdef🎉\b\b\bghi"
251+
const expected = "abcdeghi"
252+
expect(processBackspaces(input)).toBe(expected)
253+
})
254+
})
255+
192256
it("handles windows-style line endings", () => {
193257
// Create content with windows line endings
194258
const lines = Array.from({ length: 15 }, (_, i) => `line${i + 1}`)

src/integrations/misc/extract-text.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,53 @@ export function processCarriageReturns(input: string): string {
282282
return output
283283
}
284284

285+
/**
286+
* Processes backspace characters (\b) in terminal output using index operations.
287+
* Uses indexOf to efficiently locate and handle backspaces.
288+
*
289+
* Technically terminal only moves the cursor and overwrites in-place,
290+
* but we assume \b is destructive as an optimization which is acceptable
291+
* for all progress spinner cases and most terminal output cases.
292+
*
293+
* @param input The terminal output to process
294+
* @returns The processed output with backspaces handled
295+
*/
296+
export function processBackspaces(input: string): string {
297+
let output = ""
298+
let pos = 0
299+
let bsPos = input.indexOf("\b")
300+
301+
while (bsPos !== -1) {
302+
// Fast path: exclude char before backspace
303+
output += input.substring(pos, bsPos - 1)
304+
305+
// Move past backspace
306+
pos = bsPos + 1
307+
308+
// Count consecutive backspaces
309+
let count = 0
310+
while (input[pos] === "\b") {
311+
count++
312+
pos++
313+
}
314+
315+
// Trim output mathematically for consecutive backspaces
316+
if (count > 0 && output.length > 0) {
317+
output = output.substring(0, Math.max(0, output.length - count))
318+
}
319+
320+
// Find next backspace
321+
bsPos = input.indexOf("\b", pos)
322+
}
323+
324+
// Add remaining content
325+
if (pos < input.length) {
326+
output += input.substring(pos)
327+
}
328+
329+
return output
330+
}
331+
285332
/**
286333
* Helper function to process a single line with carriage returns.
287334
* Handles the overwrite logic for a line that contains one or more carriage returns (\r).

src/integrations/terminal/Terminal.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as vscode from "vscode"
22
import pWaitFor from "p-wait-for"
33
import { ExitCodeDetails, mergePromise, TerminalProcess, TerminalProcessResultPromise } from "./TerminalProcess"
4-
import { truncateOutput, applyRunLengthEncoding, processCarriageReturns } from "../misc/extract-text"
4+
import { truncateOutput, applyRunLengthEncoding, processCarriageReturns, processBackspaces } from "../misc/extract-text"
55
// Import TerminalRegistry here to avoid circular dependencies
66
const { TerminalRegistry } = require("./TerminalRegistry")
77

@@ -296,9 +296,11 @@ export class Terminal {
296296
public static compressTerminalOutput(input: string, lineLimit: number): string {
297297
// Apply carriage return processing if the feature is enabled
298298
let processedInput = input
299-
if (Terminal.compressProgressBar && input.includes("\r")) {
300-
processedInput = processCarriageReturns(input)
299+
if (Terminal.compressProgressBar) {
300+
processedInput = processCarriageReturns(processedInput)
301+
processedInput = processBackspaces(processedInput)
301302
}
303+
302304
return truncateOutput(applyRunLengthEncoding(processedInput), lineLimit)
303305
}
304306

0 commit comments

Comments
 (0)