Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encoding support via iconv-lite #431

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .yarn/yarn.lock
Original file line number Diff line number Diff line change
@@ -5703,7 +5703,7 @@ __metadata:
languageName: node
linkType: hard

"iconv-lite@npm:^0.6.2":
"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
dependencies:
@@ -9560,6 +9560,7 @@ __metadata:
"@vscode/vsce": ^2.18.0
common: "workspace:*"
event-stream: ^4.0.1
iconv-lite: ^0.6.3
jsonc-parser: ^3.2.0
prettier: ^2.6.2
semver: ^7.3.5
2 changes: 2 additions & 0 deletions common/src/fileSystemConfig.ts
Original file line number Diff line number Diff line change
@@ -117,6 +117,8 @@ export interface FileSystemConfig extends ConnectConfig {
instantConnection?: boolean;
/** List of special flags to enable/disable certain fixes/features. Flags are usually used for issues or beta testing. Flags can disappear/change anytime! */
flags?: string[];
/** Specifies the character encoding used for the SSH terminal. If undefined or an unsupported by iconv-lite, UTF-8 will be used */
encoding?: string;
/** Internal property saying where this config comes from. Undefined if this config is merged or something */
_location?: ConfigLocation;
/** Internal property keeping track of where this config comes from (including merges) */
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -429,6 +429,7 @@
"dependencies": {
"common": "workspace:*",
"event-stream": "^4.0.1",
"iconv-lite": "^0.6.3",
"jsonc-parser": "^3.2.0",
"semver": "^7.3.5",
"socks": "^2.2.0",
25 changes: 22 additions & 3 deletions src/pseudoTerminal.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import type { EnvironmentVariable, FileSystemConfig } from 'common/fileSystemCon
import * as path from 'path';
import type { ClientChannel, PseudoTtyOptions } from 'ssh2';
import * as vscode from 'vscode';
import * as iconv from 'iconv-lite';
import { getFlagBoolean } from './flags';
import type { Connection } from './connection';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
@@ -142,6 +143,24 @@ export async function createTerminal(options: TerminalOptions): Promise<SSHPseud
const onDidClose = new vscode.EventEmitter<number>();
const onDidOpen = new vscode.EventEmitter<void>();
let terminal: vscode.Terminal | undefined;

// Encodes user input (originally UTF-8 in JS string) into the remote encoding, if configured.
// Returns a Buffer if encoding is valid, otherwise returns the original string.
let encodeTerminalInput: (data: string) => Buffer | string;

// Decodes data received from the remote side (as Buffer) into a string using the configured encoding.
// If encoding is not set or invalid, defaults to data.toString() (UTF-8).
let decodeTerminalOutput: (data: Buffer) => string;

const encoding = actualConfig.encoding;
if (encoding && iconv.encodingExists(encoding)) {
encodeTerminalInput = (data: string) => iconv.encode(data, encoding);
decodeTerminalOutput = (data: Buffer) => iconv.decode(data, encoding);
} else {
encodeTerminalInput = (data: string) => data;
decodeTerminalOutput = (data: Buffer) => data.toString();
}

// Won't actually open the remote terminal until pseudo.open(dims) is called
const pseudo: SSHPseudoTerminal = {
status: 'opening',
@@ -241,8 +260,8 @@ export async function createTerminal(options: TerminalOptions): Promise<SSHPseud
if (pseudo.status === 'opening') pseudo.status = 'open';
onDidOpen.fire();
});
channel.on('data', chunk => onDidWrite.fire(chunk.toString()));
channel.stderr!.on('data', chunk => onDidWrite.fire(chunk.toString()));
channel.on('data', chunk => onDidWrite.fire(decodeTerminalOutput(chunk)));
channel.stderr!.on('data', chunk => onDidWrite.fire(decodeTerminalOutput(chunk)));
// TODO: ^ Keep track of stdout's color, switch to red, output, then switch back?
} catch (e) {
Logging.error`Error starting SSH terminal:\n${e}`;
@@ -264,7 +283,7 @@ export async function createTerminal(options: TerminalOptions): Promise<SSHPseud
},
handleInput(data) {
if (pseudo.status === 'wait-to-close') return pseudo.close();
pseudo.channel?.write(data);
pseudo.channel?.write(encodeTerminalInput(data));
},
};
return pseudo;
9 changes: 8 additions & 1 deletion webview/src/ConfigEditor/fields.tsx
Original file line number Diff line number Diff line change
@@ -169,9 +169,16 @@ export function taskCommand(config: FileSystemConfig, onChange: FSCChanged<'task
return <FieldDropdownWithInput key="taskCommand" label="Task command" {...{ value, values, description }} onChange={callback} optional />
}

export function encoding(config: FileSystemConfig, onChange: FSCChanged<'encoding'>): React.ReactElement {
const callback = (newValue?: string) => onChange('encoding', newValue);
const description = (<>Text encoding used for terminal input/output. For a list of supported encodings, see <a href="https://github.com/ashtuchkin/iconv-lite/wiki/Supported-Encodings" target="_blank" rel="noreferrer">iconv-lite wiki</a></>);
const values = ['utf8', 'iso-8859-1', 'Shift_JIS', 'EUC-JP', 'EUC-KR'];
return <FieldDropdownWithInput key="encoding" label="Encoding" {...{ value: config.encoding, values, description }} onChange={callback} optional />
}

export type FieldFactory = (config: FileSystemConfig, onChange: FSCChanged, onChangeMultiple: FSCChangedMultiple) => React.ReactElement | null;
export const FIELDS: FieldFactory[] = [
name, label, group, merge, extend, putty, host, port,
root, agent, username, password, privateKeyPath, passphrase,
newFileMode, agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand,
newFileMode, agentForward, sftpCommand, sftpSudo, terminalCommand, taskCommand, encoding,
PROXY_FIELD];
2 changes: 1 addition & 1 deletion webview/src/FieldTypes/base.tsx
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import './index.css';

export interface Props<T> {
label?: string;
description?: string;
description?: React.ReactNode;
value: T;
optional?: boolean;
group?: FieldGroup;