Используем Eclipse Monkey чтобы сделать Flex удобнее
7 октября 2008
Новый AS3-класс во Flex я создаю следующим образом:
- выбираю во Flex Navigator каталог (пакет) где нужно создать новый класс;
- нажимаю Alt+1 -- появляется диалоговое окошко с единственным полем ввода: "Полное имя класса". При этом выбранный пакет уже подставлен в поле, нужно только дописать имя класса (или отредактировать имена пакетов, если нужно создать новые);
- Жму "ОК";
- Создается новый файл (и каталоги, если надо), в файл подставляется мой шаблон класса (заранее подготовленный и подгружаемый из другого файла), в шаблон подставляется имя файла и имя пакета. Файл открывается в редакторе. Готово.
Вступление
Всякий, кто после Eclipse + FDT пересаживался на Flex испытывал некоторые неудобства. Очень не хватает такой важной штуки, как шаблоны кода. И мириться с этим недоразумением нельзя, нужно искать средство, чтобы помочь беде.
Опыт работы с линуксом, хоть и не особо долгий, научил меня некоторым очень полезным штукам. Первое: правильный инструмент, это такой инструмент, который можно гибко настроить под себя и неограниченно расширять его функционал. Редактор Vim тому яркий пример. Второе: если тебе что-то нужно, сделай это сам.
Исходя из того, что Eclipse -- это правильный инструмент (а Flex, это расширение Eclipse, и значит он не может загубить всю его "правильность"), мы можем расчитывать, что средства для его расширения имеются. Нужно их найти.
Eclipse Monkey
Кто не хочет заморачиваться и интересуется только готовым решением, может пропустить эту часть, сразу читать про установку, копировать мой скрипт и использовать его. Однако есть смысл заморочиться. Ибо во-первых, возможности Eclipse Monkey довольно велики, и стоит хотя бы получить о них общее преставление; во-вторых, информации по этому плагину маловато, а документации вообще нет, и поэтому желательно знать, где можно поискать эту информацию.
Тем, кто намерен использовать Eclipse Monkey, желательно пройти тем же путем, что шел я. А начал я с блога 33 коровы, где, как мне помнилось, была статья о расширениях для Flex. Там я узнал про Eclipse Monkey и пошел по линку на сайт Кости Ковалева, на статью о применении Eclipse Monkey для генерации геттеров-сеттеров. Пример хороший, и уже дает кое-что полезное. Костя предложил заглянуть на сайт aptana.com, где есть кое-какие решения и даже зачатки документации (единственный толковый раздел -- про метаданные). А уже после этого я погуглил, и попал на официальную страницу проекта: wiki.eclipse.org/Eclipse_Monkey_Overview. Там полезной информации оказалось еще меньше, и выяснились две крайне неприятные вещи. Во-первых, проект мертв, по нему уже год никто не работает. Во-вторых, ссылка на официальную документацию не работает, и ее, эту документацию, нигде нельзя добыть (если кто-то имеет эту документацию или знает, где ее можно добыть, сообщите плз).
Еще один нюанс касательно Flex -- Eclipse Monkey не работает с MXML-редактором, только с AS3-редактором.
Итак, в изучении Eclipse Monkey можно опираться на примеры скриптов (несколько их устанавливаются вместе с плагином, некоторые можно найти в инете) и на документацию по Eclipse API.
Ах, да, я же не сказал, что это такое, Eclipse Monkey. Это плагин к Eclipse который позволяет писать к нему расширения на языке JavaScript (поддерживаются также Ruby, Python и Groove). Мощь этого плагина в том, что из скриптового языка мы имеем доступ к Eclipse API и ко многим классам Java. А значит мы можем работать с редакторами, видами, workspaces, проектами и т.д. -- ко всему тому, что доступно в Eclipse разработчикам его плагинов.
Установка
Выполняется непосредственно из Eclipse (точно также и из Flex). Help -> Software Updates -> Find and Install, добавляем Remote Site: http://download.eclipse.org/technology/dash/update/, выбираем и устанавливаем Eclipse Monkey.
В результате получаем папку с примерами скриптов, новый пункт в меню для запуска скриптов, и новый View для той же цели.
Используем
На самом простом уровне можно получить выделенный текст из активного редактора. Обработать его по своему усмотрению и вставить обратно в редактор. Именно так и сделан пример с генерацией геттеров-сеттеров. И некие аналоги шаблонов кода из FDT уже можно реализовать этими средствами.
Это неплохо, и из этого уже можно извлечь немало пользы. Но мне захотелось большего. Захотелось генерировать AS3-классы из собственного шаблона. А для этого нужно:
- узнать, какой каталог выделен во Flex Navigator;
- сгенерировать полное имя пакета, соответствующее этому каталогу;
- запросить у пользователя полное имя класса, предложив сгенерированное имя пакета;
- распарсить полное имя класса, введенное пользователем (оно не обязательно совпадет с предложенным);
- создать каталоги для новых пакетов, если такие есть, создать новый файл;
- открыть новый файл в AS3-редакторе;
- прочитать шаблон класса из внешнего файла;
- подставить в шаблон имя класса и пакета, записать шаблон в редактор;
- раскрыть по Flex Navigator путь к новому файлу;
Все это удалось сделать, хотя стоило оно немалых трудов. Главной сложностью было отсутствие документации, из-за чего приходилось опираться не на Eclipse Monkey API, которое было совершенно неизвестным, а на Eclipse API, которое доступно в справке самого Eclipse, раздел Platform Plug-in Developer Guide -> Programmer`s Guide (этой справки нет во Flex).
Ну и некоторые примеры, нарытые в инете, тоже немало помогли.
Отдельный вопрос то, что некоторые нужные объекты (Flex Navigator и AS3 Editor) являются частью Flex, а не Eclipse, что создает дополнительные сложности. В частности, для получения доступа к этим объектам нужно знать их ID. ID Flex Navigator я нашел, копаясь в xml-файлах в каталогах Flex, ID AS3 Editor случайно нашел в инете.
Но труды эти не пропали даром, ибо появилось понимание, как все устроено в Eclipse и как с этим работать.
Аналогичную задачу я делал в Vim под Linux, средствами VimScript и консольными скриптами. Там это было намного проще.
Monkey-скрипт для генерации AS3-классов
/*
* Menu: Create AS3 Class
* Key: M3+1
* Kudos: Zhloba Yuri http://yzh44yzh.com
* License: Public Domain
* DOM: http://download.eclipse.org/technology/dash/update/org.eclipse.eclipsemonkey.lang.javascript
*/
var flexNavigatorView = window.getActivePage().findView('com.adobe.flexbuilder.navigator');
var workspace = Packages.org.eclipse.core.resources.ResourcesPlugin.getWorkspace();
var project = workspace.getRoot().getProject("yourProjectName"); //TODO - must be some way to get current project
var srcDir = 'src';
var classTemplate = '/your/absolute/path/to/template/AS3ClassTemplate.as';
function main()
{
// get selected package from Flex Navigator
var currentPackage = GetCurrentPackage();
// ask user about full class name
var fullClassName = AskFullClassName(currentPackage);
if(fullClassName == '') return; // no class name given -- abort script
// check is file exists already
var newFilePath = srcDir + '/' + fullClassName.replace(/\./g, '/') + '.as';
var newFile = project.getFile(newFilePath);
if(newFile.exists())
{
// we don`t want to rewrite existing file
alert('File is already exists:\n' + newFile.getFullPath());
return;
}
CreateDirs(newFilePath);
// get class template and write it to file
var inputStream = new java.io.FileInputStream(classTemplate);
newFile.create(inputStream, false, null);
// open new file in editor
window.getActivePage().openEditor(
new Packages.org.eclipse.ui.part.FileEditorInput(newFile),
"com.adobe.flexbuilder.editors.actionscript.ActionScriptEditor");
// parse template in editor
var editor = editors.activeEditor;
var source = editor.source;
var source = ParseTemplate(fullClassName, editor.source);
editor.applyEdit(0, editor.source.length, source);
ExpandTree(newFilePath);
}
function GetCurrentPackage()
{
var path = '';
var pathInSrcRoot = false;
var curItem = flexNavigatorView.getViewer().getControl().getSelection()[0];
if(curItem)
{
var packageName = curItem.getText();
// check is it valid package (directory) or just a file
if(!packageName.match(/\./)) path = packageName + '.';
else { /* this is a file, skip it */ }
while(parentItem = curItem.getParentItem())
{
packageName = parentItem.getText();
if(packageName == srcDir)
{
pathInSrcRoot = true;
break;
}
path = packageName + '.' + path;
curItem = parentItem;
}
}
if(pathInSrcRoot) return path;
return '';
}
function AskFullClassName(path)
{
dialog = new Packages.org.eclipse.jface.dialogs.InputDialog(
window.getShell(),
'Create Class',
'Full class name:',
path, null);
result = dialog.open();
if(result == Packages.org.eclipse.jface.window.Window.OK)
return '' + dialog.getValue();
return '';
}
function CreateDirs(newFilePath)
{
// check and create directories
var dirs = newFilePath.split('/');
var path = dirs[0];
for(var i = 1; i < dirs.length - 1; i++)
{
path += '/' + dirs[i];
var nextFolder = project.getFolder(path);
if(!nextFolder.exists()) nextFolder.create(false, false, null);
}
}
//TODO - does not see new created directories
//and does not expand them
function ExpandTree(newFilePath)
{
// find node for srcDir in tree view
var tree = flexNavigatorView.getViewer().getControl();
var items = tree.getItem(0).getItems();
var dirs = newFilePath.split('/');
for(var i = 0; i < dirs.length - 1; i++)
{
// expand folders in Flex Navigator
for(var j = 0; j < items.length; j++)
{
var item = items[j];
if(item.getText() == dirs[i])
{
item.setExpanded(true);
items = item.getItems();
break;
}
}
}
}
function ParseTemplate(fullClassName, content)
{
names = fullClassName.split('.');
var className = names.pop();
var packageName = names.join('.');
content = content.replace(/\{packageName\}/g, packageName);
content = content.replace(/\{className\}/g, className);
return content;
}
// trace msg to Console View
function trace(msg) { out.println(msg); }
Пока здесь есть пара слабых мест (помеченных TODO). Во-первых, нежелательно явно указывать имя проекта, а нужно как-то получать ссылку на текущий проект. Но я пока не знаю, как это можно сделать. Во-вторых, путь к файлу во Flex Navigator раскрывается только если не создавались новые каталоги. Иначе это раскрытие обрывается на таком каталоге. Видимо, дерево каталогов в навигаторе просто не успевает обновится к моменту обхода.
Сам шаблон класса должен лежать где-нибудь во внешнем файле и нужно указать полный путь к этому файлу (причем использовать прямой слэш "/", а не обратный "\"). Шаблон вы можете написать любой по своему вкусу. У меня такой:
/**
* @author Yura
* TODO - don`t forget to write class description here
*/
package {packageName}
{
public class {className}
{
// constants
// properties
// constructor
public function {className}()
{
}
// getters & setters
// methods
public function toString():String { return '{className}'; }
}
}
Еще одну приятную мелочь я сделал скриптом. Известно, что в Eclipse переключение видов делается по Ctrl+F7. Но это не всегда удобно, потому что хочется сразу попасть на нужный вид (во Flex Navigator, или в Outline, или еще куда-нибудь) одним нажатием, а не перебором видов по Ctrl+F7. И это тоже можно сделать через Alt-Shift-Q,X. Но и это не очень удобно -- комбинация слишком громоздкая и не для всех View есть варианты. Ну я сделал скриптик, который по Alt-2 передает фокус на Flex Navigator. Просто и очень удобно.
/*
* Menu: Focus Flex Navigator
* Key: M3+2
*/
function main()
{
window.getActivePage().showView("com.adobe.flexbuilder.navigator");
}