commandview.lua 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. local core = require "core"
  2. local config = require "core.config"
  3. local common = require "core.common"
  4. local style = require "core.style"
  5. local Doc = require "core.doc"
  6. local DocView = require "core.docview"
  7. local View = require "core.view"
  8. local SingleLineDoc = Doc:extend()
  9. function SingleLineDoc:insert(line, col, text)
  10. SingleLineDoc.super.insert(self, line, col, text:gsub("\n", ""))
  11. end
  12. local CommandView = DocView:extend()
  13. local max_suggestions = 10
  14. local noop = function() end
  15. local default_state = {
  16. submit = noop,
  17. suggest = noop,
  18. cancel = noop,
  19. }
  20. function CommandView:new()
  21. CommandView.super.new(self, SingleLineDoc())
  22. self.suggestion_idx = 1
  23. self.suggestions = {}
  24. self.suggestions_height = 0
  25. self.last_change_id = 0
  26. self.gutter_width = 0
  27. self.gutter_text_brightness = 0
  28. self.selection_offset = 0
  29. self.state = default_state
  30. self.font = "font"
  31. self.size.y = 0
  32. self.label = ""
  33. end
  34. function CommandView:get_name()
  35. return View.get_name(self)
  36. end
  37. function CommandView:get_line_screen_position()
  38. local x = CommandView.super.get_line_screen_position(self, 1)
  39. local _, y = self:get_content_offset()
  40. local lh = self:get_line_height()
  41. return x, y + (self.size.y - lh) / 2
  42. end
  43. function CommandView:get_scrollable_size()
  44. return 0
  45. end
  46. function CommandView:scroll_to_make_visible()
  47. -- no-op function to disable this functionality
  48. end
  49. function CommandView:get_text()
  50. return self.doc:get_text(1, 1, 1, math.huge)
  51. end
  52. function CommandView:set_text(text)
  53. self.doc:remove(1, 1, math.huge, math.huge)
  54. self.doc:text_input(text)
  55. end
  56. function CommandView:move_suggestion_idx(dir)
  57. local n = self.suggestion_idx + dir
  58. self.suggestion_idx = common.clamp(n, 1, #self.suggestions)
  59. self:complete()
  60. self.last_change_id = self.doc:get_change_id()
  61. end
  62. function CommandView:complete()
  63. if #self.suggestions > 0 then
  64. self:set_text(self.suggestions[self.suggestion_idx].text)
  65. end
  66. end
  67. function CommandView:submit()
  68. local suggestion = self.suggestions[self.suggestion_idx]
  69. local text = self:get_text()
  70. local submit = self.state.submit
  71. self:exit(true)
  72. submit(text, suggestion)
  73. end
  74. function CommandView:enter(text, submit, suggest, cancel)
  75. if self.state ~= default_state then
  76. return
  77. end
  78. self.state = {
  79. submit = submit or noop,
  80. suggest = suggest or noop,
  81. cancel = cancel or noop,
  82. view = core.active_view
  83. }
  84. core.active_view = self
  85. self:update_suggestions()
  86. self.gutter_text_brightness = 100
  87. self.label = text .. ": "
  88. end
  89. function CommandView:exit(submitted, inexplicit)
  90. if core.active_view == self then
  91. core.active_view = self.state.view
  92. end
  93. local cancel = self.state.cancel
  94. self.state = default_state
  95. self.doc:reset()
  96. self.suggestions = {}
  97. if not submitted then cancel(not inexplicit) end
  98. end
  99. function CommandView:get_gutter_width()
  100. return self.gutter_width
  101. end
  102. function CommandView:get_suggestion_line_height()
  103. return self:get_font():get_height() + style.padding.y
  104. end
  105. function CommandView:update_suggestions()
  106. local t = self.state.suggest(self:get_text()) or {}
  107. local res = {}
  108. for i, item in ipairs(t) do
  109. if i == max_suggestions then
  110. break
  111. end
  112. if type(item) == "string" then
  113. item = { text = item }
  114. end
  115. res[i] = item
  116. end
  117. self.suggestions = res
  118. self.suggestion_idx = 1
  119. end
  120. function CommandView:update()
  121. CommandView.super.update(self)
  122. if core.active_view ~= self and self.state ~= default_state then
  123. self:exit(false, true)
  124. end
  125. -- update suggestions if text has changed
  126. if self.last_change_id ~= self.doc:get_change_id() then
  127. self:update_suggestions()
  128. self.last_change_id = self.doc:get_change_id()
  129. end
  130. -- update gutter text color brightness
  131. self:move_towards("gutter_text_brightness", 0, 0.1)
  132. -- update gutter width
  133. local dest = self:get_font():get_width(self.label) + style.padding.x
  134. if self.size.y <= 0 then
  135. self.gutter_width = dest
  136. else
  137. self:move_towards("gutter_width", dest)
  138. end
  139. -- update suggestions box height
  140. local lh = self:get_suggestion_line_height()
  141. local dest = #self.suggestions * lh
  142. self:move_towards("suggestions_height", dest)
  143. -- update suggestion cursor offset
  144. local dest = self.suggestion_idx * self:get_suggestion_line_height()
  145. self:move_towards("selection_offset", dest)
  146. -- update size based on whether this is the active_view
  147. local dest = 0
  148. if self == core.active_view then
  149. dest = style.font:get_height() + style.padding.y * 2
  150. end
  151. self:move_towards(self.size, "y", dest)
  152. end
  153. function CommandView:draw_line_highlight()
  154. -- no-op function to disable this functionality
  155. end
  156. function CommandView:draw_gutter_text(idx, x, y)
  157. local yoffset = self:get_line_text_y_offset()
  158. local pos = self.position
  159. local color = common.lerp(style.text, style.accent, self.gutter_text_brightness / 100)
  160. core.push_clip_rect(pos.x, pos.y, self:get_gutter_width(), self.size.y)
  161. renderer.draw_text(self:get_font(), self.label, x, y + yoffset, color)
  162. core.pop_clip_rect()
  163. end
  164. local function draw_suggestions_box(self)
  165. local lh = self:get_suggestion_line_height()
  166. local dh = style.divider_size
  167. local offsety = self:get_line_text_y_offset()
  168. local x, _ = self:get_line_screen_position()
  169. local h = self.suggestions_height
  170. local rx, ry, rw, rh = self.position.x, self.position.y - h - dh, self.size.x, h
  171. -- draw suggestions background
  172. if #self.suggestions > 0 then
  173. renderer.draw_rect(rx, ry, rw, rh, style.background3)
  174. renderer.draw_rect(rx, ry - dh, rw, dh, style.divider)
  175. local y = self.position.y - self.selection_offset - dh
  176. renderer.draw_rect(rx, y, rw, lh, style.line_highlight)
  177. end
  178. -- draw suggestion text
  179. core.push_clip_rect(rx, ry, rw, rh)
  180. for i, item in ipairs(self.suggestions) do
  181. local color = (i == self.suggestion_idx) and style.accent or style.text
  182. local y = self.position.y - i * lh - dh
  183. common.draw_text(self:get_font(), color, item.text, nil, x, y, 0, lh)
  184. if item.info then
  185. local w = self.size.x - x - style.padding.x
  186. common.draw_text(self:get_font(), style.dim, item.info, "right", x, y, w, lh)
  187. end
  188. end
  189. core.pop_clip_rect()
  190. end
  191. function CommandView:draw()
  192. CommandView.super.draw(self)
  193. core.root_view:defer_draw(draw_suggestions_box, self)
  194. end
  195. return CommandView