Skip to content

Instantly share code, notes, and snippets.

@Lexikos
Last active March 28, 2023 19:55
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Lexikos/bc17b8d55ae8a8102e35 to your computer and use it in GitHub Desktop.
Save Lexikos/bc17b8d55ae8a8102e35 to your computer and use it in GitHub Desktop.
Improved auto-complete for SciTE
-- AutoComplete v0.8 by Lexikos
--[[
Tested on SciTE4AutoHotkey 3.0.06.01; may also work on SciTE 3.1.0 or later.
To use this script with SciTE4AutoHotkey:
- Place this file in your SciTE user settings folder.
- Add the following to UserLuaScript.lua:
dofile(props['SciteUserHome'].."/AutoComplete.lua")
- Restart SciTE.
]]
-- List of styles per lexer that autocomplete should not occur within.
local IGNORE_STYLES = { -- Should include comments, strings and errors.
[SCLEX_AHK1] = {1,2,3,6,20},
[SCLEX_AHK2] = {1,2,3,5,15},
[SCLEX_LUA] = {1,2,3,6,7,8,12}
}
local INCREMENTAL = true
local IGNORE_CASE = true
local CASE_CORRECT = true
local CASE_CORRECT_INSTANT = false
local WRAP_ARROW_KEYS = false
local CHOOSE_SINGLE = props["autocomplete.choose.single"]
-- Number of chars to type before the autocomplete list appears:
local MIN_PREFIX_LEN = 2
-- Length of shortest word to add to the autocomplete list:
local MIN_IDENTIFIER_LEN = 2
-- List of regex patterns for finding suggestions for the autocomplete menu:
local IDENTIFIER_PATTERNS = {"[a-z_][a-z_0-9]+"}
-- Override settings that interfere with this script:
props["autocomplete.ahk1.start.characters"] = ""
props["autocomplete.ahk2.start.characters"] = ""
-- This feature is very awkward when combined with automatic popups:
props["autocomplete.choose.single"] = "0"
local names = {}
local notempty = next
local shouldIgnorePos -- init'd by buildNames().
local normalize
if IGNORE_CASE then
normalize = string.upper
else
normalize = function(word) return word end
end
local function setLexerSpecificStuff()
-- Disable collection of words in comments, strings, etc.
-- Also disables autocomplete popups while typing there.
if IGNORE_STYLES[editor.Lexer] then
-- Define a function for calling later:
shouldIgnorePos = function(pos)
return isInTable(IGNORE_STYLES[editor.Lexer], editor.StyleAt[pos])
end
else
-- Optional: Disable autocomplete popups for unknown lexers.
shouldIgnorePos = function(pos) return true end
end
end
local apiCache = {} -- Names from api files, stored by lexer name.
local function getApiNames()
local lexer = editor.LexerLanguage
if apiCache[lexer] then
return apiCache[lexer]
end
local apiNames = {}
local apiFiles = props["APIPath"] or ""
apiFiles:gsub("[^;]+", function(apiFile) -- For each in ;-delimited list.
for name in io.lines(apiFile) do
name = name:gsub("[\(, ].*", "") -- Discard parameters/comments.
if string.len(name) > 0 then
apiNames[name] = true
end
end
return ""
end)
apiCache[lexer] = apiNames -- Even if it's empty.
return apiNames
end
local function buildNames()
setLexerSpecificStuff()
-- Reset our array of names.
names = {}
-- Collect all words matching the given patterns.
local unique = {}
for i, pattern in ipairs(IDENTIFIER_PATTERNS) do
local startPos, endPos
endPos = 0
while true do
startPos, endPos = editor:findtext(pattern, SCFIND_REGEXP, endPos + 1)
if not startPos then
break
end
if not shouldIgnorePos(startPos) then
if endPos-startPos+1 >= MIN_IDENTIFIER_LEN then
-- Create one key-value pair per unique word:
local name = editor:textrange(startPos, endPos)
unique[normalize(name)] = name
end
end
end
end
-- Build an ordered array from the table of names.
for name in pairs(getApiNames()) do
-- This also "case-corrects"; e.g. "gui" -> "Gui".
unique[normalize(name)] = name
end
for _,name in pairs(unique) do
table.insert(names, name)
end
table.sort(names, function(a,b) return normalize(a) < normalize(b) end)
buffer.namesForAutoComplete = names -- Cache it for OnSwitchFile.
end
local lastAutoCItem = 0 -- Used by handleKey().
local menuItems
local function handleChar(char, calledByHotkey)
local pos = editor.CurrentPos
local startPos = editor:WordStartPosition(pos, true)
local len = pos - startPos
if not INCREMENTAL and editor:AutoCActive() then
-- Nothing to do.
return
end
if len < MIN_PREFIX_LEN then
if editor:AutoCActive() then
if len == 0 then
-- Happens sometimes after typing ")".
editor:AutoCCancel()
return
end
-- Otherwise, autocomplete is already showing so may as well
-- keep it updated even though len < MIN_PREFIX_LEN.
else
if char then
-- Not enough text to trigger autocomplete, so return.
return
end
-- Otherwise, we were called explicitly without a param.
end
end
if not editor:AutoCActive() and shouldIgnorePos(startPos) and not calledByHotkey then
-- User is typing in a comment or string, so don't automatically
-- pop up the auto-complete window.
return
end
local prefix = normalize(editor:textrange(startPos, pos))
menuItems = {}
for i, name in ipairs(names) do
local s = normalize(string.sub(name, 1, len))
if s >= prefix then
if s == prefix then
table.insert(menuItems, name)
else
break -- There will be no more matches.
end
end
end
if notempty(menuItems) then
-- Show or update the auto-complete list.
local list = table.concat(menuItems, "\1")
editor.AutoCIgnoreCase = IGNORE_CASE
editor.AutoCCaseInsensitiveBehaviour = 1 -- Do NOT pre-select a case-sensitive match
editor.AutoCSeparator = 1
editor.AutoCMaxHeight = 10
editor:AutoCShow(len, list)
-- Check if we should auto-auto-complete.
if normalize(menuItems[1]) == prefix and not calledByHotkey then
-- User has completely typed the only item, so cancel.
if CASE_CORRECT then
if CASE_CORRECT_INSTANT or #menuItems == 1 then
-- Make sure the correct item is selected.
editor:AutoCShow(len, menuItems[1])
editor:AutoCComplete()
end
if #menuItems > 1 then
editor:AutoCShow(len, list)
end
end
if #menuItems == 1 then
editor:AutoCCancel()
return
end
end
lastAutoCItem = #menuItems - 1
if lastAutoCItem == 0 and calledByHotkey and CHOOSE_SINGLE then
editor:AutoCComplete()
end
else
-- No relevant items.
if editor:AutoCActive() then
editor:AutoCCancel()
end
end
end
local function handleKey(key, shift, ctrl, alt)
if key == 0x20 and ctrl and not (shift or alt) then -- ^Space
handleChar(nil, true)
return true
end
if alt or not editor:AutoCActive() then return end
if key == 0x8 then -- VK_BACK
if not ctrl then
-- Need to handle it here rather than relying on the default
-- processing, which would occur after handleChar() returns:
editor:DeleteBack()
handleChar()
return true
end
elseif key == 0x25 then -- VK_LEFT
if not shift then
if ctrl then
editor:WordLeft() -- See VK_BACK for comments.
else
editor:CharLeft() -- See VK_BACK for comments.
end
handleChar()
return true
end
elseif key == 0x26 then -- VK_UP
if editor.AutoCCurrent == 0 then
-- User pressed UP when already at the top of the list.
if WRAP_ARROW_KEYS then
-- Select the last item.
editor:AutoCSelect(menuItems[#menuItems])
return true
end
-- Cancel the list and let the caret move up.
editor:AutoCCancel()
end
elseif key == 0x28 then -- VK_DOWN
if editor.AutoCCurrent == lastAutoCItem then
-- User pressed DOWN when already at the bottom of the list.
if WRAP_ARROW_KEYS then
-- Select the first item.
editor:AutoCSelect(menuItems[1])
return true
end
-- Cancel the list and let the caret move down.
editor:AutoCCancel()
end
elseif key == 0x5A and ctrl then -- ^z
editor:AutoCCancel()
end
end
-- Event handlers
local events = {
OnChar = handleChar,
OnKey = handleKey,
OnSave = buildNames,
OnSwitchFile = function()
-- Use this file's cached list if possible:
names = buffer.namesForAutoComplete
if not names then
-- Otherwise, build a new list.
buildNames()
else
setLexerSpecificStuff()
end
end,
OnOpen = function()
-- Ensure the document is styled first, so we can filter out
-- words in comments and strings.
editor:Colourise(0, editor.Length)
-- Then do the real work.
buildNames()
end
}
-- Add event handlers in a cooperative fashion:
for evt, func in pairs(events) do
local oldfunc = _G[evt]
if oldfunc then
_G[evt] = function(...) return func(...) or oldfunc(...) end
else
_G[evt] = func
end
end
@Faibbus
Copy link

Faibbus commented Nov 24, 2017

There's a bug in buildNames.
Since you initialize endPos = 0 (line 100), and then, you use endPos + 1 (line 102), the very first word of a file will get truncated in the autocomplete list.

@telppa
Copy link

telppa commented Apr 27, 2022

I created an updated version that fixes bugs, retrieves unicode words (including Chinese, Japanese, Korean, etc.), and automatically retrieves new words.

It looks like this.

自动完成增强版

-- 自动完成增强版 v1.4
-- base on AutoComplete v0.8 by Lexikos
-- https://gist.github.com/Lexikos/bc17b8d55ae8a8102e35

-- 更新日志:
-- 1.4
-- 合并调试功能。
-- 1.3
-- 增加文件编码检测功能。
-- 1.2
-- 合并fincs和Lexikos的样式。
-- 1.1
-- 修复已经打开了文件又新建空文档时自动完成功能失效的问题。bug #1
-- 1.0
-- 修复了Lexikos的关键字砍头、取词长度不正确、api文件不存在会报错的bug。
-- 实现了中文、英文(应该也包括韩文日文等)关键字的提示。
-- 实现了实时提取关键字(原版仅在打开文件时提取),解决了实时提取关键字的性能问题。
-- 改进了自动完成框的闪烁问题。
-- 实现了跨文件关键字共享,但默认关闭了这个功能。
-- lua也可以自动完成。

-- 可以继续改进的地方:
-- 自动完成框显示出的内容不变(滚动条下面的变化无所谓,反正看不见)就可以不刷新。这将进一步降低闪烁问题。
-- 最完美的实时提取关键字应该是实时全文件更新,但这样会有性能问题。
-- 比较折衷的方式是抓取文件所有被修改的的行,然后进行处理,但目前暂做不到。
-- 原来的自动完成可以直接匹配“.”开头的关键字,现在只能间接匹配。

-- List of styles per lexer that autocomplete should not occur within.
-- 不在下面样式列表中提取关键字、进行花括号自动缩进。
local IGNORE_STYLES = { -- Should include comments, strings and errors.
    [SCLEX_AHK1] = {1,2,3,6,20},
    [SCLEX_AHK2] = {1,2,3,5,15},
    [SCLEX_LUA]  = {1,2,3,6,7,8,12}
}

local DEBUG_MODE = false   -- 调试模式。
local SHARING_KEYWORDS_BETWEEN_FILES = false   -- 跨文件关键字共享。比如同时打开A、B两个文件,A文件中抓到的关键字“haha”,在B文件中也可以提示。
local INCREMENTAL = true    -- 根据关键字变化不断减少自动完成列表内容。false则和SciTE原版一致,只变动光标,不改变列表。
local IGNORE_CASE = true    -- 关键字匹配时忽略大小写
local CASE_CORRECT = true   -- 自动纠正大小写
local CASE_CORRECT_INSTANT = false
local WRAP_ARROW_KEYS = false
local CHOOSE_SINGLE = props["autocomplete.choose.single"]

-- Number of chars to type before the autocomplete list appears:
local MIN_PREFIX_LEN = 2    -- 最少输入几个字符后,开始匹配自动完成列表。
-- Length of shortest word to add to the autocomplete list:
local MIN_IDENTIFIER_LEN = 3    -- 添加到自动完成列表的关键字的最小长度(不影响从“ahk.api”添加关键字的长度)。
-- List of regex patterns for finding suggestions for the autocomplete menu:
-- 这个表达式的本质,就是通过排除ASCII码表的字符,来构建一个字符组。
-- 特别注意,这个表达式是给“editor:findtext()”使用的,那么看起来应该遵循SciTE的正则语法。
-- 可是实际上它是调用的lua,并且默认大小写不敏感,即使指定“SCFIND_REGEXP”参数也如此!!!
-- 因此还需要手动加上“SCFIND_MATCHCASE”参数!!!!!!!!!!!!!!!!!!!!!!!
-- [^\1-\64\91-\94\96\123-\127] 在ASCII码表中等于 [a-zA-Z_] 但因为使用了排除的形式,所以可以匹配到中文。
-- [^\1-\47\58-\64\91-\94\96\123-\127] 在ASCII码表中等于 [a-zA-Z0-9_] 但因为使用了排除的形式,所以可以匹配到中文。
-- 因此整个表达式的意思就是{"[a-zA-Z_中文日文韩文][a-zA-Z0-9_中文日文韩文]+"}
-- 需要注意的是,如果在SciTE编辑器中使用这个表达式,需要把\1替换为\x00。
-- 原因是\0 也就是空字符会让lua出错,因此lua使用这个表达式就必须从\1开始。
-- 另一个需要注意的地方是,这个表达式会同样匹配到中文的标点,例如“,”“。”等等。
-- 之所以这里让它匹配到标点的原因是,lua和scite都不支持unicode字符的匹配,因此难以单独只匹配汉字。
-- 另一个原因则是,实际上在代码中,你是可以用中文标点作为变量名的,加上表达式被限制为不在注释等地方匹配,因此确实可以匹配到正确结果。
-- 这是SciTE编辑器中Ctrl+F使用的版本 {"[^\x00-\x40\x5B-\x5E\x60\x7B-\x7F][^\x00-\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]+"}
local IDENTIFIER_PATTERNS = {"[^\1-\64\91-\94\96\123-\127][^\1-\47\58-\64\91-\94\96\123-\127]+"}

-- Override settings that interfere with this script:
props["autocomplete.ahk1.start.characters"] = ""
props["autocomplete.ahk2.start.characters"] = ""

-- This feature is very awkward when combined with automatic popups:
props["autocomplete.choose.single"] = "0"


-- 不区分大小写的话,normalize函数就直接大写取到的字符。
-- 将大写后的字符存为key,可以避免重复记录“word” “Word”这样的单词。
local normalize
if IGNORE_CASE then
    normalize = string.upper
else
    normalize = function(word) return word end
end


local shouldIgnorePos -- init'd by buildNames().
local function setLexerSpecificStuff()
    -- Disable collection of words in comments, strings, etc.
    -- Also disables autocomplete popups while typing there.
    if IGNORE_STYLES[editor.Lexer] then
        -- Define a function for calling later:
        shouldIgnorePos = function(pos)
            return isInTable(IGNORE_STYLES[editor.Lexer], editor.StyleAt[pos])
        end
    else
        -- Optional: Disable autocomplete popups for unknown lexers.
        shouldIgnorePos = function(pos) return true end
    end
end


local names = {}
local unique = {}   -- 让unique持久的储存数据。这个对象是以unique["key"]=value 形式储存值的,所以key都是唯一的。
local function unique2names()
    names = {}
    for _,name in pairs(unique) do
        table.insert(names, name)
    end
    table.sort(names, function(a,b) return normalize(a) < normalize(b) end)
end
local function loadCache()
    names = buffer.namesForAutoComplete
    unique = buffer.uniqueForAutoComplete
end
local function saveCache()
    buffer.namesForAutoComplete = names
    buffer.uniqueForAutoComplete = unique
end
local function buildNamesFromPos(_startPos, _endPos)
    setLexerSpecificStuff()   -- 获取需要忽略的样式的坐标,例如所有注释部分的坐标。
    local startPos, endPos
    endPos = _startPos - 1   -- 修复了Lexikos的bug,这里必须 -1 ,否则文首抓到的关键字会被砍头“msgbox”变“sgbox”。
    -- Collect all words matching the given patterns. 根据规则提取关键字。
    for i, pattern in ipairs(IDENTIFIER_PATTERNS) do
        while true do
            -- SCFIND_REGEXP 表示使用正则表达式去匹配。默认大小写不敏感,因此需要手动加上大小写敏感的参数 SCFIND_MATCHCASE!
            startPos, endPos = editor:findtext(pattern, SCFIND_REGEXP + SCFIND_MATCHCASE, endPos + 1)
            if (not startPos) or (endPos > _endPos) then
                break
            end
            -- 匹配到的内容不在注释中
            if not shouldIgnorePos(startPos) then
                if endPos-startPos >= MIN_IDENTIFIER_LEN then   -- 修复了Lexikos的bug。原版是“endPos-startPos+1”。
                    -- Create one key-value pair per unique word:
                    local name = editor:textrange(startPos, endPos)
                    if DEBUG_MODE then print("本次取到的关键字:"..name) end
                    unique[normalize(name)] = name    -- normalize 在忽略大小写的情况下,实际上是把所有单词最大化了一遍,以避免重复添加单词。
                end
            end
        end
    end
end
local function buildApiNames()  -- 从“ahk.api”和“user\user.ahk.api”文件中取关键字。
    local apiFiles = props["APIPath"] or ""
    apiFiles:gsub("[^;]+", function(apiFile) -- For each in ;-delimited list. 删除“;”之外的字符,也就是删得只剩分隔符“;”了。
        if FileExists(apiFile) then   -- 文件存在再进行操作。不然会报错找不到文件。
            for name in io.lines(apiFile) do
                name = name:gsub("[\(, ].*", "") -- Discard parameters/comments. 删除“(, ”左括号、逗号、空格及其后面的全部字符。
                if string.len(name) > 0 then
                    unique[normalize(name)] = name    -- This also "case-corrects"; e.g. "gui" -> "Gui".
                end
                if string.sub(name, 0, 1) == "." then   -- 把“.MaxIndex”之类带“.”的关键字再存一遍“MaxIndex”形式的。
                    name = string.sub(name, 2)
                    unique[normalize(name)] = name
                end
            end
        end
    end)
end
local function buildDocNames()
    buildNamesFromPos(0, editor.TextLength)
end
local function buildNames()
    unique = {}
    -- 从API文件创建关键字
    buildApiNames()
    -- 从当前打开文件创建关键字
    buildDocNames()
    -- 将关键字转为自动完成列表
    unique2names()
end


local notempty = next
local lastAutoCItem = 0 -- Used by handleKey().
local menuItems
local list_old = ""   -- 储存旧的list的值,用于和新的list做对比,没变化就不刷新列表,降低闪烁。
local function handleChar(char, calledByHotkey)
    -- This function should only run when the Editor pane is focused.
    if not editor.Focus then return false end
    if DEBUG_MODE then print("--------------------") end
    -- 实际上,如果打开SciTE时没有任何文件被打开,那么此时OnOpen事件是会被激活的。
    -- 有问题的就是已经打开了文件,此时新建文件,OnOpen事件又不会被激活。
    -- 此时 OnClear() 是唯一能在创建新文件时被激活的事件。
    -- 但是激活此事件时,buffer并没有被清空,值还是前一个文件的。
    -- 也就是说在唯一能够变相捕获创建新文件的事件中,没法做任何事。
    -- 因此只能在此处验证并建立关键字列表。bug #1
    if not buffer.namesCache then
        if DEBUG_MODE then print("OnNewFile_no_cache|   endpos:"..editor.TextLength) end
        -- Otherwise, build a new list.
        buildNames()
        saveCache()
        buffer.namesCache = true
    end
    if not INCREMENTAL and editor:AutoCActive() then
        -- Nothing to do.
        return false
    end

    -- 行模式的意义就是——关键字在换行后就被实时创建,同时又不会因为每次更新关键字都是全文更新而造成性能问题。
    -- 缺点就是最完美的做法实际上是获取文件所有被修改的行,然后提取这些行的关键字。
    -- 但目前的做法是提取按了回车的那一行,这当然包括了大部分代码被修改的情况,但显然修改某行后,鼠标点走的情况没被包括在内。
    if char == "\n" then
        -- 获取按下回车时的那行的首尾坐标。
        local line_num = editor:LineFromPosition(editor.CurrentPos)
        local line_start = editor:PositionFromLine(line_num-1)
        local line_end = editor.LineEndPosition[line_num-1]
        -- 这里是更新unique,所以不要去清空unique。
        buildNamesFromPos(line_start, line_end)
        unique2names()
        saveCache()
        -- 自动缩进
        local prevStyle = editor.StyleAt[getPrevLinePos()]
        if not isInTable(IGNORE_STYLES[editor.Lexer], prevStyle) then
            return AutoIndent_OnNewLine()   -- fincs原版这里就用的return。
        end
    elseif char == "{" then
        local curStyle = editor.StyleAt[editor.CurrentPos-2]
        if not isInTable(IGNORE_STYLES[editor.Lexer], curStyle) then
            AutoIndent_OnOpeningBrace()
        end
    elseif char == "}" then
        local curStyle = editor.StyleAt[editor.CurrentPos-2]
        if not isInTable(IGNORE_STYLES[editor.Lexer], curStyle) then
            AutoIndent_OnClosingBrace()
        end
    end

    local pos = editor.CurrentPos
    local startPos = editor:WordStartPosition(pos, true)
    local len = pos - startPos
    if len < MIN_PREFIX_LEN then
        if editor:AutoCActive() then
            if len == 0 then
            if DEBUG_MODE then print(1) end
                -- Happens sometimes after typing ")".
                editor:AutoCCancel()
                return
            end
            -- Otherwise, autocomplete is already showing so may as well
            -- keep it updated even though len < MIN_PREFIX_LEN.
        else
            if char then
            if DEBUG_MODE then print(2) end
                -- Not enough text to trigger autocomplete, so return.
                return
            end
            -- Otherwise, we were called explicitly without a param.
        end
    end
    if not editor:AutoCActive() then
    if DEBUG_MODE then print(3) end
        -- 由于自动完成可以通过包括但不限于 中途{enter}、中途{tab}、全部输完等方式完成。
        -- 为了避免bug也为了避免到处去清空list_old,因此统一在只要没有自动完成框时就清空旧变量!
        list_old = ""
        if shouldIgnorePos(startPos) and not calledByHotkey then
        -- User is typing in a comment or string, so don't automatically
        -- pop up the auto-complete window.
            return
        end
    end
    local prefix = normalize(editor:textrange(startPos, pos))
    menuItems = {}
    for i, name in ipairs(names) do
        local s = normalize(string.sub(name, 1, len))
        -- 这里字符开始插入自动完成框了。
        -- 字符串是可以比较大小的,“ABC”是大于“ABB”的。
        -- 当我们输入“ms”时,names会被遍历,直到“Multi”时停下,因为在names中,Multi就是MsgBox的下一个关键字。
        -- 之所以要在下一个关键字的地方停下,是因为列表被排序过,比如为了完整获取所有“gui”开头的关键字,那么就得在“guj”停下。
        if s >= prefix then   -- 大于等于用得很巧妙。等于时 代表关键字匹配,大于时 代表匹配完了,可以跳出。
            if s == prefix then
                table.insert(menuItems, name)   -- 这里已经把所有匹配的关键字全部存入menuItems了。
            else
                break -- There will be no more matches.
            end
        end
    end
    if DEBUG_MODE then print("3.1|匹配列表:"..table.concat(menuItems, "\1")) end
    if notempty(menuItems) then
    if DEBUG_MODE then print(4) end
        -- Show or update the auto-complete list.
        local list = table.concat(menuItems, "\1")
        editor.AutoCIgnoreCase = IGNORE_CASE
        editor.AutoCCaseInsensitiveBehaviour = 1 -- Do NOT pre-select a case-sensitive match
        editor.AutoCSeparator = 1
        editor.AutoCMaxHeight = 5
        -- if not editor:AutoCActive() then   -- 另一种降低闪烁的方式,和SciTE原版一样,只要自动完成框出现了,就只跳转位置而不刷新。
        if list~=list_old then    -- 降低自动完成框的闪烁,只有匹配的关键字发生变化时才刷新。
        if DEBUG_MODE then print(5) end
            editor:AutoCShow(len, list)
            list_old=list
        end
        -- Check if we should auto-auto-complete.
        if normalize(menuItems[1]) == prefix and not calledByHotkey then
            -- User has completely typed the only item, so cancel.
            if CASE_CORRECT then
                if CASE_CORRECT_INSTANT or #menuItems == 1 then
                if DEBUG_MODE then print(6) end
                    -- Make sure the correct item is selected.
                    editor:AutoCShow(len, menuItems[1])
                    editor:AutoCComplete()
                end
                if #menuItems > 1 then
                if DEBUG_MODE then print(7) end
                    editor:AutoCShow(len, list)
                end
            end
            if #menuItems == 1 then
            if DEBUG_MODE then print(8) end
                editor:AutoCCancel()
                return
            end
        end
        lastAutoCItem = #menuItems - 1
        if lastAutoCItem == 0 and calledByHotkey and CHOOSE_SINGLE then
        if DEBUG_MODE then print(9) end
            editor:AutoCComplete()
        end
    else
        -- No relevant items.
        if editor:AutoCActive() then
        if DEBUG_MODE then print(10) end
            editor:AutoCCancel()
        end
    end
    -- 不让其它OnChar函数继续处理,否则会造成中文关键字显示被打断。
    return true
end


local function handleKey(key, shift, ctrl, alt)
    if key == 0x20 and ctrl and not (shift or alt) then -- ^Space
        handleChar(nil, true)
        return true
    end
    if alt or not editor:AutoCActive() then return end
    if key == 0x8 then -- VK_BACK
        if not ctrl then
            -- Need to handle it here rather than relying on the default
            -- processing, which would occur after handleChar() returns:
            editor:DeleteBack()
            handleChar()
            return true
        end
    elseif key == 0x25 then -- VK_LEFT
        if not shift then
            if ctrl then
                editor:WordLeft() -- See VK_BACK for comments.
            else
                editor:CharLeft() -- See VK_BACK for comments.
            end
            handleChar()
            return true
        end
    elseif key == 0x26 then -- VK_UP
        if editor.AutoCCurrent == 0 then
            -- User pressed UP when already at the top of the list.
            if WRAP_ARROW_KEYS then
                -- Select the last item.
                editor:AutoCSelect(menuItems[#menuItems])
                return true
            end
            -- Cancel the list and let the caret move up.
            editor:AutoCCancel()
        end
    elseif key == 0x28 then -- VK_DOWN
        if editor.AutoCCurrent == lastAutoCItem then
            -- User pressed DOWN when already at the bottom of the list.
            if WRAP_ARROW_KEYS then
                -- Select the first item.
                editor:AutoCSelect(menuItems[1])
                return true
            end
            -- Cancel the list and let the caret move down.
            editor:AutoCCancel()
        end
    elseif key == 0x5A and ctrl then -- ^z
        editor:AutoCCancel()
    end
end


local function checkCodepage()
    if props['FileExt']=="ahk" then
        if editor.CodePage ~= 65001 then
            -- local FileNameExt = props['FileNameExt']
            -- 3.5.6开始才支持editor:EncodedFromUTF8()……
            -- editor:EncodedFromUTF8(FileNameExt)
            -- print("注意:当前文件 "..FileNameExt.." 编码不是“UTF-8带BOM”。\nAHK本身可能因为错误的编码造成难以发现的 Bug。\n同时这也影响TillaGoto的跳转、自动完成增强版对中文关键字的支持。\n所以强烈建议你将文件编码转换为“UTF-8带BOM”。")
            print("注意:当前文件编码不是“UTF-8带BOM”。\nAHK本身可能因为错误的编码造成难以发现的 Bug。同时这也影响 TillaGoto 的跳转、自动完成增强版对中文关键字的支持。\n所以强烈建议你将文件编码转换为“UTF-8带BOM”。")
        end
    end
end


-- Event handlers
local events = {
    OnChar          = handleChar,
    OnKey           = handleKey,
    OnSwitchFile    = function()
        -- 不同文件的buffer值是唯一的,因此用它来缓存对应文件的关键字。
        if not buffer.namesCache then
            -- 打开或切换一个文件时,正常的事件顺序是OnOpen、OnSwitchFile。
            -- 但当打开SciTE时,已经打开了一个lua文件,那么事件被激活的顺序会是OnSwitchFile、OnOpen。
            -- 由于OnSwitchFile先被激活,此时editor.TextLength的值就会是0。
            -- 所以此时buildNames()无法获得正确结果。
            -- 因此需要跳过,在OnOpen事件中再处理。
            if editor.TextLength > 0 then
                if DEBUG_MODE then print("OnSwitchFile_no_cache|   endpos:"..editor.TextLength) end
                -- Otherwise, build a new list.
                buildNames()
                saveCache()
                buffer.namesCache = true
            end
        else
            if DEBUG_MODE then print("OnSwitchFile_have_cache|   endpos:"..editor.TextLength) end
            loadCache()
        end
    end,
    OnOpen          = function()
        -- Ensure the document is styled first, so we can filter out
        -- words in comments and strings.
        editor:Colourise(0, editor.Length)
        -- Then do the real work.
        -- checkCodepage()
        if not buffer.namesCache then
            if DEBUG_MODE then print("OnOpen_no_cache|   endpos:"..editor.TextLength) end
            -- Otherwise, build a new list.
            buildNames()
            saveCache()
            buffer.namesCache = true
        else
            if DEBUG_MODE then print("OnOpen_have_cache|   endpos:"..editor.TextLength) end
            loadCache()
        end
    end
}
-- Add event handlers in a cooperative fashion:
for evt, func in pairs(events) do
    local oldfunc = _G[evt]
    if oldfunc then
        _G[evt] = function(...) return func(...) or oldfunc(...) end
    else
        _G[evt] = func
    end
end

@thaihoa89
Copy link

thaihoa89 commented Jun 1, 2022

Hi @telppa , I installed your version 1.4. It works fine. But there is 1 error. That's when I use the if statement or the loop command. When I press Enter, the next line indents 2 tab spaces instead of 1 as usual. You can see in the following photo. Please show me how to fix it back to 1 tab

v1 4

@telppa
Copy link

telppa commented Jun 1, 2022

Hi @telppa , I installed your version 1.4. It works fine. But there is 1 error. That's when I use the if statement or the loop command. When I press Enter, the next line indents 2 tab spaces instead of 1 as usual. You can see in the following photo. Please show me how to fix it back to 1 tab

v1 4

https://github.com/telppa/SciTE4AutoHotkey-Plus/tree/master/SciTE/extensions

here to download ahk.lua and AutoCompletePlus.lua

the latest version is 1.6

@thaihoa89

@thaihoa89
Copy link

thaihoa89 commented Jun 1, 2022

@telppa , i downloaded and installed the latest vesion 1.6. But it still has this error
Capture

And when i installed ahk.lua. It has this error

Capture

@telppa
Copy link

telppa commented Jun 1, 2022

@thaihoa89

if you use SciTE4AutoHotkey v3.1.0

try download

ahk.api
ahk.lua
AutoCompletePlus.lua

ahk.api overwrite SciTE\ahk.api
ahk.lua overwrite SciTE\ahk.lua
AutoCompletePlus.lua overwrite SciTE\AutoCompletePlus.lua

then

ahk.lua comment line 677 678 681 682
ahk.lua line 679 -> dofile(props['SciteDefaultHome'].."/AutoCompletePlus.lua")

AutoCompletePlus.lua line 166 -> name = name:gsub("[(, ].*", "")


the other way is use https://github.com/telppa/SciTE4AutoHotkey-Plus
but SciTE4AutoHotkey-Plus only have chinese now.

@thaihoa89
Copy link

thaihoa89 commented Jun 1, 2022

@telppa , thank you so much, i fixed it.
Can you do me one more favor please? How can i install this function for my SciTE4AutoHotkey v3.1.0
Capture

@telppa
Copy link

telppa commented Jun 1, 2022

@thaihoa89

This is a complicated issue.
Because it involves a lot of files named in Chinese and I don't know how to tell you which one is you need, later I will try to merge them with scite 3.1.0. Maybe in two months.

@thaihoa89
Copy link

thaihoa89 commented Jun 1, 2022

@telppa

Oh. I will wait for that. It's a very cool feature for SciTE 3.1.0 since SciTE 3.1.0 has removed Calltip functionality.
Thank you a lot.

@Lexikos
Copy link
Author

Lexikos commented Jun 15, 2022

@telppa
It sounds like you've made some great improvements, but I think posting it entirely within a comment on this gist was the wrong way to share it. Perhaps you can edit your comment to replace the code with a link?

I created an updated version that fixes bugs,

Which bugs?

retrieves unicode words (including Chinese, Japanese, Korean, etc.),

How are words delineated in those languages? Did it not work before because of some issue with handling Unicode, or are the words not space-delimited?

and automatically retrieves new words.

Do you mean while they are typed, as opposed to when the file is saved?

@thepragmatic15
Copy link

In Lexikos -- AutoComplete v0.8 by Lexikos,
In Scite4AutoHotkey Version 3.1.00, I get an error message for line 80, which reads:
AutoComplete.lua:80: invalid escape sequence near '"[('
Line 80 in the original Lexikos code reads:
name = name:gsub("[(, ].*", "") -- Discard parameters/comments.
In Lexicos code, on line 80, the ( characters are highlighted with a red frame.

How to fix this error?
THANKS.

  1. `local function getApiNames()
  2. local lexer = editor.LexerLanguage
    
  3. if apiCache[lexer] then
    
  4.     return apiCache[lexer]
    
  5. end
    
  6. local apiNames = {}
    
  7. local apiFiles = props["APIPath"] or ""
    
  8. apiFiles:gsub("[^;]+", function(apiFile) -- For each in ;-delimited list.
    
  9.     for name in io.lines(apiFile) do
    
  10.         name = name:gsub("[\(, ].*", "") -- Discard parameters/comments.
    
  11.         if string.len(name) > 0 then
    
  12.             apiNames[name] = true
    
  13.         end
    
  14.     end
    
  15.     return ""
    
  16. end)
    
  17. apiCache[lexer] = apiNames -- Even if it's empty.
    
  18. return apiNames
    
  19. end`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment