Merge pull request #2886 from danielgtaylor/repl-history

Add history to the coffee interactive interpreter that persists between ...
This commit is contained in:
Jeremy Ashkenas
2013-03-27 15:55:15 -07:00
3 changed files with 111 additions and 3 deletions

View File

@@ -1,6 +1,10 @@
// Generated by CoffeeScript 1.6.2
(function() {
var CoffeeScript, addMultilineHandler, merge, nodeREPL, prettyErrorMessage, replDefaults, vm, _ref;
var CoffeeScript, addHistory, addMultilineHandler, fs, merge, nodeREPL, path, prettyErrorMessage, replDefaults, vm, _ref;
fs = require('fs');
path = require('path');
vm = require('vm');
@@ -12,6 +16,8 @@
replDefaults = {
prompt: 'coffee> ',
historyFile: path.join(process.env.HOME, '.coffee_history'),
historyMaxInputSize: 10240,
"eval": function(input, context, filename, cb) {
var Assign, Block, Literal, Value, ast, err, js, _ref1;
input = input.replace(/\uFF00/g, '\n');
@@ -86,6 +92,52 @@
});
};
addHistory = function(repl, filename, maxSize) {
var buffer, fd, readFd, size, stat;
try {
stat = fs.statSync(filename);
size = Math.min(maxSize, stat.size);
readFd = fs.openSync(filename, 'r');
buffer = new Buffer(size);
fs.readSync(readFd, buffer, 0, size, stat.size - size);
repl.rli.history = buffer.toString().split('\n').reverse();
if (size === maxSize) {
repl.rli.history.pop();
}
if (repl.rli.history[0] === '') {
repl.rli.history.shift();
}
repl.rli.historyIndex = -1;
} catch (_error) {}
fd = fs.openSync(filename, 'a');
repl.rli.addListener('line', function(code) {
if (code && code.length && code !== '.history') {
return fs.write(fd, "" + code + "\n");
}
});
process.on('exit', function() {
return fs.closeSync(fd);
});
return repl.commands['.history'] = {
help: 'Show command history',
action: function() {
var history, k;
history = ((function() {
var _i, _len, _ref1, _results;
_ref1 = Object.keys(repl.rli.history);
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
k = _ref1[_i];
_results.push(repl.rli.history[k]);
}
return _results;
})()).reverse();
repl.outputStream.write("" + (history.join('\n')) + "\n");
return repl.displayPrompt();
}
};
};
module.exports = {
start: function(opts) {
var build, major, minor, repl, _ref1;
@@ -105,6 +157,9 @@
return repl.outputStream.write('\n');
});
addMultilineHandler(repl);
if (opts.historyFile) {
addHistory(repl, opts.historyFile, opts.historyMaxInputSize);
}
return repl;
}
};

View File

@@ -1,3 +1,5 @@
fs = require 'fs'
path = require 'path'
vm = require 'vm'
nodeREPL = require 'repl'
CoffeeScript = require './coffee-script'
@@ -5,6 +7,8 @@ CoffeeScript = require './coffee-script'
replDefaults =
prompt: 'coffee> ',
historyFile: path.join(process.env.HOME, '.coffee_history')
historyMaxInputSize: 10240
eval: (input, context, filename, cb) ->
# XXX: multiline hack.
input = input.replace /\uFF00/g, '\n'
@@ -76,6 +80,42 @@ addMultilineHandler = (repl) ->
rli.prompt true
return
# Store and load command history from a file
addHistory = (repl, filename, maxSize) ->
try
# Get file info and at most 10KB of command history
stat = fs.statSync filename
size = Math.min maxSize, stat.size
# Read last `size` bytes from the file
readFd = fs.openSync filename, 'r'
buffer = new Buffer(size)
fs.readSync readFd, buffer, 0, size, stat.size - size
# Set the history on the interpreter
repl.rli.history = buffer.toString().split('\n').reverse()
# If the history file was truncated we should pop off a potential partial line
repl.rli.history.pop() if size is maxSize
# Shift off the final blank newline
repl.rli.history.shift() if repl.rli.history[0] is ''
repl.rli.historyIndex = -1
fd = fs.openSync filename, 'a'
repl.rli.addListener 'line', (code) ->
if code and code.length and code isnt '.history'
# Save the latest command in the file
fs.write fd, "#{code}\n"
process.on 'exit', ->
fs.closeSync fd
# Add a command to show the history stack
repl.commands['.history'] =
help: 'Show command history'
action: ->
history = (repl.rli.history[k] for k in Object.keys(repl.rli.history)).reverse()
repl.outputStream.write "#{history.join '\n'}\n"
repl.displayPrompt()
module.exports =
start: (opts = {}) ->
[major, minor, build] = process.versions.node.split('.').map (n) -> parseInt(n)
@@ -88,4 +128,5 @@ module.exports =
repl = nodeREPL.start opts
repl.on 'exit', -> repl.outputStream.write '\n'
addMultilineHandler repl
addHistory repl, opts.historyFile, opts.historyMaxInputSize if opts.historyFile
repl

View File

@@ -1,5 +1,7 @@
return if global.testingBrowser
fs = require 'fs'
# REPL
# ----
Stream = require 'stream'
@@ -25,16 +27,23 @@ class MockOutputStream extends Stream
lastWrite: (fromEnd = -1) ->
@written[@written.length - 1 + fromEnd].replace /\n$/, ''
# Create a dummy history file
historyFile = '.coffee_history_test'
fs.writeFileSync historyFile, '1 + 2\n'
testRepl = (desc, fn) ->
input = new MockInputStream
output = new MockOutputStream
Repl.start {input, output}
test desc, -> fn input, output
repl = Repl.start {input, output, historyFile}
test desc, -> fn input, output, repl
ctrlV = { ctrl: true, name: 'v'}
testRepl 'reads history file', (input, output, repl) ->
input.emitLine repl.rli.history[0]
eq '3', output.lastWrite()
testRepl "starts with coffee prompt", (input, output) ->
eq 'coffee> ', output.lastWrite(0)
@@ -97,3 +106,6 @@ testRepl "keeps running after runtime error", (input, output) ->
eq 0, output.lastWrite().indexOf 'ReferenceError: b is not defined'
input.emitLine 'a'
eq 'undefined', output.lastWrite()
process.on 'exit', ->
fs.unlinkSync historyFile