TymurGubayev
Junior Member | Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору --[[-------------------------------------------------- AutocompleteObject.lua mozers™ version 2.03 revised edition by Tymur, 26.11.08 ------------------------------------------------------ Ввод разделителя, заданного в autocomplete.[lexer].start.characters вызывает список свойств и медодов объекта из соответствующего api файла На данный момент нет: Ввод пробела или разделителя изменяют регистр символов в имени объекта в соответствии с записью в api файле (например "ucase" при вводе автоматически заменяется на "UCase") Внимание: В скрипте используется функция IsComment (обязательно подключение COMMON.lua) props["APIPath"] доступно только в SciTE-Ru ------------------------------------------------------ Inputting of the symbol set in autocomplete.[lexer].start.characters causes the popup list of properties and methods of input_object. They undertake from corresponding api-file. In the same case inputting of a space or a separator changes the case of symbols in input_object's name according to a api-file. (for example "ucase" is automatically replaced on "UCase".) Warning: This script needed function IsComment (COMMON.lua) props["APIPath"] available only in SciTE-Ru ------------------------------------------------------ Подключение: В файл SciTEStartup.lua добавьте строку: dofile (props["SciteDefaultHome"].."\\tools\\AutocompleteObject.lua") задайте в файле .properties соответствующего языка символ, после ввода которого, будет включатся автодополнение: autocomplete.lua.start.characters=.: ------------------------------------------------------ Connection: In file SciTEStartup.lua add a line: dofile (props["SciteDefaultHome"].."\\tools\\AutocompleteObject.lua") Set in a file .properties: autocomplete.lua.start.characters=.: ------------------------------------------------------ Для понимания алгоритма работы скрипта, условимся, что в записи azimuth:left;list-style-|type:upper-roman где курсор стоит в позиции, отмеченной знаком "|", часть list-style - будет называться "объект" type - будет называться "метод" - - один из разделителей. Все вышесказанное относится ко всем языкам программирования (css тут - только для примера) Скрипт будет корректно работать только с "правильными" api файлами (см. описание формата в ActiveX.api) ------------------------------------------------------ Совет: Если после ввода разделителя список свойств и методов не возник (хотя они описаны в api файле) то, возможно, скрипт не смог распознать имя вашего объекта. Помогите ему в этом, дописав такую строчку в редактируемый документ: mydoc = document где mydoc - имя Вашего объекта document - имя этого же объекта, заданное в api файле ------------------------------------------------------ На что не хватило терпения: 1. Объединить функции CreateObjectsTable и CreateAliasTable в одну (чтобы обрабатывать api файлы за один проход) (сделано) 2. Сделать вызов функций постоения таблиц более редким (сейчас они строются постоянно после ввода символа-разделителя) (сделано) 3. Провести ревизию всех регулярных выражений (больше всего ошибок происходит из за них). Возможно паттерны стоит формировать динамически (сейчас функция fPattern не используется). (теперь fPattern один раз используется) ----------------- Tymur: * переписал таблицы objects_table, alias_table из массивов строк в таблицы со строковыми ключами * попутно выполнил задачи 1 и 2 из списка выше + добавил фичу для луа: распознавание строковых переменных в коде (ассоциируются с таблицей string) + добавил в тестовом режиме слегка изменённый режим распознавания имён объектов. См. new_delim_behavior * FindDeclaration() ищет определения в соответствии с языком: допустимые символы берутся из $(word.characters.$(file.patterns.LANGUAGE))$(autocomplete.LANGUAGE.start.characters) * в целом скрипт должен работать быстрее (особенно после первого вызова) за счёт объединения выполнения старых CreateObjectsTable() и CreateAliasTable() за один проход, и только по необходимости (get_api == true) Проверено только для Луа, пока что работает --]]---------------------------------------------------- local current_pos = 0 -- текущая позиция курсора, важно для InsertMethod local sep_char = '' -- введенный с клавиатуры символ (в нашем случае - один из разделителей ".:-") local autocom_chars = '' -- паттерн, содержащий экранированные символы из параметра autocomplete.lexer.start.characters local get_api = true -- флаг, определяющий необходимость перезагрузки api файла local api_table = {} -- все строки api файла (очищенные от ненужной нам информации) local objects_table = {} -- все "объекты", найденные в api файле в виде objects_table[objname]=true local alias_table = {} -- сопоставления "синоним = объект" local methods_table = {} -- все "методы" заданного "объекта", найденные в api файле local object_names = {} -- все имена имеющихся api файле "объектов", которым соответствует найденный в текущем файле "объект" -- Tymur: local is_Lua = false -- в Луа автоматически распознаются объекты как строки, если в файле есть строка вида obj = "". Трогать этот параметр не надо, он сам local new_delim_behavior = true -- вкл./выкл. мою попытку сделать распознавание объектов с разделителями. -- Т.е. если в .api-файле есть строчка вида "socket.dns.gethostname()", то в файле при вводе точки после "socket.dns" появится список с методом "gethostname". -- внимание: есть два варианта, один дубовый, но надёжный, другой менее дубовый, но почему-то сбоит. По умолчанию включен второй. См. функцию GetInputObject. local new_delim_behavior_better_buggy = false -- переключатель. Если true, то используется метод, который может не работать из-за (предположительно) бага SciTE. ------------------------------------------------------ -- Тест для распечатки содержимого заданной таблицы local function prnTable(name) print('> ________________') for i = 1, #name do print(name[i]) end print('> ^^^^^^^^^^^^^^^') end -- Преобразовывает стринг в паттерн для поиска --[[local function fPattern(str) local str_out = '' for i = 1, string.len(str) do str_out = '%'..string.sub(str, i, i+1) end return str_out end]] -- Долго медитировал над вышеприведенным кодом... Может, имелось в виду так: local function fPattern(str) -- паттерн для ловли управляющих паттернами символов Луа: local lua_patt_chars = "[%(%)%.%+%-%*%?%[%]%^%$]" -- return str:gsub('.','%%%0') -- можно конечно и так, но заэскейпить всё подряд - некошерно. return str:gsub(lua_patt_chars,'%%%0') end -- Сортирует таблицу по алфавиту и удаляет дубликаты local function TableSort(table_name) table.sort(table_name, function(a, b) return string.upper(a) < string.upper(b) end) -- remove duplicates for i = #table_name-1, 0, -1 do if table_name[i] == table_name[i+1] then table.remove (table_name, i+1) end end return table_name end ------------------------------------------------------ -- Извлечение из api-файла реальных имен объектов, которые соответствуют введенному -- т.е. введен "объект" wShort, а методы будем искать для WshShortcut и WshURLShortcut local function GetObjectNames(text) local obj_names = {} -- Поиск по таблице имен "объектов" if objects_table[text] then obj_names[#obj_names+1] = text return obj_names -- если успешен, то завершаем поиск end -- Поиск по таблице сопоставлений "объект - синоним" if alias_table[text] then for k,_ in pairs(alias_table[text]) do obj_names[#obj_names+1] = k end end -- prnTable(obj_names) return obj_names end --================================================================ local GetInputObject -- дальше будет if-block, так что для правильной области видимости декларируем тут. if not new_delim_behavior then -- Старый метод: -- всю работу делает editor:WordStartPosition(pos) в сооответствии со своим стандартным поведением: -- ищет ближайший слева от pos символ НЕ из "word.characters.$(file.patterns.LANGUAGE) -- декларация на сей раз без local, чтобы переписать переменную, декларированную чуть выше function GetInputObject() return editor:textrange(editor:WordStartPosition(current_pos-1),current_pos-1) end else -- новый метод работы: -- Извлечение из текущего файла имени объекта, с которым "работаем": -- движемся по словам влево от курсора по не закончаться разделители (для Луа: ".:") -- т.о. корректно распознаётся для aa.bb.cc ввод точки после aa.bb -- Реализовано через танцы с бубном, чтобы в других местах не считалась за переменную фигня вроде "..bumsbums" if new_delim_behavior_better_buggy then -- local здесь НЕЛЬЗЯ, ибо внутри if function GetInputObject(delimiters) -- это менее дубовый вариант. -- нужный нам набор символов вытаскиваем из соотв. настройки local word_sett = "word.characters.$(file.patterns."..editor.LexerLanguage..")" -- чтобы не потерять local tmp = props[word_sett] -- @todo: Вообще-то, эти две переменные нужно перегружать только при api_get == true, но это будет в финальном релизе "нового" метода, если оно кому надо. -- добавляем разделители -- это теперь тоже часть слова props[word_sett] = props[word_sett]..(delimiters or "") -- пусть за нас сделает всю работу editor:WordStartPosition local word_start_pos = editor:WordStartPosition(current_pos-1) -- возвращаем настройки назад props[word_sett] = tmp return editor:textrange(word_start_pos,current_pos-1) end -- GetInputObject, менее дубовый, более глючный вариант. else -- better not so buggy -- альтернативный, тот самый более дубовый вариант. -- надёжен на все 100. Я надеюсь. function GetInputObject(delimiters) local delimiters = delimiters or '' local word_start_pos = current_pos -- Делаем до упора: while true do -- получаем начало текущего слова word_start_pos = editor:WordStartPosition(word_start_pos-1) -- смотрим на один символ левее local word_start_char = editor:textrange(word_start_pos-1,word_start_pos) -- Символ есть в delimiters?.. if string.find(delimiters, word_start_char, 1, 1) then -- да: ещё шаг влево, повторить сначала. word_start_pos = word_start_pos -1 else -- нет: закончили -- print(word_start_pos, current_pos) return editor:textrange(word_start_pos,current_pos-1) end end -- while true end --func GetInputObject, более дубовый, зато надёжный вариант. end -- if изящный, но багнутый метод. end -- if new_delim_behavior --================================================================ -- adds alias to the "global" alias_table local function AddAlias(obj, alias) -- если впервые такое слово, создаём таблицу alias_table[obj] = alias_table[obj] or {} -- добавляем синоним alias к объекту obj alias_table[obj][alias] = true end --func AddAlias -- Поиск деклараций присвоения пользовательской переменной реального объекта -- т.е. в текущем файле ищем конструкции вида "синоним = объект" local function FindDeclaration() local text_all = editor:GetText() local _start, _end, sVar, sRightString -- берём то, что хранится в, например, word.characters.$(file.patterns.lua) local wordpatt = '['..props["word.characters.$(file.patterns."..editor.LexerLanguage..")"]..autocom_chars..']+' -- @todo: правую часть также хорошо бы слегка поправить. local pattern = '('..wordpatt..')%s*=%s*(%C+)' _start = 1 while true do _start, _end, sVar, sRightString = string.find(text_all, pattern, _start) if _start == nil then break end if sRightString ~= '' then -- анализируем текст справа от знака "=" --@todo: Анализ на данный момент _очень_ грубый -- Не строка ли это? if is_Lua and (sRightString:match([[^%s*".*"]]) or sRightString:match([[^%s*'.*']]) or sRightString:match("^%s*%[%[.*%]%]")) then AddAlias(sVar, 'string') -- (проверяем, объект ли там содержится) else for sValue in string.gmatch(sRightString, wordpatt) do -- print('sValue = "'..sValue..'"') local objects = GetObjectNames(sValue) for i = 1, #objects do if objects[i] ~= '' then -- print(objects[i]) -- если действительно, такой "объект" существует, то добавляем его в таблицу сопоставлений "объект - синоним" AddAlias(sVar, objects[i]) break end end end end -- if is_Lua end _start = _end + 1 end end -- Чтение api файла в таблицу api_table (чтобы потом не опрашивать диск, а все тащить из нее) local function CreateAPITable() api_table = {} for api_filename in string.gmatch(props["APIPath"], "[^;]+") do if api_filename ~= '' then local api_file = io.open(api_filename) if api_file then for line in api_file:lines() do -- обрезаем комментарии line = line:match('^[^%s%(]+') if line ~= '' then api_table[#api_table+1] = line end end api_file:close() else api_table = {} end end end get_api = false return false end -- Создание таблицы, содержащей все имена "объектов" описанных в api файле -- Создание таблицы, содержащей все сопоставления "#синоним = объект" описанные в api файле local function CreateObjectsAndAliasTables() objects_table = {} alias_table = {} for i = 1, #api_table do local line = api_table[i] -- здесь КРАЙНЕ ВАЖНО, чтобы в матче был именно [autocom_chars], т.е. например "[.:]" для Луа -- т.к. эта таблица строится только при api_get, может выйти фигня. local obj_name = line:match('^([^#]+)['..autocom_chars..']') if obj_name then objects_table[obj_name]=true end -- для строк вида "#a=b" записываем a,b поочерёдно в таблицу алиасов local sVar, sValue = line:match('^#(%w+)=([^%s]+)$') --@todo: подумать над паттерном... if sVar then AddAlias(sValue, sVar) end end -- for k in pairs(objects_table) do print(k) end end -- Создание таблицы "методов" заданного "объекта" local function CreateMethodsTable(obj) for i = 1, #api_table do local line = api_table[i] -- ищем строки, которые начинаются с заданного "объекта" local _, _end = string.find(line, obj..sep_char, 1, 1) if _end ~= nil then -- ^%[ нужно для Луа, в api-файле может быть строчка вида: t["a-b+c"]\nэто очень хитрый параметр -- для стандартных библиотек неважно. local _start, _end, str_method = string.find(line, '([^%s%.%:%[%-]+)', _end) if _start ~= nil then methods_table[#methods_table+1] = str_method end end end end -- Показываем раскрывающийся список "методов" local function ShowUserList() -- prnTable(methods_table) if #methods_table == 0 then return false end local s = table.concat(methods_table, " ") if s == '' then return false end editor:UserListShow(7, s) return true end -- Вставляет выбранный из раскрывающегося списка метод в редактируемую строку local function InsertMethod(str) -- первый параметр в тексте утанавливается лишь однажды editor:SetSel(current_pos, editor.CurrentPos) editor:ReplaceSel(str) end -- ОСНОВНАЯ ПРОЦЕДУРА (обрабатываем нажатия на клавиши) local function AutocompleteObject(char) if IsComment(editor.CurrentPos-2) then return false end -- Если строка закомментирована, то выходим local autocomplete_start_characters = props["autocomplete."..editor.LexerLanguage..".start.characters"] -- Если введенного символа нет в параметре autocomplete.lexer.start.characters, то выходим if autocomplete_start_characters == '' then return false end if string.find(autocomplete_start_characters, char, 1, 1) == nil then return false end -- Наконец то мы поняли что введенный символ - именно тот разделитель! sep_char = char autocom_chars = fPattern(autocomplete_start_characters) -- а не в Луа ли мы часом? is_Lua = (editor.LexerLanguage == 'lua') if get_api then -- print('get_api = true') CreateAPITable() CreateObjectsAndAliasTables() end -- если в api_table пусто - выходим. if not next(api_table) then return false end FindDeclaration() -- prnTable(objects_table) -- prnTable(alias_table) -- Важно: запоминаем текщую позицию курсора (Иначе "string.b|[Enter]" превратиться в "string.bbyte") current_pos = editor.CurrentPos -- Берем в качестве объекта слово слева от курсора local input_object = GetInputObject(autocomplete_start_characters) -- local input_object = editor:textrange(editor:WordStartPosition(current_pos-1),current_pos) -- print('AutocompleteObject: input_object = ',input_object) -- Если слева от курсора отсутствует слово, которое можно истолковать как имя объекта, то выходим if input_object == '' then return '' end -- print(input_object) object_names = GetObjectNames(input_object) if not next(object_names) then return false end -- prnTable(object_names) -- убиваем остатки старых методов, заполняем новыми methods_table = {} for i = 1, #object_names do CreateMethodsTable(object_names[i]) end methods_table = TableSort(methods_table) -- prnTable(methods_table) return ShowUserList() end ------------------------------------------------------ -- Add user event handler OnChar local old_OnChar = OnChar function OnChar(char) local result if old_OnChar then result = old_OnChar(char) end if props['macro-recording'] ~= '1' and AutocompleteObject(char) then return true end return result end -- Add user event handler OnUserListSelection local old_OnUserListSelection = OnUserListSelection function OnUserListSelection(tp,sel_value) local result if old_OnUserListSelection then result = old_OnUserListSelection(tp,sel_value) end if tp == 7 then if InsertMethod(sel_value) then return true end end return result end -- Add user event handler OnSwitchFile local old_OnSwitchFile = OnSwitchFile function OnSwitchFile(file) local result if old_OnSwitchFile then result = old_OnSwitchFile(file) end get_api = true return result end -- Add user event handler OnOpen local old_OnOpen = OnOpen function OnOpen(file) local result if old_OnOpen then result = old_OnOpen(file) end get_api = true return result end -- Add user event handler OnBeforeSave local old_OnBeforeSave = OnBeforeSave function OnBeforeSave(file) local result if old_OnBeforeSave then result = old_OnBeforeSave(file) end get_api = true return result end |