search.lua 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. -- helpers for the search bar (C-f)
  2. function Text.draw_search_bar(State)
  3. local h = State.line_height+2
  4. local y = App.screen.height-h
  5. love.graphics.setColor(0.9,0.9,0.9)
  6. love.graphics.rectangle('fill', 0, y-10, App.screen.width-1, h+8)
  7. love.graphics.setColor(0.6,0.6,0.6)
  8. love.graphics.line(0, y-10, App.screen.width-1, y-10)
  9. love.graphics.setColor(1,1,1)
  10. love.graphics.rectangle('fill', 20, y-6, App.screen.width-40, h+2, 2,2)
  11. love.graphics.setColor(0.6,0.6,0.6)
  12. love.graphics.rectangle('line', 20, y-6, App.screen.width-40, h+2, 2,2)
  13. App.color(Text_color)
  14. App.screen.print(State.search_term, 25,y-5)
  15. Text.draw_cursor(State, 25+State.font:getWidth(State.search_term),y-5)
  16. end
  17. function Text.search_next(State)
  18. if #State.search_term == 0 then return end
  19. -- search current line from cursor
  20. local curr_pos = State.cursor1.pos
  21. local curr_line = State.lines[State.cursor1.line].data
  22. local curr_offset = Text.offset(curr_line, curr_pos)
  23. local offset = find(curr_line, State.search_term, curr_offset, --[[literal]] true)
  24. if offset then
  25. State.cursor1.pos = utf8.len(curr_line, 1, offset)
  26. end
  27. if offset == nil then
  28. -- search lines below cursor
  29. for i=State.cursor1.line+1,#State.lines do
  30. local curr_line = State.lines[i].data
  31. offset = find(curr_line, State.search_term, --[[from start]] nil, --[[literal]] true)
  32. if offset then
  33. State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
  34. break
  35. end
  36. end
  37. end
  38. if offset == nil then
  39. -- wrap around
  40. for i=1,State.cursor1.line-1 do
  41. local curr_line = State.lines[i].data
  42. offset = find(curr_line, State.search_term, --[[from start]] nil, --[[literal]] true)
  43. if offset then
  44. State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
  45. break
  46. end
  47. end
  48. end
  49. if offset == nil then
  50. -- search current line until cursor
  51. local curr_line = State.lines[State.cursor1.line].data
  52. offset = find(curr_line, State.search_term, --[[from start]] nil, --[[literal]] true)
  53. local pos = utf8.len(curr_line, 1, offset)
  54. if pos and pos < State.cursor1.pos then
  55. State.cursor1.pos = pos
  56. end
  57. end
  58. if offset == nil then
  59. State.cursor1.line = State.search_backup.cursor.line
  60. State.cursor1.pos = State.search_backup.cursor.pos
  61. State.screen_top1.line = State.search_backup.screen_top.line
  62. State.screen_top1.pos = State.search_backup.screen_top.pos
  63. end
  64. local screen_bottom1 = Text.screen_bottom1(State)
  65. if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
  66. State.screen_top1.line = State.cursor1.line
  67. local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
  68. State.screen_top1.pos = pos
  69. end
  70. end
  71. function Text.search_previous(State)
  72. if #State.search_term == 0 then return end
  73. -- search current line before cursor
  74. local curr_pos = State.cursor1.pos
  75. local curr_line = State.lines[State.cursor1.line].data
  76. local curr_offset = Text.offset(curr_line, curr_pos)
  77. local offset = rfind(curr_line, State.search_term, curr_offset-1, --[[literal]] true)
  78. if offset then
  79. State.cursor1.pos = utf8.len(curr_line, 1, offset)
  80. end
  81. if offset == nil then
  82. -- search lines above cursor
  83. for i=State.cursor1.line-1,1,-1 do
  84. local curr_line = State.lines[i].data
  85. offset = rfind(curr_line, State.search_term, --[[from end]] nil, --[[literal]] true)
  86. if offset then
  87. State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
  88. break
  89. end
  90. end
  91. end
  92. if offset == nil then
  93. -- wrap around
  94. for i=#State.lines,State.cursor1.line+1,-1 do
  95. local curr_line = State.lines[i].data
  96. offset = rfind(curr_line, State.search_term, --[[from end]] nil, --[[literal]] true)
  97. if offset then
  98. State.cursor1 = {line=i, pos=utf8.len(curr_line, 1, offset)}
  99. break
  100. end
  101. end
  102. end
  103. if offset == nil then
  104. -- search current line after cursor
  105. local curr_line = State.lines[State.cursor1.line].data
  106. offset = rfind(curr_line, State.search_term, --[[from end]] nil, --[[literal]] true)
  107. local pos = utf8.len(curr_line, 1, offset)
  108. if pos and pos > State.cursor1.pos then
  109. State.cursor1.pos = pos
  110. end
  111. end
  112. if offset == nil then
  113. State.cursor1.line = State.search_backup.cursor.line
  114. State.cursor1.pos = State.search_backup.cursor.pos
  115. State.screen_top1.line = State.search_backup.screen_top.line
  116. State.screen_top1.pos = State.search_backup.screen_top.pos
  117. end
  118. local screen_bottom1 = Text.screen_bottom1(State)
  119. if Text.lt1(State.cursor1, State.screen_top1) or Text.lt1(screen_bottom1, State.cursor1) then
  120. State.screen_top1.line = State.cursor1.line
  121. local pos = Text.pos_at_start_of_screen_line(State, State.cursor1)
  122. State.screen_top1.pos = pos
  123. end
  124. end
  125. function find(s, pat, i, plain)
  126. if s == nil then return end
  127. return s:find(pat, i, plain)
  128. end
  129. -- TODO: avoid the expensive reverse() operations
  130. -- Particularly if we only care about literal matches, we don't need all of string.find
  131. function rfind(s, pat, i, plain)
  132. if s == nil then return end
  133. if #pat == 0 then return #s end
  134. local rs = s:reverse()
  135. local rpat = pat:reverse()
  136. if i == nil then i = #s end
  137. local ri = #s - i + 1
  138. local rendpos = rs:find(rpat, ri, plain)
  139. if rendpos == nil then return nil end
  140. local endpos = #s - rendpos + 1
  141. assert (endpos >= #pat, ('rfind: endpos %d should be >= #pat %d at this point'):format(endpos, #pat))
  142. return endpos-#pat+1
  143. end
  144. function test_rfind()
  145. check_eq(rfind('abc', ''), 3, 'empty pattern')
  146. check_eq(rfind('abc', 'c'), 3, 'final char')
  147. check_eq(rfind('acbc', 'c', 3), 2, 'previous char')
  148. check_nil(rfind('abc', 'd'), 'missing char')
  149. check_nil(rfind('abc', 'c', 2), 'no more char')
  150. end