luaforwindows/SciTE/scite-debug/extman.lua

883 lines
22 KiB
Lua
Executable File
Raw Permalink Blame History

-- Extman is a Lua script manager for SciTE. It enables multiple scripts to capture standard events
-- without interfering with each other. For instance, scite_OnDoubleClick() will register handlers
-- for scripts that need to know when a double-click event has happened. (To know whether it
-- was in the output or editor pane, just test editor.Focus). It provides a useful function scite_Command
-- which allows you to define new commands without messing around with property files (see the
-- examples in the scite_lua directory.)
-- extman defines three new convenience handlers as well:
--scite_OnWord (called when user has entered a word)
--scite_OnEditorLine (called when a line is entered into the editor)
--scite_OnOutputLine (called when a line is entered into the output pane)
-- this is an opportunity for you to make regular Lua packages available to SciTE
--~ package.path = package.path..';C:\\lang\\lua\\lua\\?.lua'
--~ package.cpath = package.cpath..';c:\\lang\\lua\\?.dll'
-- useful function for getting a property, or a default if not present.
function scite_GetProp(key,default)
local val = props[key]
if val and val ~= '' then return val
else return default end
end
function scite_GetPropBool(key,default)
local res = scite_GetProp(key,default)
if not res or res == '0' or res == 'false' then return false
else return true
end
end
local GTK = scite_GetProp('PLAT_GTK')
local _MarginClick,_DoubleClick,_SavePointLeft = {},{},{}
local _SavePointReached,_Open,_SwitchFile = {},{},{}
local _BeforeSave,_Save,_Char = {},{},{}
local _Word,_LineEd,_LineOut = {},{},{}
local _OpenSwitch = {}
local _UpdateUI = {}
local _UserListSelection
-- new with 1.74!
local _Key = {}
local _DwellStart = {}
local _Close = {}
-- new
local _remove = {}
local append = table.insert
local find = string.find
local size = table.getn
local sub = string.sub
local gsub = string.gsub
-- file must be quoted if it contains spaces!
function quote_if_needed(target)
local quote = '"'
if find(target,'%s') and sub(target,1,1) ~= quote then
target = quote..target..quote
end
return target
end
function OnUserListSelection(tp,str)
if _UserListSelection then
local callback = _UserListSelection
_UserListSelection = nil
return callback(str)
else return false end
end
local function DispatchOne(handlers,arg)
for i,handler in pairs(handlers) do
local fn = handler
if _remove[fn] then
handlers[i] = nil
_remove[fn] = nil
end
local ret = fn(arg)
if ret then return ret end
end
return false
end
local function Dispatch4(handlers,arg1,arg2,arg3,arg4)
for i,handler in pairs(handlers) do
local fn = handler
if _remove[fn] then
handlers[i] = nil
_remove[fn] = nil
end
local ret = fn(arg1,arg2,arg3,arg4)
if ret then return ret end
end
return false
end
DoDispatchOne = DispatchOne -- export this!
-- these are the standard SciTE Lua callbacks - we use them to call installed extman handlers!
function OnMarginClick()
return DispatchOne(_MarginClick)
end
function OnDoubleClick()
return DispatchOne(_DoubleClick)
end
function OnSavePointLeft()
return DispatchOne(_SavePointLeft)
end
function OnSavePointReached()
return DispatchOne(_SavePointReached)
end
function OnChar(ch)
return DispatchOne(_Char,ch)
end
function OnSave(file)
return DispatchOne(_Save,file)
end
function OnBeforeSave(file)
return DispatchOne(_BeforeSave,file)
end
function OnSwitchFile(file)
return DispatchOne(_SwitchFile,file)
end
function OnOpen(file)
return DispatchOne(_Open,file)
end
function OnUpdateUI()
if editor.Focus then
return DispatchOne(_UpdateUI)
else
return false
end
end
-- new with 1.74
function OnKey(key,shift,ctrl,alt)
return Dispatch4(_Key,key,shift,ctrl,alt)
end
function OnDwellStart(pos,s)
return Dispatch4(_DwellStart,pos,s)
end
function OnClose()
return DispatchOne(_Close)
end
-- may optionally ask that this handler be immediately
-- removed after it's called
local function append_unique(tbl,fn,rem)
local once_only
if type(fn) == 'string' then
once_only = fn == 'once'
fn = rem
rem = nil
if once_only then
_remove[fn] = fn
end
else
_remove[fn] = nil
end
local idx
for i,handler in pairs(tbl) do
if handler == fn then idx = i; break end
end
if idx then
if rem then
table.remove(tbl,idx)
end
else
if not rem then
append(tbl,fn)
end
end
end
ex_append_unique = append_unique
-- this is how you register your own handlers with extman
function scite_OnMarginClick(fn,rem)
append_unique(_MarginClick,fn,rem)
end
function scite_OnDoubleClick(fn,rem)
append_unique(_DoubleClick,fn,rem)
end
function scite_OnSavePointLeft(fn,rem)
append_unique(_SavePointLeft,fn,rem)
end
function scite_OnSavePointReached(fn,rem)
append_unique(_SavePointReached,fn,rem)
end
function scite_OnOpen(fn,rem)
append_unique(_Open,fn,rem)
end
function scite_OnSwitchFile(fn,rem)
append_unique(_SwitchFile,fn,rem)
end
function scite_OnBeforeSave(fn,rem)
append_unique(_BeforeSave,fn,rem)
end
function scite_OnSave(fn,rem)
append_unique(_Save,fn,rem)
end
function scite_OnUpdateUI(fn,rem)
append_unique(_UpdateUI,fn,rem)
end
function scite_OnChar(fn,rem)
append_unique(_Char,fn,rem)
end
function scite_OnOpenSwitch(fn,rem)
append_unique(_OpenSwitch,fn,rem)
end
--new 1.74
function scite_OnKey(fn,rem)
append_unique(_Key,fn,rem)
end
function scite_OnDwellStart(fn,rem)
append_unique(_DwellStart,fn,rem)
end
function scite_OnClose(fn,rem)
append_unique(_Close,fn,rem)
end
local function buffer_switch(f)
--- OnOpen() is also called if we move to a new folder
if not find(f,'[\\/]$') then
DispatchOne(_OpenSwitch,f)
end
end
scite_OnOpen(buffer_switch)
scite_OnSwitchFile(buffer_switch)
local next_user_id = 13 -- arbitrary
-- the handler is always reset!
function scite_UserListShow(list,start,fn)
local separators = {' ', ';', '@', '?', '~', ':'}
local separator
local s = table.concat(list)
for i, sep in ipairs(separators) do
if not string.find(s, sep, 1, true) then
s = table.concat(list, sep, start)
separator = sep
break
end
end
-- we could not find a good separator, set it arbitrarily
if not separator then
separator = '@'
s = table.concat(list, separator, start)
end
_UserListSelection = fn
local pane = editor
if not pane.Focus then pane = output end
pane.AutoCSeparator = string.byte(separator)
pane:UserListShow(next_user_id,s)
pane.AutoCSeparator = string.byte(' ')
return true
end
local word_start,in_word,current_word
-- (Nicolas) this is in Ascii as SciTE always passes chars in this "encoding" to OnChar
local wordchars = '[A-Za-z<>-<2D><>-<2D>]' -- wuz %w
local function on_word_char(s)
if not in_word then
if find(s,wordchars) then
-- we have hit a word!
word_start = editor.CurrentPos
in_word = true
current_word = s
end
else -- we're in a word
-- and it's another word character, so collect
if find(s,wordchars) then
current_word = current_word..s
else
-- leaving a word; call the handler
local word_end = editor.CurrentPos
DispatchOne(_Word, {word=current_word,
startp=word_start,endp=editor.CurrentPos,
ch = s
})
in_word = false
end
end
-- don't interfere with usual processing!
return false
end
function scite_OnWord(fn,rem)
append_unique(_Word,fn,rem)
if not rem then
scite_OnChar(on_word_char)
else
scite_OnChar(on_word_char,'remove')
end
end
local last_pos = 0
function get_line(pane,lineno)
if not pane then pane = editor end
if not lineno then
local line_pos = pane.CurrentPos
lineno = pane:LineFromPosition(line_pos)-1
end
-- strip linefeeds (Windows is a special case as usual!)
local endl = 2
if pane.EOLMode == 0 then endl = 3 end
local line = pane:GetLine(lineno)
if not line then return nil end
return string.sub(line,1,-endl)
end
-- export this useful function...
scite_Line = get_line
local function on_line_char(ch,was_output)
if ch == '\n' then
local in_editor = editor.Focus
if in_editor and not was_output then
DispatchOne(_LineEd,get_line(editor))
return false -- DO NOT interfere with any editor processing!
elseif not in_editor and was_output then
DispatchOne(_LineOut,get_line(output))
return true -- prevent SciTE from trying to evaluate the line
end
end
return false
end
local function on_line_editor_char(ch)
return on_line_char(ch,false)
end
local function on_line_output_char(ch)
return on_line_char(ch,true)
end
local function set_line_handler(fn,rem,handler,on_char)
append_unique(handler,fn,rem)
if not rem then
scite_OnChar(on_char)
else
scite_OnChar(on_char,'remove')
end
end
function scite_OnEditorLine(fn,rem)
set_line_handler(fn,rem,_LineEd,on_line_editor_char)
end
-- with this scheme, there is a primary handler, and secondary prompt handlers
-- can temporarily take charge of input. There is only one prompt in charge
-- at any particular time, however.
local primary_handler
function scite_OnOutputLine(fn,rem)
if not rem then
if not primary_handler then primary_handler = fn end
end
_LineOut = {}
set_line_handler(fn,rem,_LineOut,on_line_output_char)
if rem and fn ~= primary_handler then
set_line_handler(primary_handler,false,_LineOut,on_line_output_char)
end
end
local path_pattern
local tempfile
local dirsep
if GTK then
tempfile = '/tmp/.scite-temp-files'
path_pattern = '(.*)/[^%./]+%.%w+$'
dirsep = '/'
else
tempfile = '\\scite_temp1'
path_pattern = '(.*)\\[^%.\\]+%.%w+$'
dirsep = '\\'
end
function path_of(s)
local _,_,res = find(s,path_pattern)
if _ then return res else return s end
end
local extman_path = path_of(props['ext.lua.startup.script'])
local lua_path = scite_GetProp('ext.lua.directory',extman_path..dirsep..'scite_lua')
function extman_Path()
return extman_path
end
-- this version of scite-gdb uses the new spawner extension library.
local fn,err,spawner_path
if package then loadlib = package.loadlib end
-- by default, the spawner lib sits next to extman.lua
spawner_path = scite_GetProp('spawner.extension.path',extman_path)
if GTK then
fn,err = loadlib(spawner_path..'/unix-spawner-ex.so','luaopen_spawner')
else
fn,err = loadlib(spawner_path..'\\spawner-ex.dll','luaopen_spawner')
end
if fn then
fn() -- register spawner
else
print('cannot load spawner '..err)
end
-- a general popen function that uses the spawner library if found; otherwise falls back
-- on os.execute
function scite_Popen(cmd)
if spawner then
return spawner.popen(cmd)
else
cmd = cmd..' > '..tempfile
if GTK then -- io.popen is dodgy; don't use it!
os.execute(cmd)
else
if Execute then -- scite_other was found!
Execute(cmd)
else
os.execute(cmd)
end
end
return io.open(tempfile)
end
end
function dirmask(mask,isdir)
local attrib = ''
if isdir then
if not GTK then
attrib = ' /A:D '
else
attrib = ' -F '
end
end
if not GTK then
mask = gsub(mask,'/','\\')
return 'dir /b '..attrib..quote_if_needed(mask)
else
return 'ls -1 '..attrib..quote_if_needed(mask)
end
end
-- p = globtopattern(g)
--
-- Converts glob string (g) into Lua pattern string (p).
-- Always succeeds.
--
-- Warning: could be better tested.
--
-- (c) 2008 D.Manura, Licensed under the same terms as Lua (MIT License).
local function globtopattern(g)
-- Some useful references:
-- - apr_fnmatch in Apache APR. For example,
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
-- which cites POSIX 1003.2-1992, section B.6.
local p = "^" -- pattern being built
local i = 0 -- index in g
local c -- char at index i in g.
-- unescape glob char
local function unescape()
if c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
end
end
return true
end
-- escape pattern char
local function escape(c)
return c:match("^%w$") and c or '%' .. c
end
-- Convert tokens at end of charset.
local function charset_end()
while 1 do
if c == '' then
p = '[^]'
break
elseif c == ']' then
p = p .. ']'
break
else
if not unescape() then break end
local c1 = c
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
break
elseif c == '-' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
break
elseif c == ']' then
p = p .. escape(c1) .. '%-]'
break
else
if not unescape() then break end
p = p .. escape(c1) .. '-' .. escape(c)
end
elseif c == ']' then
p = p .. escape(c1) .. ']'
break
else
p = p .. escape(c1)
i = i - 1 -- put back
end
end
i = i + 1; c = g:sub(i,i)
end
end
-- Convert tokens in charset.
local function charset()
p = p .. '['
i = i + 1; c = g:sub(i,i)
if c == '' or c == ']' then
p = p .. '[^]'
elseif c == '^' or c == '!' then
p = p .. '^'
i = i + 1; c = g:sub(i,i)
if c == ']' then
-- ignored
else
charset_end()
end
else
charset_end()
end
end
-- Convert tokens.
while 1 do
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '$'
break
elseif c == '?' then
p = p .. '.'
elseif c == '*' then
p = p .. '.*'
elseif c == '[' then
charset()
elseif c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '\\$'
break
end
p = p .. escape(c)
else
p = p .. escape(c)
end
end
return p
end
-- grab all files matching @mask, which is assumed to be a path with a wildcard.
-- 2008-06-27 Now uses David Manura's globtopattern(), which is not fooled by cases
-- like test.lua and test.lua~ !
function scite_Files(mask)
local f,path,pat,cmd,_
if not GTK then
cmd = dirmask(mask)
path = mask:match('(.*\\)') or '.\\'
local file = mask:match('([^\\]*)$')
pat = globtopattern(file)
else
cmd = 'ls -1 '..mask
path = ''
end
f = scite_Popen(cmd)
local files = {}
if not f then return files end
for line in f:lines() do
if not pat or line:match(pat) then
append(files,path..line)
end
end
f:close()
return files
end
-- grab all directories in @path, excluding anything that matches @exclude_path
-- As a special exception, will also any directory called 'examples' ;)
function scite_Directories(path,exclude_pat)
local cmd
--print(path)
if not GTK then
cmd = dirmask(path..'\\*.',true)
else
cmd = dirmask(path,true)
end
path = path..dirsep
local f = scite_Popen(cmd)
local files = {}
if not f then return files end
for line in f:lines() do
-- print(line)
if GTK then
if line:sub(-1,-1) == dirsep then
line = line:sub(1,-2)
else
line = nil
end
end
if line and not line:find(exclude_pat) and line ~= 'examples' then
append(files,path..line)
end
end
f:close()
return files
end
function scite_FileExists(f)
local f = io.open(f)
if not f then return false
else
f:close()
return true
end
end
function scite_CurrentFile()
return props['FilePath']
end
-- (Nicolas)
if GTK then
function scite_DirectoryExists(path)
return os.execute('test -d "'..path..'"') == 0
end
else
-- what is the Win32 equivalent??
function scite_DirectoryExists(path)
return true
end
end
function split(s,delim)
res = {}
while true do
p = find(s,delim)
if not p then
append(res,s)
return res
end
append(res,sub(s,1,p-1))
s = sub(s,p+1)
end
end
function splitv(s,delim)
return unpack(split(s,delim))
end
local idx = 10
local shortcuts_used = {}
local alt_letter_map = {}
local alt_letter_map_init = false
local name_id_map = {}
local function set_command(name,cmd,mode)
local _,_,pattern,md = find(mode,'(.+){(.+)}')
if not _ then
pattern = mode
md = 'savebefore:no'
end
local which = '.'..idx..pattern
props['command.name'..which] = name
props['command'..which] = cmd
props['command.subsystem'..which] = '3'
props['command.mode'..which] = md
name_id_map[name] = 1100+idx
return which
end
local function check_gtk_alt_shortcut(shortcut,name)
-- Alt+<letter> shortcuts don't work for GTK, so handle them directly...
local _,_,letter = shortcut:find('Alt%+([A-Z])$')
if _ then
alt_letter_map[letter:lower()] = name
if not alt_letter_map_init then
alt_letter_map_init = true
scite_OnKey(function(key,shift,ctrl,alt)
if alt and key < 255 then
local ch = string.char(key)
if alt_letter_map[ch] then
scite_MenuCommand(alt_letter_map[ch])
end
end
end)
end
end
end
local function set_shortcut(shortcut,name,which)
if shortcut == 'Context' then
local usr = 'user.context.menu'
if props[usr] == '' then -- force a separator
props[usr] = '|'
end
props[usr] = props[usr]..'|'..name..'|'..(1100+idx)..'|'
else
local cmd = shortcuts_used[shortcut]
if cmd then
print('Error: shortcut already used in "'..cmd..'"')
else
shortcuts_used[shortcut] = name
if GTK then check_gtk_alt_shortcut(shortcut,name) end
props['command.shortcut'..which] = shortcut
end
end
end
-- allows you to bind given Lua functions to shortcut keys
-- without messing around in the properties files!
-- Either a string or a table of strings; the string format is either
-- menu text|Lua command|shortcut
-- or
-- menu text|Lua command|mode|shortcut
-- where 'mode' is the file extension which this command applies to,
-- e.g. 'lua' or 'c', optionally followed by {mode specifier}, where 'mode specifier'
-- is the same as documented under 'command.mode'
-- 'shortcut' can be a usual SciTE key specifier, like 'Alt+R' or 'Ctrl+Shift+F1',
-- _or_ it can be 'Context', meaning that the menu item should also be added
-- to the right-hand click context menu.
function scite_Command(tbl)
if type(tbl) == 'string' then
tbl = {tbl}
end
for i,v in pairs(tbl) do
local name,cmd,mode,shortcut = splitv(v,'|')
if not shortcut then
shortcut = mode
mode = '.*'
else
mode = '.'..mode
end
-- has this command been defined before?
local old_idx = 0
for ii = 10,idx do
if props['command.name.'..ii..mode] == name then old_idx = ii end
end
if old_idx == 0 then
local which = set_command(name,cmd,mode)
if shortcut then
set_shortcut(shortcut,name,which)
end
idx = idx + 1
end
end
end
-- use this to launch Lua Tool menu commands directly by name
-- (commands are not guaranteed to work properly if you just call the Lua function)
function scite_MenuCommand(cmd)
if type(cmd) == 'string' then
cmd = name_id_map[cmd]
if not cmd then return end
end
scite.MenuCommand(cmd)
end
local loaded = {}
local current_filepath
-- this will quietly fail....
local function silent_dofile(f)
if scite_FileExists(f) then
if not loaded[f] then
dofile(f)
loaded[f] = true
end
return true
end
return false
end
function scite_dofile(f)
f = extman_path..'/'..f
silent_dofile(f)
end
function scite_require(f)
local path = lua_path..dirsep..f
if not silent_dofile(path) then
silent_dofile(current_filepath..dirsep..f)
end
end
if not GTK then
scite_dofile 'scite_other.lua'
end
if not scite_DirectoryExists(lua_path) then
print('Error: directory '..lua_path..' not found')
return
end
function load_script_list(script_list,path)
if not script_list then
print('Error: no files found in '..path)
else
current_filepath = path
for i,file in pairs(script_list) do
silent_dofile(file)
end
end
end
-- Load all scripts in the lua_path (usually 'scite_lua'), including within any subdirectories
-- that aren't 'examples' or begin with a '_'
local script_list = scite_Files(lua_path..dirsep..'*.lua')
load_script_list(script_list,lua_path)
local dirs = scite_Directories(lua_path,'^_')
for i,dir in ipairs(dirs) do
load_script_list(scite_Files(dir..dirsep..'*.lua'),dir)
end
function scite_WordAtPos(pos)
if not pos then pos = editor.CurrentPos end
local p2 = editor:WordEndPosition(pos,true)
local p1 = editor:WordStartPosition(pos,true)
if p2 > p1 then
return editor:textrange(p1,p2)
end
end
function scite_GetSelOrWord()
local s = editor:GetSelText()
if s == '' then
return scite_WordAtPos()
else
return s
end
end
--~ scite_Command 'Reload Script|reload_script|Shift+Ctrl+R'
--~ function reload_script()
--~ current_file = scite_CurrentFile()
--~ print('Reloading... '..current_file)
--~ loaded[current_file] = false
--~ silent_dofile(current_file)
--~ end
--~ require"remdebug.engine"
--~ remdebug.engine.start()