mirror of
https://github.com/samsonjs/repl-edit.git
synced 2026-03-25 09:25:49 +00:00
Compare commits
22 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a0da46ad4 | ||
|
|
11b8a6246b | ||
|
|
5ccbb46c81 | ||
|
|
91028f439b | ||
|
|
80b5dbf3c9 | ||
|
|
9c36c8f9c3 | ||
|
|
53d322e0ca | ||
|
|
c83c0105b6 | ||
|
|
d6d800a44b | ||
|
|
0bbd589e92 | ||
|
|
a1b1d791aa | ||
|
|
1f0b036dcb | ||
|
|
52af119494 | ||
|
|
a260912766 | ||
|
|
89cce91212 | ||
|
|
934b3980b6 | ||
|
|
20c49da9ca | ||
|
|
6ba1449bbd | ||
|
|
acb188db44 | ||
|
|
092379ca4e | ||
|
|
29ab97d58d | ||
|
|
39a37af01f |
4 changed files with 344 additions and 210 deletions
74
Readme.md
74
Readme.md
|
|
@ -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
|
||||
=========
|
||||
|
||||
|
|
@ -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))
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
npm install repl-edit
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
You can fire up a repl with editing capabilities by running `node-repl-edit`.
|
||||
|
||||
(It would be nice to extend an existing repl session but that's not possible with
|
||||
Node's repl module right now.)
|
||||
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.
|
||||
|
||||
(You can also fire up a repl with editing capabilities by running `node-repl-edit` in your shell)
|
||||
|
||||
Commands
|
||||
========
|
||||
|
||||
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
|
||||
.edit
|
||||
-----
|
||||
|
||||
`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
|
||||
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
|
||||
--------
|
||||
|
||||
`.unstash /path/to/a/file` restores the contents of that file for you to run and/or edit.
|
||||
|
||||
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)
|
||||
|
|
|
|||
331
lib/index.js
331
lib/index.js
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright 2010 Sami Samhuri @_sjs
|
||||
// Copyright 2010 - 2011 Sami Samhuri @_sjs
|
||||
// MIT license
|
||||
//
|
||||
// github.com/samsonjs/repl-edit
|
||||
|
|
@ -8,153 +8,250 @@
|
|||
// TODO proper error handling, better intregration with node/lib/repl.js
|
||||
|
||||
var fs = require('fs')
|
||||
, repl = 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')
|
||||
, replModule = require('repl')
|
||||
, spawn = require('child_process').spawn
|
||||
, sys = require('sys')
|
||||
, _tmpfile = path.join(_tmpdir, 'node-repl-' + process.pid + '.js')
|
||||
, util = require('util')
|
||||
, vm = require('vm')
|
||||
, Hint = 'Commands: .edit, .run, .stash <filename>, .unstash <filename>, .editor <editor>'
|
||||
, theRepl
|
||||
|
||||
obj.edit = function(editor) {
|
||||
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()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
module.exports = { startRepl: startRepl, extendRepl: extendRepl }
|
||||
|
||||
obj.run = function() {
|
||||
pausingRepl(function(unpause) {
|
||||
runFile(_tmpfile, function() { unpause() })
|
||||
})
|
||||
}
|
||||
|
||||
obj.setEditor = function(editor) {
|
||||
process.ENV['EDITOR'] = editor
|
||||
}
|
||||
|
||||
obj.stash = function(dest) {
|
||||
try {
|
||||
fs.statSync(_tmpfile)
|
||||
} catch (e) {
|
||||
console.log('nothing to stash')
|
||||
// Start a repl
|
||||
function startRepl() {
|
||||
if (theRepl) {
|
||||
console.error('repl is already running, only one instance is allowed')
|
||||
return
|
||||
}
|
||||
pausingRepl(function(unpause) {
|
||||
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))
|
||||
})
|
||||
return extendRepl(replModule.start('> '))
|
||||
}
|
||||
|
||||
obj.unstash = function(source) {
|
||||
try {
|
||||
fs.statSync(source)
|
||||
} catch (e) {
|
||||
console.log('no stash at ' + source)
|
||||
function log(s) {
|
||||
if (theRepl.outputStream) {
|
||||
theRepl.outputStream.write(s + '\n' + theRepl.prompt)
|
||||
theRepl.displayPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
function extendRepl(aRepl) {
|
||||
if (theRepl) {
|
||||
console.error('repl is already running, only one instance is allowed')
|
||||
return
|
||||
}
|
||||
pausingRepl(function(unpause) {
|
||||
var read = fs.createReadStream(source)
|
||||
read.on('end', function() {
|
||||
console.log('unstashed')
|
||||
unpause()
|
||||
})
|
||||
sys.pump(read, fs.createWriteStream(_tmpfile))
|
||||
})
|
||||
|
||||
if (!aRepl || typeof aRepl.defineCommand !== 'function') {
|
||||
console.error('bad argument, repl is not compatible')
|
||||
return
|
||||
}
|
||||
|
||||
theRepl = aRepl
|
||||
|
||||
var tmpfile = makeTempfile()
|
||||
process.on('exit', function() {
|
||||
try {
|
||||
fs.unlinkSync(_tmpfile)
|
||||
} catch (e) {
|
||||
fs.unlinkSync(tmpfile)
|
||||
}
|
||||
catch (e) {
|
||||
// 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) {
|
||||
var origPrompt = _repl.prompt || '> '
|
||||
_repl.stream.pause()
|
||||
_repl.rli.enabled = false
|
||||
_repl.prompt = ''
|
||||
fn(function() {
|
||||
_repl.prompt = origPrompt
|
||||
_repl.rli.enabled = true
|
||||
_repl.stream.resume()
|
||||
_repl.displayPrompt()
|
||||
// Commands
|
||||
|
||||
function edit(cmdFile, editor) {
|
||||
editor = editor || process.env['VISUAL'] || process.env['EDITOR']
|
||||
|
||||
var fds = [process.openStdin(), process.stdout, process.stdout]
|
||||
, args = [cmdFile]
|
||||
|
||||
// handle things like 'mate -w' and 'emacsclient --server-file <filename>'
|
||||
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) {
|
||||
var Script = process.binding('evals').Script
|
||||
, evalcx = Script.runInContext
|
||||
, read = fs.createReadStream(filename)
|
||||
, s = ''
|
||||
read.on('data', function(d) { s += d })
|
||||
function stash(cmdFile, dest) {
|
||||
try {
|
||||
fs.statSync(cmdFile)
|
||||
}
|
||||
catch (e) {
|
||||
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() {
|
||||
// The catchall for errors
|
||||
try {
|
||||
// Use evalcx to supply the global context
|
||||
var ret = evalcx(s, _repl.context, "repl");
|
||||
if (ret !== undefined) {
|
||||
_repl.context._ = ret;
|
||||
repl.writer(ret) + "\n"
|
||||
var ret = vm.runInContext(cmd, theRepl.context, 'repl');
|
||||
theRepl.context._ = ret
|
||||
theRepl.outputStream.write(replModule.writer(ret) + '\n')
|
||||
}
|
||||
} catch (e) {
|
||||
catch (e) {
|
||||
// On error: Print the error and clear the buffer
|
||||
if (e.stack) {
|
||||
_repl.stream.write(e.stack + "\n");
|
||||
} else {
|
||||
_repl.stream.write(e.toString() + "\n");
|
||||
log(e.stack + '\n')
|
||||
}
|
||||
else {
|
||||
log(e.toString() + '\n')
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
53
package.json
53
package.json
|
|
@ -1,23 +1,34 @@
|
|||
{ "name" : "repl-edit"
|
||||
, "description" : "Edit code in the repl using a real text editor"
|
||||
, "version" : "0.0.2"
|
||||
, "homepage" : "http://samhuri.net/node/repl-edit"
|
||||
, "author" : "Sami Samhuri <sami@samhuri.net>"
|
||||
, "repository" :
|
||||
{ "type" : "git"
|
||||
, "url" : "http://github.com/samsonjs/repl-edit.git"
|
||||
{
|
||||
"name": "repl-edit",
|
||||
"description": "Edit code in the repl using a real text editor",
|
||||
"version": "0.9.5",
|
||||
"homepage": "http://samhuri.net/proj/repl-edit",
|
||||
"author": "Sami Samhuri <sami@samhuri.net>",
|
||||
"repository": {
|
||||
"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"
|
||||
, "web" : "http://github.com/samsonjs/repl-edit/issues"
|
||||
}
|
||||
, "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"
|
||||
}
|
||||
]
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"optionalDependencies": {}
|
||||
}
|
||||
42
repl.js
42
repl.js
|
|
@ -1,3 +1,43 @@
|
|||
#!/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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue