Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions common/src/fileSystemConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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) */
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,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",
Expand Down
21 changes: 17 additions & 4 deletions src/pseudoTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type { EnvironmentVariable, FileSystemConfig } from 'common/fileSystemCon
import * as path from 'path';
import type { ClientChannel, PseudoTtyOptions } from 'ssh2';
import * as vscode from 'vscode';
import { getFlagBoolean } from './flags';
import type { Connection } from './connection';
import { getFlagBoolean } from './flags';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import { environmentToExportString, joinCommands, mergeEnvironment, toPromise } from './utils';

Expand Down Expand Up @@ -135,13 +135,26 @@ export async function replaceVariablesRecursive<T>(object: T, handler: (value: s
return object;
}

async function getEncodingHandlers(encoding?: string): Promise<[encode: (data: string) => Buffer, decode: (data: Buffer) => string]> {
if (encoding) {
const iconv = await import('iconv-lite');
if (!iconv.encodingExists(encoding))
throw new Error(`Unknown character encoding '${encoding}'`);
return [data => iconv.encode(data, encoding), data => iconv.decode(data, encoding)];
}
return [data => Buffer.from(data, 'utf-8'), data => data.toString('utf-8')];
}

export async function createTerminal(options: TerminalOptions): Promise<SSHPseudoTerminal> {
const { connection } = options;
const { actualConfig, client, shellConfig } = connection;
const onDidWrite = new vscode.EventEmitter<string>();
const onDidClose = new vscode.EventEmitter<number>();
const onDidOpen = new vscode.EventEmitter<void>();
let terminal: vscode.Terminal | undefined;

const [encodeInput, decodeOutput] = await getEncodingHandlers(actualConfig.encoding);

// Won't actually open the remote terminal until pseudo.open(dims) is called
const pseudo: SSHPseudoTerminal = {
status: 'opening',
Expand Down Expand Up @@ -241,8 +254,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(decodeOutput(chunk)));
channel.stderr!.on('data', chunk => onDidWrite.fire(decodeOutput(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}`;
Expand All @@ -264,7 +277,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(encodeInput(data));
},
};
return pseudo;
Expand Down
9 changes: 8 additions & 1 deletion webview/src/ConfigEditor/fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -4,7 +4,7 @@ import './index.css';

export interface Props<T> {
label?: string;
description?: string;
description?: React.ReactNode;
value: T;
optional?: boolean;
group?: FieldGroup;
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10317,6 +10317,7 @@ __metadata:
"@vscode/vsce": "npm:^2.18.0"
common: "workspace:*"
event-stream: "npm:^4.0.1"
iconv-lite: "npm:^0.6.3"
jsonc-parser: "npm:^3.2.0"
prettier: "npm:^2.6.2"
semver: "npm:^7.3.5"
Expand Down