treeview.lua 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. local core = require "core"
  2. local common = require "core.common"
  3. local command = require "core.command"
  4. local config = require "core.config"
  5. local keymap = require "core.keymap"
  6. local style = require "core.style"
  7. local View = require "core.view"
  8. local TreeView = View:extend()
  9. local function get_depth(filename)
  10. local n = 0
  11. for sep in filename:gmatch("[\\/]") do
  12. n = n + 1
  13. end
  14. return n
  15. end
  16. function TreeView:new()
  17. TreeView.super.new(self)
  18. self.scrollable = true
  19. self.focusable = false
  20. self.visible = true
  21. self.cache = {}
  22. end
  23. function TreeView:get_cached(item)
  24. local t = self.cache[item.filename]
  25. if not t then
  26. t = {}
  27. t.filename = item.filename
  28. t.abs_filename = system.absolute_path(item.filename)
  29. t.path, t.name = t.filename:match("^(.*)[\\/](.+)$")
  30. t.depth = get_depth(t.filename)
  31. t.type = item.type
  32. self.cache[t.filename] = t
  33. end
  34. return t
  35. end
  36. function TreeView:get_name()
  37. return "Project"
  38. end
  39. function TreeView:get_item_height()
  40. return style.font:get_height() + style.padding.y
  41. end
  42. function TreeView:check_cache()
  43. -- invalidate cache's skip values if project_files has changed
  44. if core.project_files ~= self.last_project_files then
  45. for _, v in pairs(self.cache) do
  46. v.skip = nil
  47. end
  48. self.last_project_files = core.project_files
  49. end
  50. end
  51. function TreeView:each_item()
  52. return coroutine.wrap(function()
  53. self:check_cache()
  54. local ox, oy = self:get_content_offset()
  55. local y = oy + style.padding.y
  56. local w = self.size.x
  57. local h = self:get_item_height()
  58. local i = 1
  59. while i <= #core.project_files do
  60. local item = core.project_files[i]
  61. local cached = self:get_cached(item)
  62. coroutine.yield(cached, ox, y, w, h)
  63. y = y + h
  64. i = i + 1
  65. if not cached.expanded then
  66. if cached.skip then
  67. i = cached.skip
  68. else
  69. local depth = cached.depth
  70. while i <= #core.project_files do
  71. local filename = core.project_files[i].filename
  72. if get_depth(filename) <= depth then break end
  73. i = i + 1
  74. end
  75. cached.skip = i
  76. end
  77. end
  78. end
  79. end)
  80. end
  81. function TreeView:on_mouse_moved(px, py)
  82. self.hovered_item = nil
  83. for item, x,y,w,h in self:each_item() do
  84. if px > x and py > y and px <= x + w and py <= y + h then
  85. self.hovered_item = item
  86. break
  87. end
  88. end
  89. end
  90. function TreeView:on_mouse_pressed(button, x, y)
  91. if not self.hovered_item then
  92. return
  93. elseif self.hovered_item.type == "dir" then
  94. self.hovered_item.expanded = not self.hovered_item.expanded
  95. else
  96. core.try(function()
  97. core.root_view:open_doc(core.open_doc(self.hovered_item.filename))
  98. end)
  99. end
  100. end
  101. function TreeView:update()
  102. self.scroll.to.y = math.max(0, self.scroll.to.y)
  103. -- update width
  104. local dest = self.visible and config.treeview_size or 0
  105. self:move_towards(self.size, "x", dest)
  106. TreeView.super.update(self)
  107. end
  108. function TreeView:draw()
  109. self:draw_background(style.background2)
  110. local h = self:get_item_height()
  111. local icon_width = style.icon_font:get_width("D")
  112. local spacing = style.font:get_width(" ") * 2
  113. local root_depth = get_depth(core.project_dir) + 1
  114. local doc = core.active_view.doc
  115. local active_filename = doc and system.absolute_path(doc.filename or "")
  116. for item, x,y,w,h in self:each_item() do
  117. local color = style.text
  118. -- highlight active_view doc
  119. if item.abs_filename == active_filename then
  120. color = style.accent
  121. end
  122. -- hovered item background
  123. if item == self.hovered_item then
  124. renderer.draw_rect(x, y, w, h, style.line_highlight)
  125. color = style.accent
  126. end
  127. -- icons
  128. x = x + (item.depth - root_depth) * style.padding.x + style.padding.x
  129. if item.type == "dir" then
  130. local icon1 = item.expanded and "e" or "c"
  131. local icon2 = item.expanded and "D" or "d"
  132. common.draw_text(style.icon_font, color, icon1, nil, x, y, 0, h)
  133. x = x + style.padding.x
  134. common.draw_text(style.icon_font, color, icon2, nil, x, y, 0, h)
  135. x = x + icon_width
  136. else
  137. x = x + style.padding.x
  138. common.draw_text(style.icon_font, color, "f", nil, x, y, 0, h)
  139. x = x + icon_width
  140. end
  141. -- text
  142. x = x + spacing
  143. x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
  144. end
  145. end
  146. -- init
  147. local view = TreeView()
  148. local node = core.root_view:get_active_node()
  149. view.size.x = config.treeview_size
  150. node:split("left", view, true)
  151. -- register commands and keymap
  152. command.add(nil, {
  153. ["treeview:toggle"] = function()
  154. view.visible = not view.visible
  155. end,
  156. })
  157. keymap.add { ["ctrl+\\"] = "treeview:toggle" }