c9-core/plugins/c9.ide.language.core/refactor.js

306 wiersze
11 KiB
JavaScript

/**
* Cloud9 Language Foundation
*
* @copyright 2013, Ajax.org B.V.
*/
define(function(require, exports, module) {
main.consumes = [
"Plugin", "tabManager", "language", "ui", "menus", "dialog.question",
"ace", "language.marker", "language", "commands",
"language.complete"
];
main.provides = ["language.refactor"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var plugin = new Plugin("Ajax.org", main.consumes);
var language = imports.language;
var menus = imports.menus;
var commands = imports.commands;
var complete = imports["language.complete"];
var ui = imports.ui;
var tabs = imports.tabManager;
var aceHandle = imports.ace;
var marker = imports["language.marker"];
var question = imports["dialog.question"];
var completeUtil = require("plugins/c9.ide.language/complete_util");
var PlaceHolder = require("ace/placeholder").PlaceHolder;
var Range = require("ace/range").Range;
var worker;
var mnuRename;
var mnuRename2;
var placeHolder;
var oldCommandKey;
var oldIdentifier;
var lastAce;
var isGenericRefactor;
var oldContinuousCompletion;
var selectVar;
var loaded;
function load() {
if (loaded)
return;
loaded = true;
language.getWorker(function(err, langWorker) {
if (err)
return console.error(err);
worker = langWorker;
worker.on("refactoringsResult", function(event) {
enableRefactorings(event);
});
worker.on("renamePositionsResult", function(event) {
if (!tabs.focussedTab || !tabs.focussedTab.editor || tabs.focussedTab.editor.ace !== lastAce || !event.data)
return;
isGenericRefactor = event.data.isGeneric;
initRenameUI(event.data, lastAce);
worker.emit("onRenameBegin", { data: {}});
});
worker.on("commitRenameResult", function(event) {
var data = event.data;
if (data.err) {
question.show(
"Rename",
"Are you sure you would like to rename '" + data.oldName + "' to '" + data.newName + "'?",
data.err,
function() { // yes
cleanup();
},
function() { // no
cancelRename();
},
{
yes: "Rename",
no: "Cancel",
}
);
} else {
cleanup();
}
});
});
commands.addCommand({
name: "renameVar",
hint: "Rename refactor",
bindKey: { mac: "Option-Command-R", win: "Ctrl-Alt-R" },
exec: function(editor) {
selectVar = false;
beginRename(editor);
}
}, plugin);
commands.addCommand({
name: "selectVar",
hint: "select all instances of variable",
exec: function(editor) {
selectVar = true;
beginRename(editor);
}
}, plugin);
mnuRename = new ui.item({
disabled: true,
command: "renameVar",
caption: "Rename Variable"
});
mnuRename2 = new ui.item({
id: "mnuCtxEditorRename",
caption: "Rename",
command: "renameVar"
});
menus.addItemByPath("Tools/~", new ui.divider(), 10000, plugin);
menus.addItemByPath("Tools/Rename Variable", mnuRename, 2100, plugin);
// right click context item in ace
aceHandle.getElement("menu", function(menu) {
menus.addItemToMenu(menu, mnuRename2, 750, plugin);
menu.on("prop.visible", function(e) {
// only fire when visibility is set to true
if (e.value) {
// because of delays we'll disable by default
mnuRename2.disable();
var ace = tabs.focussedTab.editor.ace;
if (ace && worker)
worker.emit("refactorings", { data: ace.getSelection().getCursor() });
}
});
});
}
function enableRefactorings(event) {
var names = event.data;
var enabled = false;
for (var i = 0; i < names.length; i++) {
var name = names[i];
if (name === "renameVariable" || name === "rename") {
enabled = true;
}
}
if (enabled) {
mnuRename.enable();
mnuRename2.enable();
}
else {
mnuRename.disable();
mnuRename2.disable();
}
}
function beginRename(editor) {
var ace = lastAce = editor.ace;
ace.focus();
var curPos = ace.getCursorPosition();
var doc = ace.getSession().getDocument();
var line = doc.getLine(curPos.row);
oldIdentifier = getFullIdentifier(line, curPos, ace);
worker.emit("renamePositions", { data: curPos });
}
function initRenameUI(data, ace) {
var cursor = ace.getCursorPosition();
var mainPos = data.pos;
// Exclude the main position from others
var others = data.others.filter(function (o) {
return !(o.row === mainPos.row && o.column === mainPos.column);
});
if (selectVar) {
ace.selection.toSingleRange();
others.push(mainPos);
others.forEach(function(pos) {
ace.selection.addRange(new Range(
pos.row, pos.column, pos.row, pos.column + data.length
));
});
return;
}
// Temporarily disable these markers, to prevent weird slow-updating events whilst typing
marker.disableMarkerType('occurrence_main', ace);
marker.disableMarkerType('occurrence_other', ace);
if (placeHolder)
return;
placeHolder = new PlaceHolder(ace.session, data.length, mainPos, others, "language_rename_main", "language_rename_other");
if (cursor.row !== mainPos.row || cursor.column < mainPos.column || cursor.column > mainPos.column + data.length) {
// Cursor is not "inside" the main identifier, move it there
ace.moveCursorTo(mainPos.row, mainPos.column);
}
placeHolder.showOtherMarkers();
oldContinuousCompletion = language.isContinuousCompletionEnabled();
language.setContinuousCompletionEnabled(false);
// Monkey patch
if (!oldCommandKey) {
oldCommandKey = ace.keyBinding.onCommandKey;
ace.keyBinding.onCommandKey = onKeyPress;
}
placeHolder.on("cursorLeave", function() {
commitRename();
});
}
function commitRename() {
// Finished refactoring in editor
// -> continue with the worker giving the initial refactor cursor position
var doc = lastAce.getSession().getDocument();
var oPos = placeHolder.pos;
var line = doc.getLine(oPos.row);
var newIdentifier = getFullIdentifier(line, oPos, lastAce);
worker.emit("commitRename", { data: { oldId: oldIdentifier, newName: newIdentifier.value, isGeneric: isGenericRefactor }});
}
function cancelRename() {
if (placeHolder)
placeHolder.cancel();
worker.emit("onRenameCancel", { data: {}});
// todo: currently onRenameCancel in the worker doesn't do anything
// so we need to call cleanup here
cleanup();
}
function cleanup() {
if (placeHolder)
placeHolder.detach();
language.setContinuousCompletionEnabled(oldContinuousCompletion);
marker.enableMarkerType('occurrence_main');
marker.enableMarkerType('occurrence_other');
placeHolder = null;
oldIdentifier = null;
if (oldCommandKey) {
lastAce.keyBinding.onCommandKey = oldCommandKey;
oldCommandKey = null;
}
}
function onKeyPress(e, hashKey, keyCode) {
var keyBinding = lastAce.keyBinding;
var selection = placeHolder.session.selection;
var col = 0;
switch (keyCode) {
case 32: // Space can't be accepted as it will ruin the logic of retrieveFullIdentifier
if (e.ctrlKey || e.metaKey || e.altKey)
return oldCommandKey.apply(keyBinding, arguments);
col = selection.lead.column;
var start = placeHolder.pos.column;
var end = start + placeHolder.length;
if (col == start || col == end)
commitRename();
break;
case 27: // Esc
cancelRename();
e.preventDefault();
break;
case 13: // Enter
commitRename();
e.preventDefault();
break;
case 35: // End
col = placeHolder.length;
/* falls through */
case 36: // Home
var row = selection.lead.row;
col += placeHolder.pos.column;
if (e.shiftKey)
selection.selectTo(row, col);
else
selection.moveTo(row, col);
break;
default:
return oldCommandKey.apply(keyBinding, arguments);
}
e.preventDefault();
e.stopPropagation();
}
function getFullIdentifier(line, pos, ace) {
var regex = complete.getIdentifierRegex(null, ace);
var prefix = completeUtil.retrievePrecedingIdentifier(line, pos.column, regex);
var postfix = completeUtil.retrieveFollowingIdentifier(line, pos.column, regex);
return {
column: pos.column - prefix.length,
row: pos.row,
value: prefix + postfix
};
}
plugin.on("load", function() {
load();
});
register(null, {
"language.refactor": plugin
});
}
});