Кому интересно: слегка переписанный AutocompleteObject.lua
[more=тык]--[[--------------------------------------------------
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[/more]
[more=тык]--[[--------------------------------------------------
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[/more]