Index: Libs/SyntaxAnalysis.js ================================================================== --- Libs/SyntaxAnalysis.js +++ Libs/SyntaxAnalysis.js @@ -269,11 +269,11 @@ return this.textWindow.Range(method.StartLine + 1, 1, method.EndLine + 1).GetText(); } /* Возвращает таблицу значений с описаниями методов модуля. */ _1CModule.prototype.getMethodsTable = function() { - return this.context._vtAllMethods; + return this.context._vtAllMethods.Copy(); } /* Возвращает описание метода по номеру строки, находящейся внутри метода. */ _1CModule.prototype.getMethodByLineNumber = function (lineNo) { Index: Tests/Automated/SyntaxAnalysis/testSyntaxAnalysis.js ================================================================== --- Tests/Automated/SyntaxAnalysis/testSyntaxAnalysis.js +++ Tests/Automated/SyntaxAnalysis/testSyntaxAnalysis.js @@ -15,11 +15,10 @@ function tearDown() { } //} setUp/tearDown //{ tests of AnalyseModule - function macrosTestAnalyseModule1() { var moduleText = "" + "Перем мПеременнаяМодуля;\n\n" + "Перем ЕщеОднаПеременная;\n" @@ -237,11 +236,11 @@ // Локальные переменные процедуры. assertArrayEqualsIgnoringOrder(['МояПерем1'], proc.DeclaredVars); assertArrayEqualsIgnoringOrder(['АвтоматическаяПеременная'], proc.AutomaticVars); -} +} function macrosTestAnalyseModule9_ОпределениеМетодаНаРазныхСтроках() { var moduleText = "" + "Процедура \n" + " Проверки ( Перем1,Перем2, Перем3)\n" @@ -251,11 +250,23 @@ assertEquals('Неправильно определено количество методов!', 1, cnt.Methods.length); assertEquals('Неправильно определено количество переменных модуля!', 0, cnt.ModuleVars.length); } -function macrosTestAnalyseModule10_ОпределениеПараметровМетодаНаРазныхСтроках() { +function macrosTestAnalyseModule10_ОпределениеМетодаНаРазныхСтроках2() { + var moduleText = "" + + "Процедура Проверки ( Перем1, \n" + + " Перем2, Перем3)\n" + + "КонецПроцедуры" + + var cnt = SyntaxAnalysis.AnalyseModule(moduleText); + + assertEquals('Неправильно определено количество методов!', 1, cnt.Methods.length); + assertEquals('Неправильно определено количество переменных модуля!', 0, cnt.ModuleVars.length); +} + +function macrosTestAnalyseModule11_ОпределениеПараметровМетодаНаРазныхСтроках() { var moduleText = "" + "Процедура Проверки ( Перем1, \n" + " Перем2, Перем3)\n" + "КонецПроцедуры" //debugger @@ -266,11 +277,11 @@ var proc = cnt.getMethodByName('Проверки'); assertNotNull("Метод Проверки не найден", proc); assertArrayEqualsIgnoringOrder(['Перем1', 'Перем2', 'Перем3'], proc.Params); } -function macrosTestAnalyseModule11_ОпределениеПеременныхМетодаПоУмолчанию() { +function macrosTestAnalyseModule12_ОпределениеПеременныхМетодаПоУмолчанию() { var moduleText = "" + "Процедура Проверки (Знач Парам1, Парам2 = Ложь)\n" + "КонецПроцедуры" //debugger var cnt = SyntaxAnalysis.AnalyseModule(moduleText); @@ -281,11 +292,11 @@ assertNotNull("Метод Проверки не найден", proc); assertArrayEqualsIgnoringOrder(['Парам1', 'Парам2'], proc.Params); } -function macrosTestAnalyseModule12_ОпределениеКонеткстаКомпиляции() { +function macrosTestAnalyseModule13_ОпределениеКонеткстаКомпиляции() { var moduleText = "" + "&НаКлиенте\n" + "Процедура Проверки (Знач Парам1, Парам2 = Ложь)\n" + "КонецПроцедуры" var cnt = SyntaxAnalysis.AnalyseModule(moduleText); @@ -296,11 +307,11 @@ assertNotNull("Метод Проверки не найден", proc); assertEquals("Конетекст компиляции не обнаружен", "НаКлиенте", proc.Context) assertArrayEqualsIgnoringOrder(['Парам1', 'Парам2'], proc.Params); } -function macrosTestAnalyseModule13_ОпределениеПараметровМетодаНаРазныхСтрокахСКомментариями() { +function macrosTestAnalyseModule14_ОпределениеПараметровМетодаНаРазныхСтрокахСКомментариями() { var moduleText = "" + "Процедура Проверки ( Перем1, //Текстовый комментарий перемменной, да и такое может быть. \n" + " Перем2, Перем3)\n" + "КонецПроцедуры" debugger @@ -311,11 +322,11 @@ var proc = cnt.getMethodByName('Проверки'); assertNotNull("Метод Проверки не найден", proc); assertArrayEqualsIgnoringOrder(['Перем1', 'Перем2', 'Перем3'], proc.Params); } -function macrosTestAnalyseModule14_ОпределениеПараметровМетодаНаРазныхСтрокахСКомментариямиИСкобками() { +function macrosTestAnalyseModule15_ОпределениеПараметровМетодаНаРазныхСтрокахСКомментариямиИСкобками() { var moduleText = "" + "Процедура Проверки ( Перем1, //Текстовый комментарий перемменной, да и такое может быть. \n" + " Перем2, // Любой текст и ссылка на процедуру или функцию МояПроцедура()\n" + " Перем3)\n" + "КонецПроцедуры" @@ -326,10 +337,9 @@ assertEquals('Неправильно определено количество переменных модуля!', 0, cnt.ModuleVars.length); var proc = cnt.getMethodByName('Проверки'); assertNotNull("Метод Проверки не найден", proc); assertArrayEqualsIgnoringOrder(['Перем1', 'Перем2', 'Перем3'], proc.Params); } - //} tests of AnalyseModule ADDED refactoring.extractMethod.ssf Index: refactoring.extractMethod.ssf ================================================================== --- refactoring.extractMethod.ssf +++ refactoring.extractMethod.ssf cannot compute difference between binary files ADDED refactoring.js Index: refactoring.js ================================================================== --- refactoring.js +++ refactoring.js @@ -0,0 +1,523 @@ +$engine JScript +$uname Refactoring +$dname Рефакторинг +$addin global +$addin stdlib +$addin vbs + +//////////////////////////////////////////////////////////////////////////////////////// +////{ Cкрипт "Рефакторинг" (refactoring.js) для проекта "Снегопат" +//// +//// Описание: Реализует простейшие инструменты рефакторинга. +//// Автор: Александр Кунташов , http://compaud.ru/blog +////} +//////////////////////////////////////////////////////////////////////////////////////// + +stdlib.require('TextWindow.js', SelfScript); +stdlib.require('SettingsManagement.js', SelfScript); + +stdlib.require('SyntaxAnalysis.js', SelfScript); +//Для отладки: stdlib.require(profileRoot.getValue("Snegopat/MainFolder") + 'user\\Libs\\SyntaxAnalysis.js', SelfScript); + +global.connectGlobals(SelfScript); + +//////////////////////////////////////////////////////////////////////////////////////// +////{ Макросы +//// + +SelfScript.self['macrosВыделить метод (extract method)'] = function () { + refactor(ExtractMethodRefactoring); +} + +SelfScript.self['macrosПоказать список процедур и функций модуля'] = function () { + var tw = GetTextWindow(); + if (!tw) return; + var module = SyntaxAnalysis.AnalyseTextDocument(tw); + var methList = new MethodListForm(module); + if (methList.selectMethod()) + Message(methList.SelectedMethod.Name); +} + +SelfScript.self['macrosСоздать заглушку для несуществующего метода'] = function () { + refactor(CreateMethodStubRefactoring, true); +} + +////} Макросы + +function refactor(refactorerClass, withoutSelection) { + + var tw = GetTextWindow(); + if (!tw) return; + + var selText = tw.GetSelectedText(); + if (!selText && !withoutSelection) + { + Message("Не выделен текст, к которому применяется рефакторинг!"); + return; + } + + var module = SyntaxAnalysis.AnalyseTextDocument(tw); + var refactorer = new refactorerClass(module); + refactorer.refactor(selText); +} + +//////////////////////////////////////////////////////////////////////////////////////// +////{ MethodListForm +//// + +function MethodListForm(module) { + + this.module = module; + this.originalMethodList = module.getMethodsTable(); + + this.form = loadScriptForm(SelfScript.fullPath.replace(/\.js$/, '.methodList.ssf'), this); + this.SelectedMethod = undefined; + + this.settings = SettingsManagement.CreateManager(SelfScript.uniqueName + "/MethodListForm", { + 'DoNotFilter': false, 'SortByName' : false + }); + + this.settings.LoadSettings(); + + var methListForm = this; + this.tcWatcher = new TextChangesWatcher(this.form.Controls.SearchText, 3, function(t){methListForm.fillMethodList(t)}); + + this.icons = { + 'Proc': this.form.Controls.picProc.Picture, + 'Func': this.form.Controls.picFunc.Picture + } + + this.fillMethodList(); +} + +MethodListForm.prototype.selectMethod = function () { + this.SelectedMethod = this.form.DoModal(); + return this.SelectedMethod ? true : false; +} + +MethodListForm.prototype.MethodListSelection = function (Control, SelectedRow, Column, DefaultHandler) { + this.form.Close(SelectedRow.val._method); +} + +MethodListForm.prototype.MethodListOnRowOutput = function (Control, RowAppearance, RowData) { + var nameCell = RowAppearance.val.Cells.Name; + nameCell.SetPicture( RowData.val.IsProc ? this.icons.Proc : this.icons.Func); +} + +MethodListForm.prototype.CmdBarSortByName = function (button) { + button.val.Check = !button.val.Check; + this.form.SortByName = button.val.Check; + this.sortMethodList(button.val.Check); +} + +MethodListForm.prototype.CmdBarDoNotFilter = function (button) { + button.val.Check = !button.val.Check; + this.form.DoNotFilter = button.val.Check; + this.fillMethodList(this.form.SearchText); +} + +MethodListForm.prototype.CmdBarMainОК = function (Кнопка) { + var SelectedRow = this.form.Controls.MethodList.CurrentRow; + if (SelectedRow) + this.form.Close(SelectedRow._method); + else + this.form.CurrentControl = this.form.Controls.SearchText; +} + +MethodListForm.prototype.OnOpen = function () { + + this.settings.ApplyToForm(this.form); + + this.form.Controls.CmdBar.Buttons.SortByName.Check = this.form.SortByName; + this.form.Controls.CmdBar.Buttons.DoNotFilter.Check = this.form.DoNotFilter; + + this.loadedOnOpen = true; + this.tcWatcher.start(); +} + +MethodListForm.prototype.BeforeClose = function (Cancel, StandardHandler) { + this.tcWatcher.stop(); + this.saveSettings(); +} + +MethodListForm.prototype.fillMethodList = function (newText) { + + if (!newText || newText.match(/^\s*$/)) + { + if (this.loadedOnOpen) + this.loadedOnOpen = false; + else + this.form.Controls.MethodList.Value = this.originalMethodList.Copy(); + } + else + { + var a = newText.split(/\s+/); + for (var i=0; i 0) { + retVal = this.ReturnValue.Get(0).Name; + } + newMethod += "\tВозврат " + retVal + ";"; + newMethod += "\n\n" + 'КонецФункции'; + } + + // Получим метод, внутри которого мы находимся. + var curMethod = this.module.getActiveLineMethod(); + + // Добавим в модуль определение выделенного метода. + tw.InsertLine(curMethod.EndLine + 2, "\n" + newMethod); + + // Заменим выделенный код на вызов нового метода. + var methCall = this.form.Name + '(' + params.join(', ') + ");\n"; + + if (!this.form.IsProc && this.ReturnValue.Count() > 0) { + retVal = this.ReturnValue.Get(0).Name; + methCall = retVal + ' = ' + methCall; + } + + tw.SetSelection(sel.beginRow, sel.beginCol, sel.endRow, sel.endCol); + tw.SetSelectedText(srcIndent + methCall); +} + +ExtractMethodRefactoring.prototype.prepareSource = function(source) { + + var lines = StringUtils.toLines(source); + if (lines.length < 2) + return source; + + var startIndex = 0; + while (startIndex < lines.length && lines[startIndex].match(/^\s*$/)) + startIndex++; + + var endIndex = lines.length - 1; + while (endIndex > 0 && lines[endIndex].match(/^\s*$/)) + endIndex--; + + if (startIndex <= endIndex) + return StringUtils.fromLines(lines.splice(startIndex, endIndex - startIndex + 1)); + + return source; +} + +////} ExtractMethodRefactoring + +//////////////////////////////////////////////////////////////////////////////////////// +////{ TextChangesWatcher (Александр Орефков) +//// + +// Класс для отслеживания изменения текста в поле ввода, для замены +// события АвтоПодборТекста. Штатное событие плохо тем, что не возникает +// - при установке пустого текста +// - при изменении текста путем вставки/вырезания из/в буфера обмена +// - при отмене редактирования (Ctrl+Z) +// не позволяет регулировать задержку +// Параметры конструктора +// field - элемент управления поле ввода, чье изменение хотим отслеживать +// ticks - величина задержки после ввода текста в десятых секунды (т.е. 3 - 300 мсек) +// invoker - функция обратного вызова, вызывается после окончания изменения текста, +// новый текст передается параметром функции +function TextChangesWatcher(field, ticks, invoker) +{ + this.ticks = ticks + this.invoker = invoker + this.field = field +} + +// Начать отслеживание изменения текста +TextChangesWatcher.prototype.start = function() +{ + this.lastText = this.field.Значение.replace(/^\s*|\s*$/g, '').toLowerCase() + this.noChangesTicks = 0 + this.timerID = createTimer(100, this, "onTimer") +} +// Остановить отслеживание изменения текста +TextChangesWatcher.prototype.stop = function() +{ + killTimer(this.timerID) +} +// Обработчик события таймера +TextChangesWatcher.prototype.onTimer = function() +{ + // Получим текущий текст из поля ввода + vbs.var0 = this.field + vbs.DoExecute("var0.GetTextSelectionBounds var1, var2, var3, var4") + this.field.УстановитьГраницыВыделения(1, 1, 1, 10000) + var newText = this.field.ВыделенныйТекст.replace(/^\s*|\s*$/g, '').toLowerCase() + this.field.УстановитьГраницыВыделения(vbs.var1, vbs.var2, vbs.var3, vbs.var4) + // Проверим, изменился ли текст по сравению с прошлым разом + if(newText != this.lastText) + { + // изменился, запомним его + this.lastText = newText + this.noChangesTicks = 0 + } + else + { + // Текст не изменился. Если мы еще не сигнализировали об этом, то увеличим счетчик тиков + if(this.noChangesTicks <= this.ticks) + { + if(++this.noChangesTicks > this.ticks) // Достигли заданного количества тиков. + this.invoker(newText) // Отрапортуем + } + } +} + +//// +////} TextChangesWatcher (Александр Орефков) +//////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////// +////{ Вспомогательные функции +//// + + + +////} Вспомогательные функции ADDED refactoring.methodList.ssf Index: refactoring.methodList.ssf ================================================================== --- refactoring.methodList.ssf +++ refactoring.methodList.ssf cannot compute difference between binary files