define(function(require, exports, module) { main.consumes = [ "Plugin", "ui", "layout", "commands", "fs", "navigate", "save", "tabbehavior", "ace", "commands", "tabManager" ]; main.provides = ["vim.cli"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var ui = imports.ui; var commands = imports.commands; var layout = imports.layout; var fs = imports.fs; var navigate = imports.navigate; var save = imports.save; var tabbehavior = imports.tabbehavior; var tabManager = imports.tabManager; var ace = imports.ace; var Vim = require('ace/keyboard/vim').Vim; var Editor = require("ace/editor").Editor; var lang = require("ace/lib/lang"); var pathLib = require("path"); /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); var cmdLine; var searchStore = { current: "", options: { needle: "", backwards: false, wrap: true, caseSensitive: false, wholeWord: false, regExp: false } }; var loaded = false; function load() { if (loaded) return false; loaded = true; } var drawn = false; function draw() { if (drawn) return; drawn = true; cmdLine = new apf.codebox(); layout.findParent(plugin).appendChild(cmdLine); cmdLine.setHeight(23); cmdLine.$ext.className = "searchbox tb_console vimInput"; updateTheme(); initCmdLine(cmdLine.ace); ace.on("themeChange", updateTheme, plugin); emit("draw"); } function updateTheme() { if (!cmdLine) return; var activeTheme = ace.theme; // themes.getActiveTheme(); if (!activeTheme) return; cmdLine.ace.setTheme(activeTheme); var htmlNode = cmdLine.ace.container.parentNode; var style = htmlNode.style; style.background = activeTheme.bg; activeTheme.isDark ? ui.setStyleClass(htmlNode, "dark") : ui.setStyleClass(htmlNode, "", ["dark"]); } function show() { draw(); layout.setFindArea(cmdLine, { isDefault: true }); } function hide() { if (cmdLine) { layout.setFindArea(null, { isDefault: true }); } } function toggle(force, a, b, callback) { if (force == -1) hide(); else show(); callback && callback(); } /***** Methods *****/ var cliCmds = {}; cliCmds.ascii = { name: "ascii", description: "", exec: function(editor) { var onSelectionChange = lang.delayedCall(function(e) { var c = editor.getCursorPosition(); var ch = editor.session.getLine(c.row)[c.column - 1] || "\n"; var code = ch.charCodeAt(0); var msg = JSON.stringify(ch).slice(1, -1) + "=" + " "; var str = code.toString(16); str = [, "\\x0", "\\x", "\\u0", "\\u"][str.length] + str; msg += str + " "; msg += "\\" + code.toString(8) + " "; msg += "&#" + code + ";"; editor.cmdLine.setMessage(msg); clear.delay(2000); }); var clear = lang.delayedCall(function(e) { editor.removeListener(editor.asciiMessageListener); editor.asciiMessageListener = null; editor.cmdLine.setMessage(""); }); if (editor.asciiMessageListener) { return clear.call(); } editor.on("changeSelection", editor.asciiMessageListener = function() { onSelectionChange.schedule(200); }); onSelectionChange.call(); } }; cliCmds.set = { name: "set", description: "set editor option", exec: function(editor, args) { var cmd = args.text.split(" "); var optName = cmd[1]; var optValue = parseOption(cmd[2]); editor.setOption(optName, optValue); }, getCompletions: function() { return Object.keys(Editor.prototype.$options); } }; function parseOption(optValue) { if (/^\d+$/.test(optValue)) optValue = parseInt(optValue, 10); if (/^[\d.\^e]+$/.test(optValue)) optValue = parseFloat(optValue); else if (/^(true|false)$/i.test(optValue)) optValue = optValue.length = 4; return optValue; } cliCmds["/"] = { name: "/", history: [], cliExec: function(ed, data) { } }; cliCmds["?"] = { name: "?", history: cliCmds["/"].history, cliExec: cliCmds["/"].cliExec }; cliCmds[":"] = { name: ":", history: [], getCompletions: function() { return Object.keys(this.commands).concat(Object.keys(commands.commands)); } }; cliCmds[':'].reCommands = { /** * @see {@link http://vim.wikia.com/wiki/Search_and_replace|Vim wiki - sed} */ 'sed': { regex: /^(%|'<,'>|(?:\d+|\.),(?:\+?\d+|\$|\.))?s(\/|#)(.*?)\2(.*?)\2([giIc]*)$/, action: function (editor, cmd, data) { Vim.handleEx(editor.state.cm, cmd); } } }; cliCmds[":"].commands = { w: function(editor, data, callback) { var tab = tabManager.focussedTab; if (!tab || !tab.path) return; var lines = editor.session.getLength(); if (data.argv.length === 2 && data.argv[1]) { var path = pathLib.join(pathLib.dirname(tab.path), data.argv[1]); save.save(tab, { path: path }, function(err) { if (!err) editor.cmdLine.setTimedMessage(path + " [New] " + lines + "L, ##C written to "); callback && callback(); }); } else { save.save(tab, {}, function(err) { if (!err) editor.cmdLine.setTimedMessage(tab.path + " " + lines + "L, ##C written"); callback && callback(); }); } }, e: function(editor, data) { var path = data.argv[1]; if (!path) { navigate.show(); return false; } else { var currentPath = tabManager.focussedTab && tabManager.focussedTab.path || "/"; path = pathLib.join(pathLib.dirname(currentPath), data.argv[1]); fs.exists(path, function(exists) { if (exists) { tabManager.openFile(path, { focus: true }, function() {}); } else { tabManager.open({ path: path, focus: true, document: { meta: { newfile: true, cli: true } } }); } }); } }, x: function(editor, data) { var page = tabManager.focussedTab; if (!page) return; if (page.document.changed) { cliCmds[":"].commands.wq(editor, data); return; } else { cliCmds[":"].commands.q(); } }, wq: function(editor, data) { cliCmds[":"].commands.w(editor, data, function() { cliCmds[":"].commands.q(); }); }, wa: function(editor, data) { commands.exec("saveall"); }, q: function(editor, data) { var page = tabManager.focussedTab; if (!page) return; if (data && data.force) page.document.undoManager.bookmark(); page.close(); }, "q!": function() { cliCmds[":"].commands.q(null, { force: true }); }, tabn: "gototabright", tabp: "gototableft", tabfirst: function() { tabbehavior.cycleTab("first", { editorType: "ace" }); }, tablast: function() { tabbehavior.cycleTab("last", { editorType: "ace" }); }, tabnew: function(editor, data) { var path = data.argv[1]; if (!path) { tabManager.open({ path: "", document: { meta: { newfile: true } }, focus: true }); } else { cliCmds[":"].commands.e(editor, data); } }, tabclose: "closetab", tabmove: function(editor, data) { // todo }, ascii: cliCmds.ascii, sort: function(editor, data) { commands.exec("sortlines"); }, }; // aliases cliCmds[":"].commands["tab drop"] = cliCmds[":"].commands.e; cliCmds[":"].commands.write = cliCmds[":"].commands.w; cliCmds[":"].commands.tabNext = cliCmds[":"].commands.tabn; cliCmds[":"].commands.tabPrevious = cliCmds[":"].commands.tabp; cliCmds[":"].commands.tabc = cliCmds[":"].commands.tabclose; cliCmds[":"].commands.set = { vimOpts: [ "cursorline", "cul", "nocursorline", "nocul", //, "highlightActiveLine", "expandtab", "et", "noexpandtab", "noet", //"useSoftTabs", "number", "nu", "nonumber", "nonu" // "showGutter" // ["relativenumber", "rnu", "norelativenumber", "nornu"] // 'softtabstop' 'sts' number // 'tabstop' 'ts' number ], exec: function(ed, args) { var optName = args.argv[1]; var optval = optName.slice(0, 2) != "no"; if (optName[optName.length - 1] == "!") { var toggle = true; optName = optName.slice(0, -1); } var i = this.vimOpts.indexOf(optName); if (i == -1) { ed.cmdLine.setTimedMessage("Unrecognised option '" + optName + "'.", 1500); return; } else if (i < 4) { optName = "highlightActiveLine"; } else if (i < 8) { optName = "useSoftTabs"; } else if (i < 12) { optName = "showGutter"; } if (toggle) optval = !ed.getOption(optName); ed.setOption(optName, optval); }, getCompletions: function(e) { return this.vimOpts; //.concat(Object.keys(Editor.prototype.$options)); } }; cliCmds[":"].commands.syntax = commands.commands.syntax; cliCmds[":"].cliExec = function(ed, cmd, tokens) { var last = tokens[tokens.length - 1]; if (last && last.type == "invisible") cmd += last.value; cmd = cmd.substr(1).trim(); var args = cmd.split(/\s+/); var firstCmd = args[0]; if (this.commands[firstCmd]) { cmd = this.commands[firstCmd]; if (typeof cmd == "string") return commands.exec(cmd, null, { argv: args }); else if (typeof cmd == "function") return cmd(ed, { argv: args }); else if (cmd.exec) return cmd.exec(ed, { argv: args }); } else if (commands.commands[firstCmd]) { commands.exec(firstCmd, null, { argv: args }); } else if (/^[+\-\d,]+$/.test(cmd)) { Vim.handleEx(ed.state.cm, cmd); } else { for (var key in this.reCommands) { var reCmd = this.reCommands[key]; var match = reCmd.regex.exec(cmd); if (match) { return reCmd.action(ed, cmd, { match: match, argv: cmd.split(match[0], 1).slice(-1)[0].split(/\s+/) }); } } ed.cmdLine.setTimedMessage( 'Vim command "' + cmd + '" not implemented.', 3500); } }; var allCommands; function getCompletions(command) { if (command) { if (command.getCompletions) return command.getCompletions() || []; if (command.commands) return Object.keys(command.commands); return []; } if (!allCommands) { allCommands = Object.keys(commands.commands) .concat(Object.keys(cliCmds)); } return allCommands; } function getCommand(name, root) { if (root && root.commands && root.commands[name]) return root.commands[name]; if (root) return root; return cliCmds[name] || commands.commands[name]; } function getActiveEditor() { var tab = tabManager.focussedTab; if (tab && tab.editorType == "ace") return tab.editor.ace; } function processCommandParts(ed, tokens, text) { for (var i = 0; i < tokens.length; i++) { var tok = tokens[i]; var cmd = tok.command; if (!cmd) continue; if (cmd.name !== tok.value) { var next = tokens[i + 1]; if (!next || next.type !== "invisible") continue; } if (cmd.cliExec) return cmd.cliExec(ed, text, tokens); else if (cmd.exec) return cmd.exec(ed, { argv: text.split(/\s+/), text: text, tokens: tokens }); } } function endCommandInput(cmdLine) { cmdLine.addToHistory(); cmdLine.setValue(""); var editor = getActiveEditor(); if (editor) editor.textInput.focus(); } function initCmdLine(cmdLine) { cmdLine.commands.addCommands([{ bindKey: "Shift-Return|Ctrl-Return|Alt-Return", name: "insertNewLine", exec: function(cmdLine) { cmdLine.insert("\n"); }, }, { bindKey: "Esc|Shift-Esc|Ctrl-[", name: "cancel", exec: function(cmdLine) { endCommandInput(cmdLine); } }, { bindKey: "Return", name: "run", exec: function run(cmdLine) { var editor = cmdLine.editor || getActiveEditor(); var tokens = cmdLine.session.getTokens(0); if (editor) editor.cmdLine = cmdLine; processCommandParts(editor, tokens, cmdLine.getValue()); endCommandInput(cmdLine); }, }, { bindKey: "Tab", name: "tabNext", exec: function tabNext(ed) { tabCycle(ed, 1); }, }, { bindKey: "Shift-Tab", name: "tabPrevious", exec: function tabPrevious(ed) { tabCycle(ed, -1); }, }, { bindKey: "Right", name: "arrowCompleteRight", exec: function arrowCompleteRight(ed) { var session = ed.session; var col = ed.selection.isEmpty() ? ed.selection.lead.column : -1; ed.navigateRight(); var tok = session.getTokenAt(0, col + 1); if (col == ed.selection.lead.column && tok && tok.type == "invisible") session.doc.insertInLine({ row: 0, column: col }, tok.value); }, }, { bindKey: "Up", name: "Up", exec: function(cmdLine) {cmdLine.navigateHistory(-1);}, }, { bindKey: "Down", name: "Down", exec: function(cmdLine) {cmdLine.navigateHistory(1);}, }, { bindKey: "Ctrl-Home|PageUp", name: "firstInHistory", exec: function(cmdLine) {cmdLine.navigateHistory(0);}, }, { bindKey: "Ctrl-End|PageDown", name: "lastInHistory", exec: function(cmdLine) {cmdLine.navigateHistory();} }]); function tabCycle(ed, dir) { var session = ed.session; var range = ed.getSelectionRange(); var line = session.getLine(0); var len = line.length; if (range.end.column != len || !range.isEmpty()) { ed.navigateLineEnd(); return; } if (!ed.$tabCycle) { var tok = session.getTokenAt(0, len) || { value: "", type: "" }; var matches = session.getState(0); if (matches == "start") matches = getCompletions(); if (!matches) return; if (matches.length == 1 && tok.value == matches[0]) { if (tok.command) { matches = getCompletions(tok.command); tok = { value: "", type: "" }; } if (!matches) return; } ed.$tabCycle = { matches: matches, index: tok.value == matches[0] ? 0 : -1, start: len - tok.value.length }; ed.commands.on("exec", function onExec(e) { var name = e.command && e.command.name; if (name !== "tabNext" && name !== "tabPrevious") { ed.$tabCycle = null; ed.commands.removeListener("exec", onExec); } }); } var matches = ed.$tabCycle.matches; var index = ed.$tabCycle.index; var start = ed.$tabCycle.start; index += dir; index %= matches.length; if (index < 0) index = matches.length + index; ed.$tabCycle.index = index; var match = matches[index]; if (!match) return; var i = 0; while (match[i] && match[i] == line[start + i]) i++; start += i; match = match.substr(i); if (i === 0 && (/\w/.test(match[0]) && /\w/.test(line[start - 1]))) match = " " + match; if (/\w$/.test(match)) match += " "; range.start.column = start; range.end.column = len; session.replace(range, match); if (ed.$tabCycle.matches.length == 1) ed.$tabCycle = null; } cmdLine.history = []; cmdLine.navigateHistory = function(dir) { var cliCmd = this.getCurrentCommandWithHistory() || {}; var history = cliCmd.history || this.history; var index = history.index; var cmd = history[index] || ""; if (dir === 0) { index = 0; } else if (dir === null) { index = history.length; } else if (typeof dir == "number") { index += dir; if (index < 0) index = 0; if (index > history.length) index = history.length; } cmd = history[index] || ""; if (cliCmd.history && cliCmd.name) cmd = cliCmd.name + cmd; // TODO keep history.lastTyped this.setValue(cmd, 1); history.index = index; }; cmdLine.addToHistory = function(val) { var cliCmd = this.getCurrentCommandWithHistory() || {}; var history = cliCmd.history || this.history; val = val || this.getValue(); if (cliCmd.name && cliCmd.history) val = val.substr(cliCmd.name.length); if (val && val != history[history.index]) { history.push(val); history.index = history.length; } }; cmdLine.getCurrentCommand = function() { var tokens = this.session.getTokens(0); var tok = tokens[tokens.length - 1]; return tok && tok.command; }; cmdLine.getCurrentCommandWithHistory = function() { var tokens = this.session.getTokens(0); for (var i = tokens.length; i--;) { var tok = tokens[i]; if (tok && tok.command && tok.command.history) return tok.command; } }; cmdLine.on("blur", function() { cmdLine.renderer.$cursorLayer.element.style.opacity = 0; if (!cmdLine.getValue()) { cmdLine.renderer.content.style.visibility = "hidden"; cmdLine.$messageNode.style.display = ""; cmdLine.$inMessageMode = true; cmdLine.$messageNode.textContent = cmdLine.$message || ""; } }); cmdLine.on("focus", function() { cmdLine.renderer.$cursorLayer.element.style.opacity = ""; cmdLine.renderer.content.style.visibility = ""; cmdLine.$messageNode.style.display = "none"; cmdLine.$inMessageMode = false; }); cmdLine.commands.on("exec", function(e) { if (!e.command) return; if (e.command.name == "insertstring") { } cmdLine.commands.lastCommandName = e.command.name; }); cmdLine.session.bgTokenizer.$tokenizeRow = function(row) { var line = this.doc.getLine(row); var command = null; var tokens = []; function add(type, value) { tokens.push({ type: type, value: value, command: command }); } while (line.length) { var names = getCompletions(command); var matches = matchCommand(line, names); if (!matches.length) { add("text", line); break; } var cur = matches[0]; command = getCommand(cur, command); if (cur.length >= line.length) { add("keyword", line); add("invisible", cur.substring(line.length)); } else { add("keyword", cur); } line = line.substr(cur.length); var i = line.search(/\S|$/); if (i > 0) { add("text", line.substring(0, i)); line = line.substr(i); if (!line.length) matches = getCompletions(command); } } this.lines[row] = tokens; this.states[row] = matches; return tokens; }; function matchCommand(line, names) { var matches = []; names.forEach(function(name) { if (name.length < line.length) { var isMatch = line.lastIndexOf(name, 0) === 0; if (isMatch && /\w/.test(name[name.length - 1])) isMatch = !/\w/.test(line[name.length]); } else { var isMatch = name.lastIndexOf(line, 0) === 0; } if (isMatch) matches.push(name); }); return matches; } cmdLine.$messageNode = document.createElement("div"); cmdLine.$messageNode.style.cssText = "position:absolute;" + "opacity:0.8;padding:0 5px;top:0;font-size:11px"; cmdLine.container.appendChild(cmdLine.$messageNode); cmdLine.$clearMessageDelayed = lang.delayedCall(function() { cmdLine.setMessage(""); }); cmdLine.setTimedMessage = function(text, timeout) { this.setMessage(text); this.once("setMessage", function() { cmdLine.$clearMessageDelayed.cancel(); }); cmdLine.$clearMessageDelayed.schedule(timeout || 2000); }; cmdLine.setMessage = function(text, from) { this._signal("setMessage", text); this.$message = text; if (this.$inMessageMode) this.$messageNode.textContent = text; }; if (!cmdLine.isFocused()) cmdLine._emit("blur"); cmdLine.commands.removeCommands( ["find", "gotoline", "findall", "replace", "replaceall"]); } /***** Lifecycle *****/ plugin.on("load", function() { load(); }); plugin.on("enable", function() { }); plugin.on("disable", function() { }); plugin.on("unload", function() { loaded = false; }); /***** Register and define API *****/ /** * **/ plugin.freezePublicAPI({ /** * @ignore */ searchStore: searchStore, /** * */ get aml() { return cmdLine; }, /** * */ get ace() { return cmdLine.ace; }, /** * */ show: show, /** * */ hide: hide, /** * */ toggle: toggle }); register(null, { "vim.cli": plugin }); } });