ccmanager 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,178 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { SessionManager } from './sessionManager.js';
3
- import { spawn } from 'node-pty';
4
- // Create mock pty process
5
- const createMockPtyProcess = () => {
6
- const handlers = {
7
- data: [],
8
- exit: [],
9
- };
10
- return {
11
- write: vi.fn(),
12
- resize: vi.fn(),
13
- onData: vi.fn((handler) => {
14
- handlers.data.push(handler);
15
- }),
16
- onExit: vi.fn((handler) => {
17
- handlers.exit.push(handler);
18
- }),
19
- kill: vi.fn(),
20
- _emit: (event, ...args) => {
21
- if (event === 'data' && handlers.data.length > 0) {
22
- handlers.data.forEach(h => h(args[0]));
23
- }
24
- else if (event === 'exit' && handlers.exit.length > 0) {
25
- handlers.exit.forEach(h => h(args[0]));
26
- }
27
- },
28
- };
29
- };
30
- // Mock node-pty
31
- vi.mock('node-pty', () => ({
32
- spawn: vi.fn(),
33
- }));
34
- // Mock @xterm/headless
35
- vi.mock('@xterm/headless', () => ({
36
- default: {
37
- Terminal: vi.fn().mockImplementation(() => ({
38
- buffer: {
39
- active: {
40
- length: 10,
41
- cursorY: 0,
42
- cursorX: 0,
43
- getLine: vi.fn(),
44
- },
45
- },
46
- write: vi.fn(),
47
- resize: vi.fn(),
48
- clear: vi.fn(),
49
- onData: vi.fn(),
50
- })),
51
- },
52
- }));
53
- describe('SessionManager - Partial TUI Update Integration', () => {
54
- let sessionManager;
55
- const mockWorktreePath = '/test/worktree';
56
- beforeEach(() => {
57
- sessionManager = new SessionManager();
58
- vi.clearAllMocks();
59
- });
60
- it('should not accumulate duplicate content in output history', () => {
61
- // Create a mock PTY process
62
- const mockProcess = createMockPtyProcess();
63
- vi.mocked(spawn).mockReturnValue(mockProcess);
64
- // Create a session
65
- sessionManager.createSession(mockWorktreePath);
66
- const session = sessionManager.sessions.get(mockWorktreePath);
67
- expect(session).toBeDefined();
68
- // Simulate multiple partial updates from Claude Code
69
- const updates = [
70
- '+ Exploring... (10s ・ 53 tokens ・ esc to interrupt)\r',
71
- '\x1B[1A\x1B[K+ Exploring... (10s ・ 57 tokens ・ esc to interrupt)\r',
72
- '\x1B[1A\x1B[K+ Exploring... (10s ・ 76 tokens ・ esc to interrupt)\r',
73
- '\x1B[1A\x1B[K+ Exploring... (10s ・ 89 tokens ・ esc to interrupt)\r',
74
- '\x1B[1A\x1B[K+ Exploring... (10s ・ 102 tokens ・ esc to interrupt)\r',
75
- ];
76
- // Process each update
77
- updates.forEach(update => {
78
- // Simulate PTY data event
79
- mockProcess._emit('data', update);
80
- });
81
- // Check that the virtual terminal received all updates
82
- expect(session.terminal.write).toHaveBeenCalledTimes(5);
83
- // The outputHistory should be empty since we removed that functionality
84
- expect(session.outputHistory).toEqual([]);
85
- });
86
- it('should use virtual terminal buffer for session restoration', () => {
87
- // Create a mock PTY process
88
- const mockProcess = createMockPtyProcess();
89
- vi.mocked(spawn).mockReturnValue(mockProcess);
90
- sessionManager.createSession(mockWorktreePath);
91
- const session = sessionManager.sessions.get(mockWorktreePath);
92
- // Mock the terminal buffer to contain the final state
93
- // Type cast is acceptable for test mocks
94
- const mockTerminal = session.terminal;
95
- mockTerminal.buffer.active.getLine = vi.fn((index) => {
96
- const lines = [
97
- 'Welcome to Claude Code',
98
- '+ Exploring... (10s ・ 218 tokens ・ esc to interrupt)',
99
- '',
100
- 'Task completed successfully',
101
- '> ',
102
- ];
103
- if (index < lines.length) {
104
- return {
105
- translateToString: () => lines[index],
106
- };
107
- }
108
- return null;
109
- });
110
- mockTerminal.buffer.active.length = 5;
111
- mockTerminal.buffer.active.cursorY = 4;
112
- mockTerminal.buffer.active.cursorX = 2;
113
- // Emit restore event
114
- sessionManager.emit('sessionRestore', session);
115
- // The terminal buffer should be used for restoration, not output history
116
- // This prevents duplicate content issues
117
- expect(session.outputHistory).toEqual([]);
118
- });
119
- it('should handle ANSI escape sequences correctly in virtual terminal', () => {
120
- // Create a mock PTY process
121
- const mockProcess = createMockPtyProcess();
122
- vi.mocked(spawn).mockReturnValue(mockProcess);
123
- sessionManager.createSession(mockWorktreePath);
124
- const session = sessionManager.sessions.get(mockWorktreePath);
125
- // Simulate data with ANSI escape sequences
126
- const dataWithEscapes = [
127
- 'Line 1\n',
128
- 'Line 2\n',
129
- '\x1B[1A\x1B[KReplaced Line 2\n', // Move up one line, clear line, write new text
130
- '\x1B[2J\x1B[H', // Clear screen and move to home
131
- 'Fresh start\n',
132
- ];
133
- dataWithEscapes.forEach(data => {
134
- mockProcess._emit('data', data);
135
- });
136
- // Virtual terminal should handle all the escape sequences
137
- expect(session.terminal.write).toHaveBeenCalledTimes(5);
138
- // No raw output should be stored
139
- expect(session.outputHistory).toEqual([]);
140
- });
141
- it('should emit sessionData events for active sessions only', () => {
142
- // Create a mock PTY process
143
- const mockProcess = createMockPtyProcess();
144
- vi.mocked(spawn).mockReturnValue(mockProcess);
145
- const dataHandler = vi.fn();
146
- sessionManager.on('sessionData', dataHandler);
147
- sessionManager.createSession(mockWorktreePath);
148
- const session = sessionManager.sessions.get(mockWorktreePath);
149
- // Session is not active by default
150
- mockProcess._emit('data', 'Test data 1');
151
- // Should not emit data when inactive
152
- expect(dataHandler).not.toHaveBeenCalled();
153
- // Activate session
154
- sessionManager.setSessionActive(mockWorktreePath, true);
155
- // Now data should be emitted
156
- mockProcess._emit('data', 'Test data 2');
157
- expect(dataHandler).toHaveBeenCalledWith(session, 'Test data 2');
158
- });
159
- it('should restore session without replaying output history', () => {
160
- // Create a mock PTY process
161
- const mockProcess = createMockPtyProcess();
162
- vi.mocked(spawn).mockReturnValue(mockProcess);
163
- const restoreHandler = vi.fn();
164
- sessionManager.on('sessionRestore', restoreHandler);
165
- sessionManager.createSession(mockWorktreePath);
166
- const session = sessionManager.sessions.get(mockWorktreePath);
167
- // Add some data to the session
168
- mockProcess._emit('data', 'Old output that should not be replayed\n');
169
- mockProcess._emit('data', 'More old output\n');
170
- // Deactivate then reactivate session
171
- sessionManager.setSessionActive(mockWorktreePath, false);
172
- sessionManager.setSessionActive(mockWorktreePath, true);
173
- // Should emit restore event
174
- expect(restoreHandler).toHaveBeenCalledWith(session);
175
- // But should not have any output history to replay
176
- expect(session.outputHistory).toEqual([]);
177
- });
178
- });
@@ -1,119 +0,0 @@
1
- import type { Terminal } from '../types/index.js';
2
- /**
3
- * TerminalSerializer: Converts terminal screen content to text while preserving colors and formatting.
4
- *
5
- * Imagine taking a screenshot of your terminal, but instead of an image, you get text that
6
- * can recreate the exact same appearance with all colors and styles when displayed again.
7
- */
8
- export declare class TerminalSerializer {
9
- /**
10
- * ESC: The "magic" prefix that tells the terminal "the next characters are instructions, not text"
11
- * '\x1b[' is like saying "Hey terminal, listen up for special commands!"
12
- */
13
- private static readonly ESC;
14
- /**
15
- * RESET: The command that tells terminal "go back to normal text" (no colors, no bold, etc.)
16
- * Like clicking "clear formatting" in a word processor
17
- */
18
- private static readonly RESET;
19
- /**
20
- * Determines which color system is being used for a given color value.
21
- *
22
- * Terminal supports different color systems:
23
- * - Mode 0 (DEFAULT): Basic terminal colors (like white text on black background)
24
- * - Mode 1 (P16): 16 basic colors (8 normal + 8 bright versions)
25
- * - Mode 2 (P256): 256 colors (used for more variety)
26
- * - Mode 3 (RGB): True color with Red, Green, Blue values (16.7 million colors)
27
- *
28
- * @param colorValue - The packed color information from the terminal
29
- * @returns 0, 1, 2, or 3 indicating which color system to use
30
- */
31
- private static getColorMode;
32
- /**
33
- * Extracts the actual color value from the packed color information.
34
- *
35
- * The extraction method depends on the color mode:
36
- * - For RGB mode: Extracts all three color components (R, G, B)
37
- * - For palette modes: Extracts just the color index number
38
- *
39
- * Think of it like unpacking a suitcase - different items are packed differently
40
- *
41
- * @param colorValue - The packed color information from the terminal
42
- * @returns The actual color value (either RGB values or palette index)
43
- */
44
- private static extractColor;
45
- /**
46
- * Converts a color value into the special text codes that terminals understand.
47
- *
48
- * Terminals use "ANSI escape sequences" - special character combinations that control
49
- * how text appears. It's like HTML tags, but for terminals.
50
- *
51
- * Examples of what this function produces:
52
- * - Red text: "\x1b[31m" (tells terminal "make the following text red")
53
- * - Blue background: "\x1b[44m" (tells terminal "make the background blue")
54
- * - RGB color: "\x1b[38;2;255;128;0m" (orange text using RGB values)
55
- *
56
- * @param colorValue - The color to convert (packed number with mode and color data)
57
- * @param isBackground - true for background color, false for text color
58
- * @returns ANSI escape sequence string that terminals can interpret
59
- */
60
- private static colorToAnsi;
61
- /**
62
- * Converts a single line of terminal content into text with color/style codes.
63
- *
64
- * This function processes each character in a line and:
65
- * 1. Extracts the character itself
66
- * 2. Checks its color (text and background)
67
- * 3. Checks its style (bold, italic, underline, etc.)
68
- * 4. Adds the necessary codes to recreate that appearance
69
- *
70
- * It's like going through a line character by character and noting:
71
- * "This letter is red, this one is bold, this one has blue background..."
72
- *
73
- * @param line - One line from the terminal screen
74
- * @param cols - Number of columns (width) of the terminal
75
- * @param trimRight - Whether to remove trailing spaces (like right-trim in text editors)
76
- * @returns The line as text with embedded color/style codes
77
- */
78
- private static serializeLine;
79
- /**
80
- * Converts the entire terminal screen (or part of it) into text with colors preserved.
81
- *
82
- * This is the main function that processes multiple lines of terminal content.
83
- * It's like taking a "text screenshot" of your terminal that can be replayed later
84
- * with all the colors and formatting intact.
85
- *
86
- * @param terminal - The terminal object containing the screen buffer
87
- * @param options - Configuration options:
88
- * - startLine: First line to include (default: 0, the top)
89
- * - endLine: Last line to include (default: bottom of screen)
90
- * - trimRight: Remove trailing spaces from each line (default: true)
91
- * - includeEmptyLines: Keep blank lines in output (default: true)
92
- * @returns Multi-line string with embedded ANSI codes for colors/styles
93
- */
94
- static serialize(terminal: Terminal, options?: {
95
- startLine?: number;
96
- endLine?: number;
97
- trimRight?: boolean;
98
- includeEmptyLines?: boolean;
99
- }): string;
100
- /**
101
- * Convenience function to get just the last few lines from the terminal.
102
- *
103
- * Useful when you only need recent output, like:
104
- * - Getting the last error message
105
- * - Showing recent command output
106
- * - Displaying the current prompt
107
- *
108
- * Example: getLastLines(terminal, 10) gets the last 10 lines
109
- *
110
- * @param terminal - The terminal object containing the screen buffer
111
- * @param lineCount - How many lines from the bottom to include
112
- * @param options - Same options as serialize() for controlling output format
113
- * @returns The requested lines as text with color/style codes
114
- */
115
- static getLastLines(terminal: Terminal, lineCount: number, options?: {
116
- trimRight?: boolean;
117
- includeEmptyLines?: boolean;
118
- }): string;
119
- }
@@ -1,376 +0,0 @@
1
- /**
2
- * Constants for decoding color information stored in terminal cells.
3
- * Terminal colors are packed into a single number where different bits represent different information.
4
- * Think of it like a compressed file - multiple pieces of data stored in one number.
5
- *
6
- * These constants are based on xterm.js internal implementation:
7
- * Source: https://github.com/xtermjs/xterm.js/blob/master/src/common/buffer/Constants.ts
8
- *
9
- * The color storage format uses bit packing where:
10
- * - Bits 1-8: Blue component (also used for palette color indices)
11
- * - Bits 9-16: Green component (RGB mode only)
12
- * - Bits 17-24: Red component (RGB mode only)
13
- * - Bits 25-26: Color mode indicator
14
- *
15
- * Reference implementation:
16
- * https://github.com/xtermjs/xterm.js/blob/master/src/common/buffer/AttributeData.ts
17
- */
18
- const Attributes = {
19
- /**
20
- * BLUE_MASK: Used to extract blue color value (bits 1-8)
21
- * In 256-color and 16-color modes, the entire color number is stored here
22
- * Example: 0x0000FF masks out everything except the last 8 bits
23
- */
24
- BLUE_MASK: 0xff,
25
- BLUE_SHIFT: 0,
26
- /**
27
- * GREEN_MASK: Used to extract green color value (bits 9-16)
28
- * Only used in RGB (true color) mode
29
- * Example: 0x00FF00 masks out everything except bits 9-16
30
- */
31
- GREEN_MASK: 0xff00,
32
- GREEN_SHIFT: 8,
33
- /**
34
- * RED_MASK: Used to extract red color value (bits 17-24)
35
- * Only used in RGB (true color) mode
36
- * Example: 0xFF0000 masks out everything except bits 17-24
37
- */
38
- RED_MASK: 0xff0000,
39
- RED_SHIFT: 16,
40
- /**
41
- * CM_MASK: Used to determine which color system is being used (bits 25-26)
42
- * Like a label that says "this is 16-color" or "this is RGB"
43
- *
44
- * Color modes from xterm.js:
45
- * https://github.com/xtermjs/xterm.js/blob/master/src/common/Types.d.ts#L33
46
- */
47
- CM_MASK: 0x3000000,
48
- CM_DEFAULT: 0, // Default terminal colors (usually white text on black background)
49
- CM_P16: 0x1000000, // 16-color palette (basic colors like red, blue, green)
50
- CM_P256: 0x2000000, // 256-color palette (more shades and colors)
51
- CM_RGB: 0x3000000, // RGB/true color (millions of colors, like in photos)
52
- };
53
- /**
54
- * TerminalSerializer: Converts terminal screen content to text while preserving colors and formatting.
55
- *
56
- * Imagine taking a screenshot of your terminal, but instead of an image, you get text that
57
- * can recreate the exact same appearance with all colors and styles when displayed again.
58
- */
59
- export class TerminalSerializer {
60
- /**
61
- * Determines which color system is being used for a given color value.
62
- *
63
- * Terminal supports different color systems:
64
- * - Mode 0 (DEFAULT): Basic terminal colors (like white text on black background)
65
- * - Mode 1 (P16): 16 basic colors (8 normal + 8 bright versions)
66
- * - Mode 2 (P256): 256 colors (used for more variety)
67
- * - Mode 3 (RGB): True color with Red, Green, Blue values (16.7 million colors)
68
- *
69
- * @param colorValue - The packed color information from the terminal
70
- * @returns 0, 1, 2, or 3 indicating which color system to use
71
- */
72
- static getColorMode(colorValue) {
73
- const mode = colorValue & Attributes.CM_MASK;
74
- if (mode === Attributes.CM_P16)
75
- return 1; // P16
76
- if (mode === Attributes.CM_P256)
77
- return 2; // P256
78
- if (mode === Attributes.CM_RGB)
79
- return 3; // RGB
80
- return 0; // DEFAULT
81
- }
82
- /**
83
- * Extracts the actual color value from the packed color information.
84
- *
85
- * The extraction method depends on the color mode:
86
- * - For RGB mode: Extracts all three color components (R, G, B)
87
- * - For palette modes: Extracts just the color index number
88
- *
89
- * Think of it like unpacking a suitcase - different items are packed differently
90
- *
91
- * @param colorValue - The packed color information from the terminal
92
- * @returns The actual color value (either RGB values or palette index)
93
- */
94
- static extractColor(colorValue) {
95
- const mode = colorValue & Attributes.CM_MASK;
96
- if (mode === Attributes.CM_RGB) {
97
- // For RGB, extract R, G, B components (all 24 bits of color data)
98
- return colorValue & 0xffffff;
99
- }
100
- // For palette colors, it's stored in the blue channel (just 8 bits)
101
- return colorValue & Attributes.BLUE_MASK;
102
- }
103
- /**
104
- * Converts a color value into the special text codes that terminals understand.
105
- *
106
- * Terminals use "ANSI escape sequences" - special character combinations that control
107
- * how text appears. It's like HTML tags, but for terminals.
108
- *
109
- * Examples of what this function produces:
110
- * - Red text: "\x1b[31m" (tells terminal "make the following text red")
111
- * - Blue background: "\x1b[44m" (tells terminal "make the background blue")
112
- * - RGB color: "\x1b[38;2;255;128;0m" (orange text using RGB values)
113
- *
114
- * @param colorValue - The color to convert (packed number with mode and color data)
115
- * @param isBackground - true for background color, false for text color
116
- * @returns ANSI escape sequence string that terminals can interpret
117
- */
118
- static colorToAnsi(colorValue, isBackground) {
119
- // -1 is a special value meaning "use the terminal's default color"
120
- if (colorValue === -1) {
121
- return isBackground ? `${this.ESC}49m` : `${this.ESC}39m`;
122
- }
123
- // Different prefixes for text color (38) vs background color (48)
124
- const prefix = isBackground ? '48' : '38';
125
- const mode = this.getColorMode(colorValue);
126
- const color = this.extractColor(colorValue);
127
- switch (mode) {
128
- case 0: // DEFAULT
129
- // Reset to terminal's default colors
130
- return isBackground ? `${this.ESC}49m` : `${this.ESC}39m`;
131
- case 1: // P16 - Basic 16 colors
132
- // Colors 0-7 are normal intensity (black, red, green, yellow, blue, magenta, cyan, white)
133
- // Colors 8-15 are bright/bold versions of the same colors
134
- if (color < 8) {
135
- // Standard colors: 30-37 for text, 40-47 for background
136
- return `${this.ESC}${(isBackground ? 40 : 30) + color}m`;
137
- }
138
- else {
139
- // Bright colors: 90-97 for text, 100-107 for background
140
- return `${this.ESC}${(isBackground ? 100 : 90) + (color - 8)}m`;
141
- }
142
- case 2: // P256 - Extended 256 color palette
143
- // Format: ESC[38;5;{color}m for foreground, ESC[48;5;{color}m for background
144
- // The ;5; tells terminal "the next number is a color from the 256-color palette"
145
- return `${this.ESC}${prefix};5;${color}m`;
146
- case 3: {
147
- // RGB - True color (24-bit, millions of colors)
148
- // Extract individual Red, Green, Blue components from the packed color
149
- const r = (color >> 16) & 0xff; // Red: bits 17-24
150
- const g = (color >> 8) & 0xff; // Green: bits 9-16
151
- const b = color & 0xff; // Blue: bits 1-8
152
- // Format: ESC[38;2;{r};{g};{b}m for foreground
153
- // The ;2; tells terminal "the next three numbers are RGB values"
154
- return `${this.ESC}${prefix};2;${r};${g};${b}m`;
155
- }
156
- default:
157
- return '';
158
- }
159
- }
160
- /**
161
- * Converts a single line of terminal content into text with color/style codes.
162
- *
163
- * This function processes each character in a line and:
164
- * 1. Extracts the character itself
165
- * 2. Checks its color (text and background)
166
- * 3. Checks its style (bold, italic, underline, etc.)
167
- * 4. Adds the necessary codes to recreate that appearance
168
- *
169
- * It's like going through a line character by character and noting:
170
- * "This letter is red, this one is bold, this one has blue background..."
171
- *
172
- * @param line - One line from the terminal screen
173
- * @param cols - Number of columns (width) of the terminal
174
- * @param trimRight - Whether to remove trailing spaces (like right-trim in text editors)
175
- * @returns The line as text with embedded color/style codes
176
- */
177
- static serializeLine(line, cols, trimRight = true) {
178
- if (!line)
179
- return '';
180
- let result = '';
181
- // Keep track of the current colors/styles to avoid repeating the same codes
182
- let lastFgColor = null;
183
- let lastBgColor = null;
184
- let lastStyles = {
185
- bold: false,
186
- italic: false,
187
- underline: false,
188
- strikethrough: false,
189
- };
190
- // Find the last character that isn't a space (for trimming)
191
- // Start from the right and work backwards to find content
192
- let lastNonEmptyIndex = -1;
193
- if (trimRight) {
194
- for (let x = cols - 1; x >= 0; x--) {
195
- const cell = line.getCell(x);
196
- if (cell) {
197
- const chars = cell.getChars();
198
- if (chars && chars.trim() !== '') {
199
- lastNonEmptyIndex = x;
200
- break;
201
- }
202
- }
203
- }
204
- }
205
- // If the line is completely empty and we're trimming, return empty string
206
- if (trimRight && lastNonEmptyIndex === -1) {
207
- return '';
208
- }
209
- const endCol = trimRight ? lastNonEmptyIndex + 1 : cols;
210
- // Process each character position in the line
211
- for (let x = 0; x < endCol; x++) {
212
- const cell = line.getCell(x);
213
- if (!cell) {
214
- // No cell data at this position, just add a space
215
- result += ' ';
216
- continue;
217
- }
218
- // Get the actual character at this position
219
- const chars = cell.getChars();
220
- const cellChar = chars || ' ';
221
- // Build up any necessary escape sequences for this character
222
- let escapeSequence = '';
223
- // STEP 1: Check text color
224
- // Get both the color value and the color mode (which type of color it is)
225
- const fgColor = cell.getFgColor();
226
- const fgColorMode = cell.getFgColorMode();
227
- // Combine them into a single value that includes both color and mode information
228
- const fgColorValue = fgColorMode === 0 ? fgColor : fgColorMode | fgColor;
229
- // Only add color code if it's different from the previous character
230
- if (fgColorValue !== lastFgColor) {
231
- escapeSequence += this.colorToAnsi(fgColorValue, false);
232
- lastFgColor = fgColorValue;
233
- }
234
- // STEP 2: Check background color (same process as text color)
235
- const bgColor = cell.getBgColor();
236
- const bgColorMode = cell.getBgColorMode();
237
- const bgColorValue = bgColorMode === 0 ? bgColor : bgColorMode | bgColor;
238
- if (bgColorValue !== lastBgColor) {
239
- escapeSequence += this.colorToAnsi(bgColorValue, true);
240
- lastBgColor = bgColorValue;
241
- }
242
- // STEP 3: Check text styles (bold, italic, etc.)
243
- const currentStyles = {
244
- bold: !!cell.isBold(), // Is this character bold?
245
- italic: !!cell.isItalic(), // Is it italicized?
246
- underline: !!cell.isUnderline(), // Is it underlined?
247
- strikethrough: !!cell.isStrikethrough(), // Is it crossed out?
248
- };
249
- // If any style changed from the previous character, update them
250
- if (currentStyles.bold !== lastStyles.bold ||
251
- currentStyles.italic !== lastStyles.italic ||
252
- currentStyles.underline !== lastStyles.underline ||
253
- currentStyles.strikethrough !== lastStyles.strikethrough) {
254
- // First, turn off all styles with reset codes
255
- // 22m = not bold, 23m = not italic, 24m = not underline, 29m = not strikethrough
256
- escapeSequence += `${this.ESC}22m${this.ESC}23m${this.ESC}24m${this.ESC}29m`;
257
- // Then turn on only the styles we need
258
- // 1m = bold, 3m = italic, 4m = underline, 9m = strikethrough
259
- if (currentStyles.bold)
260
- escapeSequence += `${this.ESC}1m`;
261
- if (currentStyles.italic)
262
- escapeSequence += `${this.ESC}3m`;
263
- if (currentStyles.underline)
264
- escapeSequence += `${this.ESC}4m`;
265
- if (currentStyles.strikethrough)
266
- escapeSequence += `${this.ESC}9m`;
267
- lastStyles = { ...currentStyles };
268
- }
269
- // Add the escape sequences (if any) followed by the actual character
270
- result += escapeSequence + cellChar;
271
- }
272
- return result;
273
- }
274
- /**
275
- * Converts the entire terminal screen (or part of it) into text with colors preserved.
276
- *
277
- * This is the main function that processes multiple lines of terminal content.
278
- * It's like taking a "text screenshot" of your terminal that can be replayed later
279
- * with all the colors and formatting intact.
280
- *
281
- * @param terminal - The terminal object containing the screen buffer
282
- * @param options - Configuration options:
283
- * - startLine: First line to include (default: 0, the top)
284
- * - endLine: Last line to include (default: bottom of screen)
285
- * - trimRight: Remove trailing spaces from each line (default: true)
286
- * - includeEmptyLines: Keep blank lines in output (default: true)
287
- * @returns Multi-line string with embedded ANSI codes for colors/styles
288
- */
289
- static serialize(terminal, options = {}) {
290
- // Get the current screen content and dimensions
291
- const buffer = terminal.buffer.active;
292
- const cols = terminal.cols;
293
- // Apply default options
294
- const { startLine = 0, endLine = buffer.length, trimRight = true, includeEmptyLines = true, } = options;
295
- const lines = [];
296
- // Process each line in the specified range
297
- for (let y = startLine; y < Math.min(endLine, buffer.length); y++) {
298
- const line = buffer.getLine(y);
299
- if (line) {
300
- // Convert this line to text with color codes
301
- const serializedLine = this.serializeLine(line, cols, trimRight);
302
- // Skip empty lines if user doesn't want them
303
- if (!includeEmptyLines && serializedLine.trim() === '') {
304
- continue;
305
- }
306
- lines.push(serializedLine);
307
- }
308
- else if (includeEmptyLines) {
309
- // Line doesn't exist but we want to preserve empty lines
310
- lines.push('');
311
- }
312
- }
313
- // Remove trailing empty lines if trimming is enabled
314
- // This is like removing blank lines at the end of a document
315
- if (trimRight && lines.length > 0) {
316
- let lastNonEmptyIndex = lines.length - 1;
317
- while (lastNonEmptyIndex >= 0) {
318
- const line = lines[lastNonEmptyIndex];
319
- if (line && line.trim() !== '') {
320
- break;
321
- }
322
- lastNonEmptyIndex--;
323
- }
324
- // Join all lines and add a reset code at the end to clear any formatting
325
- return lines.slice(0, lastNonEmptyIndex + 1).join('\n') + this.RESET;
326
- }
327
- // Join all lines and add a reset code at the end
328
- return lines.join('\n') + this.RESET;
329
- }
330
- /**
331
- * Convenience function to get just the last few lines from the terminal.
332
- *
333
- * Useful when you only need recent output, like:
334
- * - Getting the last error message
335
- * - Showing recent command output
336
- * - Displaying the current prompt
337
- *
338
- * Example: getLastLines(terminal, 10) gets the last 10 lines
339
- *
340
- * @param terminal - The terminal object containing the screen buffer
341
- * @param lineCount - How many lines from the bottom to include
342
- * @param options - Same options as serialize() for controlling output format
343
- * @returns The requested lines as text with color/style codes
344
- */
345
- static getLastLines(terminal, lineCount, options = {}) {
346
- const buffer = terminal.buffer.active;
347
- // Calculate where to start (can't go below 0)
348
- const startLine = Math.max(0, buffer.length - lineCount);
349
- // Use the main serialize function but with a specific range
350
- return this.serialize(terminal, {
351
- startLine,
352
- endLine: buffer.length,
353
- ...options,
354
- });
355
- }
356
- }
357
- /**
358
- * ESC: The "magic" prefix that tells the terminal "the next characters are instructions, not text"
359
- * '\x1b[' is like saying "Hey terminal, listen up for special commands!"
360
- */
361
- Object.defineProperty(TerminalSerializer, "ESC", {
362
- enumerable: true,
363
- configurable: true,
364
- writable: true,
365
- value: '\x1b['
366
- });
367
- /**
368
- * RESET: The command that tells terminal "go back to normal text" (no colors, no bold, etc.)
369
- * Like clicking "clear formatting" in a word processor
370
- */
371
- Object.defineProperty(TerminalSerializer, "RESET", {
372
- enumerable: true,
373
- configurable: true,
374
- writable: true,
375
- value: '\x1b[0m'
376
- });
@@ -1 +0,0 @@
1
- export {};