Add binding to termios, implement readline for repl
This commit is contained in:
parent
a0134ff0f8
commit
41f213be18
@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
puts = require("sys").puts;
|
puts = require("sys").puts;
|
||||||
|
|
||||||
puts("Welcome to the Node.js REPL.");
|
|
||||||
puts("Enter ECMAScript at the prompt.");
|
|
||||||
puts("Tip 1: Use 'rlwrap node-repl' for a better interface");
|
|
||||||
puts("Tip 2: Type Control-D to exit.");
|
|
||||||
puts("Type '.help' for options.");
|
puts("Type '.help' for options.");
|
||||||
|
|
||||||
require('repl').start();
|
require('repl').start();
|
||||||
|
251
lib/readline.js
Normal file
251
lib/readline.js
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// Insperation for this code comes from Salvatore Sanfilippo's linenoise.
|
||||||
|
// http://github.com/antirez/linenoise
|
||||||
|
// Reference:
|
||||||
|
// * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
// * http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
|
||||||
|
|
||||||
|
var kHistorySize = 30;
|
||||||
|
var kBufSize = 10*1024;
|
||||||
|
|
||||||
|
|
||||||
|
var Buffer = require('buffer').Buffer;
|
||||||
|
var inherits = require('sys').inherits;
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var stdio = process.binding('stdio');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.createInterface = function (output, isTTY) {
|
||||||
|
return new Interface(output, isTTY);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Interface (output, isTTY) {
|
||||||
|
this.output = output;
|
||||||
|
|
||||||
|
this.setPrompt("node> ");
|
||||||
|
|
||||||
|
// Current line
|
||||||
|
this.buf = new Buffer(kBufSize);
|
||||||
|
this.buf.used = 0;
|
||||||
|
|
||||||
|
if (!isTTY) {
|
||||||
|
this._tty = false;
|
||||||
|
} else {
|
||||||
|
// input refers to stdin
|
||||||
|
|
||||||
|
// Check process.env.TERM ?
|
||||||
|
stdio.setRawMode(true);
|
||||||
|
this._tty = true;
|
||||||
|
this.columns = stdio.getColumns();
|
||||||
|
|
||||||
|
// Cursor position on the line.
|
||||||
|
this.cursor = 0;
|
||||||
|
|
||||||
|
this.history = [];
|
||||||
|
this.historyIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inherits(Interface, EventEmitter);
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype.setPrompt = function (prompt, length) {
|
||||||
|
this._prompt = prompt;
|
||||||
|
this._promptLength = length ? length : Buffer.byteLength(prompt);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype.prompt = function () {
|
||||||
|
if (this._tty) {
|
||||||
|
this.buf.used = 0;
|
||||||
|
this.cursor = 0;
|
||||||
|
this._refreshLine();
|
||||||
|
} else {
|
||||||
|
this.output.write(this._prompt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype._addHistory = function () {
|
||||||
|
if (this.buf.used === 0) return "";
|
||||||
|
|
||||||
|
var b = new Buffer(this.buf.used);
|
||||||
|
this.buf.copy(b, 0, 0, this.buf.used);
|
||||||
|
this.buf.used = 0;
|
||||||
|
|
||||||
|
this.history.unshift(b);
|
||||||
|
this.historyIndex = -1;
|
||||||
|
|
||||||
|
this.cursor = 0;
|
||||||
|
|
||||||
|
// Only store so many
|
||||||
|
if (this.history.length > kHistorySize) this.history.pop();
|
||||||
|
|
||||||
|
return b.toString('utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype._refreshLine = function () {
|
||||||
|
if (this._closed) return;
|
||||||
|
|
||||||
|
stdio.setRawMode(true);
|
||||||
|
|
||||||
|
// Cursor to left edge.
|
||||||
|
this.output.write('\x1b[0G');
|
||||||
|
|
||||||
|
// Write the prompt and the current buffer content.
|
||||||
|
this.output.write(this._prompt);
|
||||||
|
if (this.buf.used > 0) {
|
||||||
|
this.output.write(this.buf.slice(0, this.buf.used));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erase to right.
|
||||||
|
this.output.write('\x1b[0K');
|
||||||
|
|
||||||
|
// Move cursor to original position.
|
||||||
|
this.output.write('\x1b[0G\x1b[' + (this._promptLength + this.cursor) + 'C');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype.close = function (d) {
|
||||||
|
if (this._tty) {
|
||||||
|
stdio.setRawMode(false);
|
||||||
|
}
|
||||||
|
this.emit('close');
|
||||||
|
this._closed = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype.write = function (d) {
|
||||||
|
if (this._closed) return;
|
||||||
|
return this._tty ? this._ttyWrite(d) : this._normalWrite(d);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype._normalWrite = function (b) {
|
||||||
|
for (var i = 0; i < b.length; i++) {
|
||||||
|
var code = b instanceof Buffer ? b[i] : b.charCodeAt(i);
|
||||||
|
if (code === '\n'.charCodeAt(0) || code === '\r'.charCodeAt(0)) {
|
||||||
|
var s = this.buf.toString('utf8', 0, this.buf.used);
|
||||||
|
this.emit('line', s);
|
||||||
|
this.buf.used = 0;
|
||||||
|
} else {
|
||||||
|
this.buf[this.buf.used++] = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Interface.prototype._ttyWrite = function (b) {
|
||||||
|
switch (b[0]) {
|
||||||
|
/* ctrl+c */
|
||||||
|
case 3:
|
||||||
|
//process.kill(process.pid, "SIGINT");
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: /* ctrl+d */
|
||||||
|
this.close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 13: /* enter */
|
||||||
|
var line = this._addHistory();
|
||||||
|
this.output.write('\n\x1b[0G');
|
||||||
|
stdio.setRawMode(false);
|
||||||
|
this.emit('line', line);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 127: /* backspace */
|
||||||
|
case 8: /* ctrl+h */
|
||||||
|
if (this.cursor > 0 && this.buf.used > 0) {
|
||||||
|
for (var i = this.cursor; i < this.buf.used; i++) {
|
||||||
|
this.buf[i-1] = this.buf[i];
|
||||||
|
}
|
||||||
|
this.cursor--;
|
||||||
|
this.buf.used--;
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 21: /* Ctrl+u, delete the whole line. */
|
||||||
|
this.cursor = this.buf.used = 0;
|
||||||
|
this._refreshLine();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 11: /* Ctrl+k, delete from current to end of line. */
|
||||||
|
this.buf.used = this.cursor;
|
||||||
|
this._refreshLine();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: /* Ctrl+a, go to the start of the line */
|
||||||
|
this.cursor = 0;
|
||||||
|
this._refreshLine();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5: /* ctrl+e, go to the end of the line */
|
||||||
|
this.cursor = this.buf.used;
|
||||||
|
this._refreshLine();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 27: /* escape sequence */
|
||||||
|
if (b[1] === 91 && b[2] === 68) {
|
||||||
|
// left arrow
|
||||||
|
if (this.cursor > 0) {
|
||||||
|
this.cursor--;
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
} else if (b[1] === 91 && b[2] === 67) {
|
||||||
|
// right arrow
|
||||||
|
if (this.cursor != this.buf.used) {
|
||||||
|
this.cursor++;
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
} else if (b[1] === 91 && b[2] === 65) {
|
||||||
|
// up arrow
|
||||||
|
if (this.historyIndex + 1 < this.history.length) {
|
||||||
|
this.historyIndex++;
|
||||||
|
this.history[this.historyIndex].copy(this.buf, 0);
|
||||||
|
this.buf.used = this.history[this.historyIndex].length;
|
||||||
|
// set cursor to end of line.
|
||||||
|
this.cursor = this.buf.used;
|
||||||
|
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (b[1] === 91 && b[2] === 66) {
|
||||||
|
// down arrow
|
||||||
|
if (this.historyIndex > 0) {
|
||||||
|
this.historyIndex--;
|
||||||
|
this.history[this.historyIndex].copy(this.buf, 0);
|
||||||
|
this.buf.used = this.history[this.historyIndex].length;
|
||||||
|
// set cursor to end of line.
|
||||||
|
this.cursor = this.buf.used;
|
||||||
|
this._refreshLine();
|
||||||
|
|
||||||
|
} else if (this.historyIndex === 0) {
|
||||||
|
this.historyIndex = -1;
|
||||||
|
this.cursor = 0;
|
||||||
|
this.buf.used = 0;
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (this.buf.used < kBufSize) {
|
||||||
|
for (var i = this.buf.used + 1; this.cursor < i; i--) {
|
||||||
|
this.buf[i] = this.buf[i-1];
|
||||||
|
}
|
||||||
|
this.buf[this.cursor++] = b[0];
|
||||||
|
this.buf.used++;
|
||||||
|
|
||||||
|
if (this.buf.used == this.cursor) {
|
||||||
|
this.output.write(b);
|
||||||
|
} else {
|
||||||
|
this._refreshLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
90
lib/repl.js
90
lib/repl.js
@ -15,6 +15,7 @@
|
|||||||
var sys = require('sys');
|
var sys = require('sys');
|
||||||
var evalcx = process.binding('evals').Script.runInNewContext;
|
var evalcx = process.binding('evals').Script.runInNewContext;
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
|
var rl = require('readline');
|
||||||
var scope;
|
var scope;
|
||||||
|
|
||||||
function cwdRequire (id) {
|
function cwdRequire (id) {
|
||||||
@ -40,38 +41,26 @@ function REPLServer(prompt, stream) {
|
|||||||
if (!scope) setScope();
|
if (!scope) setScope();
|
||||||
self.scope = scope;
|
self.scope = scope;
|
||||||
self.buffered_cmd = '';
|
self.buffered_cmd = '';
|
||||||
self.prompt = prompt || "node> ";
|
|
||||||
self.stream = stream || process.openStdin();
|
self.stream = stream || process.openStdin();
|
||||||
self.stream.setEncoding('utf8');
|
self.prompt = "node> " || prompt;
|
||||||
|
|
||||||
|
var isTTY = (self.stream.fd < 3);
|
||||||
|
var rli = self.rli = rl.createInterface(self.stream, isTTY);
|
||||||
|
rli.setPrompt(self.prompt);
|
||||||
|
|
||||||
self.stream.addListener("data", function (chunk) {
|
self.stream.addListener("data", function (chunk) {
|
||||||
self.readline.call(self, chunk);
|
rli.write(chunk);
|
||||||
});
|
});
|
||||||
self.displayPrompt();
|
|
||||||
}
|
|
||||||
exports.REPLServer = REPLServer;
|
|
||||||
|
|
||||||
// prompt is a string to print on each line for the prompt,
|
rli.addListener('line', function (cmd) {
|
||||||
// source is a stream to use for I/O, defaulting to stdin/stdout.
|
cmd = trimWhitespace(cmd);
|
||||||
exports.start = function (prompt, source) {
|
|
||||||
return new REPLServer(prompt, source);
|
|
||||||
};
|
|
||||||
|
|
||||||
REPLServer.prototype.displayPrompt = function () {
|
var flushed = true;
|
||||||
var self = this;
|
|
||||||
self.stream.write(self.buffered_cmd.length ? '... ' : self.prompt);
|
|
||||||
};
|
|
||||||
|
|
||||||
// read a line from the stream, then eval it
|
|
||||||
REPLServer.prototype.readline = function (cmd) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
cmd = self.trimWhitespace(cmd);
|
|
||||||
|
|
||||||
// Check to see if a REPL keyword was used. If it returns true,
|
// Check to see if a REPL keyword was used. If it returns true,
|
||||||
// display next prompt and return.
|
// display next prompt and return.
|
||||||
if (self.parseREPLKeyword(cmd) === true) {
|
if (self.parseREPLKeyword(cmd) === true) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The catchall for errors
|
// The catchall for errors
|
||||||
try {
|
try {
|
||||||
@ -84,7 +73,7 @@ REPLServer.prototype.readline = function (cmd) {
|
|||||||
var ret = evalcx(self.buffered_cmd, scope, "repl");
|
var ret = evalcx(self.buffered_cmd, scope, "repl");
|
||||||
if (ret !== undefined) {
|
if (ret !== undefined) {
|
||||||
scope._ = ret;
|
scope._ = ret;
|
||||||
self.stream.write(exports.writer(ret) + "\n");
|
flushed = self.stream.write(exports.writer(ret) + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.buffered_cmd = '';
|
self.buffered_cmd = '';
|
||||||
@ -97,14 +86,51 @@ REPLServer.prototype.readline = function (cmd) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// On error: Print the error and clear the buffer
|
// On error: Print the error and clear the buffer
|
||||||
if (e.stack) {
|
if (e.stack) {
|
||||||
self.stream.write(e.stack + "\n");
|
flushed = self.stream.write(e.stack + "\n");
|
||||||
} else {
|
} else {
|
||||||
self.stream.write(e.toString() + "\n");
|
flushed = self.stream.write(e.toString() + "\n");
|
||||||
}
|
}
|
||||||
self.buffered_cmd = '';
|
self.buffered_cmd = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// need to make sure the buffer is flushed before displaying the prompt
|
||||||
|
// again. This is really ugly. Need to have callbacks from
|
||||||
|
// net.Stream.write()
|
||||||
|
if (flushed) {
|
||||||
self.displayPrompt();
|
self.displayPrompt();
|
||||||
|
} else {
|
||||||
|
self.displayPromptOnDrain = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.stream.addListener('drain', function () {
|
||||||
|
if (self.displayPromptOnDrain) {
|
||||||
|
self.displayPrompt();
|
||||||
|
self.displayPromptOnDrain = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rli.addListener('close', function () {
|
||||||
|
self.stream.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.displayPrompt();
|
||||||
|
}
|
||||||
|
exports.REPLServer = REPLServer;
|
||||||
|
|
||||||
|
// prompt is a string to print on each line for the prompt,
|
||||||
|
// source is a stream to use for I/O, defaulting to stdin/stdout.
|
||||||
|
exports.start = function (prompt, source) {
|
||||||
|
return new REPLServer(prompt, source);
|
||||||
|
};
|
||||||
|
|
||||||
|
REPLServer.prototype.displayPrompt = function () {
|
||||||
|
this.rli.setPrompt(this.buffered_cmd.length ? '... ' : this.prompt);
|
||||||
|
this.rli.prompt();
|
||||||
|
};
|
||||||
|
|
||||||
|
// read a line from the stream, then eval it
|
||||||
|
REPLServer.prototype.readline = function (cmd) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,20 +168,14 @@ REPLServer.prototype.parseREPLKeyword = function (cmd) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
function trimWhitespace (cmd) {
|
||||||
* Trims Whitespace from a line.
|
|
||||||
*
|
|
||||||
* @param {String} cmd The string to trim the whitespace from
|
|
||||||
* @returns {String} The trimmed string
|
|
||||||
*/
|
|
||||||
REPLServer.prototype.trimWhitespace = function (cmd) {
|
|
||||||
var trimmer = /^\s*(.+)\s*$/m,
|
var trimmer = /^\s*(.+)\s*$/m,
|
||||||
matches = trimmer.exec(cmd);
|
matches = trimmer.exec(cmd);
|
||||||
|
|
||||||
if (matches && matches.length === 2) {
|
if (matches && matches.length === 2) {
|
||||||
return matches[1];
|
return matches[1];
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts commands that use var and function <name>() to use the
|
* Converts commands that use var and function <name>() to use the
|
||||||
|
18
src/node.cc
18
src/node.cc
@ -976,6 +976,9 @@ const char* ToCString(const v8::String::Utf8Value& value) {
|
|||||||
static void ReportException(TryCatch &try_catch, bool show_line) {
|
static void ReportException(TryCatch &try_catch, bool show_line) {
|
||||||
Handle<Message> message = try_catch.Message();
|
Handle<Message> message = try_catch.Message();
|
||||||
|
|
||||||
|
node::Stdio::DisableRawMode(STDIN_FILENO);
|
||||||
|
fprintf(stderr, "\n\n");
|
||||||
|
|
||||||
if (show_line && !message.IsEmpty()) {
|
if (show_line && !message.IsEmpty()) {
|
||||||
// Print (filename):(line number): (message).
|
// Print (filename):(line number): (message).
|
||||||
String::Utf8Value filename(message->GetScriptResourceName());
|
String::Utf8Value filename(message->GetScriptResourceName());
|
||||||
@ -1223,8 +1226,6 @@ static Handle<Value> SetUid(const Arguments& args) {
|
|||||||
|
|
||||||
v8::Handle<v8::Value> Exit(const v8::Arguments& args) {
|
v8::Handle<v8::Value> Exit(const v8::Arguments& args) {
|
||||||
HandleScope scope;
|
HandleScope scope;
|
||||||
fflush(stderr);
|
|
||||||
Stdio::Flush();
|
|
||||||
exit(args[0]->IntegerValue());
|
exit(args[0]->IntegerValue());
|
||||||
return Undefined();
|
return Undefined();
|
||||||
}
|
}
|
||||||
@ -1855,6 +1856,7 @@ static Handle<Value> Binding(const Arguments& args) {
|
|||||||
exports->Set(String::New("posix"), String::New(native_posix));
|
exports->Set(String::New("posix"), String::New(native_posix));
|
||||||
exports->Set(String::New("querystring"), String::New(native_querystring));
|
exports->Set(String::New("querystring"), String::New(native_querystring));
|
||||||
exports->Set(String::New("repl"), String::New(native_repl));
|
exports->Set(String::New("repl"), String::New(native_repl));
|
||||||
|
exports->Set(String::New("readline"), String::New(native_readline));
|
||||||
exports->Set(String::New("sys"), String::New(native_sys));
|
exports->Set(String::New("sys"), String::New(native_sys));
|
||||||
exports->Set(String::New("tcp"), String::New(native_tcp));
|
exports->Set(String::New("tcp"), String::New(native_tcp));
|
||||||
exports->Set(String::New("uri"), String::New(native_uri));
|
exports->Set(String::New("uri"), String::New(native_uri));
|
||||||
@ -2078,6 +2080,14 @@ static void ParseArgs(int *argc, char **argv) {
|
|||||||
option_end_index = i;
|
option_end_index = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void AtExit() {
|
||||||
|
node::Stdio::Flush();
|
||||||
|
node::Stdio::DisableRawMode(STDIN_FILENO);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
|
||||||
|
|
||||||
@ -2179,12 +2189,12 @@ int main(int argc, char *argv[]) {
|
|||||||
Persistent<Context> context = Context::New();
|
Persistent<Context> context = Context::New();
|
||||||
Context::Scope context_scope(context);
|
Context::Scope context_scope(context);
|
||||||
|
|
||||||
|
atexit(node::AtExit);
|
||||||
|
|
||||||
// Create all the objects, load modules, do everything.
|
// Create all the objects, load modules, do everything.
|
||||||
// so your next reading stop should be node::Load()!
|
// so your next reading stop should be node::Load()!
|
||||||
node::Load(argc, argv);
|
node::Load(argc, argv);
|
||||||
|
|
||||||
node::Stdio::Flush();
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// Clean up.
|
// Clean up.
|
||||||
context.Dispose();
|
context.Dispose();
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <termios.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
using namespace v8;
|
using namespace v8;
|
||||||
namespace node {
|
namespace node {
|
||||||
|
|
||||||
@ -14,6 +17,81 @@ static int stdout_flags = -1;
|
|||||||
static int stdin_flags = -1;
|
static int stdin_flags = -1;
|
||||||
|
|
||||||
|
|
||||||
|
static struct termios orig_termios; /* in order to restore at exit */
|
||||||
|
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
|
||||||
|
|
||||||
|
|
||||||
|
static int EnableRawMode(int fd) {
|
||||||
|
struct termios raw;
|
||||||
|
|
||||||
|
if (rawmode) return 0;
|
||||||
|
|
||||||
|
//if (!isatty(fd)) goto fatal;
|
||||||
|
if (tcgetattr(fd, &orig_termios) == -1) goto fatal;
|
||||||
|
|
||||||
|
raw = orig_termios; /* modify the original mode */
|
||||||
|
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||||
|
* no start/stop output control. */
|
||||||
|
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
/* output modes - disable post processing */
|
||||||
|
raw.c_oflag &= ~(OPOST);
|
||||||
|
/* control modes - set 8 bit chars */
|
||||||
|
raw.c_cflag |= (CS8);
|
||||||
|
/* local modes - choing off, canonical off, no extended functions,
|
||||||
|
* no signal chars (^Z,^C) */
|
||||||
|
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||||
|
/* control chars - set return condition: min number of bytes and timer.
|
||||||
|
* We want read to return every single byte, without timeout. */
|
||||||
|
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
|
||||||
|
|
||||||
|
/* put terminal in raw mode after flushing */
|
||||||
|
if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) goto fatal;
|
||||||
|
rawmode = 1;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fatal:
|
||||||
|
errno = ENOTTY;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Stdio::DisableRawMode(int fd) {
|
||||||
|
/* Don't even check the return value as it's too late. */
|
||||||
|
if (rawmode && tcsetattr(fd, TCSAFLUSH, &orig_termios) != -1) {
|
||||||
|
rawmode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process.binding('stdio').setRawMode(true);
|
||||||
|
static Handle<Value> SetRawMode (const Arguments& args) {
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
if (args[0]->IsFalse()) {
|
||||||
|
Stdio::DisableRawMode(STDIN_FILENO);
|
||||||
|
} else {
|
||||||
|
if (0 != EnableRawMode(STDIN_FILENO)) {
|
||||||
|
return ThrowException(ErrnoException(errno, "EnableRawMode"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawmode ? True() : False();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// process.binding('stdio').getColumns();
|
||||||
|
static Handle<Value> GetColumns (const Arguments& args) {
|
||||||
|
HandleScope scope;
|
||||||
|
|
||||||
|
struct winsize ws;
|
||||||
|
|
||||||
|
if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
|
||||||
|
return scope.Close(Integer::New(80));
|
||||||
|
}
|
||||||
|
|
||||||
|
return scope.Close(Integer::NewFromUnsigned(ws.ws_col));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* STDERR IS ALWAY SYNC ALWAYS UTF8 */
|
/* STDERR IS ALWAY SYNC ALWAYS UTF8 */
|
||||||
static Handle<Value>
|
static Handle<Value>
|
||||||
WriteError (const Arguments& args)
|
WriteError (const Arguments& args)
|
||||||
@ -94,13 +172,12 @@ void Stdio::Flush() {
|
|||||||
fcntl(STDIN_FILENO, F_SETFL, stdin_flags & ~O_NONBLOCK);
|
fcntl(STDIN_FILENO, F_SETFL, stdin_flags & ~O_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (STDOUT_FILENO >= 0) {
|
|
||||||
if (stdout_flags != -1) {
|
if (stdout_flags != -1) {
|
||||||
fcntl(STDOUT_FILENO, F_SETFL, stdout_flags & ~O_NONBLOCK);
|
fcntl(STDOUT_FILENO, F_SETFL, stdout_flags & ~O_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(STDOUT_FILENO);
|
fflush(stdout);
|
||||||
}
|
fflush(stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -120,6 +197,8 @@ void Stdio::Initialize(v8::Handle<v8::Object> target) {
|
|||||||
NODE_SET_METHOD(target, "openStdin", OpenStdin);
|
NODE_SET_METHOD(target, "openStdin", OpenStdin);
|
||||||
NODE_SET_METHOD(target, "isStdoutBlocking", IsStdoutBlocking);
|
NODE_SET_METHOD(target, "isStdoutBlocking", IsStdoutBlocking);
|
||||||
NODE_SET_METHOD(target, "isStdinBlocking", IsStdinBlocking);
|
NODE_SET_METHOD(target, "isStdinBlocking", IsStdinBlocking);
|
||||||
|
NODE_SET_METHOD(target, "setRawMode", SetRawMode);
|
||||||
|
NODE_SET_METHOD(target, "getColumns", GetColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ class Stdio {
|
|||||||
public:
|
public:
|
||||||
static void Initialize (v8::Handle<v8::Object> target);
|
static void Initialize (v8::Handle<v8::Object> target);
|
||||||
static void Flush ();
|
static void Flush ();
|
||||||
|
static void DisableRawMode(int fd);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace node
|
} // namespace node
|
||||||
|
Loading…
x
Reference in New Issue
Block a user