473 lines
17 KiB
JavaScript
473 lines
17 KiB
JavaScript
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Distributed under the BSD license:
|
||
|
*
|
||
|
* Copyright (c) 2012, Ajax.org B.V.
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions are met:
|
||
|
* * Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* * Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
* * Neither the name of Ajax.org B.V. nor the
|
||
|
* names of its contributors may be used to endorse or promote products
|
||
|
* derived from this software without specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
define(function(require, exports, module) {
|
||
|
"use strict";
|
||
|
|
||
|
var Range = require("../range").Range;
|
||
|
var dom = require("../lib/dom");
|
||
|
var shortcuts = require("../ext/menu_tools/get_editor_keyboard_shortcuts");
|
||
|
var FilteredList= require("../autocomplete").FilteredList;
|
||
|
var AcePopup = require('../autocomplete/popup').AcePopup;
|
||
|
var $singleLineEditor = require('../autocomplete/popup').$singleLineEditor;
|
||
|
var UndoManager = require("../undomanager").UndoManager;
|
||
|
var Tokenizer = require("ace/tokenizer").Tokenizer;
|
||
|
var overlayPage = require('./menu_tools/overlay_page').overlayPage;
|
||
|
var modelist = require("ace/ext/modelist");
|
||
|
var openPrompt;
|
||
|
|
||
|
function prompt(editor, message, options, callback) {
|
||
|
if (typeof message == "object") {
|
||
|
return prompt(editor, "", message, options);
|
||
|
}
|
||
|
if (openPrompt) {
|
||
|
var lastPrompt = openPrompt;
|
||
|
editor = lastPrompt.editor;
|
||
|
lastPrompt.close();
|
||
|
if (lastPrompt.name && lastPrompt.name == options.name)
|
||
|
return;
|
||
|
}
|
||
|
if (options.$type)
|
||
|
return prompt[options.$type](editor, callback);
|
||
|
|
||
|
var cmdLine = $singleLineEditor();
|
||
|
cmdLine.session.setUndoManager(new UndoManager());
|
||
|
cmdLine.setOption("fontSize", editor.getOption("fontSize"));
|
||
|
|
||
|
var el = dom.buildDom(["div", {class: "ace_prompt_container"}]);
|
||
|
var overlay = overlayPage(editor, el, done);
|
||
|
el.appendChild(cmdLine.container);
|
||
|
|
||
|
editor.cmdLine = cmdLine;
|
||
|
cmdLine.setValue(message, 1);
|
||
|
if (options.selection) {
|
||
|
cmdLine.selection.setRange({
|
||
|
start: cmdLine.session.doc.indexToPosition(options.selection[0]),
|
||
|
end: cmdLine.session.doc.indexToPosition(options.selection[1])
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (options.getCompletions) {
|
||
|
var popup = new AcePopup();
|
||
|
popup.renderer.setStyle("ace_autocomplete_inline");
|
||
|
popup.container.style.display = "block";
|
||
|
popup.container.style.maxWidth = "600px";
|
||
|
popup.container.style.width = "100%";
|
||
|
popup.container.style.marginTop = "3px";
|
||
|
popup.renderer.setScrollMargin(2, 2, 0, 0);
|
||
|
popup.autoSelect = false;
|
||
|
popup.renderer.$maxLines = 15;
|
||
|
popup.setRow(-1);
|
||
|
popup.on("click", function(e) {
|
||
|
var data = popup.getData(popup.getRow());
|
||
|
if (!data.error) {
|
||
|
cmdLine.setValue(data.value || data.name || data);
|
||
|
accept();
|
||
|
e.stop();
|
||
|
}
|
||
|
});
|
||
|
el.appendChild(popup.container);
|
||
|
updateCompletions();
|
||
|
}
|
||
|
|
||
|
if (options.$rules) {
|
||
|
var tokenizer = new Tokenizer(options.$rules);
|
||
|
cmdLine.session.bgTokenizer.setTokenizer(tokenizer);
|
||
|
}
|
||
|
|
||
|
function accept() {
|
||
|
var val;
|
||
|
if (popup.getCursorPosition().row > 0) {
|
||
|
val = valueFromRecentList();
|
||
|
} else {
|
||
|
val = cmdLine.getValue();
|
||
|
}
|
||
|
var curData = popup.getData(popup.getRow());
|
||
|
if (curData && !curData.error) {
|
||
|
done();
|
||
|
options.onAccept && options.onAccept({
|
||
|
value: val,
|
||
|
item: curData
|
||
|
}, cmdLine);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cmdLine.commands.bindKeys({
|
||
|
"Enter": accept,
|
||
|
"Esc|Shift-Esc": function() {
|
||
|
options.onCancel && options.onCancel(cmdLine.getValue(), cmdLine);
|
||
|
done();
|
||
|
},
|
||
|
"Up": function(editor) { popup.goTo("up"); valueFromRecentList();},
|
||
|
"Down": function(editor) { popup.goTo("down"); valueFromRecentList();},
|
||
|
"Ctrl-Up|Ctrl-Home": function(editor) { popup.goTo("start"); valueFromRecentList();},
|
||
|
"Ctrl-Down|Ctrl-End": function(editor) { popup.goTo("end"); valueFromRecentList();},
|
||
|
"Tab": function(editor) {
|
||
|
popup.goTo("down"); valueFromRecentList();
|
||
|
},
|
||
|
"PageUp": function(editor) { popup.gotoPageUp(); valueFromRecentList();},
|
||
|
"PageDown": function(editor) { popup.gotoPageDown(); valueFromRecentList();}
|
||
|
});
|
||
|
|
||
|
function done() {
|
||
|
overlay.close();
|
||
|
callback && callback();
|
||
|
openPrompt = null;
|
||
|
}
|
||
|
|
||
|
cmdLine.on("input", function() {
|
||
|
options.onInput && options.onInput();
|
||
|
updateCompletions();
|
||
|
});
|
||
|
|
||
|
function updateCompletions() {
|
||
|
if (options.getCompletions) {
|
||
|
var prefix;
|
||
|
if (options.getPrefix) {
|
||
|
prefix = options.getPrefix(cmdLine);
|
||
|
}
|
||
|
|
||
|
var completions = options.getCompletions(cmdLine);
|
||
|
popup.setData(completions, prefix);
|
||
|
popup.resize(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function valueFromRecentList() {
|
||
|
var current = popup.getData(popup.getRow());
|
||
|
if (current && !current.error)
|
||
|
return current.value || current.caption || current;
|
||
|
}
|
||
|
|
||
|
cmdLine.resize(true);
|
||
|
popup.resize(true);
|
||
|
cmdLine.focus();
|
||
|
|
||
|
openPrompt = {
|
||
|
close: done,
|
||
|
name: options.name,
|
||
|
editor: editor
|
||
|
};
|
||
|
}
|
||
|
|
||
|
prompt.gotoLine = function(editor, callback) {
|
||
|
function stringifySelection(selection) {
|
||
|
if (!Array.isArray(selection))
|
||
|
selection = [selection];
|
||
|
return selection.map(function(r) {
|
||
|
var cursor = r.isBackwards ? r.start: r.end;
|
||
|
var anchor = r.isBackwards ? r.end: r.start;
|
||
|
var row = anchor.row;
|
||
|
var s = (row + 1) + ":" + anchor.column;
|
||
|
|
||
|
if (anchor.row == cursor.row) {
|
||
|
if (anchor.column != cursor.column)
|
||
|
s += ">" + ":" + cursor.column;
|
||
|
} else {
|
||
|
s += ">" + (cursor.row + 1) + ":" + cursor.column;
|
||
|
}
|
||
|
return s;
|
||
|
}).reverse().join(", ");
|
||
|
}
|
||
|
|
||
|
prompt(editor, ":" + stringifySelection(editor.selection.toJSON()), {
|
||
|
name: "gotoLine",
|
||
|
selection: [1, Number.MAX_VALUE],
|
||
|
onAccept: function(data) {
|
||
|
var value = data.value;
|
||
|
var _history = prompt.gotoLine._history;
|
||
|
if (!_history)
|
||
|
prompt.gotoLine._history = _history = [];
|
||
|
if (_history.indexOf(value) != -1)
|
||
|
_history.splice(_history.indexOf(value), 1);
|
||
|
_history.unshift(value);
|
||
|
if (_history.length > 20) _history.length = 20;
|
||
|
|
||
|
|
||
|
var pos = editor.getCursorPosition();
|
||
|
var ranges = [];
|
||
|
value.replace(/^:/, "").split(/,/).map(function(str) {
|
||
|
var parts = str.split(/([<>:+-]|c?\d+)|[^c\d<>:+-]+/).filter(Boolean);
|
||
|
var i = 0;
|
||
|
function readPosition() {
|
||
|
var c = parts[i++];
|
||
|
if (!c) return;
|
||
|
if (c[0] == "c") {
|
||
|
var index = parseInt(c.slice(1)) || 0;
|
||
|
return editor.session.doc.indexToPosition(index);
|
||
|
}
|
||
|
var row = pos.row;
|
||
|
var column = 0;
|
||
|
if (/\d/.test(c)) {
|
||
|
row = parseInt(c) - 1;
|
||
|
c = parts[i++];
|
||
|
}
|
||
|
if (c == ":") {
|
||
|
c = parts[i++];
|
||
|
if (/\d/.test(c)) {
|
||
|
column = parseInt(c) || 0;
|
||
|
}
|
||
|
}
|
||
|
return {row: row, column: column};
|
||
|
}
|
||
|
pos = readPosition();
|
||
|
var range = Range.fromPoints(pos, pos);
|
||
|
if (parts[i] == ">") {
|
||
|
i++;
|
||
|
range.end = readPosition();
|
||
|
}
|
||
|
else if (parts[i] == "<") {
|
||
|
i++;
|
||
|
range.start = readPosition();
|
||
|
}
|
||
|
ranges.unshift(range);
|
||
|
});
|
||
|
editor.selection.fromJSON(ranges);
|
||
|
var scrollTop = editor.renderer.scrollTop;
|
||
|
editor.renderer.scrollSelectionIntoView(
|
||
|
editor.selection.anchor,
|
||
|
editor.selection.cursor,
|
||
|
0.5
|
||
|
);
|
||
|
editor.renderer.animateScrolling(scrollTop);
|
||
|
},
|
||
|
history: function() {
|
||
|
var undoManager = editor.session.getUndoManager();
|
||
|
if (!prompt.gotoLine._history)
|
||
|
return [];
|
||
|
return prompt.gotoLine._history;
|
||
|
|
||
|
},
|
||
|
getCompletions: function(cmdLine) {
|
||
|
var value = cmdLine.getValue();
|
||
|
var m = value.replace(/^:/, "").split(":");
|
||
|
var row = Math.min(parseInt(m[0]) || 1, editor.session.getLength()) - 1;
|
||
|
var line = editor.session.getLine(row);
|
||
|
var current = value + " " + line;
|
||
|
return [current].concat(this.history());
|
||
|
},
|
||
|
$rules: {
|
||
|
start: [{
|
||
|
regex: /\d+/,
|
||
|
token: "string"
|
||
|
}, {
|
||
|
regex: /[:,><+\-c]/,
|
||
|
token: "keyword"
|
||
|
}]
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
prompt.commands = function(editor, callback) {
|
||
|
function normalizeName(name) {
|
||
|
return (name || "").replace(/^./, function(x) {
|
||
|
return x.toUpperCase(x);
|
||
|
}).replace(/[a-z][A-Z]/g, function(x) {
|
||
|
return x[0] + " " + x[1].toLowerCase(x);
|
||
|
});
|
||
|
}
|
||
|
function getEditorCommandsByName(excludeCommands) {
|
||
|
var commandsByName = [];
|
||
|
var commandMap = {};
|
||
|
editor.keyBinding.$handlers.forEach(function(handler) {
|
||
|
var platform = handler.platform;
|
||
|
var cbn = handler.byName;
|
||
|
for (var i in cbn) {
|
||
|
var key;
|
||
|
if (cbn[i].bindKey && cbn[i].bindKey[platform] !== null) {
|
||
|
key = cbn[i].bindKey["win"];
|
||
|
} else {
|
||
|
key = "";
|
||
|
}
|
||
|
|
||
|
var commands = cbn[i];
|
||
|
var description = commands.description || normalizeName(commands.name);
|
||
|
if (!Array.isArray(commands))
|
||
|
commands = [commands];
|
||
|
commands.forEach(function(command) {
|
||
|
if (typeof command != "string")
|
||
|
command = command.name;
|
||
|
var needle = excludeCommands.find(function(el) {
|
||
|
return el === command;
|
||
|
});
|
||
|
if (!needle) {
|
||
|
if (commandMap[command]) {
|
||
|
commandMap[command].key += "|" + key;
|
||
|
} else {
|
||
|
commandMap[command] = {key: key, command: command, description: description};
|
||
|
commandsByName.push(commandMap[command]);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
return commandsByName;
|
||
|
}
|
||
|
// exclude commands that can not be executed without args
|
||
|
var excludeCommandsList = ["insertstring", "inserttext", "setIndentation", "paste"];
|
||
|
var shortcutsArray = getEditorCommandsByName(excludeCommandsList);
|
||
|
shortcutsArray = shortcutsArray.map(function(item) {
|
||
|
return {value: item.description, meta: item.key, command: item.command};
|
||
|
});
|
||
|
prompt(editor, "", {
|
||
|
name: "commands",
|
||
|
selection: [0, Number.MAX_VALUE],
|
||
|
maxHistoryCount: 5,
|
||
|
onAccept: function(data) {
|
||
|
if (data.item) {
|
||
|
var commandName = data.item.command;
|
||
|
this.addToHistory(data.item);
|
||
|
|
||
|
editor.execCommand(commandName);
|
||
|
}
|
||
|
},
|
||
|
addToHistory: function(item) {
|
||
|
var history = this.history();
|
||
|
history.unshift(item);
|
||
|
delete item.message;
|
||
|
for (var i = 1; i < history.length; i++) {
|
||
|
if (history[i]["command"] == item.command ) {
|
||
|
history.splice(i, 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (this.maxHistoryCount > 0 && history.length > this.maxHistoryCount) {
|
||
|
history.splice(history.length - 1, 1);
|
||
|
}
|
||
|
prompt.commands.history = history;
|
||
|
},
|
||
|
history: function() {
|
||
|
return prompt.commands.history || [];
|
||
|
},
|
||
|
getPrefix: function(cmdLine) {
|
||
|
var currentPos = cmdLine.getCursorPosition();
|
||
|
var filterValue = cmdLine.getValue();
|
||
|
return filterValue.substring(0, currentPos.column);
|
||
|
},
|
||
|
getCompletions: function(cmdLine) {
|
||
|
function getFilteredCompletions(commands, prefix) {
|
||
|
var resultCommands = JSON.parse(JSON.stringify(commands));
|
||
|
|
||
|
var filtered = new FilteredList(resultCommands);
|
||
|
return filtered.filterCompletions(resultCommands, prefix);
|
||
|
}
|
||
|
|
||
|
function getUniqueCommandList(commands, usedCommands) {
|
||
|
if (!usedCommands || !usedCommands.length) {
|
||
|
return commands;
|
||
|
}
|
||
|
var excludeCommands = [];
|
||
|
usedCommands.forEach(function(item) {
|
||
|
excludeCommands.push(item.command);
|
||
|
});
|
||
|
|
||
|
var resultCommands = [];
|
||
|
|
||
|
commands.forEach(function(item) {
|
||
|
if (excludeCommands.indexOf(item.command) === -1) {
|
||
|
resultCommands.push(item);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return resultCommands;
|
||
|
}
|
||
|
|
||
|
var prefix = this.getPrefix(cmdLine);
|
||
|
var recentlyUsedCommands = getFilteredCompletions(this.history(), prefix);
|
||
|
var otherCommands = getUniqueCommandList(shortcutsArray, recentlyUsedCommands);
|
||
|
otherCommands = getFilteredCompletions(otherCommands, prefix);
|
||
|
|
||
|
if (recentlyUsedCommands.length && otherCommands.length) {
|
||
|
recentlyUsedCommands[0]["message"] = " Recently used";
|
||
|
otherCommands[0]["message"] = " Other commands";
|
||
|
}
|
||
|
|
||
|
var completions = recentlyUsedCommands.concat(otherCommands);
|
||
|
return completions.length > 0 ? completions : [{
|
||
|
value: "No matching commands",
|
||
|
error: 1
|
||
|
}];
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
prompt.modes = function(editor, callback) {
|
||
|
var modesArray = modelist.modes;
|
||
|
modesArray = modesArray.map(function(item) {
|
||
|
return {value: item.caption, mode: item.name};
|
||
|
});
|
||
|
prompt(editor, "", {
|
||
|
name: "modes",
|
||
|
selection: [0, Number.MAX_VALUE],
|
||
|
onAccept: function(data) {
|
||
|
if (data.item) {
|
||
|
var modeName = "ace/mode/" + data.item.mode;
|
||
|
editor.session.setMode(modeName);
|
||
|
}
|
||
|
},
|
||
|
getPrefix: function(cmdLine) {
|
||
|
var currentPos = cmdLine.getCursorPosition();
|
||
|
var filterValue = cmdLine.getValue();
|
||
|
return filterValue.substring(0, currentPos.column);
|
||
|
},
|
||
|
getCompletions: function(cmdLine) {
|
||
|
function getFilteredCompletions(modes, prefix) {
|
||
|
var resultCommands = JSON.parse(JSON.stringify(modes));
|
||
|
|
||
|
var filtered = new FilteredList(resultCommands);
|
||
|
return filtered.filterCompletions(resultCommands, prefix);
|
||
|
}
|
||
|
|
||
|
var prefix = this.getPrefix(cmdLine);
|
||
|
var completions = getFilteredCompletions(modesArray, prefix);
|
||
|
return completions.length > 0 ? completions : [{
|
||
|
"caption": "No mode matching",
|
||
|
"value": "No mode matching",
|
||
|
"error": 1
|
||
|
}];
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
dom.importCssString(".ace_prompt_container {\
|
||
|
max-width: 600px;\
|
||
|
width: 100%;\
|
||
|
margin: 20px auto;\
|
||
|
padding: 3px;\
|
||
|
background: white;\
|
||
|
border-radius: 2px;\
|
||
|
box-shadow: 0px 2px 3px 0px #555;\
|
||
|
}");
|
||
|
|
||
|
|
||
|
exports.prompt = prompt;
|
||
|
|
||
|
});
|