blob: 601720a73fd0d0aaad949f8567fa04ab5e1b90eb [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Input/Output interface used by commands to communicate with the terminal.
*
* Commands like `nassh` and `crosh` receive an instance of this class as
* part of their argv object. This allows them to write to and read from the
* terminal without exposing them to an entire hterm.Terminal instance.
*
* The active command must override the onVTKeystroke() and sendString() methods
* of this class in order to receive keystrokes and send output to the correct
* destination.
*
* Isolating commands from the terminal provides the following benefits:
* - Provides a mechanism to save and restore onVTKeystroke and sendString
* handlers when invoking subcommands (see the push() and pop() methods).
* - The isolation makes it easier to make changes in Terminal and supporting
* classes without affecting commands.
* - In The Future commands may run in web workers where they would only be able
* to talk to a Terminal instance through an IPC mechanism.
*
* @param {!hterm.Terminal} terminal
* @constructor
*/
hterm.Terminal.IO = function(terminal) {
this.terminal_ = terminal;
// The IO object to restore on IO.pop().
this.previousIO_ = null;
// Any data this object accumulated while not active.
this.buffered_ = '';
// Decoder to maintain UTF-8 decode state.
this.textDecoder_ = new TextDecoder();
};
/**
* Show the terminal overlay for a given amount of time.
*
* The terminal overlay appears in inverse video, centered over the terminal.
*
* @param {string|!Node} message The message to display in the overlay.
* @param {number=} timeout The amount of time to wait before fading out
* the overlay. Defaults to 1.5 seconds. Pass null to have the overlay
* stay up forever (or until the next overlay).
*/
hterm.Terminal.IO.prototype.showOverlay = function(
message, timeout = undefined) {
this.terminal_.showOverlay(message, timeout);
};
/**
* Hide the current overlay immediately.
*
* Useful when we show an overlay for an event with an unknown end time.
*/
hterm.Terminal.IO.prototype.hideOverlay = function() {
this.terminal_.hideOverlay();
};
/**
* Open an frame in the current terminal window, pointed to the specified
* url.
*
* Eventually we'll probably need size/position/decoration options.
* The user should also be able to move/resize the frame.
*
* @param {string} url The URL to load in the frame.
* @param {!Object=} options Optional frame options. Not implemented.
* @return {!hterm.Frame}
*/
hterm.Terminal.IO.prototype.createFrame = function(url, options = undefined) {
return new hterm.Frame(this.terminal_, url, options);
};
/**
* Change the preference profile for the terminal.
*
* @param {string} profileName The name of the preference profile to activate.
*/
hterm.Terminal.IO.prototype.setTerminalProfile = function(profileName) {
this.terminal_.setProfile(profileName);
};
/**
* Create a new hterm.Terminal.IO instance and make it active on the Terminal
* object associated with this instance.
*
* This is used to pass control of the terminal IO off to a subcommand. The
* IO.pop() method can be used to restore control when the subcommand completes.
*
* @return {!hterm.Terminal.IO} The new foreground IO instance.
*/
hterm.Terminal.IO.prototype.push = function() {
const io = new hterm.Terminal.IO(this.terminal_);
io.keyboardCaptured_ = this.keyboardCaptured_;
io.columnCount = this.columnCount;
io.rowCount = this.rowCount;
io.previousIO_ = this.terminal_.io;
this.terminal_.io = io;
return io;
};
/**
* Restore the Terminal's previous IO object.
*
* We'll flush out any queued data.
*/
hterm.Terminal.IO.prototype.pop = function() {
this.terminal_.io = this.previousIO_;
this.previousIO_.flush();
};
/**
* Flush accumulated data.
*
* If we're not the active IO, the connected process might still be writing
* data to us, but we won't be displaying it. Flush any buffered data now.
*/
hterm.Terminal.IO.prototype.flush = function() {
if (this.buffered_) {
this.terminal_.interpret(this.buffered_);
this.buffered_ = '';
}
};
/**
* Called when data needs to be sent to the current command.
*
* Clients should override this to receive notification of pending data.
*
* @param {string} string The data to send.
*/
hterm.Terminal.IO.prototype.sendString = function(string) {
// Override this.
console.log('Unhandled sendString: ' + string);
};
/**
* Called when a terminal keystroke is detected.
*
* Clients should override this to receive notification of keystrokes.
*
* @param {string} string The VT key sequence.
*/
hterm.Terminal.IO.prototype.onVTKeystroke = function(string) {
// Override this.
console.log('Unobserverd VT keystroke: ' + JSON.stringify(string));
};
/**
* Receives notification when the terminal is resized.
*
* @param {number} width The new terminal width.
* @param {number} height The new terminal height.
*/
hterm.Terminal.IO.prototype.onTerminalResize_ = function(width, height) {
let obj = this;
while (obj) {
obj.columnCount = width;
obj.rowCount = height;
obj = obj.previousIO_;
}
this.onTerminalResize(width, height);
};
/**
* Called when terminal size is changed.
*
* Clients should override this to receive notification of resize.
*
* @param {string|number} width The new terminal width.
* @param {string|number} height The new terminal height.
*/
hterm.Terminal.IO.prototype.onTerminalResize = function(width, height) {
// Override this.
};
/**
* Write a UTF-8 encoded byte string to the terminal.
*
* @param {string|!ArrayBuffer} string The UTF-8 encoded string to print.
*/
hterm.Terminal.IO.prototype.writeUTF8 = function(string) {
// We can't use instanceof here on string to see if it's an ArrayBuffer as it
// might be constructed in a different runtime context whose ArrayBuffer was
// not the same. See https://crbug.com/930171#5 for more details.
if (typeof string == 'string') {
if (this.terminal_.vt.characterEncoding != 'raw') {
const bytes = lib.codec.stringToCodeUnitArray(string);
string = this.textDecoder_.decode(bytes, {stream: true});
}
} else {
// Handle array buffers & typed arrays by normalizing into a typed array.
const u8 = new Uint8Array(string);
if (this.terminal_.vt.characterEncoding == 'raw') {
string = lib.codec.codeUnitArrayToString(u8);
} else {
string = this.textDecoder_.decode(u8, {stream: true});
}
}
this.print(string);
};
/**
* Write a UTF-8 encoded byte string to the terminal followed by crlf.
*
* @param {string} string The UTF-8 encoded string to print.
*/
hterm.Terminal.IO.prototype.writelnUTF8 = function(string) {
this.writeUTF8(string);
// We need to use writeUTF8 to make sure we flush the decoder state.
this.writeUTF8('\r\n');
};
/**
* Write a UTF-16 JavaScript string to the terminal.
*
* @param {string} string The string to print.
*/
hterm.Terminal.IO.prototype.print =
hterm.Terminal.IO.prototype.writeUTF16 = function(string) {
// If another process has the foreground IO, buffer new data sent to this IO
// (since it's in the background). When we're made the foreground IO again,
// we'll flush everything.
if (this.terminal_.io != this) {
this.buffered_ += string;
return;
}
this.terminal_.interpret(string);
};
/**
* Print a UTF-16 JavaScript string to the terminal followed by a newline.
*
* @param {string} string The string to print.
*/
hterm.Terminal.IO.prototype.println =
hterm.Terminal.IO.prototype.writelnUTF16 = function(string) {
this.print(string + '\r\n');
};