Skip to content

Instantly share code, notes, and snippets.

@Lexikos
Last active March 28, 2023 19:55
Show Gist options
  • 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
@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