$engine JScript
$uname scriptdev
$dname Разработка скриптов
$addin global
$addin stdlib
$addin stdcommands
$addin snegopatwnd
/* Скрипт-помощник для разработчиков скриптов для Снегопата.
* Автор : Александр Кунташов, kuntashov@compaud.ru
* Дата создания: 18.08.2011
* Описание:
* - Добавляет в контекстное меню окна Снегопата пункт "Редактировать скрипт",
* при выборе которого исходный код скрипта открывается в установленном в настройках
* текстовом редакторе.
*
* - Отслеживает изменения в открытом на редактирование файле скрипта и автоматически
* перезагружает его, таким образом, изменения, внесенные в скрипт вступают сразу без
* необходимости перезагрузки скрипта.
*/
stdlib.require("jshint.js", SelfScript);
stdlib.require("TextWindow.js", SelfScript);
stdlib.require("SelectValueDialog.js", SelfScript);
/* **********************************************************
* Настройки скрипта по умолчанию.
* ********************************************************* */
// Интервал проверки редактируемых файлов.
var checkInterval = 2;
// Спрашивать перед перезагрузкой.
var askBeforeReload = true;
// Команда для запуска редактора скрипта.
var runEditorCmd = "notepad.exe \"%1\"";
/* **********************************************************
* Макросы.
* ********************************************************* */
function macrosНастройка()
{
var pathToForm = SelfScript.fullPath.replace(/js$/, 'ssf')
// Обработку событий формы привяжем к самому скрипту
form = loadScriptForm(pathToForm, SelfScript.self);
form.checkInterval = checkInterval;
form.askBeforeReload = askBeforeReload;
form.runEditorCmd = runEditorCmd;
form.ОткрытьМодально();
form = null;
}
function macrosСформироватьКодОбработчиковФормыНаJavaScript() {
var w = stdlib.require('TextWindow.js').GetTextWindow();
if (!w) return;
// Если есть выделение - используем его, иначе конвертируем весь модуль.
var codeToConvert = w.GetSelectedText();
if (codeToConvert == "")
codeToConvert = w.GetText();
var className = snegopat.parseTemplateString('<?"Укажите имя класса (если не задано - создаются функции скрипта)">');
var jsCode = codeToConvert;
var rep = className ? className + ".prototype.$1 = function ($2) {" : "function $1 ($2) {";
jsCode = jsCode.replace(/Процедура\s+([\wА-я\d_]+)\s*\((.*?)\)/ig, rep);
jsCode = jsCode.replace(/КонецПроцедуры/ig, "}");
stdcommands.Frntend.ClearMessageWindow.send(); // Очистить окно сообщений.
Message(jsCode);
return true;
}
function macrosПерезагрузитьТекущийСкрипт() {
var w = stdlib.require('TextWindow.js').GetTextWindow();
if (!w) return;
var view = w.GetView();
if (!view) return;
var doc = view.getDocument();
if (doc.isModified || doc.path.match(/^\s*$/)) {
var answer = (new QueryDialogEx("Скрипт был изменен и перед [пере]загрузкой будет записан. Продолжить?")).Show();
if (answer == QueryDialogEx.ReturnCodes.No) {
return;
}
stdcommands.Frame.FileSave.send();
}
var fullpath = 'script:' + doc.path.replace(/^file:\/\//i, '').replace(/\//g, '\\');
var addinGroup = addins.byUniqueName('SnegopatMainScript').object.AddinsTreeGroups.UserAddins;
var scriptAddin = addins.byFullPath(fullpath);
if (scriptAddin) {
addinGroup = scriptAddin.group;
addins.unloadAddin(scriptAddin);
}
addins.loadAddin(fullpath, addinGroup);
Message("Скрипт " + fullpath + " перезагружен!");
}
function macrosСохранитьСкриптЗаменивПробелыНаТабуляцию() {
var w = stdlib.require('TextWindow.js').GetTextWindow();
if (!w) return;
var sel = w.GetCaretPos();
var teExt = stdlib.require(stdlib.getSnegopatMainFolder() + 'scripts\\textEditorExt.js');
stdcommands.Frame.SelectAll.send();
teExt.replaceTabsToSpacesInSelectedText(true);
stdcommands.Frame.FileSave.send();
w.SetCaretPos(sel.beginRow - 15, sel.beginCol);
w.SetCaretPos(sel.beginRow, sel.beginCol);
}
function macrosСохранитьСкриптЗаменитьТабуляциюПерезагрузить() {
macrosСохранитьСкриптЗаменивПробелыНаТабуляцию();
macrosПерезагрузитьТекущийСкрипт();
}
function macrosВставитьRequire() {
var tw = new TextWindow();
if (!tw.IsActive()) {
return;
}
var requires = {};
// Сформируем список файлов-библиотек из каталога Libs.
var re_js = /\.js$/i;
var fso = new ActiveXObject("Scripting.FileSystemObject");
var libsFolder = fso.GetFolder(stdlib.getSnegopatMainFolder() + 'scripts\\Libs');
var files = new Enumerator(libsFolder.Files);
for (; !files.atEnd(); files.moveNext()) {
if (re_js.test(files.item().Name)) {
requires[files.item().Name] = 'stdlib.require("' + files.item().Name + '", SelfScript);';
}
}
// Предложим пользователю выбрать библиотеку из списка.
var dlg = new SelectValueDialog("Выберите скрипт-библиотеку");
if (dlg.selectValue(requires)) {
tw.InsertLine(tw.GetCaretPos().beginRow, dlg.selectedValue);
tw.SetCaretPos(tw.GetCaretPos().beginRow, dlg.selectedValue.length + 1);
}
}
/* Возвращает название макроса по умолчанию - вызывается, когда пользователь
дважды щелкает мышью по названию скрипта в окне Снегопата. */
function getDefaultMacros() {
return 'Настройка';
}
/* **********************************************************
* Реализация функционала скрипта.
* ********************************************************* */
// Пути хранения настроек скрипта в профайле.
var pflPaths = {
checkInterval : 'scriptdev/checkInterval',
runEditorCmd: 'scriptdev/runCmdEditor',
askBeforeReload: 'scriptdev/askBeforeReload'
};
// Список редактируемых файлов скриптов.
var devFiles = v8New("Структура");
// Время последней проверки
var lastCheckTime = new Date().getTime() / 1000;
// Подключаем глобальные контексты.
//global.connectGlobals(SelfScript);
addins.byUniqueName("global").object.connectGlobals(SelfScript);
events.connect(Designer, "onIdle", SelfScript.self)
function onIdle()
{
var curTime = new Date().getTime() / 1000;
if (curTime - lastCheckTime > checkInterval)
{
CheckFiles();
}
}
function QueryDialogEx(question, needYesNoToAll)
{
this.question = question;
this.needYesNoToAll = needYesNoToAll;
}
QueryDialogEx.ReturnCodes = {
'Yes' : 'Да',
'YesToAll' : 'Да для всех',
'No' : 'Нет',
'NoToAll' : 'Нет для всех'
}
QueryDialogEx.prototype.Show = function()
{
// Кнопки диалога Вопрос.
var buttons = v8New("СписокЗначений");
buttons.Добавить(QueryDialogEx.ReturnCodes.Yes);
buttons.Добавить(QueryDialogEx.ReturnCodes.No);
if (this.needYesNoToAll)
{
buttons.Вставить(2, QueryDialogEx.ReturnCodes.YesToAll);
buttons.Добавить(QueryDialogEx.ReturnCodes.NoToAll);
}
return Вопрос(this.question, buttons);
}
function CheckFiles()
{
var scriptsToReload = new Array();
for(var addins=new Enumerator(devFiles); !addins.atEnd(); addins.moveNext())
{
var aInfo = addins.item().Значение;
if (aInfo.CheckIfModified())
scriptsToReload.push(aInfo);
}
var needToAsk = askBeforeReload;
var doReload = !askBeforeReload;
var needYesNoToAll = (scriptsToReload.length > 1);
for(var i=0; i<scriptsToReload.length; i++)
{
var s = scriptsToReload[i];
if (needToAsk)
{
var msg = "Скрипт \"" + s.object.displayName
+ "\" (" + s.object.uniqueName + ") был изменен.\n"
+ "Перезагрузить скрипт?";
var answer = (new QueryDialogEx(msg, needYesNoToAll)).Show();
doReload = (answer == QueryDialogEx.ReturnCodes.Yes || answer == QueryDialogEx.ReturnCodes.YesToAll);
needToAsk = (answer == QueryDialogEx.ReturnCodes.Yes || answer == QueryDialogEx.ReturnCodes.No);
}
if (doReload)
s.Reload();
}
}
function getModificationStamp(f)
{
if (f.Существует())
{
return new Date(f.ПолучитьВремяИзменения()).getTime();
}
return null;
}
function OnSnegopatWndEditScriptMenuItem(currentRow)
{
var isAddin = currentRow.Picture != 0;
if (!isAddin)
return;
var addinObject = currentRow.object;
if (0 != addinObject.fullPath.indexOf("script:"))
{
MessageBox("Это не скрипт");
return;
}
if (addinObject.uniqueName == SelfScript.uniqueName)
{
MessageBox("Скрипт \"Разработка скриптов\" нельзя использовать для отладки самого себя!");
return;
}
var fullPath = GetAddinFilePath(addinObject);
var command = runEditorCmd.replace(/%1/, fullPath);
if (!devFiles.Свойство(addinObject.uniqueName))
devFiles.Вставить(addinObject.uniqueName, new AddinInfo(addinObject));
ЗапуститьПриложение(command);
}
function InitScriptAndRun()
{
// Проинициализируем настройки скрипта...
profileRoot.createValue(pflPaths.checkInterval, checkInterval, pflSnegopat)
profileRoot.createValue(pflPaths.runEditorCmd, runEditorCmd, pflSnegopat)
profileRoot.createValue(pflPaths.askBeforeReload, askBeforeReload, pflSnegopat)
// ...и прочитаем их:
checkInterval = profileRoot.getValue(pflPaths.checkInterval);
runEditorCmd = profileRoot.getValue(pflPaths.runEditorCmd);
askBeforeReload = profileRoot.getValue(pflPaths.askBeforeReload);
// Внедряемся в контекстное меню окна Снегопата.
with (snegopatwnd.getSnegopatWnd())
AddContextMenuItem("Редактировать скрипт...", OnSnegopatWndEditScriptMenuItem,
"Редактировать файл скрипта в заданном редакторе, с авто-перезагрузкой");
}
function GetAddinFilePath(addinObject)
{
return addinObject.fullPath.replace(/^\w+:/,'');
}
/* **********************************************************
* Класс AddinInfo. Хранит информацию о редактируемом аддине.
* ********************************************************* */
function AddinInfo(addinObject)
{
this.object = addinObject;
this.file = v8New("Файл", GetAddinFilePath(addinObject));
this.lastModified = getModificationStamp(this.file);
}
AddinInfo.prototype.Reload = function()
{
var oldInstance = this.object;
var uniqueName = oldInstance.uniqueName;
var displayName = oldInstance.displayName;
var loadString = oldInstance.fullPath;
var addinGroup = oldInstance.group;
try
{
addins.unloadAddin(oldInstance);
}
catch(e)
{
Message("Ошибка при выгрузке аддина " + displayName + ": " + e.description);
}
if(!oldInstance.uniqueName.length) // аддин реально выгрузился
{
try
{
this.object = addins.loadAddin(loadString, addinGroup);
}
catch(e)
{
Message("Ошибка при загрузке: " + e.description);
}
}
delete oldInstance;
// Надо найти новый объект аддина по его имени и запомнить:
//this.object = addins.byUniqueName(uniqueName);
}
AddinInfo.prototype.CheckIfModified = function()
{
var curStamp = getModificationStamp(this.file);
if (curStamp != this.lastModified)
{
this.lastModified = curStamp;
return true;
}
return false;
}
/* **********************************************************
* Обработчики событий ЭУ формы настройки скрипта.
* ********************************************************* */
function ОКНажатие(Элемент)
{
if (checkInterval != form.checkInterval)
{
checkInterval = form.checkIterval;
profileRoot.setValue(pflPaths.checkIterval, checkInterval);
}
if (runEditorCmd != form.runEditorCmd)
{
runEditorCmd = form.runEditorCmd;
profileRoot.setValue(pflPaths.runEditorCmd, runEditorCmd);
}
if (askBeforeReload != form.askBeforeReload)
{
askBeforeReload = form.askBeforeReload;
profileRoot.setValue(pflPaths.askBeforeReload, askBeforeReload);
}
form.Закрыть();
}
function runEditorCmdНачалоВыбора(Элемент, СтандартнаяОбработка)
{
СтандартнаяОбработка.val = false;
var selDlg = v8New("ДиалогВыбораФайла", v8New("ПеречислениеРежимДиалогаВыбораФайла").Открытие);
selDlg.Заголовок = "Выберите исполняемый файл редактора/IDE";
selDlg.ПолноеИмяФайла = "";
selDlg.ПредварительныйПросмотр = false;
selDlg.Фильтр = "Исполняемые файлы (*.exe)|*.exe|Все файлы|*";
if (selDlg.Выбрать())
{
form.runEditorCmd = selDlg.ПолноеИмяФайла;
if (form.runEditorCmd.match(/exe$/))
form.runEditorCmd += ' "%1"';
}
}
function lbScriptAboutНажатие()
{
ЗапуститьПриложение("http://snegopat.ru/scripts/wiki?name=DevTools/scriptdev.js");
}
////////////////////////////////////////////////////////////////////////////////////////
//// Поддержка проверки синтаксиса скриптов (JSHint)
////
function macrosПроверитьСкрипт() {
var errCount = 0,
scr = new ScriptWindow();
if (!scr.IsScript()) {
stdcommands.Frntend.SyntaxCheck.send();
return;
}
if (!JSHINT(scr.GetCodeLines())) {
if (JSHINT.errors.length) {
var offset = scr.GetCodeStartLine();
for (var i=0; i<JSHINT.errors.length; i++) {
if (JSHINT.errors[i]) {
errCount++;
showError(scr, JSHINT.errors[i], offset);
}
}
}
}
if (errCount === 0) {
Message("Синтаксических ошибок не обнаружено!");
}
return true;
}
function macrosПоказатьСписокМетодовСкрипта() {
var errCount = 0,
scr = new ScriptWindow(),
data = null,
vlFuncs = v8New('ValueList');
if (!scr.IsScript()) {
snegopat.showMethodsList();
return;
}
JSHINT(scr.GetCodeLines());
JSHINT.data().functions.forEach(function(func) {
vlFuncs.Add(func, func.name.replace(/^\"(.+?)\"$/, "$1"));
});
var dlg = new SelectValueDialog("Выберите метод", vlFuncs);
if (dlg.selectValue()) {
scr.SetCaretPos(dlg.selectedValue.line + scr.GetCodeStartLine(), 1);
}
return true;
}
ScriptWindow = TextWindow.extend({
construct: function() {
this._super();
},
IsScript: function() {
if (this.IsActive()) {
var view = this.GetView();
if (view) {
var doc = view.getDocument();
if (doc) {
if (doc.path.match(/\.(js|vbs)$/i)) {
return true;
}
}
}
var lines = this.GetLines();
return lines.length && lines[0].match(/^\$/);
}
return false;
},
GetCodeStartLine: function() {
var start = 0,
lines = this.GetLines();
while (start < lines.length) {
if (!lines[start].match(/^\$/) &&
!lines[start].match(/^\s*$/)) {
break;
}
start++;
}
return start;
},
GetCodeLines : function() {
return this.GetLines().slice(this.GetCodeStartLine());
}
});
function showError(tw, e, offset) {
var evidence = '';
/* Формат вывода ошибки как в 1С:
{Где}: Что
часть строки с ошибкой<<?>>
<<?>> - маркер позиции, в котором произошла ошибка.
*/
if (e.evidence) {
evidence = "\n" + e.evidence;
}
Message("{" + e.line + "} " + e.reason + evidence, mExc2,
function (o) {o.w.SetCaretPos(o.r, o.c);},
{w:tw, r:e.line + offset, c:e.character}
);
}
////
//// Поддержка проверки синтаксиса скриптов (JSHint)
////////////////////////////////////////////////////////////////////////////////////////
/* **********************************************************
* Инициализация скрипта.
* ********************************************************* */
InitScriptAndRun();
function macrostestThrow()
{
throw "exception"
}