Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Improved auto-complete for SciTE


  • Please log in to reply
43 replies to this topic
Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006


See the new forum for all updates.


Scite Auto Complete Any Language

This is a SciTE startup script that provides auto-completion for any file type. It scans the file for identifiers on save, open and buffer switch and uses those identifiers in auto-completion. It doesn't use any external files for lists of identifiers.

What constitutes an identifier is determined by the list of patterns in IDENTIFIER_PATTERNS. This example allows identifiers to include dots (e.g. to suggest a whole "object.member.property" string) and dashes, (e.g. "lisp-or-css-identifier").

This script may not behave well if you have auto-completion enabled in SciTE properties.

http://lua-users.org...leteAnyLanguage

This allows any word in the document (whether they are variables, functions, labels, comments, etc.) to be auto-completed. I found this to be a step up from SciTE4AutoHotkey's auto-completion, which often annoys me by auto-completing the wrong word or interfering with my use of single-character variable names. wink.png

Take 2

I took the script above and modified it to include everything from the current language's API files (such as AutoHotkey commands) and IMO improve the usability of the auto-complete popups. The following are features of my script (the ones I can remember):
  • Automatically brings up an auto-complete list after typing a minimum of MIN_PREFIX_LEN characters, except while typing in a comment or quoted string. Ctrl+Space also triggers the auto-complete list.
  • Auto-completes any word (greater than MIN_IDENTIFIER_LEN characters) saved in the document, except for words in comments or quoted strings. Only words consisting of alphanumeric characters and/or underscore are used, but this can be changed via IDENTIFIER_PATTERNS.
  • Case is ignored by default.
  • If INCREMENTAL is set to true, the list is narrowed down while typing, to show only the relevant items. This is disabled by default because current versions of SciTE destroy and recreate the auto-complete window when editor:AutoCShow() is called, and this doesn't look nice.
  • If INCREMENTAL is set to true, the auto-complete popup is automatically dismissed if there's only one item and you type it in full.
  • Pressing UP at the top or DOWN at the bottom of the list does one of the following:
  • Wraps around to the other end of the list if WRAP_ARROW_KEYS is true.
  • Cancels the list and allows the caret to move to the line above/below the current line.
  • Other options are available in the script (in ALL_CAPS).
  • The list is updated only on saving or opening the document.
  • It is disabled for languages other than AutoHotkey and Lua, but this can be changed in the script (see setLexerSpecificStuff; or add the appropriate list of styles to IGNORE_STYLES).
  • It should play nice with other scripts, such as the one included in SciTE4AutoHotkey.
AutoComplete.lua

To use it with SciTE4AutoHotkey, save the file as UserLuaScript.lua in your SciTE user home directory (i.e. %A_MyDocuments%\AutoHotkey\SciTE). To prevent auto-complete from popping up after typing a single character, the following may need to be added to the User properties file (SciTEUser.properties):



autocomplete.ahk1.start.characters=
Note: The current version of the script has been tested on SciTE4AutoHotkey 3.0 proper - the 3.0 release candidate and earlier versions are not supported.

Uberi
  • Moderators
  • 1119 posts
  • Last active: May 02 2015 06:05 PM
  • Joined: 23 Aug 2010
When downloading from AutoHotkey.net, the Lua script was enclosed in HTML tags and HTML special characters were escaped. Copying and pasting the contents of that page into the UserLuaScript.lua worked, though.

Excellent work!

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I suppose that was caused by your browser and/or the way you saved the file. "Save link as" and "Save page as" in Chrome work perfectly for me. Saving the page as text in IE7 also works, but saving as "Webpage, complete (*.htm; *.html)" adds HTML (naturally).

Uberi
  • Moderators
  • 1119 posts
  • Last active: May 02 2015 06:05 PM
  • Joined: 23 Aug 2010
Strangely, saving the exact same way as yesterday now no longer yields HTML tags... :?

Must have been a fluke.

Iggy_
  • Members
  • 90 posts
  • Last active: Nov 16 2012 05:55 PM
  • Joined: 29 May 2011
Cool, thanks for the script.

I tried to make a small change so pressing ctrl+space in comments lets you use the auto complete then only, but for some reason it only seems to work for the lua language, and not in ahk scripts. In ahk the auto-complete closes as soon as you type.

All I did was added module var explicitAutoC which is set true when ^Space is pressed and false whenever editor:AutoCCancel() is called, and used it to force shouldIgnorePos() result to not ignore comments after ^Space. Or that was what I intended anyway, but I don't know lua, or much about scite either. Changes are highlighted red below.
-- v0.1

local SCLEX_AHK1           = 200

local IGNORE_STYLES = { -- comment or string styles
	[SCLEX_AHK1] = {1,2,6,20},
	[SCLEX_LUA]  = {1,2,3,6,7,8,12} -- comments, strings or error
}

local INCREMENTAL = true
local IGNORE_CASE = true

-- 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]+"}


local names = {}


local notempty = next
local shouldIgnorePos -- init'd by buildNames().
[color=red]local explicitAutoC = false -- set when auto complete is explicitly shown by pressing ctrl+space[/color]
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])[color=red] and not explicitAutoC[/color]
		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:GetLexerLanguage()
	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:
					unique[editor] = true
				end
			end
		end
	end
	-- Build an ordered array from the table of names.
	for name in pairs(getApiNames()) do
		unique[name] = true
	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 function handleChar(char)
	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()[color=red]
				explicitAutoC = false[/color]
				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) 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))
	local 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
		if #menuItems == 1 and normalize(menuItems[1]) == prefix then
			-- User has completely typed the only item, so cancel.
			editor:AutoCCancel()[color=red]
			explicitAutoC = false[/color]
		else
			-- Show or update the auto-complete list.
			editor.AutoCIgnoreCase = IGNORE_CASE
			editor.AutoCSeparator = 1
			editor:AutoCShow(len, table.concat(menuItems, "\1"))
			lastAutoCItem = #menuItems - 1
		end
	else
		-- No relevant items.
		if editor:AutoCActive() then
			editor:AutoCCancel()[color=red]
			explicitAutoC = false[/color]
		end
	end
end


local function handleKey(key, shift, ctrl, alt)
	if key == 0x20 and ctrl and not (shift or alt) then -- ^Space[color=red]
		explicitAutoC = true  -- auto complete was explicitly displayed[/color]
		handleChar()
		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:AutoCGetCurrent() == 0 then
			-- User is probably trying to move the caret, since they're
			-- already at the top of the list.  So cancel the list:
			editor:AutoCCancel()[color=red]
			explicitAutoC = false[/color]
		end
	elseif key == 0x28 then -- VK_DOWN
		if editor:AutoCGetCurrent() == lastAutoCItem then
			-- As above, but for the bottom of the list.
			editor:AutoCCancel()[color=red]
			explicitAutoC = false[/color]
		end
	elseif key == 0x5A and ctrl then -- ^z
		editor:AutoCCancel()[color=red]
		explicitAutoC = false[/color]
	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
Anyone got any ideas why it's behaving this way?

P.S I was not sure if I should post this in ask for help or here.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I guess you're using SciTE4AutoHotkey. Go to Options, Open Global Lua Script, then find and disable the following:
[color=green]-- Disable AutoComplete on comment/string/error/etc.[/color]
if isInTable(ignoreStyles, curStyle) then
    return CancelAutoComplete()
end
Edit: I liked your idea, so I've updated the script to support ^Space in comments. I used a slightly simpler approach. It still requires the block of code above to be disabled.

Iggy_
  • Members
  • 90 posts
  • Last active: Nov 16 2012 05:55 PM
  • Joined: 29 May 2011
Sweet, thanks Lexikos!

Actually, that was the approach I first tried too, but it didn't seem to work and I didn't know if/how optional parameters worked in lua.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
FYI, if a parameter is omitted, it receives a nil value, which is the only value considered false other than false itself.

Iggy_
  • Members
  • 90 posts
  • Last active: Nov 16 2012 05:55 PM
  • Joined: 29 May 2011
Hmmm ok, interesting, thanks.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Minor update: If autocomplete.choose.single=1 is in effect, the script disables it and enables its own implementation of that feature, which only triggers when you explicitly invoke auto-completion via Ctrl+Space (and there's only one match for the current word). In other words, if you type "dl" it won't automatically insert "DllCall" unless you press Ctrl+Space.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Updates:
[*:3ko0x3yj]If IGNORE_CASE is true, the auto-complete list will no longer show the same word multiple times with different case. Words pulled in from the API file take precedence over words in the active document.
[*:3ko0x3yj]If CASE_CORRECT is true and you type the last character of the only item in the auto-complete list, auto-completion is automatically triggered instead of being cancelled. For instance, typing "msgbox" gives "MsgBox".
[*:3ko0x3yj]Pressing Ctrl+Space now triggers auto-completion immediately if the only match is already "complete", instead of doing nothing. For instance, pressing Ctrl+Space when the caret is at the end of "msgbox" replaces it with "MsgBox".

  • Guests
  • Last active:
  • Joined: --
Great improvement,thanks!

  • Guests
  • Last active:
  • Joined: --
Seems can't autocomplete variable name string starts with "$".

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
It's not designed to. If you need that, edit the identifier pattern:
-- List of regex patterns for finding suggestions for the autocomplete menu:
local IDENTIFIER_PATTERNS = {"[a-z_[color=red]$[/color]][a-z_0-9]+"}


meochain
  • Members
  • 12 posts
  • Last active: Jun 10 2012 04:25 PM
  • Joined: 10 Jun 2012
Nice information...Great