--[[--------------------------------------------------
SideBar.lua
Authors: Frank Wunderlich, mozers™, VladVRO, frs, BioInfo, Tymur Gubayev
version 1.14a
------------------------------------------------------
Note: Require gui.dll
lpeg.dll
shell.dll
COMMON.lua (function GetCurrentWord)
Connection:
In file SciTEStartup.lua add a line:
dofile (props["SciteDefaultHome"].."\\tools\\SideBar.lua")
Set in a file .properties:
command.checked.17.*=$(sidebar.show)
command.name.17.*=SideBar
command.17.*=SideBar_ShowHide
command.mode.17.*=subsystem:lua,savebefore:no
# Set show(1) or hide(0) to SciTE start
sidebar.show=1
# Set default settings for Functions/Procedures List
sidebar.functions.flags=1
sidebar.functions.params=1
--]]--------------------------------------------------
require 'gui'
require 'lpeg'
require 'shell'
-- you can choose to make it a stand-alone window; just uncomment this line:
-- local win = true
-- local _DEBUG = true --включает вывод отладочной информации
-- отображение флагов/параметров по умолчанию:
local _show_flags = tonumber(props['sidebar.functions.flags']) == 1
local _show_params = tonumber(props['sidebar.functions.params']) == 1
local tab_index = 0
local panel_width = 200
local win_height = props['position.height']
if win_height == '' then win_height = 600 end
local style = props['style.*.32']
local colorback = style:match('back:(#%x%x%x%x%x%x)')
local colorfore
if colorback then
colorfore = style:match('fore:(#%x%x%x%x%x%x)')
if colorfore == nil then colorfore = '' end
end
----------------------------------------------------------
-- Common functions
----------------------------------------------------------
local function ReplaceWithoutCase(text, s_find, s_rep)
local i, j = 1
local replaced = nil
repeat
i, j = text:lower():find(s_find:lower(), j, true)
if j == nil then return text, replaced end
text = text:sub(1, i-1)..s_rep..text:sub(j+1)
replaced = true
until false
end
local function ShowCompactedLine(line_num)
local function GetFoldLine(ln)
while editor.FoldExpanded[ln] do ln = ln-1 end
return ln
end
while not editor.LineVisible[line_num] do
local x = GetFoldLine(line_num)
editor:ToggleFold(x)
line_num = x - 1
end
end
if _DEBUG then
local nametotime = {} -- maps names to starttimes
_DEBUG = {}
_DEBUG.timerstart = function (name)
nametotime[name] = os.clock()
end -- _DEBUG.timerstart
_DEBUG.timer = function (name,...)
if nametotime[name] then
local d = os.clock() - nametotime[name]
print(name,('%.5fs'):format(d),...)
end
return d
end -- _DEBUG.timer
_DEBUG.timerstop = function (name,...)
local d = _DEBUG.timer(name,...)
nametotime[name] = nil
return d
end --_DEBUG.timerstop
else
_DEBUG = {}
local empty = function (...) end
_DEBUG.timer, _DEBUG.timerstart, _DEBUG.timerstop = empty, empty, empty
end
----------------------------------------------------------
-- Create panels
----------------------------------------------------------
local tab0 = gui.panel(panel_width + 18)
local memo_path = gui.memo()
tab0:add(memo_path, "top", 22)
if colorback then memo_path:set_memo_colour('', colorback) end
local list_dir_height = win_height/3
if list_dir_height <= 0 then list_dir_height = 200 end
local list_favorites = gui.list(true)
list_favorites:add_column("Favorites", 600)
tab0:add(list_favorites, "bottom", list_dir_height)
if colorback then list_favorites:set_list_colour(colorfore,colorback) end
local list_dir = gui.list()
tab0:client(list_dir)
if colorback then list_dir:set_list_colour(colorfore,colorback) end
tab0:context_menu {
'FileMan: Change Dir|FileMan_ChangeDir',
'FileMan: Show All|FileMan_MaskAllFiles',
'FileMan: Only current ext|FileMan_MaskOnlyCurrentExt',
'', -- separator
'FileMan: Copy to...|FileMan_FileCopy',
'FileMan: Move to...|FileMan_FileMove',
'FileMan: Rename|FileMan_FileRename',
'FileMan: Delete\tDel|FileMan_FileDelete',
'FileMan: Execute|FileMan_FileExec',
'FileMan: Exec with Params|FileMan_FileExecWithParams',
'FileMan: Add to Favorites\tIns|Favorites_AddFile',
'', -- separator
'Favorites: Add active buffer|Favorites_AddCurrentBuffer',
'Favorites: Delete item\tDel|Favorites_DeleteItem',
}
-------------------------
local tab1 = gui.panel(panel_width + 18)
local list_func_height = win_height/3
if list_func_height <= 0 then list_func_height = 200 end
local list_bookmarks = gui.list(true)
list_bookmarks:add_column("@", 24)
list_bookmarks:add_column("Bookmarks", 600)
tab1:add(list_bookmarks, "bottom", list_func_height)
if colorback then list_bookmarks:set_list_colour(colorfore,colorback) end
local list_func = gui.list(true)
list_func:add_column("Functions/Procedures", 600)
tab1:client(list_func)
if colorback then list_func:set_list_colour(colorfore,colorback) end
tab1:context_menu {
'Functions: Sort by Order|Functions_SortByOrder',
'Functions: Sort by Name|Functions_SortByName',
'Functions: Show/Hide Flags|Functions_ToggleFlags',
'Functions: Show/Hide Parameters|Functions_ToggleParams',
}
-------------------------
local tab2 = gui.panel(panel_width + 18)
local list_abbrev = gui.list(true)
list_abbrev:add_column("Abbrev", 60)
list_abbrev:add_column("Expansion", 600)
tab2:client(list_abbrev)
if colorback then list_abbrev:set_list_colour(colorfore,colorback) end
-------------------------
local win_parent
if win then
win_parent = gui.window "Side Bar"
else
win_parent = gui.panel(panel_width)
end
local tabs = gui.tabbar(win_parent)
tabs:add_tab("FileMan", tab0)
tabs:add_tab("Func/Bmk", tab1)
tabs:add_tab("Abbrev", tab2)
win_parent:client(tab2)
win_parent:client(tab1)
win_parent:client(tab0)
if tonumber(props['sidebar.show'])==1 then
if win then
win_parent:size(panel_width + 24, 600)
win_parent:show()
else
gui.set_panel(win_parent,"right")
end
end
----------------------------------------------------------
-- tab0:memo_path Path and Mask
----------------------------------------------------------
local current_path = ''
local file_mask = '*.*'
local function FileMan_ShowPath()
local rtf = [[{\rtf\ansi\ansicpg1251{\fonttbl{\f0\fcharset204 Helv;}}{\colortbl;\red0\green0\blue255;\red255\green0\blue0;}\f0\fs16]]
local path = '\\cf1'..current_path:gsub('\\', '\\\\')
local mask = '\\cf2'..file_mask..'}'
memo_path:set_text(rtf..path..mask)
end
----------------------------------------------------------
-- tab0:list_dir File Manager
----------------------------------------------------------
local function FileMan_ListFILL()
if current_path == '' then return end
local folders = gui.files(current_path..'*', true)
if not folders then return end
list_dir:clear()
list_dir:add_item ('[..]', {'..','d'})
for i, d in ipairs(folders) do
list_dir:add_item('['..d..']', {d,'d'})
end
local files = gui.files(current_path..file_mask)
if files then
for i, filename in ipairs(files) do
list_dir:add_item(filename, {filename})
end
end
list_dir:set_selected_item(0)
FileMan_ShowPath()
end
local function FileMan_GetSelectedItem()
local idx = list_dir:get_selected_item()
if idx == -1 then return '' end
local data = list_dir:get_item_data(idx)
local dir_or_file = data[1]
local attr = data[2]
return dir_or_file, attr
end
function FileMan_ChangeDir()
local newPath = gui.select_dir_dlg('Please change current directory', current_path)
if newPath == nil then return end
if newPath:match('[\\/]$') then
current_path = newPath
else
current_path = newPath..'\\'
end
FileMan_ListFILL()
end
function FileMan_MaskAllFiles()
file_mask = '*.*'
FileMan_ListFILL()
end
function FileMan_MaskOnlyCurrentExt()
local filename, attr = FileMan_GetSelectedItem()
if filename == '' then return end
if attr == 'd' then return end
file_mask = '*.'..filename:gsub('.+%.','')
FileMan_ListFILL()
end
function FileMan_FileCopy()
local filename = FileMan_GetSelectedItem()
if filename == '' or filename == '..' then return end
local path_destination = gui.select_dir_dlg("Copy to...")
if path_destination == nil then return end
os_copy(current_path..filename, path_destination..'\\'..filename)
FileMan_ListFILL()
end
function FileMan_FileMove()
local filename = FileMan_GetSelectedItem()
if filename == '' or filename == '..' then return end
local path_destination = gui.select_dir_dlg("Move to...")
if path_destination == nil then return end
os.rename(current_path..filename, path_destination..'\\'..filename)
FileMan_ListFILL()
end
function FileMan_FileRename()
local filename = FileMan_GetSelectedItem()
if filename == '' or filename == '..' then return end
local filename_new = shell.inputbox("Rename", "Enter new file name:", filename, function(name) return not name:match('[\\/:|*?"<>]') end)
if filename_new == nil then return end
if filename_new ~= '' and filename_new ~= filename then
os.rename(current_path..filename, current_path..filename_new)
FileMan_ListFILL()
end
end
function FileMan_FileDelete()
local filename, attr = FileMan_GetSelectedItem()
if filename == '' then return end
if attr == 'd' then return end
if shell.msgbox("Are you sure you want to DELETE this file?\n"..filename, "DELETE", 4+256) == 6 then
-- if gui.message("Are you sure you want to DELETE this file?\n"..filename, "query") then
os.remove(current_path..filename)
FileMan_ListFILL()
end
end
local function FileMan_FileExecWithSciTE(cmd, mode)
local p0 = props["command.0.*"]
local p1 = props["command.mode.0.*"]
props["command.name.0.*"] = 'tmp'
props["command.0.*"] = cmd
if mode == nil then mode = 'console' end
props["command.mode.0.*"] = 'subsystem:'..mode..',savebefore:no'
scite.MenuCommand(9000)
props["command.0.*"] = p0
props["command.mode.0.*"] = p1
end
function FileMan_FileExec(params)
if params == nil then params = '' end
local filename = FileMan_GetSelectedItem()
if filename == '' then return end
local file_ext = filename:match("[^.]+$")
if file_ext == nil then return end
file_ext = '%*%.'..string.lower(file_ext)
local cmd = ''
local function CommandBuild(lng)
local cmd = props['command.build.$(file.patterns.'..lng..')']
cmd = cmd:gsub(props["FilePath"], current_path..filename)
return cmd
end
-- Lua
if string.match(props['file.patterns.lua'], file_ext) ~= nil then
dostring(params)
dofile(current_path..filename)
-- Batch
elseif string.match(props['file.patterns.batch'], file_ext) ~= nil then
FileMan_FileExecWithSciTE(CommandBuild('batch'))
return
-- WSH
elseif string.match(props['file.patterns.wscript']..props['file.patterns.wsh'], file_ext) ~= nil then
FileMan_FileExecWithSciTE(CommandBuild('wscript'))
-- Other
else
local ret, descr = shell.exec(current_path..filename..params)
if not ret then
print (">Exec: "..filename)
print ("Error: "..descr)
end
end
end
function FileMan_FileExecWithParams()
if scite.ShowParametersDialog('Exec "'..FileMan_GetSelectedItem()..'". Please set params:') then
local params = ''
for p = 1, 4 do
local ps = props[tostring(p)]
if ps ~= '' then
params = params..' '..ps
end
end
FileMan_FileExec(params)
end
end
local function OpenFile(filename)
if filename:match(".session$") ~= nil then
filename = filename:gsub('\\','\\\\')
scite.Perform ("loadsession:"..filename)
else
scite.Open(filename)
end
gui.pass_focus()
end
local function FileMan_OpenItem()
local dir_or_file, attr = FileMan_GetSelectedItem()
if dir_or_file == '' then return end
if attr == 'd' then
gui.chdir(dir_or_file)
if dir_or_file == '..' then
local new_path = current_path:gsub('(.*\\).*\\$', '%1')
if not gui.files(new_path..'*',true) then return end
current_path = new_path
else
current_path = current_path..dir_or_file..'\\'
end
FileMan_ListFILL()
else
OpenFile(current_path..dir_or_file)
end
end
list_dir:on_double_click(function()
FileMan_OpenItem()
end)
list_dir:on_key(function(key)
if key == 13 then -- Enter
FileMan_OpenItem()
elseif key == 8 then -- BackSpace
list_dir:set_selected_item(0)
FileMan_OpenItem()
elseif key == 46 then -- Delete
FileMan_FileDelete()
elseif key == 45 then -- Insert
Favorites_AddFile()
end
end)
----------------------------------------------------------
-- tab0:list_favorites Favorites
----------------------------------------------------------
local favorites_filename = props['SciteUserHome']..'\\favorites.lst'
local list_fav_table = {}
local function Favorites_ListFILL()
list_favorites:clear()
table.sort(list_fav_table,
function(a, b)
local function IsSession(filepath)
return filepath:gsub('^.*%.',''):upper() == 'SESSION'
end
local isAses = IsSession(a)
local isBses = IsSession(b)
if (isAses and isBses) or not (isAses or isBses) then
return a < b
else
return isAses
end
end
)
for _, s in ipairs(list_fav_table) do
list_favorites:add_item(s:gsub('.+\\',''), s)
end
end
local function Favorites_OpenList()
local favorites_file = io.open(favorites_filename)
if favorites_file then
for line in favorites_file:lines() do
if line ~= '' then
line = ReplaceWithoutCase(line, '$(SciteDefaultHome)', props['SciteDefaultHome'])
list_fav_table[#list_fav_table+1] = line
end
end
favorites_file:close()
end
Favorites_ListFILL()
end
Favorites_OpenList()
local function Favorites_SaveList()
io.output(favorites_filename)
local list_string = table.concat(list_fav_table,'\n')
list_string = ReplaceWithoutCase(list_string, props['SciteDefaultHome'], '$(SciteDefaultHome)')
io.write(list_string)
io.close()
end
function Favorites_AddFile()
local filename, attr = FileMan_GetSelectedItem()
if filename == '' then return end
if attr == 'd' then return end
list_fav_table[#list_fav_table+1] = current_path..filename
Favorites_ListFILL()
end
function Favorites_AddCurrentBuffer()
list_fav_table[#list_fav_table+1] = props['FilePath']
Favorites_ListFILL()
end
function Favorites_DeleteItem()
local idx = list_favorites:get_selected_item()
if idx == -1 then return end
list_favorites:delete_item(idx)
table.remove (list_fav_table, idx+1)
end
local function Favorites_OpenFile()
local idx = list_favorites:get_selected_item()
if idx == -1 then return end
local filename = list_favorites:get_item_data(idx)
OpenFile(filename)
end
local function Favorites_ShowFilePath()
local sel_item = list_favorites:get_selected_item()
if sel_item == -1 then return end
local expansion = list_favorites:get_item_data(sel_item)
editor:CallTipCancel()
editor:CallTipShow(-2, expansion)
end
list_favorites:on_select(function()
Favorites_ShowFilePath()
end)
list_favorites:on_double_click(function()
Favorites_OpenFile()
end)
list_favorites:on_key(function(key)
if key == 13 then -- Enter
Favorites_OpenFile()
elseif key == 46 then -- Delete
Favorites_DeleteItem()
end
end)
----------------------------------------------------------
-- tab1:list_func Functions/Procedures
----------------------------------------------------------
local table_functions = {}
-- 1 - function names
-- 2 - line number
-- 3 - function paramaters with parentheses
local _sort = 'order'
local _backjumppos -- store position if jumping
local Lang2lpeg = {}
do
local P, V, Cg, Ct, Cc, S, R, C, Carg, Cf, Cb, Cp, Cmt = lpeg.P, lpeg.V, lpeg.Cg, lpeg.Ct, lpeg.Cc, lpeg.S, lpeg.R, lpeg.C, lpeg.Carg, lpeg.Cf, lpeg.Cb, lpeg.Cp, lpeg.Cmt
--@todo: переписать с использованием lpeg.Cf
local function AnyCase(str)
local res = P'' --empty pattern to start with
local ch, CH
for i = 1, #str do
ch = str:sub(i,i):lower()
CH = ch:upper()
res = res * S(CH..ch)
end
assert(res:match(str))
return res
end
local PosToLine = function (pos) return editor:LineFromPosition(pos) end
--v------- common patterns -------v--
-- basics
local EOF = P(-1)
local BOF = P(function(s,i) return (i==1) and 1 end)
local NL = P"\n"-- + P"\f" -- pattern matching newline, platform-specific. \f = page break marker
local AZ = R('AZ','az')+"_"
local N = R'09'
local ANY = P(1)
local ESCANY = P'\\'*ANY + ANY
local SINGLESPACE = S'\n \t\r\f'
local SPACE = SINGLESPACE^1
-- simple tokens
local IDENTIFIER = AZ * (AZ+N)^0 -- simple identifier, without separators
local Str1 = P'"' * ( ESCANY - (S'"'+NL) )^0 * (P'"' + NL)--NL == error'unfinished string')
local Str2 = P"'" * ( ESCANY - (S"'"+NL) )^0 * (P"'" + NL)--NL == error'unfinished string')
local STRING = Str1 + Str2
-- c-like-comments
local line_comment = '//' * (ESCANY - NL)^0*NL
local block_comment = '/*' * (ESCANY - P'*/')^0 * (P('*/') + EOF)
local COMMENT = (line_comment + block_comment)^1
local SC = SPACE + COMMENT
local IGNORED = SPACE + COMMENT + STRING
-- special captures
local cp = Cp() -- pos capture, Carg(1) is the shift value, comes from start_code_pos
local cl = cp/PosToLine -- line capture, uses editor:LineFromPosition
local par = C(P"("*(1-P")")^0*P")") -- captures parameters in parentheses
--^------- common patterns -------^--
do --v------- asm -------v--
-- redefine common patterns
local SPACE = S' \t'^1
local NL = P"\r\n"
local IGNORED = (ESCANY - NL)^0 * NL -- just skip line by line
-- define local patterns
local p = P"proc"
local F = P"FRAME"
-- create flags:
F = Cg(F*Cc(true),'F')
-- create additional captures
I = C(IDENTIFIER)*cl
-- definitions to capture:
local par = C((ESCANY - NL)^0)
local def1 = I*SPACE*(p+F)
local def2 = p*SPACE*I*P','^-1
local def = (SPACE+P'')*Ct((def1+def2)*(SPACE*par)^-1)*NL
-- resulting pattern, which does the work
local patt = (def + IGNORED + 1)^0 * EOF
Lang2lpeg.Assembler = lpeg.Ct(patt)
end --do --^------- ASM -------^--
do --v------- Lua -------v--
-- redefine common patterns
local IDENTIFIER = IDENTIFIER*(P'.'*IDENTIFIER)^0*(P':'*IDENTIFIER)^-1
-- LONG BRACKETS
local long_brackets = #(P'[' * P'='^0 * P'[') *
function (subject, i1)
local level = _G.assert( subject:match('^%[(=*)%[', i1) )
local _, i2 = subject:find(']'..level..']', i1, true) -- true = plain "find substring"
return (i2 and (i2+1)) or #subject+1--error('unfinished long brackets')
-- ^ if unfinished long brackets then capture till EOF (at #subject+1)
end
local LUALONGSTR = long_brackets
local multi = P'--' * long_brackets
local single = P'--' * (1 - NL)^0 * NL
local COMMENT = multi + single
local SC = SPACE + COMMENT
local IGNORED = SPACE + COMMENT + STRING + LUALONGSTR
-- define local patterns
local f = P"function"
local l = P"local"
-- create flags
l = Cg(l*SC^1*Cc(true),'l')^-1
-- create additional captures
I = C(IDENTIFIER)*cl
-- definitions to capture:
local funcdef1 = l*f*SC^1*I*SC^0*par -- usual function declaration
local funcdef2 = l*I*SC^0*"="*SC^0*f*SC^0*par -- declaration through assignment
local def = Ct(funcdef1 + funcdef2)
-- resulting pattern, which does the work
local patt = (def + IGNORED^1 + IDENTIFIER + 1)^0 * (EOF) --+ error'invalid character')
Lang2lpeg.Lua = lpeg.Ct(patt)
end --do --^------- Lua -------^--
do --v----- Pascal ------v--
-- redefine common patterns
local IDENTIFIER = IDENTIFIER*(P'.'*IDENTIFIER)^0
local STRING = P"'" *( ANY - (P"'"+NL) )^0 *(P"'"+NL) --NL == error'unfinished string')
--^ there's no problem with pascal strings with double single quotes in the middle, like this:
-- 'first''second'
-- in the loop, STRING just matches the 'first'-part, and then the 'second'.
local multi1 = P'(*' *(1-P'*)')^0 * (P'*)' + EOF)--unfinished long comment
local multi2 = P'{' *(1-P'}')^0 * (P'}' + EOF)--unfinished long comment
local single = P'//' * (1 - NL)^0 * NL
local COMMENT = multi1 + multi2 + single
local SC = SPACE + COMMENT
local IGNORED = SPACE + COMMENT + STRING
-- define local patterns
local f = AnyCase"function"
local p = AnyCase"procedure"
local c = AnyCase"constructor"
local d = AnyCase"destructor"
local restype = AZ^1
-- create flags:
-- f = Cg(f*Cc(true),'f')
restype = Cg(C(restype),'')
p = Cg(p*Cc(true),'p')
c = Cg(c*Cc(true),'c')
d = Cg(d*Cc(true),'d')
-- create additional captures
local I = C(IDENTIFIER)*cl
-- definitions to capture:
local procdef = Ct((p+c+d)*SC^1*I*SC^0*par^-1)
local funcdef = Ct(f*SC^1*I*SC^0*par^-1*SC^0*P':'*SC^0*restype*SC^0*P';')
-- resulting pattern, which does the work
local patt = (procdef + funcdef + IGNORED^1 + IDENTIFIER + 1)^0 * EOF
Lang2lpeg.Pascal = lpeg.Ct(patt)
end --^----- Pascal ------^--
do --v----- C++ ------v--
-- define local patterns
local keywords = P'if'+P'else'+P'switch'+P'case'+P'while'+P'for'
local nokeyword = -(keywords)
local type = P"static "^-1*P"const "^-1*P"enum "^-1*P'*'^-1*IDENTIFIER*P'*'^-1
local funcbody = P"{"*(ESCANY-P"}")^0*P"}"
-- redefine common patterns
local IDENTIFIER = P'*'^-1*P'~'^-1*IDENTIFIER
IDENTIFIER = IDENTIFIER*(P"::"*IDENTIFIER)^-1
-- create flags:
type = Cg(type,'')
-- create additional captures
local I = nokeyword*C(IDENTIFIER)*cl
-- definitions to capture:
local funcdef = nokeyword*Ct((type*SC^1)^-1*I*SC^0*par*SC^0*(#funcbody))
local classconstr = nokeyword*Ct((type*SC^1)^-1*I*SC^0*par*SC^0*P':'*SC^0*IDENTIFIER*SC^0*(P"("*(1-P")")^0*P")")*SC^0*(#funcbody)) -- this matches smthing like PrefDialog::PrefDialog(QWidget *parent, blabla) : QDialog(parent)
-- resulting pattern, which does the work
local patt = (classconstr + funcdef + IGNORED^1 + IDENTIFIER + ANY)^0 * EOF
Lang2lpeg['C++'] = lpeg.Ct(patt)
end --^----- C++ ------^--
do --v----- JS ------v--
-- redefine common patterns
local NL = NL + P"\f"
local regexstr = P'/' * (ESCANY - (P'/' + NL))^0*(P'/' * S('igm')^0 + NL)
local STRING = STRING + regexstr
-- define local patterns
local f = P"function"
local funcbody = P"{"*(ESCANY-P"}")^0*P"}"
-- create additional captures
local I = C(IDENTIFIER)*cl
-- definitions to capture:
local funcdef = Ct(f*SC^1*I*SC^0*par*SC^0*(#funcbody))
-- resulting pattern, which does the work
local patt = (funcdef + IGNORED^1 + IDENTIFIER + 1)^0 * EOF
Lang2lpeg.JScript = lpeg.Ct(patt)
end --^----- JS ------^--
do --v----- VB ------v--
-- redefine common patterns
local STRING = P'"' * (ANY - (P'"' + NL))^0*(P'"' + NL)
local COMMENT = (P"'" + P"REM ") * (ANY - NL)^0*NL
local SC = SPACE
-- define local patterns
local f = AnyCase"function"
local p = AnyCase"property"
local let = AnyCase"let"
local get = AnyCase"get"
local set = AnyCase"set"
local s = AnyCase"sub"
-- create flags:
-- f = Cg(f*Cc(true),'f')
local restype = (P"As"+P"as")*SPACE*Cg(C(AZ^1),'')
let = Cg(let*Cc(true),'pl')
get = Cg(get*Cc(true),'pg')
set = Cg(set*Cc(true),'ps')
p = p*SC^1*(let+get+set)
-- create additional captures
local I = C(IDENTIFIER)*cl
-- definitions to capture:
f = f*SC^1*I*SC^0*par
p = p*SC^1*I*SC^0*par
s = s*SC^1*I*SC^0*par
local def = Ct((f + s + p)*(SPACE*restype)^-1)
-- resulting pattern, which does the work
local patt = (def + IGNORED^1 + IDENTIFIER + 1)^0 * EOF
Lang2lpeg.VisualBasic = lpeg.Ct(patt)
end --^----- VB ------^--
do --v------- Python -------v--
-- redefine common patterns
local SPACE = S' \t'^1
local IGNORED = (ESCANY - NL)^0 * NL -- just skip line by line
-- define local patterns
local c = P"class"
local d = P"def"
-- create flags:
c = Cg(c*Cc(true),'class')
-- create additional captures
I = C(IDENTIFIER)*cl
-- definitions to capture:
local def = (c+d)*SPACE*I
def = (SPACE+P'')*Ct(def*SPACE^-1*par)*SPACE^-1*P':'
-- resulting pattern, which does the work
local patt = (def + IGNORED + 1)^0 * EOF
Lang2lpeg.Python = lpeg.Ct(patt)
end --do --^------- Python -------^--
do --v------- nnCron -------v--
-- redefine common patterns
local IDENTIFIER = (ANY - SPACE)^1
local SPACE = S' \t'^1
local IGNORED = (ESCANY - NL)^0 * NL -- just skip line by line
-- define local patterns
local d = P":"
-- create additional captures
I = C(IDENTIFIER)*cl
-- definitions to capture:
local def = d*SPACE*I
def = Ct(def*(SPACE*par)^-1)*IGNORED
-- resulting pattern, which does the work
local patt = (def + IGNORED + 1)^0 * EOF
Lang2lpeg.nnCron = lpeg.Ct(patt)
end --do --^------- nnCron -------^--
do --v------- CSS -------v--
-- helper
local function clear_spaces(s)
return s:gsub('%s+',' ')
end
-- redefine common patterns
local IDENTIFIER = (ANY - SPACE)^1
local NL = P"\r\n"
local SPACE = S' \t'^1
local IGNORED = (ANY - NL)^0 * NL -- just skip line by line
local par = C(P"{"*(1-P"}")^0*P"}")/clear_spaces -- captures parameters in parentheses
-- create additional captures
I = C(IDENTIFIER)*cl
-- definitions to capture:
local def = Ct(I*SPACE*par)--*IGNORED
-- resulting pattern, which does the work
local patt = (def + IGNORED + 1)^0 * EOF
Lang2lpeg.CSS = lpeg.Ct(patt)
end --do --^------- CSS -------^--
do --v----- * ------v--
-- redefine common patterns
local NL = P"\r\n"+P"\n"+P"\f"
local SC = S" \t\160" -- без понятия что за символ с кодом 160, но он встречается в SciTEGlobal.properties непосредственно после [Warnings] 10 раз.
local COMMENT = P'#'*(ANY - NL)^0*NL
-- define local patterns
local somedef = S'fFsS'*S'uU'*S'bBnN'*AZ^0 --пытаемся поймать что-нибудь, похожее на определение функции...
local section = P'['*(ANY-P']')^1*P']'
-- create flags
local somedef = Cg(somedef, '')
-- create additional captures
local I = C(IDENTIFIER)*cl
section = C(section)*cl
local tillNL = C((ANY-NL)^0)
-- definitions to capture:
local def1 = Ct(somedef*SC^1*I*SC^0*(par+tillNL))
local def2 = (NL+BOF)*Ct(section*SC^0*tillNL)*NL
-- resulting pattern, which does the work
local patt = (def2 + def1 + COMMENT + IDENTIFIER + 1)^0 * EOF
-- local patt = (def2 + def1 + IDENTIFIER + 1)^0 * EOF -- чуть медленнее
Lang2lpeg['*'] = lpeg.Ct(patt)
end --^----- * ------^--
end
local Lang2CodeStart = {
['Pascal']='^IMPLEMENTATION$',
}
local Lexer2Lang = {
['asm']='Assembler',
['cpp']='C++',
['js']='JScript',
['vb']='VisualBasic',
['vbscript']='VisualBasic',
['css']='CSS',
['pascal']='Pascal',
['python']='Python',
['lua']='Lua',
['nncrontab']='nnCron',
}
local Ext2Lang = {}
do -- Fill_Ext2Lang
local patterns = {
[props['file.patterns.asm']]='Assembler',
[props['file.patterns.cpp']]='C++',
[props['file.patterns.wsh']]='JScript',
[props['file.patterns.vb']]='VisualBasic',
[props['file.patterns.wscript']]='VisualBasic',
['*.css']='CSS',
[props['file.patterns.pascal']]='Pascal',
[props['file.patterns.py']]='Python',
[props['file.patterns.lua']]='Lua',
[props['file.patterns.nncron']]='nnCron',
}
for i,v in pairs(patterns) do
for ext in (i..';'):gfind("%*%.([^;]+);") do
Ext2Lang[ext] = v
end
end
end -- Fill_Ext2Lang
local function GetFlagsAndCut(findString)
local findString = findString
local t = {}
findString,f = ReplaceWithoutCase(findString, "Sub ", "") -- VB
t["s"] = f and true
findString,f = ReplaceWithoutCase(findString, "Function ", "") -- JS, VB,...
t["f"] = f and true
findString,f = ReplaceWithoutCase(findString, "Procedure ", "") -- Pascal
t["p"] = f and true
findString,f = ReplaceWithoutCase(findString, "Proc ", "") -- C
t["p"] = t.p or (f and true)
findString,f = ReplaceWithoutCase(findString, "Property Let ", "") -- VB
t["pl"] = f and true
findString,f = ReplaceWithoutCase(findString, "Property Get ", "") -- VB
t["pg"] = f and true
findString,f = ReplaceWithoutCase(findString, "Property Set ", "") -- VB
t["ps"] = f and true
findString,f = ReplaceWithoutCase(findString, "CLASS ", "") -- Phyton
t["c"] = f and true
findString,f = ReplaceWithoutCase(findString, "DEF ", "") -- Phyton
t["d"] = f and true
return findString, t
end
local function Functions_GetNames()
_DEBUG.timerstart('Functions_GetNames')
table_functions = {}
if editor.Length == 0 then return end
local ext = props["FileExt"]:lower() -- a bit unsafe...
local lang = Ext2Lang[ext]
local start_code = Lang2CodeStart[lang]
local lpegPattern = Lang2lpeg[lang]
if not lpegPattern then
lang = Lexer2Lang[editor.LexerLanguage]
start_code = Lang2CodeStart[lang]
lpegPattern = Lang2lpeg[lang]
if not tablePattern then
start_code = Lang2CodeStart['*']
lpegPattern = Lang2lpeg['*']
end
end
local textAll = editor:GetText()
local start_code_pos = start_code and editor:findtext(start_code, SCFIND_REGEXP) or 0
-- lpegPattern = nil
table_functions = lpegPattern:match(textAll, start_code_pos+1) -- 2nd arg is the symbol index to start with
_DEBUG.timerstop('Functions_GetNames','lpeg')
end
local function Functions_ListFILL()
if tonumber(props['sidebar.show'])~=1 or tab_index~=1 then return end
if _sort == 'order' then
table.sort(table_functions, function(a, b) return a[2] < b[2] end)
else
table.sort(table_functions, function(a, b) return a[1]:lower() < b[1]:lower() end)
end
-- remove duplicates
for i = #table_functions, 2, -1 do
if table_functions[i][2] == table_functions[i-1][2] then
table.remove (table_functions, i)
end
end
list_func:clear()
local function emptystr(...) return '' end
local function GetParams (funcitem)
return (funcitem[3] and ' '..funcitem[3]) or ''
end
local function GetFlags (funcitem)
local res = ''
local add = ''
for flag,value in pairs(funcitem) do
if type(flag)=='string' then
if type(value)=='string' then add = flag .. value
elseif type(value)=='number' then add = flag..':'..value
else add = flag end
res = res .. '['.. add ..']'
end
end
if res~='' then res = res .. ' ' end
return res or ''
end
if not _show_params then GetParams = emptystr end
if not _show_flags then GetFlags = emptystr end
local function fixname (funcitem)
return GetFlags(funcitem)..funcitem[1]..GetParams(funcitem)
end
for _, a in ipairs(table_functions) do
list_func:add_item(fixname(a), a[2])
end
end
function Functions_SortByOrder()
_sort = 'order'
Functions_ListFILL()
end
function Functions_SortByName()
_sort = 'name'
Functions_ListFILL()
end
function Functions_ToggleParams ()
_show_params = not _show_params
Functions_ListFILL()
end
function Functions_ToggleFlags ()
_show_flags = not _show_flags
Functions_ListFILL()
end
local function Functions_GotoLine()
local sel_item = list_func:get_selected_item()
if sel_item == -1 then return end
local pos = list_func:get_item_data(sel_item)
if pos then
ShowCompactedLine(pos)
editor:GotoLine(pos)
gui.pass_focus()
end
end
list_func:on_double_click(function()
Functions_GotoLine()
end)
list_func:on_key(function(key)
if key == 13 then -- Enter
Functions_GotoLine()
end
end)
----------------------------------------------------------
-- tab1:list_bookmarks Bookmarks
----------------------------------------------------------
local table_bookmarks = {}
local function GetBufferNumber()
local buf = props['BufferNumber']
if buf == '' then buf = 1 else buf = tonumber(buf) end
return buf
end
local function Bookmark_Add(line_number)
local line_text = editor:GetLine(line_number)
if line_text == nil then line_text = '' end
line_text = line_text:gsub('^%s+', ''):gsub('%s+', ' ')
if line_text == '' then
line_text = ' - empty line - ('..(line_number+1)..')'
end
for _, a in ipairs(table_bookmarks) do
if a.FilePath == props['FilePath'] and a.LineNumber == line_number then
return end
end
local bmk = {}
bmk.FilePath = props['FilePath']
bmk.BufferNumber = GetBufferNumber()
bmk.LineNumber = line_number
bmk.LineText = line_text
table_bookmarks[#table_bookmarks+1] = bmk
end
local function Bookmark_Delete(line_number)
for i = #table_bookmarks, 1, -1 do
if table_bookmarks[i].FilePath == props['FilePath'] then
if line_number == nil then
table.remove(table_bookmarks, i)
elseif table_bookmarks[i].LineNumber == line_number then
table.remove(table_bookmarks, i)
break
end
end
end
end
local function Bookmarks_ListFILL()
if tonumber(props['sidebar.show'])~=1 or tab_index~=1 then return end
table.sort(table_bookmarks, function(a, b)
return a.BufferNumber < b.BufferNumber or
a.BufferNumber == b.BufferNumber and
a.LineNumber < b.LineNumber
end)
list_bookmarks:clear()
for _, bmk in ipairs(table_bookmarks) do
list_bookmarks:add_item({bmk.BufferNumber, bmk.LineText}, {bmk.FilePath, bmk.LineNumber})
end
end
local function Bookmarks_RefreshTable()
Bookmark_Delete()
for i = 0, editor.LineCount do
if editor:MarkerGet(i) == 2 then
Bookmark_Add(i)
end
end
Bookmarks_ListFILL()
end
local function Bookmarks_GotoLine()
local sel_item = list_bookmarks:get_selected_item()
if sel_item == -1 then return end
local pos = list_bookmarks:get_item_data(sel_item)
if pos then
scite.Open(pos[1]) -- FilePath
ShowCompactedLine(pos[2]) -- LineNumber
editor:GotoLine(pos[2])
gui.pass_focus()
end
end
list_bookmarks:on_double_click(function()
Bookmarks_GotoLine()
end)
list_bookmarks:on_key(function(key)
if key == 13 then -- Enter
Bookmarks_GotoLine()
end
end)
-- Add user event handler OnClose
local old_OnClose = OnClose
function OnClose(file)
local result
if old_OnClose then result = old_OnClose(file) end
for i = #table_bookmarks, 1, -1 do
if table_bookmarks[i].FilePath == file then
table.remove(table_bookmarks, i)
end
end
Bookmarks_ListFILL()
return result
end
----------------------------------------------------------
-- tab2:list_abbrev Abbreviations
----------------------------------------------------------
local function Abbreviations_ListFILL()
local function ReadAbbrev(file)
local abbrev_file = io.open(file)
if abbrev_file then
for line in abbrev_file:lines() do
if line ~= '' then
local _abr, _exp = line:match('^([^#].-)=(.+)')
if _abr ~= nil then
list_abbrev:add_item({_abr, _exp}, _exp)
else
local import_file = line:match('^import%s+(.+)')
if import_file ~= nil then
ReadAbbrev(file:match('.+\\')..import_file)
end
end
end
end
abbrev_file:close()
end
end
list_abbrev:clear()
local abbrev_filename = props['AbbrevPath']
ReadAbbrev(abbrev_filename)
end
local function Abbreviations_InsertExpansion()
local sel_item = list_abbrev:get_selected_item()
if sel_item == -1 then return end
local expansion = list_abbrev:get_item_data(sel_item)
scite.InsertAbbreviation(expansion)
gui.pass_focus()
editor:CallTipCancel()
end
local function Abbreviations_ShowExpansion()
local sel_item = list_abbrev:get_selected_item()
if sel_item == -1 then return end
local expansion = list_abbrev:get_item_data(sel_item)
expansion = expansion:gsub('\\\\','\4'):gsub('\\r','\r'):gsub('(\\n','\n'):gsub('\\t','\t'):gsub('\4','\\')
editor:CallTipCancel()
editor:CallTipShow(editor.CurrentPos, expansion)
end
list_abbrev:on_double_click(function()
Abbreviations_InsertExpansion()
end)
list_abbrev:on_select(function()
Abbreviations_ShowExpansion()
end)
list_abbrev:on_key(function(key)
if key == 13 then -- Enter
Abbreviations_InsertExpansion()
end
end)
----------------------------------------------------------
-- Events
----------------------------------------------------------
local line_count
local function OnSwitch()
_DEBUG.timerstart('OnSwitch')
line_count = editor.LineCount
if tab0:bounds() then -- visible FileMan
local path = props['FileDir']
if path == '' then return end
path = path:gsub('\\$','')..'\\'
if path ~= current_path then
current_path = path
FileMan_ListFILL()
end
elseif tab1:bounds() then -- visible Funk/Bmk
Functions_GetNames()
Functions_ListFILL()
Bookmarks_ListFILL()
elseif tab2:bounds() then -- visible Abbrev
Abbreviations_ListFILL()
end
_DEBUG.timerstop('OnSwitch')
end
tabs:on_select(function(ind)
tab_index=ind
OnSwitch()
end)
-- Скрытие / показ панели
function SideBar_ShowHide()
if tonumber(props['sidebar.show'])==1 then
if win then
win_parent:hide()
else
gui.set_panel()
end
props['sidebar.show']=0
else
if win then
win_parent:show()
else
gui.set_panel(win_parent,"right")
end
props['sidebar.show']=1
OnSwitch()
end
end
local function OnDocumentCountLinesChanged(def_line_count)
if tab1:bounds() then -- visible Funk/Bmk
local cur_line = editor:LineFromPosition(editor.CurrentPos)
for i = 1, #table_functions do
local table_line = table_functions[i][2]
if table_line > cur_line then
table_functions[i][2] = table_line + def_line_count
end
end
Functions_ListFILL()
Bookmarks_RefreshTable()
end
end
-- Add user event handler OnSwitchFile
local old_OnSwitchFile = OnSwitchFile
function OnSwitchFile(file)
local result
if old_OnSwitchFile then result = old_OnSwitchFile(file) end
OnSwitch()
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
OnSwitch()
return result
end
-- Add user event handler OnUpdateUI
local old_OnUpdateUI = OnUpdateUI
function OnUpdateUI()
local result
if old_OnUpdateUI then result = old_OnUpdateUI() end
if (editor.Focus and line_count) then
local line_count_new = editor.LineCount
local def_line_count = line_count_new - line_count
if def_line_count ~= 0 then
OnDocumentCountLinesChanged(def_line_count)
line_count = line_count_new
end
end
return result
end
-- Add user event handler OnSave
local old_OnSave = OnSave
function OnSave(file)
local result
if old_OnSave then result = old_OnSave(file) end
Functions_GetNames()
Functions_ListFILL()
return result
end
-- Add user event handler OnSendEditor
local old_OnSendEditor = OnSendEditor
function OnSendEditor(id_msg, wp, lp)
local result
if old_OnSendEditor then result = old_OnSendEditor(id_msg, wp, lp) end
if id_msg == SCI_MARKERADD then
if lp == 1 then Bookmark_Add(wp) Bookmarks_ListFILL() end
elseif id_msg == SCI_MARKERDELETE then
if lp == 1 then Bookmark_Delete(wp) Bookmarks_ListFILL() end
elseif id_msg == SCI_MARKERDELETEALL then
if wp == 1 then Bookmark_Delete() Bookmarks_ListFILL() end
end
return result
end
-- Add user event handler OnFinalise
local old_OnFinalise = OnFinalise
function OnFinalise()
local result
if old_OnFinalise then result = old_OnFinalise() end
Favorites_SaveList()
return result
end
----------------------------------------------------------
-- Go to function definition
----------------------------------------------------------
-- По имени функции находим строку с ее объявлением (инфа берется из table_functions)
local function Func2Line(funcname)
if not next(table_functions) then
Functions_GetNames()
end
for i = 1, #table_functions do
if funcname == table_functions[i][1] then
return table_functions[i][2]
end
end
end
-- Переход на строку с объявлением функции
local function JumpToFuncDefinition()
local funcname = GetCurrentWord()
local line = Func2Line(funcname)
if line then
_backjumppos = editor.CurrentPos
editor:GotoLine(line)
return true
end
return false
end
local function JumpBack()
if not _backjumppos then return false end
editor:GotoPos(_backjumppos)
_backjumppos = nil
return true
end
-- Add user event handler OnDoubleClick
local old_OnDoubleClick = OnDoubleClick
function OnDoubleClick(shift, ctrl, alt)
local result
if old_OnDoubleClick then result = old_OnDoubleClick(shift, ctrl, alt) end
if shift then
if JumpToFuncDefinition() then return true end
end
return result
end
-- Add user event handler OnKey
local old_OnKey = OnKey
function OnKey(key, shift, ctrl, alt, char)
local result
if old_OnKey then result = old_OnKey(key, shift, ctrl, alt, char) end
if (editor.Focus) then
if ctrl and key == 188 and JumpBack() then return true end --char == ','
if ctrl and key == 190 and JumpToFuncDefinition() then return true end --char == '.'
end
return result
end