@@ -36,6 +36,46 @@ local function newlineCount(str)
3636 end
3737end
3838
39+ local function getColorCodeLength (str , index )
40+ if str :sub (index , index ) ~= " ^" then
41+ return 0
42+ end
43+ local nextChar = str :sub (index + 1 , index + 1 )
44+ if nextChar == " x" and str :sub (index + 2 , index + 7 ):match (" ^%x%x%x%x%x%x$" ) then
45+ return 8
46+ elseif nextChar :match (" ^%d$" ) then
47+ return 2
48+ end
49+ return 0
50+ end
51+
52+ local function buildVisibleLineMap (rawLine )
53+ local visible = " "
54+ local rawStarts = {}
55+ local rawEnds = {}
56+ local rawIndex = 1
57+ while rawIndex <= # rawLine do
58+ local colorCodeLength = getColorCodeLength (rawLine , rawIndex )
59+ if colorCodeLength > 0 then
60+ rawIndex = rawIndex + colorCodeLength
61+ else
62+ local rawEnd = utf8 .next (rawLine , rawIndex , 1 )
63+ if not rawEnd or rawEnd <= rawIndex then
64+ rawEnd = rawIndex + 1
65+ end
66+ local char = rawLine :sub (rawIndex , rawEnd - 1 )
67+ local visibleStart = # visible + 1
68+ visible = visible .. char
69+ for offset = 0 , # char - 1 do
70+ rawStarts [visibleStart + offset ] = rawIndex
71+ rawEnds [visibleStart + offset ] = rawEnd
72+ end
73+ rawIndex = rawEnd
74+ end
75+ end
76+ return visible , rawStarts , rawEnds
77+ end
78+
3979local EditClass = newClass (" EditControl" , " ControlHost" , " Control" , " UndoHandler" , " TooltipHost" , function (self , anchor , rect , init , prompt , filter , limit , changeFunc , lineHeight , allowZoom , clearable )
4080 self .ControlHost ()
4181 self .Control (anchor , rect )
@@ -55,6 +95,14 @@ local EditClass = newClass("EditControl", "ControlHost", "Control", "UndoHandler
5595 self .disableCol = " ^9"
5696 self .selCol = " ^0"
5797 self .selBGCol = " ^xBBBBBB"
98+ self .searchBGFillCol = { 0.03 , 0.03 , 0.04 , 0.78 }
99+ self .searchFocusFillCol = { 0.03 , 0.03 , 0.04 , 0.88 }
100+ self .searchBGCol = { 0.58 , 0.60 , 0.64 , 0.98 }
101+ self .searchFocusBGCol = { 0.96 , 0.97 , 0.99 , 1.00 }
102+ self .searchQuery = " "
103+ self .searchMatches = {}
104+ self .searchMatchesByLine = {}
105+ self .searchFocusIndex = nil
58106 self .blinkStart = GetTime ()
59107 self .allowZoom = allowZoom
60108 local function buttonSize ()
@@ -238,6 +286,137 @@ function EditClass:MoveCaretVertically(offset)
238286 self .blinkStart = GetTime ()
239287end
240288
289+ function EditClass :SetSearchQuery (query , centerFocused )
290+ query = tostring (query or " " )
291+ local resetFocus = query ~= self .searchQuery
292+ self .searchQuery = query
293+ self :RefreshSearch (centerFocused , resetFocus )
294+ end
295+
296+ function EditClass :AdvanceSearchMatch (direction )
297+ local matchCount = # self .searchMatches
298+ if matchCount == 0 then
299+ return false
300+ end
301+ if direction and direction < 0 then
302+ if not self .searchFocusIndex or self .searchFocusIndex <= 1 then
303+ self .searchFocusIndex = matchCount
304+ else
305+ self .searchFocusIndex = self .searchFocusIndex - 1
306+ end
307+ else
308+ if not self .searchFocusIndex or self .searchFocusIndex >= matchCount then
309+ self .searchFocusIndex = 1
310+ else
311+ self .searchFocusIndex = self .searchFocusIndex + 1
312+ end
313+ end
314+ self :CenterOnSearchMatch (self .searchFocusIndex )
315+ return true
316+ end
317+
318+ function EditClass :RefreshSearch (centerFocused , resetFocus )
319+ local query = self .searchQuery or " "
320+ local lowerQuery = query :lower ()
321+ local previousFocus = self .searchFocusIndex
322+ self .searchMatches = {}
323+ self .searchMatchesByLine = {}
324+ self .searchFocusIndex = nil
325+ if query == " " then
326+ return
327+ end
328+
329+ local lineIndex = 0
330+ for s , line in (self .buf .. " \n " ):gmatch (" ()([^\n ]*)\n " ) do
331+ lineIndex = lineIndex + 1
332+ local visibleLine , rawStarts , rawEnds = buildVisibleLineMap (line )
333+ local searchLine = visibleLine :lower ()
334+ local searchStart = 1
335+ while true do
336+ local visibleStart , visibleEnd = searchLine :find (lowerQuery , searchStart , true )
337+ if not visibleStart then
338+ break
339+ end
340+ local rawStart = rawStarts [visibleStart ]
341+ local rawEnd = rawEnds [visibleEnd ]
342+ if rawStart and rawEnd then
343+ local matchIndex = # self .searchMatches + 1
344+ local match = {
345+ index = matchIndex ,
346+ lineIndex = lineIndex ,
347+ line = line ,
348+ rawStart = rawStart ,
349+ rawEnd = rawEnd ,
350+ }
351+ self .searchMatches [matchIndex ] = match
352+ self .searchMatchesByLine [lineIndex ] = self .searchMatchesByLine [lineIndex ] or {}
353+ table.insert (self .searchMatchesByLine [lineIndex ], match )
354+ end
355+ searchStart = visibleStart + 1
356+ end
357+ end
358+
359+ if # self .searchMatches > 0 then
360+ if not resetFocus and previousFocus then
361+ self .searchFocusIndex = m_min (previousFocus , # self .searchMatches )
362+ else
363+ self .searchFocusIndex = 1
364+ end
365+ if centerFocused then
366+ self :CenterOnSearchMatch (self .searchFocusIndex )
367+ end
368+ end
369+ end
370+
371+ function EditClass :CenterOnSearchMatch (matchIndex )
372+ if not self .lineHeight then
373+ return
374+ end
375+ local match = self .searchMatches [matchIndex ]
376+ if not match then
377+ return
378+ end
379+ self :UpdateScrollBars ()
380+ if self .controls .scrollBarV .enabled then
381+ local targetY = (match .lineIndex - 1 ) * self .lineHeight
382+ self .controls .scrollBarV :SetOffset (targetY - (self .controls .scrollBarV .viewDim - self .lineHeight ) / 2 )
383+ end
384+ if self .controls .scrollBarH .enabled then
385+ local matchStartX = DrawStringWidth (self .lineHeight , self .font , match .line :sub (1 , match .rawStart - 1 ))
386+ local matchWidth = DrawStringWidth (self .lineHeight , self .font , match .line :sub (match .rawStart , match .rawEnd - 1 ))
387+ self .controls .scrollBarH :SetOffset (matchStartX + matchWidth / 2 - self .controls .scrollBarH .viewDim / 2 )
388+ end
389+ end
390+
391+ function EditClass :DrawSearchHighlightsForLine (lineIndex , line , textX , textY , textHeight )
392+ local matches = self .searchMatchesByLine [lineIndex ]
393+ if not matches then
394+ return
395+ end
396+ for _ , match in ipairs (matches ) do
397+ local matchStartX = DrawStringWidth (textHeight , self .font , line :sub (1 , match .rawStart - 1 ))
398+ local matchWidth = DrawStringWidth (textHeight , self .font , line :sub (match .rawStart , match .rawEnd - 1 ))
399+ if matchWidth > 0 then
400+ local isFocused = match .index == self .searchFocusIndex
401+ local fillColor = isFocused and self .searchFocusFillCol or self .searchBGFillCol
402+ local borderColor = isFocused and self .searchFocusBGCol or self .searchBGCol
403+ local drawX = textX + matchStartX - 2
404+ local drawWidth = matchWidth + 4
405+ local borderX = drawX - 1
406+ local borderY = textY - 1
407+ local borderWidth = drawWidth + 2
408+ local borderHeight = textHeight + 2
409+ SetDrawColor (fillColor [1 ], fillColor [2 ], fillColor [3 ], fillColor [4 ])
410+ DrawImage (nil , drawX , textY , drawWidth , textHeight )
411+ SetDrawColor (borderColor [1 ], borderColor [2 ], borderColor [3 ], borderColor [4 ])
412+ DrawImage (nil , borderX , borderY , borderWidth , 2 )
413+ DrawImage (nil , borderX , borderY + borderHeight - 2 , borderWidth , 2 )
414+ DrawImage (nil , borderX , borderY , 2 , borderHeight )
415+ DrawImage (nil , borderX + borderWidth - 2 , borderY , 2 , borderHeight )
416+ end
417+ end
418+ end
419+
241420function EditClass :Draw (viewPort , noTooltip )
242421 local x , y = self :GetPos ()
243422 local width , height = self :GetSize ()
@@ -299,6 +478,16 @@ function EditClass:Draw(viewPort, noTooltip)
299478 if self .inactiveText then
300479 local inactiveText = type (inactiveText ) == " string" and self .inactiveText or self .inactiveText (self .buf )
301480 DrawString (- self .controls .scrollBarH .offset , - self .controls .scrollBarV .offset , " LEFT" , textHeight , self .font , inactiveText )
481+ elseif self .lineHeight and # self .searchMatches > 0 then
482+ local lineIndex = 0
483+ local drawY = - self .controls .scrollBarV .offset
484+ for line in (self .buf .. " \n " ):gmatch (" ([^\n ]*)\n " ) do
485+ lineIndex = lineIndex + 1
486+ self :DrawSearchHighlightsForLine (lineIndex , line , - self .controls .scrollBarH .offset , drawY , textHeight )
487+ SetDrawColor (self .inactiveCol )
488+ DrawString (- self .controls .scrollBarH .offset , drawY , " LEFT" , textHeight , self .font , line )
489+ drawY = drawY + textHeight
490+ end
302491 elseif self .protected then
303492 DrawString (- self .controls .scrollBarH .offset , - self .controls .scrollBarV .offset , " LEFT" , textHeight , self .font , string.rep (protected_replace , # self .buf ))
304493 else
@@ -324,9 +513,13 @@ function EditClass:Draw(viewPort, noTooltip)
324513 local left = m_min (self .caret , self .sel or self .caret )
325514 local right = m_max (self .caret , self .sel or self .caret )
326515 local caretX
516+ local lineIndex = 0
327517 SetDrawColor (self .textCol )
328518 for s , line , e in (self .buf .. " \n " ):gmatch (" ()([^\n ]*)\n ()" ) do
519+ lineIndex = lineIndex + 1
329520 textX = - self .controls .scrollBarH .offset
521+ self :DrawSearchHighlightsForLine (lineIndex , line , textX , textY , textHeight )
522+ SetDrawColor (self .textCol )
330523 if left >= e or right <= s then
331524 DrawString (textX , textY , " LEFT" , textHeight , self .font , line )
332525 end
@@ -507,7 +700,7 @@ function EditClass:OnKeyDown(key, doubleClick)
507700 if self .enterFunc then
508701 self .enterFunc (self .buf )
509702 end
510- return
703+ return self
511704 end
512705 elseif key == " a" and ctrl then
513706 self :SelectAll ()
0 commit comments