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) для проекта "Снегопат"
+////
+//// Описание: Реализует простейшие инструменты рефакторинга.
+//// Автор: Александр Кунташов <kuntashov@gmail.com>, 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<a.length; i++)
+            a[i] = StringUtils.addSlashes(a[i]);
+            
+        var re = new RegExp(a.join(".*?"), 'i');    
+        
+        if (this.form.DoNotFilter)
+        {
+            var currentRow = undefined;
+            
+            var methList = this.originalMethodList.Copy();
+            for (var rowNo = 0; rowNo < methList.Count(); rowNo++)
+            {
+                var row = methList.Get(rowNo);
+                if (re.test(row.Name))
+                {
+                    currentRow = row;
+                    break;
+                }
+            }
+            
+            this.form.Controls.MethodList.Value = methList;
+            if (currentRow)
+                this.form.Controls.MethodList.CurrentRow = currentRow;
+        }
+        else
+        {
+            var methList = this.form.Controls.MethodList.Value;
+            methList.Clear();    
+            for (var rowNo = 0; rowNo < this.originalMethodList.Count(); rowNo++)
+            {
+                var row = this.originalMethodList.Get(rowNo);
+                if (re.test(row.Name))
+                    FillPropertyValues(methList.Add(), row);
+            }
+        }
+    }
+    
+    this.sortMethodList(this.form.SortByName);
+}
+
+MethodListForm.prototype.sortMethodList = function (sortByName) {
+    this.form.MethodList.Sort(sortByName ? 'Name' : 'StartLine');
+}
+
+MethodListForm.prototype.saveSettings = function () {
+    this.settings.ReadFromForm(this.form);
+    this.settings.SaveSettings();
+}
+
+////} MethodListForm
+
+////////////////////////////////////////////////////////////////////////////////////////
+////{ CreateMethodStubRefactoring
+////
+
+function CreateMethodStubRefactoring(module) {
+
+    this.module = module;
+    this.textWindow = this.module.textWindow;
+}
+
+CreateMethodStubRefactoring.prototype.refactor = function (selectedText) {
+    
+    var methodName, methodSignature, matches;
+    
+    methodName = this.textWindow.GetWordUnderCursor();
+    if (!methodName)
+        return;
+    
+    var method_call_proc = new RegExp("(?:;\\s*|^\\s*)" + methodName + '(\\(.+?\\))');
+    var method_call_func = new RegExp(methodName + "(\\(.*?\\))");
+    
+    var line = this.textWindow.GetLine(this.textWindow.GetCaretPos().beginRow);
+    
+    var matches = line.match(method_call_proc);
+    var isProc = (matches != null);
+    
+    if (!isProc)
+    {
+        matches = line.match(method_call_func);
+        if (!matches)
+            return;
+    }
+    
+    methodSignature = methodName + matches[1];
+    
+    var procTemplate = "\n"  
+    + "Процедура ИмяМетода()\n"
+    + "\t//TODO: Добавьте исходный код процедуры.\n" 
+    + "КонецПроцедуры\n";
+
+    var funcTemplate = "\n" 
+    + "Функция ИмяМетода()\n"
+    + "\t//TODO: Добавьте исходный код функции.\n" 
+    + "\tВозврат Неопределено;\n"
+    + "КонецФункции\n";
+    
+    var stubCode = isProc ? procTemplate : funcTemplate;
+    stubCode = stubCode.replace('ИмяМетода()', methodSignature);
+    
+    var methodList = new MethodListForm(this.module);
+    if (methodList.selectMethod())
+    {
+        var insertLineIndex = methodList.SelectedMethod.EndLine + 1;
+        this.textWindow.InsertLine(insertLineIndex + 1, stubCode);
+        this.textWindow.SetCaretPos(insertLineIndex + 3, 1);
+    }
+}
+
+////} CreateMethodRefactoring
+
+////////////////////////////////////////////////////////////////////////////////////////
+////{ ExtractMethodRefactoring
+////
+
+function ExtractMethodRefactoring(module) {
+    this.module = module;
+    this.form = loadScriptForm(SelfScript.fullPath.replace(/\.js$/, '.extractMethod.ssf'), this);    
+    this.Params = this.form.Params;
+    this.ReturnValue = this.form.ReturnValue;
+    this.SignaturePreview = this.form.SignaturePreview;
+}
+
+ExtractMethodRefactoring.prototype.getVarRe = function (varName) {
+    return new RegExp("([^\\w\\dА-я\.]|^)" + varName + "([^\\w\\dА-я]|$)", 'i');
+}
+
+ExtractMethodRefactoring.prototype.refactor = function (selectedText) {
+
+    var sel = this.module.textWindow.GetSelection();
+
+    // 0. Определить переменные внутри выделенного блока кода (распарсить его).
+    var extContext = this.getCodeContext(selectedText);
+    var extVars = extContext.AutomaticVars;
+
+    // 1. Определить локальные переменные части кода метода выше выделяемого кода.
+    // 2. Определить параметры метода, из которого выделяется код.
+    var curMethod = this.module.getActiveLineMethod();
+    
+    var codeBefore = this.module.textWindow.Range(curMethod.StartLine, 1, sel.beginRow-1).GetText();
+    var contextBefore = this.getCodeContext(codeBefore);
+
+    // 3. Определить, какие 1+2 инициализируются в 0 (AutomaticVars), а какие используются 
+    this.fillParams(contextBefore.AutomaticVars, extVars, selectedText);    
+    this.fillParams(curMethod.Params, extVars, selectedText);    
+        
+    // 4. Те переменные, которые используются в остальной части кода - возвращаемые значения.
+    var codeAfter = this.module.textWindow.Range(sel.endRow + 1, 1, curMethod.EndLine).GetText();
+    var contextAfter = this.getCodeContext(codeAfter);
+    
+    this.fillReturnValues(contextAfter.AutomaticVars, extVars, codeAfter);
+        
+    if (this.form.DoModal())
+        this.extractMethod(selectedText);
+}
+
+ExtractMethodRefactoring.prototype.fillParams = function (extArgs, extVars, source) {
+    for (var i=0; i<extArgs.length; i++)
+    {
+        var varName = extArgs[i];
+        var re = this.getVarRe(varName);
+        if (re.test(source) && extVars.indexOf(varName) == -1)
+            this.addParam(varName, true, false);
+    }
+}
+
+ExtractMethodRefactoring.prototype.fillReturnValues = function (extArgs, extVars, source) {
+    //debugger;
+    for (var i=0; i<extVars.length; i++)
+    {
+        var varName = extVars[i];
+        var re = this.getVarRe(varName);
+        if (re.test(source) && extArgs.indexOf(varName) == -1)
+            this.addReturnValue(varName);
+    }
+}
+
+ExtractMethodRefactoring.prototype.getCodeContext = function (code) {
+    var extractedCode = "Процедура ВыделенныйМетод()\n" + code + "\nКонецПроцедуры";
+    var extractedContext = SyntaxAnalysis.AnalyseModule(extractedCode, false);
+    return extractedContext.getMethodByName("ВыделенныйМетод");
+}
+
+ExtractMethodRefactoring.prototype.addParam = function (paramName, isParam, isVal) {
+    if (!this.Params.Find(paramName, 'Name'))
+    {
+        var paramRow = this.Params.Add();
+        paramRow.Name = paramName;
+        paramRow.isParam = isParam ? true : false;
+        paramRow.isVal = isVal ? true : false;
+    }
+}
+
+ExtractMethodRefactoring.prototype.addReturnValue = function (varName) {
+    if (!this.ReturnValue.Find(varName, 'Name'))
+    {
+        var row = this.ReturnValue.Add();
+        row.Name = varName;
+    }
+}
+
+ExtractMethodRefactoring.prototype.BtOKClick = function (Control) {
+
+    if (!this.form.Name.match(/^[_\wА-я](?:[_\w\dА-я]*)$/))
+    {
+        DoMessageBox("Имя метода должно быть правильным идентификатором!");
+        return;
+    }    
+    
+    this.form.Close(true);
+}
+
+ExtractMethodRefactoring.prototype.BtCancelClick = function (Control) {
+    this.form.Close(false);
+}
+
+ExtractMethodRefactoring.prototype.extractMethod = function(source) {
+
+    var tw = this.module.textWindow;
+    var sel = tw.GetSelection();
+
+    var params = new Array;
+    for (var i=0; i<this.Params.Count(); i++)
+    {
+        var paramRow = this.Params.Get(i);
+        if (paramRow.IsParam)
+            params.push((paramRow.IsVal ? 'Знач ' : '') + paramRow.Name);
+    }
+    
+    // Откорректируем отступ.
+    var srcIndent = StringUtils.getIndent(source);
+    source = StringUtils.shiftLeft(source, srcIndent);
+    source = StringUtils.shiftRight(source, "\t");
+    
+    // Сформируем исходный код определения выделенного метода.
+    var newMethod = this.form.IsProc ? 'Процедура' : 'Функция';
+    newMethod += ' ' + this.form.Name + '(' + params.join(', ') + ')';
+    if (this.form.Exported)
+        newMethod += " Экспорт";   
+        
+    newMethod += "\n\n" + this.prepareSource(source) + "\n\n";  
+    
+    if (this.form.IsProc) 
+    {
+        newMethod += 'КонецПроцедуры';        
+        
+    }
+    else
+    {
+        var retVal = "Неопределено";
+        if (this.ReturnValue.Count() > 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