Compare commits

...

22 commits

Author SHA1 Message Date
Sami Samhuri
0a0da46ad4 he's dead, Jim 2013-03-13 23:03:20 -07:00
Sami Samhuri
11b8a6246b 0.9.5 2012-07-11 11:18:36 -07:00
Sami Samhuri
5ccbb46c81 fix for node v0.8.x 2012-07-11 11:18:18 -07:00
Sami Samhuri
91028f439b 0.9.4 2011-11-05 16:32:18 -07:00
Sami Samhuri
80b5dbf3c9 seed the editing tmp file with the last line of history 2011-11-05 16:32:01 -07:00
Sami Samhuri
9c36c8f9c3 use vm.runInContext instead of vm.Script.runInContext 2011-11-05 16:31:09 -07:00
Sami Samhuri
53d322e0ca rename repl vars for clarity, fix style (braces, comma first) 2011-11-05 16:30:16 -07:00
Sami Samhuri
c83c0105b6 add history file support to repl.js 2011-11-05 16:26:46 -07:00
Sami Samhuri
d6d800a44b update Readme 2011-11-05 16:25:56 -07:00
Sami Samhuri
0bbd589e92 version 0.9.3 2011-05-30 01:03:32 -07:00
Sami Samhuri
a1b1d791aa fix output for new node version, how did this slip by?! 2011-05-30 01:03:17 -07:00
Sami Samhuri
1f0b036dcb bump version 2011-05-18 22:08:43 -07:00
Sami Samhuri
52af119494 ensure stash and unstash receive filenames 2011-05-18 22:08:37 -07:00
Sami Samhuri
a260912766 fix output still using the 0.2.x repl stream 2011-05-18 22:08:18 -07:00
Sami Samhuri
89cce91212 update readme 2011-05-18 00:14:54 -07:00
Sami Samhuri
934b3980b6 fix command line tool and bump version 2011-05-18 00:05:40 -07:00
Sami Samhuri
20c49da9ca update package.json and bump version to 0.9 (feature complete) 2011-05-18 00:00:14 -07:00
Sami Samhuri
6ba1449bbd node 0.4 compat, extend a running repl 2011-05-17 23:59:31 -07:00
Sami Samhuri
acb188db44 update project url 2011-04-27 22:38:30 -07:00
Sami Samhuri
092379ca4e bump version to v0.0.3 2010-10-14 08:56:15 -07:00
Sami Samhuri
29ab97d58d don't try to run a non-existent file 2010-10-14 08:56:15 -07:00
Sami Samhuri
39a37af01f Use VISUAL, and run reset after the editor exits (fixes display bugs) 2010-10-14 08:49:34 -07:00
4 changed files with 344 additions and 210 deletions

View file

@ -1,3 +1,13 @@
# I'M NOT DEAD, I'M JUST RESTING
Beautiful plumage. But this project no longer functions and some of the functionality here has been implemented
in Node proper via .load and .save in the REPL. I will no longer maintain it (poorly and infrequently).
If you want to contribute a .edit command to Node that would be pretty snazzy. Now that the REPL sucks less
it's less pressing though. You can paste in some code the REPL doesn't understand, or broken syntax, and
then Ctrl-C your way out of it.
repl-edit repl-edit
========= =========
@ -5,75 +15,51 @@ Use your text editor to edit commands in Node's repl.
(tip o' the hat to Giles Bowkett for [inspiration](http://gilesbowkett.blogspot.com/2010/09/vim-in-irb-with-utility-belt.html)) (tip o' the hat to Giles Bowkett for [inspiration](http://gilesbowkett.blogspot.com/2010/09/vim-in-irb-with-utility-belt.html))
Installation Installation
============ ============
npm install repl-edit npm install repl-edit
Usage Usage
===== =====
You can fire up a repl with editing capabilities by running `node-repl-edit`. Typically you just type `require('repl-edit')` in node's repl and it will extend it with new commands, just like `.break` and `.clear` that come with node.
(It would be nice to extend an existing repl session but that's not possible with
Node's repl module right now.)
(You can also fire up a repl with editing capabilities by running `node-repl-edit` in your shell)
Commands Commands
======== ========
edit .edit
----
The first time you run `edit()` in a repl a temporary file is created, specific to that session,
and opened in your editor. Type away and then save and close the file when you're done. The file
is loaded and executed immediately.
run
---
To run whatever command you've been working on without editing it again type `run()`.
setEditor
---------
`setEditor('mate -w')` changes your editor to TextMate for this session. Note that this
command sets the environment variable EDITOR for the repl process.
stash
----- -----
`stash('/path/to/a/file')` saves your command to the named file. The first time you run `.edit` your editor is opened containing the last statement you entered. Type away and then save and close the file when you're done. The code will be loaded and executed immediately. When you subsequently run `.edit` your editor is opened and contains whatever you left there.
Your editor is determined by the `VISUAL` and `EDITOR` environment variables, in that order. You can also change the editor for a single edit by doing something like `.edit vim`.
unstash .run
----
`.run` runs the most recent command you've edited.
.editor
------- -------
`unstash('/path/to/a/file')` restores the contents of that file for you to run and/or edit. `.editor mate -w` changes your editor to TextMate for this session, by setting the environment variable `VISUAL`.
.stash
------
Future `.stash /path/to/a/file` saves your command to the named file.
======
Instead of polluting the global namespace with functions I'd rather extend Node's repl .unstash
to allow user-defined dot commands (just like `.break` and `.clear`), and then use that --------
capability to provide commands like `.edit` and `.stash <filename>`.
The first time edit() is run in a repl instead of an empty file the command should be seeded
with the last command that was executed.
If the native repl module exports the currently running repl object it will be possible to attach
to an existing repl instead of having to run a separate binary that loads a repl.
`.unstash /path/to/a/file` restores the contents of that file for you to run and/or edit.
License License
======= =======
Copyright 2010 Sami Samhuri sami@samhuri.net Copyright 2010 - 2011 Sami Samhuri <sami@samhuri.net>
MIT (see the file named [LICENSE](/samsonjs/repl-edit/blob/master/LICENSE)) MIT license, see the included [LICENSE](/samsonjs/repl-edit/blob/master/LICENSE)

View file

@ -1,5 +1,5 @@
// //
// Copyright 2010 Sami Samhuri @_sjs // Copyright 2010 - 2011 Sami Samhuri @_sjs
// MIT license // MIT license
// //
// github.com/samsonjs/repl-edit // github.com/samsonjs/repl-edit
@ -8,153 +8,250 @@
// TODO proper error handling, better intregration with node/lib/repl.js // TODO proper error handling, better intregration with node/lib/repl.js
var fs = require('fs') var fs = require('fs')
, repl = require('repl') , replModule = require('repl')
, _repl
, _tmpdir
// Find a suitable directory to store the REPL session
if (process.env['TMPDIR']) _tmpdir = process.env['TMPDIR']
else {
try {
fs.statSync('/tmp')
_tmpdir = '/tmp'
} catch (e) {
_tmpdir = process.cwd()
}
}
exports.startRepl = function() {
console.log('Commands: edit(), run(), stash(filename), unstash(filename), setEditor(editor)')
// TODO extend the repl context instead, problem is that repl.js:resetContext() isn't exported
// so simple assignments to _repl.context can't survive .clear yet
exports.extend(global)
_repl = repl.start()
return exports
}
exports.extend = function(obj) {
var path = require('path')
, spawn = require('child_process').spawn , spawn = require('child_process').spawn
, sys = require('sys') , util = require('util')
, _tmpfile = path.join(_tmpdir, 'node-repl-' + process.pid + '.js') , vm = require('vm')
, Hint = 'Commands: .edit, .run, .stash <filename>, .unstash <filename>, .editor <editor>'
, theRepl
obj.edit = function(editor) { module.exports = { startRepl: startRepl, extendRepl: extendRepl }
editor = editor || process.ENV['EDITOR']
// TODO seed the file with _repl.context._ if the file doesn't exist yet
pausingRepl(function(unpause) {
var fds = [process.openStdin(), process.stdout, process.stdout]
, args = [_tmpfile]
// handle things like 'mate -w' and 'emacsclient --server-file <filename>'
if (editor.match(/\s/)) {
var words = editor.split(/\s+/)
args = words.slice(1).concat(args)
editor = words[0]
}
spawn(editor, args, {customFds: fds}).on('exit', function(code) {
// FIXME figure out why obj.run doesn't work properly here (output is skewed)
if (code === 0) {
runFile(_tmpfile, function() { unpause() })
} else {
unpause()
}
})
})
}
obj.run = function() { // Start a repl
pausingRepl(function(unpause) { function startRepl() {
runFile(_tmpfile, function() { unpause() }) if (theRepl) {
}) console.error('repl is already running, only one instance is allowed')
}
obj.setEditor = function(editor) {
process.ENV['EDITOR'] = editor
}
obj.stash = function(dest) {
try {
fs.statSync(_tmpfile)
} catch (e) {
console.log('nothing to stash')
return return
} }
pausingRepl(function(unpause) { return extendRepl(replModule.start('> '))
var read = fs.createReadStream(_tmpfile)
read.on('end', function() {
console.log('stashed')
unpause()
})
// TODO confirm before overwriting an existing file
sys.pump(read, fs.createWriteStream(dest))
})
} }
obj.unstash = function(source) { function log(s) {
try { if (theRepl.outputStream) {
fs.statSync(source) theRepl.outputStream.write(s + '\n' + theRepl.prompt)
} catch (e) { theRepl.displayPrompt()
console.log('no stash at ' + source) }
}
function extendRepl(aRepl) {
if (theRepl) {
console.error('repl is already running, only one instance is allowed')
return return
} }
pausingRepl(function(unpause) {
var read = fs.createReadStream(source) if (!aRepl || typeof aRepl.defineCommand !== 'function') {
read.on('end', function() { console.error('bad argument, repl is not compatible')
console.log('unstashed') return
unpause()
})
sys.pump(read, fs.createWriteStream(_tmpfile))
})
} }
theRepl = aRepl
var tmpfile = makeTempfile()
process.on('exit', function() { process.on('exit', function() {
try { try {
fs.unlinkSync(_tmpfile) fs.unlinkSync(tmpfile)
} catch (e) { }
catch (e) {
// might not exist // might not exist
} }
}) })
return exports log(Hint)
theRepl.defineCommand('edit', {
help: 'Edit the current command in your text editor'
, action: function(editor) {
edit(tmpfile, editor)
}
})
theRepl.defineCommand('run', {
help: 'Run the previously edited command'
, action: function() {
pause()
run(tmpfile, function() { unpause() })
}
})
theRepl.defineCommand('stash', {
help: 'Write the current command to the named file'
, action: function(dest) {
stash(tmpfile, dest)
}
})
theRepl.defineCommand('unstash', {
help: 'Replace the current command with the contents of the named file'
, action: function(source) {
unstash(tmpfile, source)
}
})
theRepl.defineCommand('editor', function(editor) {
process.env['VISUAL'] = editor
})
return theRepl
} }
function pausingRepl(fn) { // Commands
var origPrompt = _repl.prompt || '> '
_repl.stream.pause() function edit(cmdFile, editor) {
_repl.rli.enabled = false editor = editor || process.env['VISUAL'] || process.env['EDITOR']
_repl.prompt = ''
fn(function() { var fds = [process.openStdin(), process.stdout, process.stdout]
_repl.prompt = origPrompt , args = [cmdFile]
_repl.rli.enabled = true
_repl.stream.resume() // handle things like 'mate -w' and 'emacsclient --server-file <filename>'
_repl.displayPrompt() if (editor.match(/\s/)) {
var words = editor.split(/\s+/) // FIXME this should do proper word splitting ...
args = words.slice(1).concat(args)
editor = words[0]
}
// seed the file with repl.context._ if the file doesn't exist yet
try {
fs.statSync(cmdFile)
}
catch (e) {
// skip history[0], it's the .edit command
var lastCmd = theRepl.rli.history[1]
if (lastCmd && lastCmd[0] !== '.') {
fs.writeFileSync(cmdFile, lastCmd)
}
}
pause()
spawn(editor, args, {customFds: fds}).on('exit', function(code) {
// some editors change the terminal resulting in skewed output, clean up
spawn('reset').on('exit', function(_) {
if (code === 0) {
run(cmdFile, function() { unpause() })
}
else {
unpause()
}
})
}) })
} }
function runFile(filename, callback) { function stash(cmdFile, dest) {
var Script = process.binding('evals').Script try {
, evalcx = Script.runInContext fs.statSync(cmdFile)
, read = fs.createReadStream(filename) }
, s = '' catch (e) {
read.on('data', function(d) { s += d }) log('nothing to stash')
return
}
dest = dest.trim()
if (!dest) {
log('try: .stash /path/to/file')
return
}
var read = fs.createReadStream(cmdFile)
read.on('end', function() {
log('stashed')
unpause()
})
pause()
util.pump(read, fs.createWriteStream(dest))
}
function unstash(cmdFile, source) {
source = source.trim()
if (!source) {
log('try: .unstash /path/to/file')
return
}
try {
fs.statSync(source)
}
catch (e) {
log('no stash at ' + source)
return
}
var read = fs.createReadStream(source)
read.on('end', function() {
log('unstashed')
unpause()
})
pause()
util.pump(read, fs.createWriteStream(cmdFile))
}
function run(filename, callback) {
// check if file exists. might not have been saved.
try {
fs.statSync(filename)
}
catch (e) {
log('nothing to run\n')
callback()
return
}
var read = fs.createReadStream(filename)
, cmd = ''
read.on('data', function(d) { cmd += d })
read.on('end', function() { read.on('end', function() {
// The catchall for errors // The catchall for errors
try { try {
// Use evalcx to supply the global context var ret = vm.runInContext(cmd, theRepl.context, 'repl');
var ret = evalcx(s, _repl.context, "repl"); theRepl.context._ = ret
if (ret !== undefined) { theRepl.outputStream.write(replModule.writer(ret) + '\n')
_repl.context._ = ret;
repl.writer(ret) + "\n"
} }
} 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) {
_repl.stream.write(e.stack + "\n"); log(e.stack + '\n')
} else { }
_repl.stream.write(e.toString() + "\n"); else {
log(e.toString() + '\n')
} }
} }
if (callback) callback() if (callback) callback()
}) })
} }
if (require.main === module) exports.startRepl() // Support
var origPrompt
function unpause() {
theRepl.prompt = origPrompt
theRepl.rli.enabled = true
theRepl.outputStream.resume()
theRepl.inputStream.resume()
theRepl.displayPrompt()
}
function pause() {
theRepl.outputStream.pause()
theRepl.inputStream.pause()
theRepl.rli.enabled = false
origPrompt = theRepl.prompt || '> '
theRepl.prompt = ''
}
function makeTempfile() {
var path = require('path')
, tmpdir
// Find a suitable directory to store the REPL session
if (process.env['TMPDIR']) tmpdir = process.env['TMPDIR']
else {
try {
fs.statSync('/tmp')
tmpdir = '/tmp'
} catch (e) {
tmpdir = process.cwd()
}
}
return path.join(tmpdir, 'node-repl-' + process.pid + '.js')
}
if (require.main === module) {
startRepl()
}
else if (module.parent && module.parent.id === 'repl') {
extendRepl(module.parent.exports.repl)
}

View file

@ -1,23 +1,34 @@
{ "name" : "repl-edit" {
, "description" : "Edit code in the repl using a real text editor" "name": "repl-edit",
, "version" : "0.0.2" "description": "Edit code in the repl using a real text editor",
, "homepage" : "http://samhuri.net/node/repl-edit" "version": "0.9.5",
, "author" : "Sami Samhuri <sami@samhuri.net>" "homepage": "http://samhuri.net/proj/repl-edit",
, "repository" : "author": "Sami Samhuri <sami@samhuri.net>",
{ "type" : "git" "repository": {
, "url" : "http://github.com/samsonjs/repl-edit.git" "type": "git",
"url": "git://github.com/samsonjs/repl-edit.git"
},
"bugs": {
"email": "sami.samhuri@gmail.com",
"url": "https://github.com/samsonjs/repl-edit/issues"
},
"directories": {
"lib": "./lib"
},
"bin": {
"node-repl-edit": "./repl.js"
},
"main": "./lib/index",
"engines": {
"node": ">=0.4.0"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/samsonjs/repl-edit/raw/master/LICENSE"
} }
, "bugs" : ],
{ "mail" : "sami.samhuri+repl-edit@gmail.com" "dependencies": {},
, "web" : "http://github.com/samsonjs/repl-edit/issues" "devDependencies": {},
} "optionalDependencies": {}
, "directories" : { "lib" : "./lib" }
, "bin" : { "node-repl-edit" : "./repl.js" }
, "main" : "./lib/index"
, "engines" : { "node" : ">=0.2.0" }
, "licenses" :
[ { "type" : "MIT"
, "url" : "http://github.com/samsonjs/repl-edit/raw/master/LICENSE"
}
]
} }

42
repl.js
View file

@ -1,3 +1,43 @@
#!/usr/bin/env node #!/usr/bin/env node
require('./lib/index').startRepl() var fs = require('fs')
, path = require('path')
, repl = require('./lib/index').startRepl()
, DefaultHistoryFile = path.join(process.env.HOME, '.node_history')
, historyFile
if ('NODE_HISTORY' in process.env)
historyFile = process.env.NODE_HISTORY
else
historyFile = DefaultHistoryFile
// restore history immediately
if (historyFile) {
try {
fs.statSync(historyFile)
var json = fs.readFileSync(historyFile)
repl.rli.history = JSON.parse(json)
}
catch (e) {
if (e.code !== 'ENOENT') {
console.error('!!! Error reading history from ' + historyFile)
if (e.message === 'Unexpected token ILLEGAL') {
console.error('is this a JSON array of strings? -> ' + json)
}
else {
console.error(e.message)
}
}
}
// save history on exit
process.on('exit', function() {
try {
fs.writeFileSync(historyFile, JSON.stringify(repl.rli.history))
}
catch (e) {
console.error('Error writing history file to ' + historyFile)
console.error(e)
}
})
}