diff --git a/lib/repl.js b/lib/repl.js index 2c78ce3dff3..ea965d43526 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -16,6 +16,7 @@ var sys = require('sys'); var Script = process.binding('evals').Script; var evalcx = Script.runInContext; var path = require("path"); +var fs = require("fs"); var rl = require('readline'); var context; @@ -162,7 +163,7 @@ REPLServer.prototype.complete = function (line) { completeOn, match, filter, i, j, group, c; - // REPL comments (e.g. ".break"). + // REPL commands (e.g. ".break"). var match = null; match = line.match(/^\s*(\.\w*)$/); if (match) { @@ -173,6 +174,72 @@ REPLServer.prototype.complete = function (line) { } } + // require('...') + else if (match = line.match(/\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/)) { + //TODO: suggest require.exts be exposed to be introspec registered extensions? + //TODO: suggest include the '.' in exts in internal repr: parity with `path.extname`. + var exts = [".js", ".node"]; + var indexRe = new RegExp('^index(' + exts.map(regexpEscape).join('|') + ')$'); + + completeOn = match[1]; + var subdir = match[2] || ""; + var filter = match[1]; + var dir, files, f, name, base, ext, abs, subfiles, s; + group = []; + for (i = 0; i < require.paths.length; i++) { + dir = require.paths[i]; + if (subdir && subdir[0] === '/') { + dir = subdir; + } else if (subdir) { + dir = path.join(dir, subdir); + } + try { + files = fs.readdirSync(dir); + } catch (e) { + continue; + } + for (f = 0; f < files.length; f++) { + name = files[f]; + ext = path.extname(name); + base = name.slice(0, -ext.length); + if (base.match(/-\d+\.\d+(\.\d+)?/) || name === ".npm") { + // Exclude versioned names that 'npm' installs. + continue; + } + if (exts.indexOf(ext) !== -1) { + if (!subdir || base !== "index") { + group.push(subdir + base); + } + } else { + abs = path.join(dir, name); + try { + if (fs.statSync(abs).isDirectory()) { + group.push(subdir + name + '/'); + subfiles = fs.readdirSync(abs); + for (s = 0; s < subfiles.length; s++) { + if (indexRe.test(subfiles[s])) { + group.push(subdir + name); + } + } + } + } catch(e) {} + } + } + } + if (group.length) { + completionGroups.push(group); + } + + if (!subdir) { + // Kind of lame that this needs to be updated manually. + // Intentionally excluding moved modules: posix, utils. + var builtinLibs = ['assert', 'buffer', 'child_process', 'crypto', 'dgram', + 'dns', 'events', 'file', 'freelist', 'fs', 'http', 'net', 'path', + 'querystring', 'readline', 'repl', 'string_decoder', 'sys', 'tcp', 'url']; + completionGroups.push(builtinLibs); + } + } + // Handle variable member lookup. // We support simple chained expressions like the following (no function // calls, etc.). That is for simplicity and also because we *eval* that @@ -348,6 +415,10 @@ function trimWhitespace (cmd) { } } +function regexpEscape(s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} + /** * Converts commands that use var and function () to use the * local exports.context when evaled. This provides a local context