Skip to content
Merged
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ffda493
Fix character encoding issues in shell command processor
boylin0 Jun 26, 2025
07e8ded
Merge branch 'main' into main
boylin0 Jun 26, 2025
923b04c
Merge branch 'main' into main
boylin0 Jun 26, 2025
3b9694f
fix: validate Windows code page before encoding conversion
boylin0 Jun 30, 2025
0cb8755
fix: improve system encoding detection with robust fallbacks
boylin0 Jun 30, 2025
55fdc7c
fix: handle unsupported Windows code pages safely
boylin0 Jun 30, 2025
2676821
perf(shellCommandProcessor): cache system encoding detection
boylin0 Jul 12, 2025
72dc235
perf(shellCommandProcessor): Fix text decoder streaming and end-of-st…
boylin0 Jul 12, 2025
bde9748
Improve shell command encoding detection and error handling
boylin0 Jul 12, 2025
5f1ef8f
Merge branch 'main' into main
boylin0 Jul 12, 2025
3e6d8a9
fix(package): remove unnecessary dependency on @google/gemini-cli
boylin0 Jul 12, 2025
a6cb358
style(shellCommandProcessor): remove unnecessary blank line in execut…
boylin0 Jul 12, 2025
41b524a
fix(package): add missing chardet dependency
boylin0 Jul 12, 2025
7588ae3
Merge branch 'main' into main
jacob314 Jul 12, 2025
6224be2
fix: improve encoding detection caching
boylin0 Jul 19, 2025
2aa1706
fix: improve Windows encoding detection error messages
boylin0 Jul 19, 2025
d25e52f
fix: missing output final bytes from TextDecoder on process exit
boylin0 Jul 19, 2025
9286142
test(shellCommandProcessor): add comprehensive test coverage
boylin0 Jul 19, 2025
dd63380
Merge branch 'fix/shell-command-processor-encoding'
boylin0 Jul 19, 2025
b871ab7
refactor(cli): integrate core encoding utilities; add reset function …
boylin0 Jul 19, 2025
c629c8f
test(core): add systemEncoding utility test suite
boylin0 Jul 19, 2025
3583693
refactor(deps): move chardet dependency into packages\core
boylin0 Jul 19, 2025
30a1f7e
style(shellCommandProcessor): format code
boylin0 Jul 19, 2025
1aa9491
fix(systemEncoding): update cachedSystemEncoding use undefined for un…
boylin0 Jul 19, 2025
b14869e
style(systemEncoding): format code
boylin0 Jul 19, 2025
b474dd5
fix(shellCommandProcessor): ensure homedir mock is defined for consis…
boylin0 Jul 19, 2025
909d170
Merge branch 'main' into main
SandyTao520 Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 89 additions & 9 deletions packages/cli/src/ui/hooks/shellCommandProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { spawn } from 'child_process';
import { StringDecoder } from 'string_decoder';
import { spawn, execSync } from 'child_process';
import { TextDecoder } from 'util';
import type { HistoryItemWithoutId } from '../types.js';
import { useCallback } from 'react';
import { Config, GeminiClient } from '@google/gemini-cli-core';
Expand All @@ -22,6 +22,88 @@ import stripAnsi from 'strip-ansi';
const OUTPUT_UPDATE_INTERVAL_MS = 1000;
const MAX_OUTPUT_LENGTH = 10000;

function getSystemEncoding() {
// Windows
if (os.platform() === 'win32') {
try {
const output = execSync('chcp', { encoding: 'utf8' });
const match = output.match(/:\s*(\d+)/);
if (match) {
const codePage = parseInt(match[1], 10);
if (!isNaN(codePage)) {
return windowsCodePageToEncoding(codePage);
}
}
} catch (_e) {
console.warn('Failed to get Windows code page. Falling back to utf-8.');
}
return 'utf-8';
}

// Unix-like
// Use environment variables LC_ALL, LC_CTYPE, and LANG to determine the
// system encoding. However, these environment variables might not always
// be set or accurate. Handle cases where none of these variables are set.
const env = process.env;
let locale = env.LC_ALL || env.LC_CTYPE || env.LANG || '';

// Fallback to querying the system directly when environment variables are missing
if (!locale) {
try {
locale = execSync('locale charmap', { encoding: 'utf8' }).toString().trim();
} catch (_e) {
console.warn('Failed to get locale charmap. Falling back to utf-8.');
return 'utf-8';
}
}

const match = locale.match(/\.(.+)/); // e.g., "en_US.UTF-8"
if (match && match[1]) {
return match[1].toLowerCase();
}

// Handle cases where locale charmap returns just the encoding name
if (locale && !locale.includes('.')) {
return locale.toLowerCase();
}

return 'utf-8'; // fallback
}

function windowsCodePageToEncoding(cp: number) {
// Most common mappings; extend as needed
const map: { [key: number]: string } = {
437: 'cp437',
850: 'cp850',
852: 'cp852',
866: 'cp866',
874: 'windows-874',
932: 'shift_jis',
936: 'gb2312',
949: 'euc-kr',
950: 'big5',
1200: 'utf-16le',
1201: 'utf-16be',
1250: 'windows-1250',
1251: 'windows-1251',
1252: 'windows-1252',
1253: 'windows-1253',
1254: 'windows-1254',
1255: 'windows-1255',
1256: 'windows-1256',
1257: 'windows-1257',
1258: 'windows-1258',
65001: 'utf-8'
};

if (map[cp]) {
return map[cp];
}

console.warn(`Unknown Windows code page: ${cp}. Falling back to utf-8.`);
Comment thread
boylin0 marked this conversation as resolved.
Outdated
return 'utf-8';
}

/**
* A structured result from a shell command execution.
*/
Expand Down Expand Up @@ -66,8 +148,9 @@ function executeShellCommand(
});

// Use decoders to handle multi-byte characters safely (for streaming output).
const stdoutDecoder = new StringDecoder('utf8');
const stderrDecoder = new StringDecoder('utf8');
const systemEncoding = getSystemEncoding();
Comment thread
boylin0 marked this conversation as resolved.
Outdated
const stdoutDecoder = new TextDecoder(systemEncoding);
const stderrDecoder = new TextDecoder(systemEncoding);

let stdout = '';
let stderr = '';
Expand Down Expand Up @@ -96,8 +179,8 @@ function executeShellCommand(

const decodedChunk =
stream === 'stdout'
? stdoutDecoder.write(data)
: stderrDecoder.write(data);
? stdoutDecoder.decode(data)
: stderrDecoder.decode(data);
Comment thread
boylin0 marked this conversation as resolved.
Outdated
if (stream === 'stdout') {
stdout += stripAnsi(decodedChunk);
} else {
Expand Down Expand Up @@ -154,9 +237,6 @@ function executeShellCommand(
exited = true;
abortSignal.removeEventListener('abort', abortHandler);

// Handle any final bytes lingering in the decoders
Comment thread
boylin0 marked this conversation as resolved.
stdout += stdoutDecoder.end();
stderr += stderrDecoder.end();

const finalBuffer = Buffer.concat(outputChunks);

Expand Down