diff --git a/lib/readline-sync.js b/lib/readline-sync.js index 97224a1..c43e4f3 100644 --- a/lib/readline-sync.js +++ b/lib/readline-sync.js @@ -17,112 +17,182 @@ var DEFAULT_ERR_MSG = 'The platform doesn\'t support interactive reading', fs = require('fs'), + TTY = process.binding('tty_wrap').TTY, childProc = require('child_process'), promptText = '> ', encoding = 'utf8', bufSize = 1024, print, - tempdir, salt = 0, useShell = false; + mask = '*', + useShell = false, + + fdR = 'none', fdW, ttyR, isRawMode = false, + tempdir, salt = 0; function _readlineSync(options) { // options.display is string - var input = '', fd, buffer, rsize, res, isOpened, fsBind, constBind; + var input = '', isEditable, displayInput; - if (options.display !== '' && typeof print === 'function') - { print(options.display, encoding); } - - if (useShell || options.noEchoBack || options.keyIn) { - res = _readlineShell(options); + function tryShell() { + console.warn(''); + var res = _readlineShell(options); if (res.error) { throw res.error; } - input = res.input; - } else { + return res.input; + } - if (IS_WIN) { // r/w mode not supported - if (process.stdin.isTTY && process.stdout.isTTY) { - if (options.display !== '') { - // process.stdout.write(options.display, encoding); - fs.writeSync(process.stdout.fd, options.display); - options.display = ''; - } - fd = process.stdin.fd; - isOpened = true; + // Node v0.10- returns an error if same mode is set. + function setRawMode(mode) { + if (mode === isRawMode) { return true; } + if (ttyR.setRawMode(mode) !== 0) { return false; } + isRawMode = mode; + return true; + } + + (function() { + var fsB, constants; + + function getFsB() { + if (!fsB) { + fsB = process.binding('fs'); // For raw device path + constants = process.binding('constants'); + } + return fsB; + } + + if (typeof fdR !== 'string') { return; } + fdR = null; + + if (IS_WIN) { + if (process.stdin.isTTY) { + fdR = process.stdin.fd; + ttyR = process.stdin._handle; + console.warn('OPENED: STDIN'); } else { - try { - if (options.display !== '') { - fd = fs.openSync('\\\\.\\CON', 'w'); - fs.writeSync(fd, options.display); - fs.closeSync(fd); - options.display = ''; - } - fd = fs.openSync('\\\\.\\CON', 'rs'); - isOpened = true; + // The stream by fs.openSync('\\\\.\\CON', 'r') can't switch to raw mode. + // 'CONIN$' might fail on XP, 2000, 7 (x86). + fdR = getFsB().open('CONIN$', constants.O_RDWR, parseInt('0666', 8)); + ttyR = new TTY(fdR, true); + console.warn('OPENED: CONIN$'); } catch (e) {} + } - if (!isOpened || options.display !== '') { // Retry + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + console.warn('OPENED: STDOUT'); + } else { + try { + fdW = fs.openSync('\\\\.\\CON', 'w'); + console.warn('OPENED: \\\\.\\CON(W)'); + } catch (e) {} + if (typeof fdW !== 'number') { // Retry try { - // For raw device path - // On XP, 2000, 7 (x86), it might fail. - // And, process.binding('fs') might be no good. - fsBind = process.binding('fs'); - constBind = process.binding('constants'); - if (options.display !== '') { - fd = fsBind.open('CONOUT$', - constBind.O_RDWR | constBind.O_SYNC, parseInt('0666', 8)); - fs.writeSync(fd, options.display); - fs.closeSync(fd); - options.display = ''; - } - fd = fsBind.open('CONIN$', - constBind.O_RDWR | constBind.O_SYNC, parseInt('0666', 8)); - isOpened = true; + fdW = getFsB().open('CONOUT$', constants.O_RDWR, parseInt('0666', 8)); + console.warn('OPENED: CONOUT$'); } catch (e) {} } } } else { - try { - fd = fs.openSync('/dev/tty', 'rs+'); - isOpened = true; - if (options.display !== '') { - fs.writeSync(fd, options.display); - options.display = ''; - } - } catch (e) {} - } - - if (isOpened && options.display === '') { - - buffer = new Buffer(bufSize); - while (true) { - rsize = 0; - + if (process.stdin.isTTY) { try { - rsize = fs.readSync(fd, buffer, 0, bufSize); - } catch (e) { - if (e.code === 'EOF') { break; } - - res = _readlineShell(options); - if (res.error) { throw res.error; } - input += res.input; - break; - } - - if (rsize === 0) { break; } - input += buffer.toString(encoding, 0, rsize); - if (/[\r\n]$/.test(input)) { break; } + fdR = fs.openSync('/dev/tty', 'r'); // device file, not process.stdin + ttyR = process.stdin._handle; + console.warn('OPENED: /dev/tty(R) and STDIN handle'); + } catch (e) {} + } else { + // Node v0.12 read() fails. + try { + fdR = fs.openSync('/dev/tty', 'r'); + ttyR = new TTY(fdR, false); + console.warn('OPENED: /dev/tty(R)'); + } catch (e) {} } - } else { - res = _readlineShell(options); - if (res.error) { throw res.error; } - input = res.input; + if (process.stdout.isTTY) { + fdW = process.stdout.fd; + console.warn('OPENED: STDOUT'); + } else { + try { + fdW = fs.openSync('/dev/tty', 'w'); + console.warn('OPENED: /dev/tty(W)'); + } catch (e) {} + } + } + })(); + + // Call before tryShell() + if (options.display !== '' && typeof print === 'function') + { print(options.display, encoding); } + + isEditable = !options.noEchoBack && !options.keyIn; + + (function() { // try read + var buffer, reqSize, readSize, chunk; + + if (useShell || !ttyR || + typeof fdW !== 'number' && (options.display !== '' || !isEditable)) { + input = tryShell(); + return; } - if (isOpened && !process.stdin.isTTY) { fs.closeSync(fd); } + if (options.display !== '') { + fs.writeSync(fdW, options.display); + options.display = ''; + } + + if (!setRawMode(!isEditable)) { + input = tryShell(); + return; + } + buffer = new Buffer((reqSize = options.keyIn ? 1 : bufSize)); + + console.warn('<>: ' + isEditable); + console.warn('--------'); + + while (true) { + readSize = 0; + + try { + readSize = fs.readSync(fdR, buffer, 0, reqSize); + } catch (e) { + if (e.code === 'EOF') { break; } + setRawMode(false); + input += tryShell(); + return; + } + + console.warn('readSize: ' + readSize); + if (readSize === 0) { break; } + chunk = buffer.toString(encoding, 0, readSize); + // other ctrl-chars + if ((chunk = chunk.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')) === '') + { continue; } + + if (!isEditable) { + displayInput = chunk.replace(/[\r\n]/g, ''); + if (options.noEchoBack) { displayInput = displayInput.replace(/./g, mask); } + if (displayInput !== '') { fs.writeSync(fdW, displayInput); } + } + + input += chunk; + console.warn('<>: ' + input); + if (/[\r\n]$/.test(input) || + options.keyIn && input.length >= reqSize) { break; } + } + + if (!isEditable) { fs.writeSync(fdW, '\n'); } + setRawMode(false); + })(); + + if (typeof print === 'function') { + displayInput = input.replace(/[\r\n]/g, ''); + print((options.noEchoBack ? + displayInput.replace(/./g, mask) : displayInput) + '\n', encoding); } - return options.noTrim ? input.replace(/[\r\n]+$/, '') : input.trim(); + return options.noTrim || options.keyIn ? + input.replace(/[\r\n]+$/, '') : input.trim(); } function _readlineShell(options) {