1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
|
-- Copyright 2014-15 Paul Kulchenko, ZeroBrane LLC
local ide = ide
ide.outline = {
outlineCtrl = nil,
imglist = ide:CreateImageList("OUTLINE", "FILE-NORMAL", "VALUE-LCALL",
"VALUE-GCALL", "VALUE-ACALL", "VALUE-SCALL", "VALUE-MCALL"),
settings = {
symbols = {},
ignoredirs = {},
},
needsaving = false,
indexqueue = {[0] = {}},
indexpurged = false, -- flag that the index has been purged from old records; once per session
}
local outline = ide.outline
local image = { FILE = 0, LFUNCTION = 1, GFUNCTION = 2, AFUNCTION = 3,
SMETHOD = 4, METHOD = 5,
}
local q = EscapeMagic
local caches = {}
local function setData(ctrl, item, value)
if ide.wxver >= "2.9.5" then
local data = wx.wxLuaTreeItemData()
data:SetData(value)
ctrl:SetItemData(item, data)
end
end
local function resetOutlineTimer()
if ide.config.outlineinactivity then
ide.timers.outline:Start(ide.config.outlineinactivity*1000, wx.wxTIMER_ONE_SHOT)
end
end
local function resetIndexTimer(interval)
if ide.config.symbolindexinactivity and not ide.timers.symbolindex:IsRunning() then
ide.timers.symbolindex:Start(interval or ide.config.symbolindexinactivity*1000, wx.wxTIMER_ONE_SHOT)
end
end
local function outlineRefresh(editor, force)
if not editor then return end
local tokens = editor:GetTokenList()
local sep = editor.spec.sep
local varname = "([%w_][%w_"..q(sep:sub(1,1)).."]*)"
local funcs = {updated = TimeGet()}
local var = {}
local outcfg = ide.config.outline or {}
local scopes = {}
local funcnum = 0
local SCOPENUM, FUNCNUM = 1, 2
local text
for _, token in ipairs(tokens) do
local op = token[1]
if op == 'Var' or op == 'Id' then
var = {name = token.name, fpos = token.fpos, global = token.context[token.name] == nil}
elseif outcfg.showcurrentfunction and op == 'Scope' then
local fundepth = #scopes
if token.name == '(' then -- a function starts a new scope
funcnum = funcnum + 1 -- increment function count
local nested = fundepth == 0 or scopes[fundepth][SCOPENUM] > 0
scopes[fundepth + (nested and 1 or 0)] = {1, funcnum}
elseif fundepth > 0 then
scopes[fundepth][SCOPENUM] = scopes[fundepth][SCOPENUM] + 1
end
elseif outcfg.showcurrentfunction and op == 'EndScope' then
local fundepth = #scopes
if fundepth > 0 and scopes[fundepth][SCOPENUM] > 0 then
scopes[fundepth][SCOPENUM] = scopes[fundepth][SCOPENUM] - 1
if scopes[fundepth][SCOPENUM] == 0 then
local funcnum = scopes[fundepth][FUNCNUM]
if funcs[funcnum] then
funcs[funcnum].poe = token.fpos + (token.name and #token.name or 0)
end
table.remove(scopes)
end
end
elseif op == 'Function' then
local depth = token.context['function'] or 1
local name, pos = token.name, token.fpos
text = text or editor:GetTextDyn()
local _, _, rname, params = text:find('([^%(]*)(%b())', pos)
if name and rname:find(token.name, 1, true) ~= 1 then
name = rname:gsub("%s+$","")
end
if not name then
local s = editor:PositionFromLine(editor:LineFromPosition(pos-1))
local rest
rest, pos, name = text:sub(s+1, pos-1):match('%s*(.-)()'..varname..'%s*=%s*function%s*$')
if rest then
pos = s + pos
-- guard against "foo, bar = function() end" as it would get "bar"
if #rest>0 and rest:find(',') then name = nil end
end
end
local ftype = image.LFUNCTION
if not name then
ftype = image.AFUNCTION
elseif outcfg.showmethodindicator and name:find('['..q(sep)..']') then
ftype = name:find(q(sep:sub(1,1))) and image.SMETHOD or image.METHOD
elseif var.name == name and var.fpos == pos
or var.name and name:find('^'..var.name..'['..q(sep)..']') then
ftype = var.global and image.GFUNCTION or image.LFUNCTION
end
name = name or outcfg.showanonymous
funcs[#funcs+1] = {
name = ((name or '~')..params):gsub("%s+", " "),
skip = (not name) and true or nil,
depth = depth,
image = ftype,
pos = name and pos or token.fpos,
}
end
end
if force == nil then return funcs end
local ctrl = outline.outlineCtrl
local cache = caches[editor] or {}
caches[editor] = cache
-- add file
local filename = ide:GetDocument(editor):GetTabText()
local fileitem = cache.fileitem
if not fileitem then
local root = ctrl:GetRootItem()
if not root or not root:IsOk() then return end
if outcfg.showonefile then
fileitem = root
else
fileitem = ctrl:AppendItem(root, filename, image.FILE)
setData(ctrl, fileitem, editor)
ctrl:SetItemBold(fileitem, true)
ctrl:SortChildren(root)
end
cache.fileitem = fileitem
end
do -- check if any changes in the cached function list
local prevfuncs = cache.funcs or {}
local nochange = #funcs == #prevfuncs
local resort = {} -- items that need to be re-sorted
if nochange then
for n, func in ipairs(funcs) do
func.item = prevfuncs[n].item -- carry over cached items
if func.depth ~= prevfuncs[n].depth then
nochange = false
elseif nochange and prevfuncs[n].item then
if func.name ~= prevfuncs[n].name then
ctrl:SetItemText(prevfuncs[n].item, func.name)
if outcfg.sort then resort[ctrl:GetItemParent(prevfuncs[n].item)] = true end
end
if func.image ~= prevfuncs[n].image then
ctrl:SetItemImage(prevfuncs[n].item, func.image)
end
end
end
end
cache.funcs = funcs -- set new cache as positions may change
if nochange and not force then -- return if no visible changes
if outcfg.sort then -- resort items for all parents that have been modified
for item in pairs(resort) do ctrl:SortChildren(item) end
end
return
end
end
-- refresh the tree
-- refreshing shouldn't change the focus of the current element,
-- but it appears that DeleteChildren (wxwidgets 2.9.5 on Windows)
-- moves the focus from the current element to wxTreeCtrl.
-- need to save the window having focus and restore after the refresh.
local win = ide:GetMainFrame():FindFocus()
ctrl:Freeze()
-- disabling event handlers is not strictly necessary, but it's expected
-- to fix a crash on Windows that had DeleteChildren in the trace (#442).
ctrl:SetEvtHandlerEnabled(false)
ctrl:DeleteChildren(fileitem)
ctrl:SetEvtHandlerEnabled(true)
local edpos = editor:GetCurrentPos()+1
local stack = {fileitem}
local resort = {} -- items that need to be re-sorted
for n, func in ipairs(funcs) do
local depth = outcfg.showflat and 1 or func.depth
local parent = stack[depth]
while not parent do depth = depth - 1; parent = stack[depth] end
if not func.skip then
local item = ctrl:AppendItem(parent, func.name, func.image)
if ide.config.outline.showcurrentfunction
and edpos >= func.pos and func.poe and edpos <= func.poe then
ctrl:SetItemBold(item, true)
end
if outcfg.sort then resort[parent] = true end
setData(ctrl, item, n)
func.item = item
stack[func.depth+1] = item
end
func.skip = nil
end
if outcfg.sort then -- resort items for all parents that have been modified
for item in pairs(resort) do ctrl:SortChildren(item) end
end
if outcfg.showcompact then ctrl:Expand(fileitem) else ctrl:ExpandAllChildren(fileitem) end
-- scroll to the fileitem, but only if it's not a root item (as it's hidden)
if fileitem:GetValue() ~= ctrl:GetRootItem():GetValue() then
ctrl:ScrollTo(fileitem)
ctrl:SetScrollPos(wx.wxHORIZONTAL, 0, true)
else -- otherwise, scroll to the top
ctrl:SetScrollPos(wx.wxVERTICAL, 0, true)
end
ctrl:Thaw()
if win and win ~= ide:GetMainFrame():FindFocus() then win:SetFocus() end
end
local function indexFromQueue()
if #outline.indexqueue == 0 then return end
local editor = ide:GetEditor()
local inactivity = ide.config.symbolindexinactivity
if editor and inactivity and editor.updated > TimeGet()-inactivity then
-- reschedule timer for later time
resetIndexTimer()
else
local fname = table.remove(outline.indexqueue, 1)
outline.indexqueue[0][fname] = nil
-- check if fname is already loaded
ide:SetStatusFor(TR("Indexing %d files: '%s'..."):format(#outline.indexqueue+1, fname))
local content, err = FileRead(fname)
if content then
local editor = ide:CreateBareEditor()
editor:SetupKeywords(GetFileExt(fname))
editor:SetTextDyn(content)
editor:Colourise(0, -1)
editor:ResetTokenList()
while IndicateAll(editor) do end
outline:UpdateSymbols(fname, outlineRefresh(editor))
editor:Destroy()
else
DisplayOutputLn(TR("Can't open file '%s': %s"):format(fname, err))
end
if #outline.indexqueue == 0 then
outline:SaveSettings()
ide:SetStatusFor(TR("Indexing completed."))
end
ide:DoWhenIdle(indexFromQueue)
end
return
end
local function createOutlineWindow()
local REFRESH, REINDEX = 1, 2
local width, height = 360, 200
local ctrl = wx.wxTreeCtrl(ide.frame, wx.wxID_ANY,
wx.wxDefaultPosition, wx.wxSize(width, height),
wx.wxTR_LINES_AT_ROOT + wx.wxTR_HAS_BUTTONS
+ wx.wxTR_HIDE_ROOT + wx.wxNO_BORDER)
outline.outlineCtrl = ctrl
ide.timers.outline = wx.wxTimer(ctrl, REFRESH)
ide.timers.symbolindex = wx.wxTimer(ctrl, REINDEX)
ctrl:AddRoot("Outline")
ctrl:SetImageList(outline.imglist)
ctrl:SetFont(ide.font.fNormal)
function ctrl:ActivateItem(item_id)
local data = ctrl:GetItemData(item_id)
if ctrl:GetItemImage(item_id) == image.FILE then
-- activate editor tab
local editor = data:GetData()
if not ide:GetEditorWithFocus(editor) then ide:GetDocument(editor):SetActive() end
else
-- activate tab and move cursor based on stored pos
-- get file parent
local onefile = (ide.config.outline or {}).showonefile
local parent = ctrl:GetItemParent(item_id)
if not onefile then -- find the proper parent
while parent:IsOk() and ctrl:GetItemImage(parent) ~= image.FILE do
parent = ctrl:GetItemParent(parent)
end
if not parent:IsOk() then return end
end
-- activate editor tab
local editor = onefile and GetEditor() or ctrl:GetItemData(parent):GetData()
local cache = caches[editor]
if editor and cache then
-- move to position in the file
editor:GotoPosEnforcePolicy(cache.funcs[data:GetData()].pos-1)
-- only set editor active after positioning as this may change focus,
-- which may regenerate the outline, which may invalidate `data` value
if not ide:GetEditorWithFocus(editor) then ide:GetDocument(editor):SetActive() end
end
end
end
local function activateByPosition(event)
local mask = (wx.wxTREE_HITTEST_ONITEMINDENT + wx.wxTREE_HITTEST_ONITEMLABEL
+ wx.wxTREE_HITTEST_ONITEMICON + wx.wxTREE_HITTEST_ONITEMRIGHT)
local item_id, flags = ctrl:HitTest(event:GetPosition())
if item_id and item_id:IsOk() and bit.band(flags, mask) > 0 then
ctrl:ActivateItem(item_id)
else
event:Skip()
end
return true
end
ctrl:Connect(wx.wxEVT_TIMER, function(event)
if event:GetId() == REFRESH then outlineRefresh(GetEditor(), false) end
if event:GetId() == REINDEX then ide:DoWhenIdle(indexFromQueue) end
end)
ctrl:Connect(wx.wxEVT_LEFT_DOWN, activateByPosition)
ctrl:Connect(wx.wxEVT_LEFT_DCLICK, activateByPosition)
ctrl:Connect(wx.wxEVT_COMMAND_TREE_ITEM_ACTIVATED, function(event)
ctrl:ActivateItem(event:GetItem())
end)
ctrl:Connect(ID_OUTLINESORT, wx.wxEVT_COMMAND_MENU_SELECTED,
function()
ide.config.outline.sort = not ide.config.outline.sort
for editor, cache in pairs(caches) do
ide:SetStatus(("Refreshing '%s'..."):format(ide:GetDocument(editor):GetFileName()))
local isexpanded = ctrl:IsExpanded(cache.fileitem)
outlineRefresh(editor, true)
if not isexpanded then ctrl:Collapse(cache.fileitem) end
end
ide:SetStatus('')
end)
ctrl:Connect(wx.wxEVT_COMMAND_TREE_ITEM_MENU,
function (event)
local menu = wx.wxMenu {
{ ID_OUTLINESORT, TR("Sort By Name"), "", wx.wxITEM_CHECK },
}
menu:Check(ID_OUTLINESORT, ide.config.outline.sort)
PackageEventHandle("onMenuOutline", menu, ctrl, event)
ctrl:PopupMenu(menu)
end)
local function reconfigure(pane)
pane:TopDockable(false):BottomDockable(false)
:MinSize(150,-1):BestSize(300,-1):FloatingSize(200,300)
end
local layout = ide:GetSetting("/view", "uimgrlayout")
if not layout or not layout:find("outlinepanel") then
ide:AddPanelDocked(ide:GetProjectNotebook(), ctrl, "outlinepanel", TR("Outline"), reconfigure, false)
else
ide:AddPanel(ctrl, "outlinepanel", TR("Outline"), reconfigure)
end
end
local function eachNode(eachFunc, root, recursive)
local ctrl = outline.outlineCtrl
local item = ctrl:GetFirstChild(root or ctrl:GetRootItem())
while true do
if not item:IsOk() then break end
if eachFunc and eachFunc(ctrl, item) then break end
if recursive and ctrl:ItemHasChildren(item) then eachNode(eachFunc, item, recursive) end
item = ctrl:GetNextSibling(item)
end
end
createOutlineWindow()
local pathsep = GetPathSeparator()
local function isInSubDir(name, path)
return #name > #path and path..pathsep == name:sub(1, #path+#pathsep)
end
local function isIgnoredInIndex(name)
local ignoredirs = outline.settings.ignoredirs
if ignoredirs[name] then return true end
-- check through ignored dirs to see if any of them match the file
for path in pairs(ignoredirs) do
if isInSubDir(name, path) then return true end
end
return false
end
local function purgeIndex(path)
local symbols = outline.settings.symbols
for name in pairs(symbols) do
if isInSubDir(name, path) then outline:UpdateSymbols(name, nil) end
end
end
local function purgeQueue(path)
local curqueue = outline.indexqueue
local newqueue = {[0] = {}}
for _, name in ipairs(curqueue) do
if not isInSubDir(name, path) then
table.insert(newqueue, name)
newqueue[0][name] = true
end
end
outline.indexqueue = newqueue
end
local function disableIndex(path)
outline.settings.ignoredirs[path] = true
outline:SaveSettings(true)
-- purge the path from the index and the (current) queue
purgeIndex(path)
purgeQueue(path)
end
local function enableIndex(path)
outline.settings.ignoredirs[path] = nil
outline:SaveSettings(true)
outline:RefreshSymbols(path)
end
local package = ide:AddPackage('core.outline', {
-- remove the editor from the list
onEditorClose = function(self, editor)
local cache = caches[editor]
local fileitem = cache and cache.fileitem
caches[editor] = nil -- remove from cache
if (ide.config.outline or {}).showonefile then return end
if fileitem then outline.outlineCtrl:Delete(fileitem) end
end,
-- handle rename of the file in the current editor
onEditorSave = function(self, editor)
if (ide.config.outline or {}).showonefile then return end
local cache = caches[editor]
local fileitem = cache and cache.fileitem
local doc = ide:GetDocument(editor)
local ctrl = outline.outlineCtrl
if doc and fileitem and ctrl:GetItemText(fileitem) ~= doc:GetTabText() then
ctrl:SetItemText(fileitem, doc:GetTabText())
end
local path = doc and doc:GetFilePath()
if path and cache and cache.funcs then
outline:UpdateSymbols(path, cache.funcs.updated > editor.updated and cache.funcs or nil)
outline:SaveSettings()
end
end,
-- go over the file items to turn bold on/off or collapse/expand
onEditorFocusSet = function(self, editor)
if (ide.config.outline or {}).showonefile and ide.config.outlineinactivity then
outlineRefresh(editor, true)
return
end
local cache = caches[editor]
local fileitem = cache and cache.fileitem
local ctrl = outline.outlineCtrl
local itemname = ide:GetDocument(editor):GetTabText()
-- update file name if it changed in the editor
if fileitem and ctrl:GetItemText(fileitem) ~= itemname then
ctrl:SetItemText(fileitem, itemname)
end
-- if the editor is not in the cache, which may happen if the user
-- quickly switches between tabs that don't have outline generated,
-- regenerate it manually
if not cache then resetOutlineTimer() end
resetIndexTimer()
eachNode(function(ctrl, item)
local found = fileitem and item:GetValue() == fileitem:GetValue()
if not found and ctrl:IsBold(item) then
ctrl:SetItemBold(item, false)
ctrl:CollapseAllChildren(item)
end
end)
if fileitem and not ctrl:IsBold(fileitem) then
-- run the following changes on idle as doing them inline is causing a strange
-- issue on OSX when clicking on a tab may skip several tabs (#546);
-- this is somehow caused by `ExpandAllChildren` triggered from `SetFocus` inside
-- `PAGE_CHANGED` handler for the notebook.
ide:DoWhenIdle(function()
ctrl:SetItemBold(fileitem, true)
if (ide.config.outline or {}).showcompact then
ctrl:Expand(fileitem)
else
ctrl:ExpandAllChildren(fileitem)
end
ctrl:ScrollTo(fileitem)
ctrl:SetScrollPos(wx.wxHORIZONTAL, 0, true)
end)
end
end,
onMenuFiletree = function(self, menu, tree, event)
local item_id = event:GetItem()
local name = tree:GetItemFullName(item_id)
local symboldirmenu = wx.wxMenu {
{ID_SYMBOLDIRREFRESH, TR("Refresh Index"), TR("Refresh indexed symbols from files in the selected directory")},
{ID_SYMBOLDIRDISABLE, TR("Disable Indexing For '%s'"):format(name), TR("Ignore and don't index symbols from files in the selected directory")},
}
local _, _, projdirpos = ide:FindMenuItem(ID_PROJECTDIR, menu)
if projdirpos then
local ignored = isIgnoredInIndex(name)
local enabledirmenu = wx.wxMenu()
local paths = {}
for path in pairs(outline.settings.ignoredirs) do table.insert(paths, path) end
table.sort(paths)
for i, path in ipairs(paths) do
local id = ID("file.enablesymboldir."..i)
enabledirmenu:Append(id, path, "")
tree:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, function() enableIndex(path) end)
end
symboldirmenu:Append(wx.wxMenuItem(symboldirmenu, ID_SYMBOLDIRENABLE,
TR("Enable Indexing"), "", wx.wxITEM_NORMAL, enabledirmenu))
menu:Insert(projdirpos+1, wx.wxMenuItem(menu, ID_SYMBOLDIRINDEX,
TR("Symbol Index"), "", wx.wxITEM_NORMAL, symboldirmenu))
-- disable "enable" if it's empty
menu:Enable(ID_SYMBOLDIRENABLE, #paths > 0)
-- disable "refresh" and "disable" if the directory is ignored
-- or if any of the directories above it are ignored
menu:Enable(ID_SYMBOLDIRREFRESH, tree:IsDirectory(item_id) and not ignored)
menu:Enable(ID_SYMBOLDIRDISABLE, tree:IsDirectory(item_id) and not ignored)
tree:Connect(ID_SYMBOLDIRREFRESH, wx.wxEVT_COMMAND_MENU_SELECTED, function()
-- purge files in this directory as some might have been removed;
-- files will be purged based on time, but this is a good time to clean.
purgeIndex(name)
outline:RefreshSymbols(name)
resetIndexTimer(1) -- start after 1ms
end)
tree:Connect(ID_SYMBOLDIRDISABLE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
disableIndex(name)
end)
end
end,
onEditorPainted = function(self, editor)
local ctrl = ide.outline.outlineCtrl
if not ide:IsWindowShown(ctrl) then return end
local cache = caches[editor]
if not cache or not ide.config.outline.showcurrentfunction then return end
local edpos = editor:GetCurrentPos()+1
local edline = editor:LineFromPosition(edpos-1)+1
if cache.pos and cache.pos == edpos then return end
if cache.line and cache.line == edline then return end
cache.pos = edpos
cache.line = edline
local n = 0
local MIN, MAX = 1, 2
local visible = {[MIN] = math.huge, [MAX] = 0}
local needshown = {[MIN] = math.huge, [MAX] = 0}
ctrl:Unselect()
-- scan all items recursively starting from the current file
eachNode(function(ctrl, item)
local func = cache.funcs[ctrl:GetItemData(item):GetData()]
local val = edpos >= func.pos and func.poe and edpos <= func.poe
if edline == editor:LineFromPosition(func.pos)+1
or (func.poe and edline == editor:LineFromPosition(func.poe)+1) then
cache.line = nil
end
ctrl:SetItemBold(item, val)
if val then ctrl:SelectItem(item, val) end
if not ide.config.outline.jumptocurrentfunction then return end
n = n + 1
-- check that this and the items around it are all visible;
-- this is to avoid the situation when the current item is only partially visible
local isvisible = ctrl:IsVisible(item) and ctrl:GetNextVisible(item):IsOk() and ctrl:GetPrevVisible(item):IsOk()
if val and not isvisible then
needshown[MIN] = math.min(needshown[MIN], n)
needshown[MAX] = math.max(needshown[MAX], n)
elseif isvisible then
visible[MIN] = math.min(visible[MIN], n)
visible[MAX] = math.max(visible[MAX], n)
end
end, cache.fileitem, true)
if not ide.config.outline.jumptocurrentfunction then return end
if needshown[MAX] > visible[MAX] then
ctrl:ScrollLines(needshown[MAX]-visible[MAX]) -- scroll forward to the last hidden line
elseif needshown[MIN] < visible[MIN] then
ctrl:ScrollLines(needshown[MIN]-visible[MIN]) -- scroll backward to the first hidden line
end
end,
})
local function queuePath(path)
-- only queue if symbols inactivity is set, so files will be indexed
if ide.config.symbolindexinactivity and not outline.indexqueue[0][path] then
outline.indexqueue[0][path] = true
table.insert(outline.indexqueue, 1, path)
end
end
function outline:GetFileSymbols(path)
local symbols = self.settings.symbols[path]
-- queue path to process when appropriate
if not symbols then queuePath(path) end
return symbols
end
function outline:GetEditorSymbols(editor)
-- force token refresh (as these may be not updated yet)
if #editor:GetTokenList() == 0 then
while IndicateAll(editor) do end
end
-- only refresh the functions when none is present
if not caches[editor] or #caches[editor].funcs == 0 then outlineRefresh(editor, true) end
return caches[editor].funcs
end
function outline:RefreshSymbols(path, callback)
if isIgnoredInIndex(path) then return end
local exts = {}
for _, ext in pairs(ide:GetKnownExtensions()) do
local spec = GetSpec(ext)
if spec and spec.marksymbols then table.insert(exts, ext) end
end
local opts = {sort = false, folder = false, skipbinary = true, yield = true,
-- skip those directories that are on the "ignore" list
ondirectory = function(name) return outline.settings.ignoredirs[name] == nil end
}
local nextfile = coroutine.wrap(function() FileSysGetRecursive(path, true, table.concat(exts, ";"), opts) end)
while true do
local file = nextfile()
if not file then break end
if not isIgnoredInIndex(file) then (callback or queuePath)(file) end
end
end
function outline:UpdateSymbols(fname, symb)
local symbols = self.settings.symbols
symbols[fname] = symb
-- purge outdated records
local threshold = TimeGet() - 60*60*24*7 -- cache for 7 days
if not self.indexpurged then
for k, v in pairs(symbols) do
if v.updated < threshold then symbols[k] = nil end
end
self.indexpurged = true
end
self.needsaving = true
end
function outline:SaveSettings(force)
if self.needsaving or force then
ide:PushStatus(TR("Updating symbol index and settings..."))
package:SetSettings(self.settings, {keyignore = {depth = true, image = true, poe = true, item = true, skip = true}})
ide:PopStatus()
self.needsaving = false
end
end
MergeSettings(outline.settings, package:GetSettings())
|