1. -- Lua backend for SciTE Sidebar
  2. -- (c) Valentin Schmidt 2016
  3.  
  4. require 'winapi'
  5. require 'wmcopydata'
  6. require 'bit'
  7.  
  8. -- Simple Lua pattern based function detection vor various languages - please improve and add more!
  9.  
  10. local FUNCTION_TABLE = {}
  11.  
  12. -- C/C++
  13. -- Notice: C/C++ is tricky, therefor for now just weak implementation: only works for nicely intended code
  14. FUNCTION_TABLE['c'] = {
  15.   regex = {
  16.     '([%a]+[%a%d_ :]+)%s*%('
  17.   },
  18.   fmt = {
  19.     '%s'
  20.   }
  21. }
  22. FUNCTION_TABLE['cpp'] = FUNCTION_TABLE['c']
  23. FUNCTION_TABLE['cxx'] = FUNCTION_TABLE['c']
  24. FUNCTION_TABLE['cc'] = FUNCTION_TABLE['c']
  25. FUNCTION_TABLE['mm'] = FUNCTION_TABLE['c']
  26.  
  27. -- INI
  28. FUNCTION_TABLE['ini'] = {
  29.   regex = {'%[([%a%d_/%\\ ]*)%]'},
  30.   fmt = {'%%[%s%%]'}
  31. }
  32.  
  33. -- JavaScript
  34. FUNCTION_TABLE['js'] = {
  35.   regex = {
  36.     '[ \t]*function[ \t]+([%a%d_]+)', -- function foo()
  37.     '[ \t]*([%a%d_]+)[ |\t]*=[ |\t]*function[^%%a%%d_]' -- foo = function()
  38.   },
  39.   fmt = {
  40.     '[ \t]*function[ \t]+%s[^%%a%%d_]',
  41.     '[ \t]*%s[ |\t]*=[ |\t]*function[^%%a%%d_]'
  42.   }
  43. }
  44.  
  45. -- Lingo
  46. FUNCTION_TABLE['ls'] = {
  47.   regex = {
  48.     '[ \t]*on[ \t]+([%a%d_]+)'
  49.   },
  50.   fmt = {
  51.     '[ \t]*on[ \t]+%s[^%%a%%d_]'
  52.   }
  53. }
  54. FUNCTION_TABLE['lsw'] = FUNCTION_TABLE['ls']
  55.  
  56. -- Lua
  57. FUNCTION_TABLE['lua'] = {
  58.   regex = {
  59.     '[ \t]*function[ \t]+([%a%d_]+)',
  60.     '[ \t]*local[ \t]+function[ \t]+([%a%d_]+)'
  61.   },
  62.   fmt = {
  63.     '[ \t]*function[ \t]+%s[^%%a%%d_]',
  64.     '[ \t]*local[ \t]+function[ \t]+%s[^%%a%%d_]'
  65.   }
  66. }
  67.  
  68. -- PHP
  69. FUNCTION_TABLE['php'] = {
  70.   regex = {
  71.     '[ \t]*function[ \t]+([%a%d_]*)'
  72.   },
  73.   fmt = {
  74.     '[ \t]*function[ \t]+%s[^%%a%%d_]'
  75.   }
  76. }
  77.  
  78. ----------------------------------------
  79. -- Toggles visibility of sidebar
  80. ----------------------------------------
  81. function toggleSidebar ()
  82.   -- check if sidebar is already open
  83.   local win = winapi.find_window ('QWidget', 'Sidebar')
  84.   if win:get_handle()==0 then
  85.     winapi.shell_exec('open', props['SciteDefaultHome']..'\\lingo\\sidebar.exe')
  86.   else
  87.     if win:is_visible() then
  88.       win:show(0) -- SW_HIDE
  89.     else
  90.       win:show(4) -- SW_SHOWNOACTIVATE
  91.     end
  92.   end
  93. end
  94.  
  95. ----------------------------------------
  96. -- Sends WM_COPDATA message to sidebar
  97. ----------------------------------------
  98. function notifySidebar (data)
  99.   local win = winapi.find_window ('ImlWinCls', '~Sidebar')
  100.   local hwnd = win:get_handle()
  101.   if hwnd~=0 then
  102.     local msg = ':'..hwnd..':'..data
  103.     local res = wmcopydata.send(hwnd, msg)
  104.   end
  105. end
  106.  
  107. ----------------------------------------
  108. -- Called by sidebar: scroll to and select specified function in current buffer
  109. ----------------------------------------
  110. function onFunctionSelected(name)
  111.  
  112.   local ext = getExt(props['FilePath'])
  113.   local t = FUNCTION_TABLE[ext]
  114.   if t==nil then return end
  115.  
  116.   local code = editor:GetText()
  117.   local search
  118.  
  119.   for i,fmt in ipairs(t.fmt) do
  120.  
  121.     search = string.format(fmt, name)
  122.  
  123.     if string.sub(code, 1, string.len(search))==search then
  124.       pos = 0
  125.     else
  126.       pos = string.find(code, "[\n\r]"..search)
  127.     end
  128.  
  129.     if pos then
  130.  
  131.       -- goto line
  132.       local lineNum = editor:LineFromPosition(pos)
  133.       editor:GotoLine(lineNum)
  134.  
  135.       -- select line
  136.       posEnd = editor.LineEndPosition[lineNum]
  137.       editor:SetSel(pos, posEnd) -- Select a range of text.
  138.       editor.FirstVisibleLine = lineNum - 3
  139.       return
  140.     end
  141.   end
  142. end
  143.  
  144. ----------------------------------------
  145. -- Returns lower case extension of specified file
  146. ----------------------------------------
  147. function getExt(fn)
  148.   local i = #fn
  149.   while i > 0 do
  150.     if fn:sub(i,i) == '.' then
  151.       return fn:sub(i+1):lower()
  152.     end
  153.     i = i - 1
  154.   end
  155. end
  156.  
  157. ----------------------------------------
  158. -- Updates the function list in sidebar (if sidebar is running)
  159. ----------------------------------------
  160. function sidebarUpdateFunctions() -- fn
  161.  
  162.   local fn = props['FilePath']
  163.  
  164.   -- send list of functions to sidebar
  165.   local functions = ''
  166.   local ext = getExt(fn)
  167.   local t = FUNCTION_TABLE[ext]
  168.   if t~=nil then
  169.     local code = "\r\n"..editor:GetText()
  170.  
  171.     -- loop over regexps
  172.     for i,regex in ipairs(t.regex) do
  173.       for res in string.gmatch(code, '[\r\n]'..regex) do
  174.         functions = functions..res..','
  175.       end
  176.     end
  177.  
  178.     if (functions) then functions = string.sub(functions, 1, -2) end
  179.  
  180.   end
  181.   notifySidebar("functions:"..functions)
  182. end
  183.  
  184. ----------------------------------------
  185. -- Shows current file (buffer) in Windows Explorer
  186. -- (if file wasn't saved yet, nothing happens)
  187. ----------------------------------------
  188. function showInWindowsExplorer()
  189.   local fn = props['FilePath']
  190.   if fn:sub(#fn, #fn)~='\\' then
  191.     winapi.execute('explorer.exe /select,"'..props['FilePath']..'"')
  192.   end
  193. end
  194.  
  195. ----------------------------------------
  196. -- Shows current file (buffer) in Sidebar Explorer
  197. -- (if sidebar wasn't started or file wasn't saved yet, nothing happens)
  198. ----------------------------------------
  199. function showInSidebarExplorer()
  200.   local fn = props['FilePath']
  201.   if fn:sub(#fn, #fn)~='\\' then
  202.     local win = winapi.find_window ('QWidget', 'Sidebar')
  203.     if win:get_handle()~=0 then
  204.       win:show(4) -- SW_SHOWNOACTIVATE
  205.       notifySidebar("show:"..fn)
  206.     else
  207.       print('>Sidebar not started yet. First start it with Alt+E.')
  208.     end
  209.   end
  210. end
  211.  
  212. -- EVENT HANDLERS --
  213.  
  214. scite_OnOpen(sidebarUpdateFunctions)
  215. scite_OnSwitchFile(sidebarUpdateFunctions)
  216. scite_OnSave(sidebarUpdateFunctions)
  217.  
  218. scite_OnSave(function(fn)
  219.   notifySidebar("saved:"..fn)
  220. end)
  221. scite_OnClose(function(fn)
  222.   if fn~="" then
  223.     notifySidebar("closed:"..fn)
  224.   end
  225. end)
  226.  
  227. -- ADD MENU ITEMS --
  228.  
  229. scite_Command {
  230.   'Sidebar|toggleSidebar|Alt+E',
  231.   'Show in Windows Explorer|showInWindowsExplorer|Alt+W',
  232.   'Show in Sidebar Explorer|showInSidebarExplorer|Alt+X'
  233. }
  234.  
[raw code]