Skip to content

Commit 5c2511e

Browse files
KJ7LNWEric Wheeler
andauthored
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 23aa3b6 commit 5c2511e

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", () => {
@@ -229,6 +230,69 @@ describe("truncateOutput", () => {
229230
expect(truncateOutput("single line", 10)).toBe("single line")
230231
})
231232

233+
describe("processBackspaces", () => {
234+
it("should handle basic backspace deletion", () => {
235+
const input = "abc\b\bxy"
236+
const expected = "axy"
237+
expect(processBackspaces(input)).toBe(expected)
238+
})
239+
240+
it("should handle backspaces at start of input", () => {
241+
const input = "\b\babc"
242+
const expected = "abc"
243+
expect(processBackspaces(input)).toBe(expected)
244+
})
245+
246+
it("should handle backspaces with newlines", () => {
247+
const input = "abc\b\n123\b\b"
248+
const expected = "ab\n1"
249+
expect(processBackspaces(input)).toBe(expected)
250+
})
251+
252+
it("should handle consecutive backspaces", () => {
253+
const input = "abcdef\b\b\b\bxy"
254+
const expected = "abxy"
255+
expect(processBackspaces(input)).toBe(expected)
256+
})
257+
258+
it("should handle backspaces at end of input", () => {
259+
const input = "abc\b\b"
260+
const expected = "a"
261+
expect(processBackspaces(input)).toBe(expected)
262+
})
263+
264+
it("should handle mixed backspaces and content", () => {
265+
const input = "abc\bx\byz\b\b123"
266+
const expected = "ab123"
267+
expect(processBackspaces(input)).toBe(expected)
268+
})
269+
270+
it("should handle multiple groups of consecutive backspaces", () => {
271+
const input = "abc\b\bdef\b\b\bghi\b\b\b\bjkl"
272+
const expected = "jkl"
273+
expect(processBackspaces(input)).toBe(expected)
274+
})
275+
276+
it("should handle backspaces with empty content between them", () => {
277+
const input = "abc\b\b\b\b\b\bdef"
278+
const expected = "def"
279+
expect(processBackspaces(input)).toBe(expected)
280+
})
281+
282+
it("should handle complex mixed content with backspaces", () => {
283+
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!"
284+
// Technically terminal displays "Done!s: [" but we assume \b is destructive as an optimization
285+
const expected = "Done!"
286+
expect(processBackspaces(input)).toBe(expected)
287+
})
288+
289+
it("should handle backspaces with special characters", () => {
290+
const input = "abc😀\b\bdef🎉\b\b\bghi"
291+
const expected = "abcdeghi"
292+
expect(processBackspaces(input)).toBe(expected)
293+
})
294+
})
295+
232296
it("handles windows-style line endings", () => {
233297
// Create content with windows line endings
234298
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
@@ -288,6 +288,53 @@ export function processCarriageReturns(input: string): string {
288288
return output
289289
}
290290

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