Added and updated many plugins
This commit is contained in:
parent
782a7acc21
commit
0ebc0e789d
|
@ -70,6 +70,10 @@ need.
|
|||
|
||||
The included plugins are the following:
|
||||
|
||||
**amimodkeys**
|
||||
This plugin enables the Right Amiga and Right Alt to behave like Control,
|
||||
for those that are used to use them.
|
||||
|
||||
**autoinsert**
|
||||
Automatically inserts closing brackets and quotes. Also allows selected
|
||||
text to be wrapped with brackets or quotes.
|
||||
|
@ -124,6 +128,9 @@ folder like below:
|
|||
**indentguide**
|
||||
Adds indent guides
|
||||
|
||||
**keyhud**
|
||||
Simple key HUD display for lite-xl.
|
||||
|
||||
**language_guide**
|
||||
Syntax for the AmigaGuide scripting language
|
||||
|
||||
|
@ -142,6 +149,9 @@ Automatically inserts indentation and closing bracket/text after newline
|
|||
**markers**
|
||||
Add markers to docs and jump between them quickly
|
||||
|
||||
**memoryusage**
|
||||
Show memory usage in the status view
|
||||
|
||||
**minimap**
|
||||
Shows a minimap on the right-hand side of the docview. Please note that
|
||||
this plugin will make the editor slower on file loading and scrolling.
|
||||
|
@ -178,9 +188,19 @@ Highlights regions of code that match the current selection
|
|||
**smallclock**
|
||||
It adds a small clock at the bottom right corner.
|
||||
|
||||
**sort**
|
||||
Sorts selected lines alphabetically
|
||||
|
||||
**sortcss**
|
||||
Sort selected CSS properties alphabetically or using the concentric model.
|
||||
|
||||
**tetris**
|
||||
Play Tetris inside Lite XL.
|
||||
|
||||
**todotreeview**
|
||||
Todo tree viewer for annotations in code like TODO, BUG, FIX,
|
||||
IMPROVEMENT
|
||||
|
||||
## Tips and tricks
|
||||
### Transitions
|
||||
|
||||
|
@ -231,12 +251,21 @@ https://git.walkero.gr/walkero/lite-xl/issues
|
|||
# Changelog
|
||||
## [2.1.7r1] - 2024-12-26
|
||||
### Added
|
||||
- Added widget library
|
||||
- Added settings plugin that shows a GUI for chnaging the app settings
|
||||
- Added search_ui plugin that adds a GUI for search
|
||||
- Added the widget library
|
||||
- Added the settings plugin that shows a GUI for chnaging the app settings
|
||||
- Added the search_ui plugin that adds a GUI for search
|
||||
- Added the amimodkeys plugin that the Right Amiga and Right Alt to behave
|
||||
like Control
|
||||
- Added the memoryusage plugin
|
||||
- Added the todotreeview plugin
|
||||
- Added the keyhud plugin
|
||||
- Added the sort plugin
|
||||
- Added the sortcss plugin
|
||||
|
||||
### Updated
|
||||
- Updated the code to the upstream 2.1.7 release
|
||||
- Updated the minimap plugin
|
||||
- Updated the indentguide plugin
|
||||
|
||||
## [2.1.6r1] - 2024-12-3
|
||||
### Changed
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
-- mod-version:3
|
||||
local keymap = require "core.keymap"
|
||||
|
||||
local on_key_pressed = keymap.on_key_pressed
|
||||
local on_key_released = keymap.on_key_released
|
||||
|
||||
local function remap_key(k)
|
||||
return k:gsub("right alt", "control")
|
||||
:gsub("right amiga", "control")
|
||||
end
|
||||
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
return on_key_pressed(remap_key(k), ...)
|
||||
end
|
||||
|
||||
function keymap.on_key_released(k, ...)
|
||||
return on_key_released(remap_key(k), ...)
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ local DocView = require "core.docview"
|
|||
|
||||
config.plugins.indentguide = common.merge({
|
||||
enabled = true,
|
||||
highlight = true,
|
||||
-- The config specification used by the settings gui
|
||||
config_spec = {
|
||||
name = "Indent Guide",
|
||||
|
@ -15,28 +16,28 @@ config.plugins.indentguide = common.merge({
|
|||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = true
|
||||
},
|
||||
{
|
||||
label = "Highlight Line",
|
||||
description = "Toggle the highlight of the curent indentation indicator lines.",
|
||||
path = "highlight",
|
||||
type = "toggle",
|
||||
default = true
|
||||
}
|
||||
}
|
||||
}, config.plugins.indentguide)
|
||||
|
||||
-- TODO: replace with `doc:get_indent_info()` when 2.1 releases
|
||||
local function get_indent_info(doc)
|
||||
if doc.get_indent_info then
|
||||
return doc:get_indent_info()
|
||||
end
|
||||
return config.tab_type, config.indent_size
|
||||
end
|
||||
local indentguide = {}
|
||||
|
||||
|
||||
local function get_line_spaces(doc, line, dir)
|
||||
local _, indent_size = get_indent_info(doc)
|
||||
function indentguide.get_line_spaces(doc, line, dir)
|
||||
local _, indent_size = doc:get_indent_info()
|
||||
local text = doc.lines[line]
|
||||
if not text or #text == 1 then
|
||||
return -1
|
||||
end
|
||||
local s, e = text:find("^%s*")
|
||||
if e == #text then
|
||||
return get_line_spaces(doc, line + dir, dir)
|
||||
return indentguide.get_line_spaces(doc, line + dir, dir)
|
||||
end
|
||||
local n = 0
|
||||
for _,b in pairs({text:byte(s, e)}) do
|
||||
|
@ -46,15 +47,16 @@ local function get_line_spaces(doc, line, dir)
|
|||
end
|
||||
|
||||
|
||||
local function get_line_indent_guide_spaces(doc, line)
|
||||
function indentguide.get_line_indent_guide_spaces(doc, line)
|
||||
if doc.lines[line]:find("^%s*\n") then
|
||||
return math.max(
|
||||
get_line_spaces(doc, line - 1, -1),
|
||||
get_line_spaces(doc, line + 1, 1))
|
||||
indentguide.get_line_spaces(doc, line - 1, -1),
|
||||
indentguide.get_line_spaces(doc, line + 1, 1))
|
||||
end
|
||||
return get_line_spaces(doc, line)
|
||||
return indentguide.get_line_spaces(doc, line)
|
||||
end
|
||||
|
||||
|
||||
local docview_update = DocView.update
|
||||
function DocView:update()
|
||||
docview_update(self)
|
||||
|
@ -66,7 +68,7 @@ function DocView:update()
|
|||
local function get_indent(line)
|
||||
if line < 1 or line > #self.doc.lines then return -1 end
|
||||
if not self.indentguide_indents[line] then
|
||||
self.indentguide_indents[line] = get_line_indent_guide_spaces(self.doc, line)
|
||||
self.indentguide_indents[line] = indentguide.get_line_indent_guide_spaces(self.doc, line)
|
||||
end
|
||||
return self.indentguide_indents[line]
|
||||
end
|
||||
|
@ -76,10 +78,10 @@ function DocView:update()
|
|||
|
||||
local minline, maxline = self:get_visible_line_range()
|
||||
for i = minline, maxline do
|
||||
self.indentguide_indents[i] = get_line_indent_guide_spaces(self.doc, i)
|
||||
self.indentguide_indents[i] = indentguide.get_line_indent_guide_spaces(self.doc, i)
|
||||
end
|
||||
|
||||
local _, indent_size = get_indent_info(self.doc)
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
for _,line in self.doc:get_selections() do
|
||||
local lvl = get_indent(line)
|
||||
local top, bottom
|
||||
|
@ -121,19 +123,26 @@ function DocView:update()
|
|||
end
|
||||
|
||||
|
||||
function indentguide.get_width()
|
||||
return math.max(1, SCALE)
|
||||
end
|
||||
|
||||
|
||||
local draw_line_text = DocView.draw_line_text
|
||||
function DocView:draw_line_text(line, x, y)
|
||||
if config.plugins.indentguide.enabled and self:is(DocView) then
|
||||
local spaces = self.indentguide_indents[line] or -1
|
||||
local _, indent_size = get_indent_info(self.doc)
|
||||
local w = math.max(1, SCALE)
|
||||
local _, indent_size = self.doc:get_indent_info()
|
||||
local w = indentguide.get_width()
|
||||
local h = self:get_line_height()
|
||||
local font = self:get_font()
|
||||
local space_sz = font:get_width(" ")
|
||||
for i = 0, spaces - 1, indent_size do
|
||||
local color = style.guide or style.selection
|
||||
local active_lvl = self.indentguide_indent_active[line] or -1
|
||||
if i < active_lvl and i + indent_size >= active_lvl then
|
||||
if i < active_lvl
|
||||
and i + indent_size >= active_lvl
|
||||
and config.plugins.indentguide.highlight then
|
||||
color = style.guide_highlight or style.accent
|
||||
end
|
||||
local sw = space_sz * i
|
||||
|
@ -142,3 +151,5 @@ function DocView:draw_line_text(line, x, y)
|
|||
end
|
||||
return draw_line_text(self, line, x, y)
|
||||
end
|
||||
|
||||
return indentguide
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local CommandView = require "core.commandview"
|
||||
local RootView = require "core.rootview"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
|
||||
|
||||
local keyhud = {}
|
||||
|
||||
config.plugins.keyhud = common.merge({
|
||||
stroke_map = {
|
||||
["escape"] = "<ESC>",
|
||||
["space"] = "<SPACE>", --"␣""
|
||||
["left gui"] = "<CMD>", --"⌘"
|
||||
["right gui"] = "<CMD>",
|
||||
["left ctrl"] = "<CTRL>",
|
||||
["control"] = "<CTRL>",
|
||||
["right ctrl"] = "<CTRL>",
|
||||
["left alt"] = "<ALT>",
|
||||
["right alt"] = "<ALT>",
|
||||
["left amiga"] = "<LAMIGA>",
|
||||
["right amiga"] = "<RAMIGA>",
|
||||
["left"] = "←",
|
||||
["right"] = "→",
|
||||
["up"] = "↑",
|
||||
["down"] = "↓",
|
||||
["left shift"] = "⇧",
|
||||
["right shift"] = "⇧",
|
||||
["capslock"] = "⇪",
|
||||
["return"] = "<RETURN>", --"↵",
|
||||
["backspace"] = "⌫",
|
||||
["delete"] = "⌦",
|
||||
["pageup"] = "<UP>", --"⇞",
|
||||
["pagedown"] = "<DOWN>", --"⇟",
|
||||
["home"] = "<HOME>", --"↖",
|
||||
["end"] = "<END>", --"↘",
|
||||
["tab"] = "<TAB>", --"⇥",
|
||||
},
|
||||
max_time = 0.5,
|
||||
only_mapped = false,
|
||||
filters = {
|
||||
["commandview"] = true,
|
||||
["mouse"] = true
|
||||
},
|
||||
position = "right",
|
||||
}, config.plugins.keyhud)
|
||||
|
||||
style.keyhud = common.merge(
|
||||
{
|
||||
background = { common.color "#00000066" },
|
||||
text = { common.color "#ffffffdd" },
|
||||
font = style.big_font, -- style.code_font:copy(46 * SCALE)
|
||||
},
|
||||
style.keyhud
|
||||
)
|
||||
|
||||
keyhud.last_strokes = {}
|
||||
keyhud.last_strokes_time_stamp = {}
|
||||
|
||||
|
||||
keyhud.on_key_pressed__orig = keymap.on_key_pressed
|
||||
keyhud.on_key_released__orig = keymap.on_key_released
|
||||
|
||||
|
||||
local function dv()
|
||||
return core.active_view
|
||||
end
|
||||
|
||||
function keymap.on_key_pressed(k, ...)
|
||||
if dv():is(CommandView) and config.plugins.keyhud.filters.commandview then
|
||||
return keyhud.on_key_pressed__orig(k, ...)
|
||||
end
|
||||
if config.plugins.keyhud.filters.mouse and (string.find(k, "click", 1, true) or string.find(k, "wheel", 1, true)) then
|
||||
return keyhud.on_key_pressed__orig(k, ...)
|
||||
end
|
||||
local x = config.plugins.keyhud.stroke_map[k]
|
||||
if x == nil and not config.plugins.keyhud.only_mapped then
|
||||
if #k > 1 then
|
||||
x = '<' .. k .. '>'
|
||||
else
|
||||
x = k
|
||||
end
|
||||
end
|
||||
if x ~= nil then
|
||||
for i, key in ipairs(keyhud.last_strokes) do
|
||||
if x == key then
|
||||
keyhud.last_strokes_time_stamp[i] = -1
|
||||
x = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if x ~= nil then
|
||||
table.insert(keyhud.last_strokes, x)
|
||||
table.insert(keyhud.last_strokes_time_stamp, -1)
|
||||
end
|
||||
return keyhud.on_key_pressed__orig(k, ...)
|
||||
end
|
||||
|
||||
function keymap.on_key_released(k)
|
||||
if #keyhud.last_strokes then
|
||||
local x = config.plugins.keyhud.stroke_map[k]
|
||||
if x == nil then
|
||||
x = k
|
||||
end
|
||||
for i, key in ipairs(keyhud.last_strokes) do
|
||||
if x == key then
|
||||
keyhud.last_strokes_time_stamp[i] = system.get_time()
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return keyhud.on_key_released__orig(k)
|
||||
end
|
||||
|
||||
local rvDraw = RootView.draw
|
||||
function RootView:draw(...)
|
||||
rvDraw(self, ...)
|
||||
local position = config.plugins.keyhud.position
|
||||
if position ~= 'right' and position ~= 'left' then
|
||||
core.error("`config.plugins.keyhud.position` can be only `left` or `right`")
|
||||
return nil
|
||||
end
|
||||
local font = style.keyhud.font
|
||||
local h = font:get_height() + 20
|
||||
local w = h
|
||||
local y = self.size.y - 10
|
||||
local next_strokes = {}
|
||||
local next_timestamps = {}
|
||||
local start_i, end_i, step = 0, 0, 1
|
||||
if position == "left" then
|
||||
local x = 10
|
||||
for i = 1, #keyhud.last_strokes do
|
||||
local t0 = keyhud.last_strokes_time_stamp[i]
|
||||
if t0 < 0 or system.get_time() - t0 < config.plugins.keyhud.max_time then
|
||||
local key = keyhud.last_strokes[i]
|
||||
core.redraw = true
|
||||
-- y = self.size.y - core.status_view.size.y
|
||||
local tw = font:get_width(key)
|
||||
local th = font:get_height()
|
||||
w = h
|
||||
if tw + 20 > w then
|
||||
w = tw + 20
|
||||
end
|
||||
renderer.draw_rect(x, y - h, w, h, style.keyhud.background)
|
||||
renderer.draw_text(font, key, x + w / 2 - tw / 2, y - h / 2 - th / 2,
|
||||
style.keyhud.text)
|
||||
x = x + w + 10
|
||||
table.insert(next_strokes, key)
|
||||
table.insert(next_timestamps, t0)
|
||||
end
|
||||
end
|
||||
start_i = 1
|
||||
end_i = #next_strokes
|
||||
step = 1
|
||||
else
|
||||
local x = self.size.x - 10
|
||||
for i = #keyhud.last_strokes, 1, -1 do
|
||||
local t0 = keyhud.last_strokes_time_stamp[i]
|
||||
if t0 < 0 or system.get_time() - t0 < config.plugins.keyhud.max_time then
|
||||
local key = keyhud.last_strokes[i]
|
||||
core.redraw = true
|
||||
-- y = self.size.y - core.status_view.size.y
|
||||
local tw = font:get_width(key)
|
||||
local th = font:get_height()
|
||||
if tw + 20 > w then
|
||||
w = tw + 20
|
||||
end
|
||||
renderer.draw_rect(x - w, y - h, w, h, style.keyhud.background)
|
||||
renderer.draw_text(font, key, x - w / 2 - tw / 2, y - h / 2 - th / 2,
|
||||
style.keyhud.text)
|
||||
x = x - w - 10
|
||||
table.insert(next_strokes, key)
|
||||
table.insert(next_timestamps, t0)
|
||||
end
|
||||
end
|
||||
start_i = #next_strokes
|
||||
end_i = 1
|
||||
step = -1
|
||||
end
|
||||
keyhud.last_strokes = {}
|
||||
keyhud.last_strokes_time_stamp = {}
|
||||
for i = start_i, end_i, step do
|
||||
table.insert(keyhud.last_strokes, next_strokes[i])
|
||||
table.insert(keyhud.last_strokes_time_stamp, next_timestamps[i])
|
||||
end
|
||||
end
|
||||
|
||||
return keyhud
|
|
@ -0,0 +1,49 @@
|
|||
-- mod-version:3
|
||||
-- original implementation by AqilCont
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
local style = require "core.style"
|
||||
local StatusView = require "core.statusview"
|
||||
|
||||
config.plugins.memoryusage = common.merge({
|
||||
enabled = true,
|
||||
-- The config specification used by the settings gui
|
||||
config_spec = {
|
||||
name = "Memory Usage",
|
||||
{
|
||||
label = "Enabled",
|
||||
description = "Show or hide the lua memory usage from the status bar.",
|
||||
path = "enabled",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
on_apply = function(enabled)
|
||||
core.add_thread(function()
|
||||
if enabled then
|
||||
core.status_view:get_item("status:memory-usage"):show()
|
||||
else
|
||||
core.status_view:get_item("status:memory-usage"):hide()
|
||||
end
|
||||
end)
|
||||
end
|
||||
}
|
||||
}
|
||||
}, config.plugins.memoryusage)
|
||||
|
||||
core.status_view:add_item({
|
||||
name = "status:memory-usage",
|
||||
alignment = StatusView.Item.RIGHT,
|
||||
get_item = function()
|
||||
return {
|
||||
style.text,
|
||||
string.format(
|
||||
"%.2f MB",
|
||||
(math.floor(collectgarbage("count") / 10.24) / 100)
|
||||
)
|
||||
}
|
||||
end,
|
||||
position = 1,
|
||||
tooltip = "lua memory usage",
|
||||
separator = core.status_view.separator2
|
||||
})
|
||||
|
|
@ -135,29 +135,21 @@ config.plugins.minimap = common.merge({
|
|||
},
|
||||
{
|
||||
label = "Selection Color",
|
||||
description = "Background color of selected text in html notation eg: #FFFFFF. Leave empty to use default.",
|
||||
path = "selection_color_html",
|
||||
type = "string",
|
||||
on_apply = function(value)
|
||||
if value and value:match("#%x%x%x%x%x%x") then
|
||||
config.plugins.minimap.selection_color = { common.color(value) }
|
||||
else
|
||||
config.plugins.minimap.selection_color = nil
|
||||
end
|
||||
end
|
||||
description = "Background color of selected text.",
|
||||
path = "selection_color",
|
||||
type = "color",
|
||||
default = string.format("#%02X%02X%02X%02X",
|
||||
style.dim[1], style.dim[2], style.dim[3], style.dim[4]
|
||||
)
|
||||
},
|
||||
{
|
||||
label = "Caret Color",
|
||||
description = "Background color of active line in html notation eg: #FFFFFF. Leave empty to use default.",
|
||||
path = "caret_color_html",
|
||||
type = "string",
|
||||
on_apply = function(value)
|
||||
if value and value:match("#%x%x%x%x%x%x") then
|
||||
config.plugins.minimap.caret_color = { common.color(value) }
|
||||
else
|
||||
config.plugins.minimap.caret_color = nil
|
||||
end
|
||||
end
|
||||
description = "Background color of active line.",
|
||||
path = "caret_color",
|
||||
type = "color",
|
||||
default = string.format("#%02X%02X%02X%02X",
|
||||
style.caret[1], style.caret[2], style.caret[3], style.caret[4]
|
||||
)
|
||||
},
|
||||
{
|
||||
label = "Highlight Alignment",
|
||||
|
@ -236,8 +228,6 @@ local function reset_cache_if_needed()
|
|||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-- Move cache to make space for new lines
|
||||
local prev_insert_notify = Highlighter.insert_notify
|
||||
function Highlighter:insert_notify(line, n, ...)
|
||||
|
@ -246,13 +236,11 @@ function Highlighter:insert_notify(line, n, ...)
|
|||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
else
|
||||
local to = math.min(line + n, #self.doc.lines)
|
||||
for i=#self.doc.lines+n,to,-1 do
|
||||
highlighter_cache[self][i] = highlighter_cache[self][i - n]
|
||||
end
|
||||
for i=line,to do
|
||||
highlighter_cache[self][i] = nil
|
||||
local blanks = { }
|
||||
for i = 1, n do
|
||||
blanks[i] = false
|
||||
end
|
||||
common.splice(highlighter_cache[self], line, 0, blanks)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -264,10 +252,7 @@ function Highlighter:remove_notify(line, n, ...)
|
|||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
else
|
||||
local to = math.max(line + n, #self.doc.lines)
|
||||
for i=line,to do
|
||||
highlighter_cache[self][i] = highlighter_cache[self][i + n]
|
||||
end
|
||||
common.splice(highlighter_cache[self], line, n)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -279,7 +264,7 @@ function Highlighter:tokenize_line(idx, state, ...)
|
|||
if not highlighter_cache[self] then
|
||||
highlighter_cache[self] = {}
|
||||
end
|
||||
highlighter_cache[self][idx] = nil
|
||||
highlighter_cache[self][idx] = false
|
||||
return res
|
||||
end
|
||||
|
||||
|
@ -305,14 +290,50 @@ end
|
|||
local MiniMap = Scrollbar:extend()
|
||||
|
||||
|
||||
function MiniMap:new(dv)
|
||||
MiniMap.super.new(self, { direction = "v", alignment = "e" })
|
||||
function MiniMap:new(dv, original_v_scrollbar)
|
||||
MiniMap.super.new(self, { direction = "v", alignment = "e",
|
||||
force_status = "expanded",
|
||||
expanded_size = cached_settings.width,
|
||||
expanded_margin = 0 })
|
||||
self.original_force_status = original_v_scrollbar.force_status
|
||||
self.original_expanded_size = original_v_scrollbar.expanded_size
|
||||
self.original_expanded_margin = original_v_scrollbar.expanded_margin
|
||||
self.dv = dv
|
||||
self.enabled = nil
|
||||
self.was_enabled = true
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:line_highlight_color(line_index)
|
||||
function MiniMap:swap_to_status()
|
||||
local enabled = self:is_minimap_enabled()
|
||||
if not enabled and self.was_enabled then
|
||||
self.force_status = self.original_force_status
|
||||
self.expanded_size = self.original_expanded_size
|
||||
self.expanded_margin = self.original_expanded_margin
|
||||
self.was_enabled = false
|
||||
elseif enabled and not self.was_enabled then
|
||||
self.force_status = "expanded"
|
||||
self.expanded_size = cached_settings.width
|
||||
self.expanded_margin = 0
|
||||
self.was_enabled = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:update()
|
||||
self:swap_to_status()
|
||||
if self:is_minimap_enabled() then
|
||||
reset_cache_if_needed()
|
||||
self.expanded_size = cached_settings.width
|
||||
local lh = self.dv:get_line_height()
|
||||
local nlines = self.dv.size.y / lh
|
||||
self.minimum_thumb_size = nlines * line_spacing
|
||||
end
|
||||
MiniMap.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:line_highlight_color(line_index, docview)
|
||||
-- other plugins can override this, and return a color
|
||||
end
|
||||
|
||||
|
@ -335,97 +356,57 @@ function MiniMap:is_minimap_enabled()
|
|||
end
|
||||
|
||||
|
||||
function MiniMap:get_minimap_dimensions()
|
||||
local x, y, w, h = self:get_track_rect()
|
||||
local _, cy, _, cy2 = self.dv:get_content_bounds()
|
||||
local lh = self.dv:get_line_height()
|
||||
|
||||
local visible_lines_start = math.max(1, math.floor(cy / lh))
|
||||
local visible_lines_count = math.max(1, (cy2 - cy) / lh)
|
||||
local minimap_lines_start = 1
|
||||
local minimap_lines_count = math.floor(h / line_spacing)
|
||||
local line_count = #self.dv.doc.lines
|
||||
|
||||
local is_file_too_large = line_count > 1 and line_count > minimap_lines_count
|
||||
if is_file_too_large then
|
||||
local scroll_pos = (visible_lines_start - 1) /
|
||||
(line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1, procent of visual area scrolled
|
||||
|
||||
local thumb_height = visible_lines_count * line_spacing
|
||||
local scroll_pos_pixels = scroll_pos * (h - thumb_height)
|
||||
|
||||
minimap_lines_start = visible_lines_start -
|
||||
math.floor(scroll_pos_pixels / line_spacing)
|
||||
minimap_lines_start = math.max(1, minimap_lines_start)
|
||||
function MiniMap:_on_mouse_pressed_normal(button, x, y, clicks)
|
||||
local overlaps = self:_overlaps_normal(x, y)
|
||||
local percent = MiniMap.super._on_mouse_pressed_normal(self, button, x, y, clicks)
|
||||
if overlaps == "track" then
|
||||
-- We need to adjust the percentage to scroll to the line in the minimap
|
||||
-- that was "clicked"
|
||||
local minimap_line, _ = self:get_minimap_lines()
|
||||
local _, track_y, _, _ = self:_get_track_rect_normal()
|
||||
local line = minimap_line + (y - track_y) // line_spacing
|
||||
local _, y = self.dv:get_line_screen_position(line)
|
||||
local _, oy = self.dv:get_content_offset()
|
||||
local nr = self.normal_rect
|
||||
percent = common.clamp((y - oy - (self.dv.size.y) / 2) / (nr.scrollable - self.dv.size.y), 0, 1)
|
||||
end
|
||||
return visible_lines_start, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large
|
||||
return percent
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:_get_track_rect_normal()
|
||||
if not self:is_minimap_enabled() then return MiniMap.super._get_track_rect_normal(self) end
|
||||
return self.dv.size.x + self.dv.position.x - config.plugins.minimap.width, self.dv.position.y, config.plugins.minimap.width, self.dv.size.y
|
||||
local function get_visible_minline(dv)
|
||||
local _, y, _, _ = dv:get_content_bounds()
|
||||
local lh = dv:get_line_height()
|
||||
local minline = math.max(0, y / lh + 1)
|
||||
return minline
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:get_active_margin() if self:is_minimap_enabled() then return 0 else return MiniMap.super.get_active_margin(self) end end
|
||||
function MiniMap:get_minimap_lines()
|
||||
local _, track_y, _, h = self:_get_track_rect_normal()
|
||||
local _, thumb_y, _, _ = self:_get_thumb_rect_normal()
|
||||
|
||||
local nlines = h // line_spacing
|
||||
|
||||
function MiniMap:_get_thumb_rect_normal()
|
||||
if not self:is_minimap_enabled() then return MiniMap.super._get_thumb_rect_normal(self) end
|
||||
local visible_lines_start, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large = self:get_minimap_dimensions()
|
||||
local visible_y = self.dv.position.y + (visible_lines_start - 1) * line_spacing
|
||||
if is_file_too_large then
|
||||
local line_count = #self.dv.doc.lines
|
||||
local scroll_pos = (visible_lines_start - 1) /
|
||||
(line_count - visible_lines_count - 1)
|
||||
scroll_pos = math.min(1.0, scroll_pos) -- 0..1, procent of visual area scrolled
|
||||
|
||||
local thumb_height = visible_lines_count * line_spacing
|
||||
local scroll_pos_pixels = scroll_pos * (self.dv.size.y - thumb_height)
|
||||
visible_y = self.dv.position.y + scroll_pos_pixels
|
||||
local minline = get_visible_minline(self.dv)
|
||||
local top_lines = (thumb_y - track_y) / line_spacing
|
||||
local lines_start, offset = math.modf(minline - top_lines)
|
||||
if lines_start <= 1 and nlines >= #self.dv.doc.lines then
|
||||
offset = 0
|
||||
end
|
||||
return self.dv.size.x + self.dv.position.x - config.plugins.minimap.width, visible_y, config.plugins.minimap.width, visible_lines_count * line_spacing
|
||||
return common.clamp(lines_start, 1, #self.dv.doc.lines), common.clamp(nlines, 1, #self.dv.doc.lines), offset * line_spacing
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:on_mouse_pressed(button, x, y, clicks)
|
||||
local percent = MiniMap.super.on_mouse_pressed(self, button, x, y, clicks)
|
||||
if not self:is_minimap_enabled() or not percent then return percent end
|
||||
local _, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large = self:get_minimap_dimensions()
|
||||
local _, _, w, h = self:get_track_rect()
|
||||
local tx, ty, tw, th = self:get_thumb_rect()
|
||||
if y >= ty and y < ty + th then self.drag_start_offset = (y - ty) - th / 2 return self.percent end
|
||||
self.drag_start_offset = 0
|
||||
self.hovering.thumb = x >= tx and x < tx + tw and y >= ty and y < ty + th
|
||||
self.dragging = self.hovering.thumb
|
||||
local lh = self.dv:get_line_height()
|
||||
percent = math.max(0.0, math.min((y - self.dv.position.y) / h, 1.0))
|
||||
return (((percent * minimap_lines_count) + minimap_lines_start) * lh / self.dv:get_scrollable_size()) - (visible_lines_count / #self.dv.doc.lines / 2)
|
||||
function MiniMap:set_size(x, y, w, h, scrollable)
|
||||
if not self:is_minimap_enabled() then return MiniMap.super.set_size(self, x, y, w, h, scrollable) end
|
||||
-- If possible, use the size needed to only manage the visible minimap lines.
|
||||
-- This allows us to let Scrollbar manage the thumb.
|
||||
h = math.min(h, line_spacing * (scrollable // self.dv:get_line_height()))
|
||||
MiniMap.super.set_size(self, x, y, w, h, scrollable)
|
||||
end
|
||||
|
||||
|
||||
function MiniMap:on_mouse_moved(x, y, dx, dy)
|
||||
local percent = MiniMap.super.on_mouse_moved(self, x, y, dx, dy)
|
||||
if not self:is_minimap_enabled() or type(percent) ~= "number" then return percent end
|
||||
local _, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large = self:get_minimap_dimensions()
|
||||
local lh = self.dv:get_line_height()
|
||||
local _, _, w, h = self:get_track_rect()
|
||||
local tx, ty, tw, th = self:get_thumb_rect()
|
||||
if x >= tx and x < tx + tw and y >= ty and y < ty + th then self.hovering.thumb = true end
|
||||
if not self.hovering.thumb then return self.percent end
|
||||
y = y - self.drag_start_offset
|
||||
percent = math.max(0.0, math.min((y - self.dv.position.y) / h, 1.0))
|
||||
return (((percent * minimap_lines_count) + minimap_lines_start) * lh / self.dv:get_scrollable_size()) - (visible_lines_count / #self.dv.doc.lines / 2)
|
||||
end
|
||||
|
||||
function MiniMap:draw_thumb()
|
||||
local color = self.hovering.thumb and style.scrollbar2 or style.scrollbar
|
||||
local x, y, w, h = self:get_thumb_rect()
|
||||
renderer.draw_rect(x, y, w, h, color)
|
||||
end
|
||||
|
||||
function MiniMap:draw()
|
||||
if not self:is_minimap_enabled() then return MiniMap.super.draw(self) end
|
||||
local dv = self.dv
|
||||
|
@ -434,25 +415,27 @@ function MiniMap:draw()
|
|||
local highlight = dv.hovered_scrollbar or dv.dragging_scrollbar
|
||||
local visual_color = highlight and style.scrollbar2 or style.scrollbar
|
||||
|
||||
local visible_lines_start, visible_lines_count,
|
||||
minimap_lines_start, minimap_lines_count = self:get_minimap_dimensions()
|
||||
|
||||
if config.plugins.minimap.draw_background then
|
||||
renderer.draw_rect(x, y, w, h, style.minimap_background or style.background)
|
||||
renderer.draw_rect(x, y, w, self.dv.size.y, style.minimap_background or style.background)
|
||||
end
|
||||
self:draw_thumb()
|
||||
|
||||
local minimap_lines_start, minimap_lines_count, y_offset = self:get_minimap_lines()
|
||||
local line_selection_offset = line_spacing - char_height
|
||||
y = y - y_offset + line_selection_offset
|
||||
|
||||
-- highlight the selected lines, and the line with the caret on it
|
||||
local selection_color = config.plugins.minimap.selection_color or style.dim
|
||||
local caret_color = config.plugins.minimap.caret_color or style.caret
|
||||
|
||||
for i, line1, col1, line2, col2 in dv.doc:get_selections() do
|
||||
local selection1_y = y + (line1 - minimap_lines_start) * line_spacing
|
||||
local selection2_y = y + (line2 - minimap_lines_start) * line_spacing
|
||||
for _, line1, _, line2, _ in dv.doc:get_selections() do
|
||||
local selection1_y = y + (line1 - minimap_lines_start) * line_spacing - line_selection_offset
|
||||
local selection2_y = y + (line2 - minimap_lines_start) * line_spacing - line_selection_offset
|
||||
local selection_min_y = math.min(selection1_y, selection2_y)
|
||||
local selection_h = math.abs(selection2_y - selection1_y)+1
|
||||
local selection_h = math.abs(selection2_y - selection1_y) + 1 + line_selection_offset
|
||||
renderer.draw_rect(x, selection_min_y, w, selection_h, selection_color)
|
||||
renderer.draw_rect(x, selection1_y, w, line_spacing, caret_color)
|
||||
renderer.draw_rect(x, selection1_y, w, line_spacing + line_selection_offset, caret_color)
|
||||
end
|
||||
|
||||
local highlight_align = config.plugins.minimap.highlight_align
|
||||
|
@ -519,16 +502,15 @@ function MiniMap:draw()
|
|||
highlight_x = x + w - highlight_width
|
||||
end
|
||||
local function render_highlight(idx, line_y)
|
||||
local highlight_color = self:line_highlight_color(idx)
|
||||
local highlight_color = self:line_highlight_color(idx, self.dv)
|
||||
if highlight_color then
|
||||
renderer.draw_rect(highlight_x, line_y, highlight_width, line_spacing, highlight_color)
|
||||
renderer.draw_rect(highlight_x, line_y - line_selection_offset,
|
||||
highlight_width, line_spacing + line_selection_offset, highlight_color)
|
||||
end
|
||||
end
|
||||
|
||||
local endidx = math.min(minimap_lines_start + minimap_lines_count, #self.dv.doc.lines)
|
||||
|
||||
reset_cache_if_needed()
|
||||
|
||||
if not highlighter_cache[dv.doc.highlighter] then
|
||||
highlighter_cache[dv.doc.highlighter] = {}
|
||||
end
|
||||
|
@ -603,23 +585,9 @@ end
|
|||
local old_docview_new = DocView.new
|
||||
function DocView:new(doc)
|
||||
old_docview_new(self, doc)
|
||||
if self:is(DocView) then self.v_scrollbar = MiniMap(self) end
|
||||
if self:is(DocView) then
|
||||
self.v_scrollbar = MiniMap(self, self.v_scrollbar)
|
||||
end
|
||||
|
||||
local old_docview_scroll_to_make_visible = DocView.scroll_to_make_visible
|
||||
function DocView:scroll_to_make_visible(line, col, ...)
|
||||
if
|
||||
not self:is(DocView) or not self.v_scrollbar:is(MiniMap)
|
||||
or
|
||||
not self.v_scrollbar:is_minimap_enabled()
|
||||
then
|
||||
return old_docview_scroll_to_make_visible(self, line, col, ...)
|
||||
end
|
||||
local old_size = self.size.x
|
||||
self.size.x = math.max(0, self.size.x - config.plugins.minimap.width)
|
||||
local result = old_docview_scroll_to_make_visible(self, line, col, ...)
|
||||
self.size.x = old_size
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
@ -663,4 +631,3 @@ command.add("core.docview!", {
|
|||
})
|
||||
|
||||
return MiniMap
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
local function split_lines(text)
|
||||
local res = {}
|
||||
for line in (text .. "\n"):gmatch("(.-)\n") do
|
||||
table.insert(res, line)
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
command.add("core.docview!", {
|
||||
["sort:sort"] = function(dv)
|
||||
local doc = dv.doc
|
||||
|
||||
local l1, c1, l2, c2, swap = doc:get_selection(true)
|
||||
l1, c1 = translate.start_of_line(doc, l1, c1)
|
||||
l2, c2 = translate.end_of_line(doc, l2, c2)
|
||||
doc:set_selection(l1, c1, l2, c2, swap)
|
||||
|
||||
doc:replace(function(text)
|
||||
local head, body, foot = text:match("(\n*)(.-)(\n*)$")
|
||||
local lines = split_lines(body)
|
||||
table.sort(lines, function(a, b) return a:lower() < b:lower() end)
|
||||
return head .. table.concat(lines, "\n") .. foot, 1
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
|
@ -0,0 +1,413 @@
|
|||
-- mod-version:3
|
||||
|
||||
--[[
|
||||
Lite-XL plugin to sort CSS properties alphabetically or using
|
||||
the concentric model by https://rhodesmill.org/brandon/2011/concentric-css/
|
||||
|
||||
This plugin is based on vscode plugin: https://github.com/roubaobaozi/vscode-sort-selection-concentrically
|
||||
|
||||
Usage:
|
||||
1. Select CSS code (must have one property per line).
|
||||
2. Press control+alt+a to sort alphabetically or control+alt+c to sort concentrically.
|
||||
Alternatively you can also right-click and select the appropriate option.
|
||||
--]]
|
||||
|
||||
local core = require "core"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local keymap = require "core.keymap"
|
||||
local contextmenu = require "plugins.contextmenu"
|
||||
|
||||
local css_syntaxes = {
|
||||
"CSS",
|
||||
"HTML",
|
||||
"JSX",
|
||||
"TypeScript with JSX",
|
||||
}
|
||||
|
||||
local concentric_order = {
|
||||
-- browser default styles
|
||||
"all",
|
||||
"appearance",
|
||||
|
||||
-- box model
|
||||
"box-sizing",
|
||||
|
||||
-- position
|
||||
"display",
|
||||
"position",
|
||||
"top",
|
||||
"right",
|
||||
"bottom",
|
||||
"left",
|
||||
|
||||
"float",
|
||||
"clear",
|
||||
|
||||
-- flex
|
||||
"flex",
|
||||
"flex-basis",
|
||||
"flex-direction",
|
||||
"flex-flow",
|
||||
"flex-grow",
|
||||
"flex-shrink",
|
||||
"flex-wrap",
|
||||
|
||||
-- grid
|
||||
"grid",
|
||||
"grid-area",
|
||||
"grid-template",
|
||||
"grid-template-areas",
|
||||
"grid-template-rows",
|
||||
"grid-template-columns",
|
||||
"grid-row",
|
||||
"grid-row-start",
|
||||
"grid-row-end",
|
||||
"grid-column",
|
||||
"grid-column-start",
|
||||
"grid-column-end",
|
||||
"grid-auto-rows",
|
||||
"grid-auto-columns",
|
||||
"grid-auto-flow",
|
||||
"grid-gap",
|
||||
"grid-row-gap",
|
||||
"grid-column-gap",
|
||||
|
||||
-- flex align
|
||||
"align-content",
|
||||
"align-items",
|
||||
"align-self",
|
||||
|
||||
-- flex justify
|
||||
"justify-content",
|
||||
"justify-items",
|
||||
"justify-self",
|
||||
|
||||
-- order
|
||||
"order",
|
||||
|
||||
-- columns
|
||||
"columns",
|
||||
"column-gap",
|
||||
"column-fill",
|
||||
"column-rule",
|
||||
"column-rule-width",
|
||||
"column-rule-style",
|
||||
"column-rule-color",
|
||||
"column-span",
|
||||
"column-count",
|
||||
"column-width",
|
||||
|
||||
-- transform
|
||||
"backface-visibility",
|
||||
"perspective",
|
||||
"perspective-origin",
|
||||
"transform",
|
||||
"transform-origin",
|
||||
"transform-style",
|
||||
|
||||
-- transitions
|
||||
"transition",
|
||||
"transition-delay",
|
||||
"transition-duration",
|
||||
"transition-property",
|
||||
"transition-timing-function",
|
||||
|
||||
-- visibility
|
||||
"visibility",
|
||||
"opacity",
|
||||
"mix-blend-mode",
|
||||
"isolation",
|
||||
"z-index",
|
||||
|
||||
-- margin
|
||||
"margin",
|
||||
"margin-top",
|
||||
"margin-right",
|
||||
"margin-bottom",
|
||||
"margin-left",
|
||||
|
||||
-- outline
|
||||
"outline",
|
||||
"outline-offset",
|
||||
"outline-width",
|
||||
"outline-style",
|
||||
"outline-color",
|
||||
|
||||
-- border
|
||||
"border",
|
||||
"border-top",
|
||||
"border-right",
|
||||
"border-bottom",
|
||||
"border-left",
|
||||
"border-width",
|
||||
"border-top-width",
|
||||
"border-right-width",
|
||||
"border-bottom-width",
|
||||
"border-left-width",
|
||||
|
||||
-- border-style
|
||||
"border-style",
|
||||
"border-top-style",
|
||||
"border-right-style",
|
||||
"border-bottom-style",
|
||||
"border-left-style",
|
||||
|
||||
-- border-radius
|
||||
"border-radius",
|
||||
"border-top-left-radius",
|
||||
"border-top-right-radius",
|
||||
"border-bottom-left-radius",
|
||||
"border-bottom-right-radius",
|
||||
|
||||
-- border-color
|
||||
"border-color",
|
||||
"border-top-color",
|
||||
"border-right-color",
|
||||
"border-bottom-color",
|
||||
"border-left-color",
|
||||
|
||||
-- border-image
|
||||
"border-image",
|
||||
"border-image-source",
|
||||
"border-image-width",
|
||||
"border-image-outset",
|
||||
"border-image-repeat",
|
||||
"border-image-slice",
|
||||
|
||||
-- box-shadow
|
||||
"box-shadow",
|
||||
|
||||
-- background
|
||||
"background",
|
||||
"background-attachment",
|
||||
"background-clip",
|
||||
"background-color",
|
||||
"background-image",
|
||||
"background-origin",
|
||||
"background-position",
|
||||
"background-repeat",
|
||||
"background-size",
|
||||
"background-blend-mode",
|
||||
|
||||
-- cursor
|
||||
"cursor",
|
||||
|
||||
-- padding
|
||||
"padding",
|
||||
"padding-top",
|
||||
"padding-right",
|
||||
"padding-bottom",
|
||||
"padding-left",
|
||||
|
||||
-- width
|
||||
"width",
|
||||
"min-width",
|
||||
"max-width",
|
||||
|
||||
-- height
|
||||
"height",
|
||||
"min-height",
|
||||
"max-height",
|
||||
|
||||
-- overflow
|
||||
"overflow",
|
||||
"overflow-x",
|
||||
"overflow-y",
|
||||
"resize",
|
||||
|
||||
-- list-style
|
||||
"list-style",
|
||||
"list-style-type",
|
||||
"list-style-position",
|
||||
"list-style-image",
|
||||
"caption-side",
|
||||
|
||||
-- tables
|
||||
"table-layout",
|
||||
"border-collapse",
|
||||
"border-spacing",
|
||||
"empty-cells",
|
||||
|
||||
-- animation
|
||||
"animation",
|
||||
"animation-name",
|
||||
"animation-duration",
|
||||
"animation-timing-function",
|
||||
"animation-delay",
|
||||
"animation-iteration-count",
|
||||
"animation-direction",
|
||||
"animation-fill-mode",
|
||||
"animation-play-state",
|
||||
|
||||
-- vertical-alignment
|
||||
"vertical-align",
|
||||
|
||||
-- text-alignment & decoration
|
||||
"direction",
|
||||
"tab-size",
|
||||
"text-align",
|
||||
"text-align-last",
|
||||
"text-justify",
|
||||
"text-indent",
|
||||
"text-transform",
|
||||
"text-decoration",
|
||||
"text-decoration-color",
|
||||
"text-decoration-line",
|
||||
"text-decoration-style",
|
||||
"text-rendering",
|
||||
"text-shadow",
|
||||
"text-overflow",
|
||||
|
||||
-- text-spacing
|
||||
"line-height",
|
||||
"word-spacing",
|
||||
"letter-spacing",
|
||||
"white-space",
|
||||
"word-break",
|
||||
"word-wrap",
|
||||
"color",
|
||||
|
||||
-- font
|
||||
"font",
|
||||
"font-family",
|
||||
"font-kerning",
|
||||
"font-size",
|
||||
"font-size-adjust",
|
||||
"font-stretch",
|
||||
"font-weight",
|
||||
"font-smoothing",
|
||||
"osx-font-smoothing",
|
||||
"font-variant",
|
||||
"font-style",
|
||||
|
||||
-- content
|
||||
"content",
|
||||
"quotes",
|
||||
|
||||
-- counters
|
||||
"counter-reset",
|
||||
"counter-increment",
|
||||
|
||||
-- breaks
|
||||
"page-break-before",
|
||||
"page-break-after",
|
||||
"page-break-inside",
|
||||
|
||||
-- mouse
|
||||
"pointer-events",
|
||||
|
||||
-- intent
|
||||
"will-change"
|
||||
}
|
||||
|
||||
config.plugins.sortcss = common.merge({
|
||||
css_syntaxes = css_syntaxes,
|
||||
concentric_order = concentric_order,
|
||||
-- The config specification used by the settings gui
|
||||
config_spec = {
|
||||
name = "Sort CSS",
|
||||
{
|
||||
label = "CSS file syntaxes",
|
||||
description = "List of CSS-compatible syntax names.",
|
||||
path = "css_syntaxes",
|
||||
type = "list_strings",
|
||||
default = css_syntaxes
|
||||
},
|
||||
}
|
||||
}, config.plugins.sortcss)
|
||||
|
||||
local function compare_alphabetical(line1, line2)
|
||||
local prop1 = line1:match("^%s*([^:]+)")
|
||||
local prop2 = line2:match("^%s*([^:]+)")
|
||||
|
||||
return string.lower(prop1) < string.lower(prop2)
|
||||
end
|
||||
|
||||
local function compare_concentrical(line1, line2)
|
||||
local prop1 = line1:match("^%s*([^:]+)")
|
||||
local prop2 = line2:match("^%s*([^:]+)")
|
||||
|
||||
local index1 = 0
|
||||
local index2 = 0
|
||||
|
||||
for i, prop in ipairs(config.plugins.sortcss.concentric_order) do
|
||||
if prop == prop1 then
|
||||
index1 = i
|
||||
end
|
||||
if prop == prop2 then
|
||||
index2 = i
|
||||
end
|
||||
end
|
||||
|
||||
if index1 == 0 then
|
||||
index1 = #concentric_order + 1
|
||||
end
|
||||
|
||||
if index2 == 0 then
|
||||
index2 = #concentric_order + 1
|
||||
end
|
||||
|
||||
return index1 < index2
|
||||
end
|
||||
|
||||
local function sort_css(str, order)
|
||||
local lines = {}
|
||||
for line in str:gmatch("[^\r\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
if order == "alphabetical" then
|
||||
table.sort(lines, compare_alphabetical)
|
||||
end
|
||||
|
||||
if order == "concentrical" then
|
||||
table.sort(lines, compare_concentrical)
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
|
||||
command.add("core.docview", {
|
||||
["sortcss:alphabetical"] = function(dv)
|
||||
local doc = dv.doc
|
||||
if not doc:has_selection() then
|
||||
core.error("No text selected")
|
||||
return
|
||||
end
|
||||
|
||||
local text = doc:get_text(doc:get_selection())
|
||||
doc:text_input(sort_css(text, "alphabetical"))
|
||||
end,
|
||||
["sortcss:concentrical"] = function(dv)
|
||||
local doc = dv.doc
|
||||
if not doc:has_selection() then
|
||||
core.error("No text selected")
|
||||
return
|
||||
end
|
||||
|
||||
local text = doc:get_text(doc:get_selection())
|
||||
doc:text_input(sort_css(text, "concentrical"))
|
||||
end,
|
||||
})
|
||||
|
||||
contextmenu:register(function()
|
||||
local doc = core.active_view.doc
|
||||
if doc and doc:has_selection() then
|
||||
for _, v in pairs(config.plugins.sortcss.css_syntaxes) do
|
||||
if v == doc.syntax.name then
|
||||
return true, core.active_view
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end, {
|
||||
contextmenu.DIVIDER,
|
||||
{ text = "Sort CSS Selection Alphabetically", command = "sortcss:alphabetical" },
|
||||
{ text = "Sort CSS Selection Concentrically", command = "sortcss:concentrical" },
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+alt+a"] = "sortcss:alphabetical" }
|
||||
keymap.add { ["ctrl+alt+c"] = "sortcss:concentrical" }
|
|
@ -0,0 +1,820 @@
|
|||
-- mod-version:3
|
||||
local core = require "core"
|
||||
local common = require "core.common"
|
||||
local command = require "core.command"
|
||||
local config = require "core.config"
|
||||
local keymap = require "core.keymap"
|
||||
local style = require "core.style"
|
||||
local View = require "core.view"
|
||||
local CommandView = require "core.commandview"
|
||||
local DocView = require "core.docview"
|
||||
|
||||
local TodoTreeView = View:extend()
|
||||
|
||||
local SCOPES = {
|
||||
ALL = "all",
|
||||
FOCUSED = "focused",
|
||||
}
|
||||
|
||||
config.plugins.todotreeview = common.merge({
|
||||
todo_tags = {"TODO", "BUG", "FIX", "FIXME", "IMPROVEMENT"},
|
||||
tag_colors = {
|
||||
TODO = {tag=style.text, tag_hover=style.accent, text=style.text, text_hover=style.accent},
|
||||
BUG = {tag=style.text, tag_hover=style.accent, text=style.text, text_hover=style.accent},
|
||||
FIX = {tag=style.text, tag_hover=style.accent, text=style.text, text_hover=style.accent},
|
||||
FIXME = {tag=style.text, tag_hover=style.accent, text=style.text, text_hover=style.accent},
|
||||
IMPROVEMENT = {tag=style.text, tag_hover=style.accent, text=style.text, text_hover=style.accent},
|
||||
},
|
||||
todo_file_color = {
|
||||
name=style.text,
|
||||
hover=style.accent
|
||||
},
|
||||
-- Paths or files to be ignored
|
||||
ignore_paths = {},
|
||||
|
||||
-- Tells if the plugin should start with the nodes expanded
|
||||
todo_expanded = true,
|
||||
|
||||
-- 'tag' mode can be used to group the todos by tags
|
||||
-- 'file' mode can be used to group the todos by files
|
||||
-- 'file_tag' mode can be used to group the todos by files and then by tags inside the files
|
||||
todo_mode = "tag",
|
||||
|
||||
treeview_size = 200 * SCALE, -- default size
|
||||
|
||||
-- Only used in file mode when the tag and the text are on the same line
|
||||
todo_separator = " - ",
|
||||
|
||||
-- Text displayed when the note is empty
|
||||
todo_default_text = "blank",
|
||||
|
||||
-- Scope of the displayed tags
|
||||
-- 'all' scope to show all tags from the project all the time
|
||||
-- 'focused' scope to show the tags from the currently focused file
|
||||
todo_scope = SCOPES.ALL,
|
||||
|
||||
-- The config specification used by the settings gui
|
||||
config_spec = {
|
||||
name = "TodoTreeView",
|
||||
{
|
||||
label = "Todo Tags",
|
||||
description = "List of tags to parse and show in the todo panel.",
|
||||
path = "todo_tags",
|
||||
type = "list_strings",
|
||||
default = {"TODO", "BUG", "FIX", "FIXME", "IMPROVEMENT"},
|
||||
},
|
||||
{
|
||||
label = "Paths to ignore",
|
||||
description = "Paths to be ignored when parsing the tags.",
|
||||
path = "ignore_paths",
|
||||
type = "list_strings",
|
||||
default = {}
|
||||
},
|
||||
{
|
||||
label = "Groups Expanded",
|
||||
description = "Defines if the groups (Tags / Files) should start expanded.",
|
||||
path = "todo_expanded",
|
||||
type = "toggle",
|
||||
default = true,
|
||||
},
|
||||
{
|
||||
label = "Mode for the todos",
|
||||
description = "Mode for the todos to be displayed.",
|
||||
path = "todo_mode",
|
||||
type = "selection",
|
||||
default = "tag",
|
||||
values = {
|
||||
{"Tag", "tag"},
|
||||
{"File", "file"},
|
||||
{"FileTag", "file_tag"}
|
||||
}
|
||||
},
|
||||
{
|
||||
label = "Treeview Size",
|
||||
description = "Size of the todo tree view panel.",
|
||||
path = "treeview_size",
|
||||
type = "number",
|
||||
default = 200 * SCALE,
|
||||
},
|
||||
{
|
||||
label = "Todo separator",
|
||||
description = "Separator used in file mode when the tag and the text are on the same line.",
|
||||
path = "todo_separator",
|
||||
type = "string",
|
||||
default = " - ",
|
||||
},
|
||||
{
|
||||
label = "Empty Default Text",
|
||||
description = "Default text displayed for a note when it has no text.",
|
||||
path = "todo_default_text",
|
||||
type = "string",
|
||||
default = " - ",
|
||||
},
|
||||
{
|
||||
label = "Todo Scope",
|
||||
description = "Scope for the notes to be picked, all notes or currently focused file.",
|
||||
path = "todo_scope",
|
||||
type = "selection",
|
||||
default = "all",
|
||||
values = {
|
||||
{"All", "all"},
|
||||
{"Focused", "focused"},
|
||||
}
|
||||
},
|
||||
}
|
||||
}, config.plugins.todotreeview)
|
||||
|
||||
local icon_small_font = style.icon_font:copy(10 * SCALE)
|
||||
|
||||
function TodoTreeView:new()
|
||||
TodoTreeView.super.new(self)
|
||||
self.scrollable = true
|
||||
self.focusable = false
|
||||
self.visible = true
|
||||
self.times_cache = {}
|
||||
self.cache = {}
|
||||
self.cache_updated = false
|
||||
self.init_size = true
|
||||
self.focus_index = 0
|
||||
self.filter = ""
|
||||
self.previous_focused_file = nil
|
||||
|
||||
-- Items are generated from cache according to the mode
|
||||
self.items = {}
|
||||
end
|
||||
|
||||
local function is_file_ignored(filename)
|
||||
for _, path in ipairs(config.plugins.todotreeview.ignore_paths) do
|
||||
local s, _ = filename:find(path)
|
||||
if s then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function TodoTreeView:is_file_in_scope(filename)
|
||||
if config.plugins.todotreeview.todo_scope == SCOPES.ALL then
|
||||
return true
|
||||
elseif config.plugins.todotreeview.todo_scope == SCOPES.FOCUSED then
|
||||
if core.active_view:is(CommandView) or core.active_view:is(TodoTreeView) then
|
||||
if self.previous_focused_file then
|
||||
return self.previous_focused_file == filename
|
||||
end
|
||||
elseif core.active_view:is(DocView) then
|
||||
return core.active_view.doc.filename == filename
|
||||
end
|
||||
return true
|
||||
else
|
||||
assert(false, "Unknown scope defined ("..config.plugins.todotreeview.todo_scope..")")
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView.get_all_files()
|
||||
local all_files = {}
|
||||
for _, file in ipairs(core.project_files) do
|
||||
if file.filename then
|
||||
all_files[file.filename] = file
|
||||
end
|
||||
end
|
||||
for _, file in ipairs(core.docs) do
|
||||
if file.filename and not all_files[file.filename] then
|
||||
all_files[file.filename] = {
|
||||
filename = file.filename,
|
||||
type = "file"
|
||||
}
|
||||
end
|
||||
end
|
||||
return all_files
|
||||
end
|
||||
|
||||
function TodoTreeView:refresh_cache()
|
||||
local items = {}
|
||||
if not next(self.items) then
|
||||
items = self.items
|
||||
end
|
||||
self.updating_cache = true
|
||||
|
||||
core.add_thread(function()
|
||||
for _, item in pairs(self.get_all_files()) do
|
||||
local ignored = is_file_ignored(item.filename)
|
||||
if not ignored and item.type == "file" then
|
||||
local cached = self:get_cached(item)
|
||||
|
||||
if config.plugins.todotreeview.todo_mode == "file" then
|
||||
items[cached.filename] = cached
|
||||
elseif config.plugins.todotreeview.todo_mode == "file_tag" then
|
||||
local file_t = {}
|
||||
file_t.expanded = config.plugins.todotreeview.todo_expanded
|
||||
file_t.type = "file"
|
||||
file_t.tags = {}
|
||||
file_t.todos = {}
|
||||
file_t.filename = cached.filename
|
||||
file_t.abs_filename = cached.abs_filename
|
||||
items[cached.filename] = file_t
|
||||
for _, todo in ipairs(cached.todos) do
|
||||
local tag = todo.tag
|
||||
if not file_t.tags[tag] then
|
||||
local tag_t = {}
|
||||
tag_t.expanded = config.plugins.todotreeview.todo_expanded
|
||||
tag_t.type = "group"
|
||||
tag_t.todos = {}
|
||||
tag_t.tag = tag
|
||||
file_t.tags[tag] = tag_t
|
||||
end
|
||||
|
||||
table.insert(file_t.tags[tag].todos, todo)
|
||||
end
|
||||
else
|
||||
for _, todo in ipairs(cached.todos) do
|
||||
local tag = todo.tag
|
||||
if not items[tag] then
|
||||
local t = {}
|
||||
t.expanded = config.plugins.todotreeview.todo_expanded
|
||||
t.type = "group"
|
||||
t.todos = {}
|
||||
t.tag = tag
|
||||
items[tag] = t
|
||||
end
|
||||
|
||||
table.insert(items[tag].todos, todo)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Copy expanded from old items
|
||||
if config.plugins.todotreeview.todo_mode == "tag" and next(self.items) then
|
||||
for tag, data in pairs(self.items) do
|
||||
if items[tag] then
|
||||
items[tag].expanded = data.expanded
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.items = items
|
||||
core.redraw = true
|
||||
self.cache_updated = true
|
||||
self.updating_cache = false
|
||||
end, self)
|
||||
end
|
||||
|
||||
|
||||
local function find_file_todos(t, filename)
|
||||
local fp = io.open(filename)
|
||||
if not fp then return t end
|
||||
local n = 1
|
||||
for line in fp:lines() do
|
||||
for _, todo_tag in ipairs(config.plugins.todotreeview.todo_tags) do
|
||||
-- Add spaces at the start and end of line so the pattern will pick
|
||||
-- tags at the start and at the end of lines
|
||||
local extended_line = " "..line.." "
|
||||
local match_str = "[^a-zA-Z_\"'`]"..todo_tag.."[^\"'a-zA-Z_`]+"
|
||||
local s, e = extended_line:find(match_str)
|
||||
if s then
|
||||
local d = {}
|
||||
d.type = "todo"
|
||||
d.tag = todo_tag
|
||||
d.filename = filename
|
||||
d.text = extended_line:sub(e+1)
|
||||
if d.text == "" then
|
||||
d.text = config.plugins.todotreeview.todo_default_text
|
||||
end
|
||||
d.line = n
|
||||
d.col = s
|
||||
table.insert(t, d)
|
||||
end
|
||||
core.redraw = true
|
||||
end
|
||||
if n % 100 == 0 then coroutine.yield() end
|
||||
n = n + 1
|
||||
core.redraw = true
|
||||
end
|
||||
fp:close()
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_cached(item)
|
||||
local t = self.cache[item.filename]
|
||||
if not t then
|
||||
t = {}
|
||||
t.expanded = config.plugins.todotreeview.todo_expanded
|
||||
t.filename = item.filename
|
||||
t.abs_filename = system.absolute_path(item.filename)
|
||||
t.type = item.type
|
||||
t.todos = {}
|
||||
t.tags = {}
|
||||
find_file_todos(t.todos, t.filename)
|
||||
self.cache[t.filename] = t
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_name()
|
||||
return "Todo Tree"
|
||||
end
|
||||
|
||||
function TodoTreeView:set_target_size(axis, value)
|
||||
if axis == "x" then
|
||||
config.plugins.todotreeview.treeview_size = value
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView:get_item_height()
|
||||
return style.font:get_height() + style.padding.y
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:get_cached_time(doc)
|
||||
local t = self.times_cache[doc]
|
||||
if not t then
|
||||
local info = system.get_file_info(doc.filename)
|
||||
if not info then return nil end
|
||||
self.times_cache[doc] = info.modified
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:check_cache()
|
||||
local existing_docs = {}
|
||||
for _, doc in ipairs(core.docs) do
|
||||
if doc.filename then
|
||||
existing_docs[doc.filename] = true
|
||||
local info = system.get_file_info(doc.filename)
|
||||
local cached = self:get_cached_time(doc)
|
||||
if not info and cached then
|
||||
-- document deleted
|
||||
self.times_cache[doc] = nil
|
||||
self.cache[doc.filename] = nil
|
||||
self.cache_updated = false
|
||||
elseif cached and cached ~= info.modified then
|
||||
-- document modified
|
||||
self.times_cache[doc] = info.modified
|
||||
self.cache[doc.filename] = nil
|
||||
self.cache_updated = false
|
||||
elseif not cached then
|
||||
self.cache_updated = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, file in ipairs(core.project_files) do
|
||||
existing_docs[file.filename] = true
|
||||
end
|
||||
|
||||
-- Check for docs in cache that may not exist anymore
|
||||
-- for example: (Openend from outside of project and closed)
|
||||
for filename, doc in pairs(self.cache) do
|
||||
local exists = existing_docs[filename]
|
||||
if not exists then
|
||||
self.times_cache[doc] = nil
|
||||
self.cache[filename] = nil
|
||||
self.cache_updated = false
|
||||
end
|
||||
end
|
||||
|
||||
if core.project_files ~= self.last_project_files then
|
||||
self.last_project_files = core.project_files
|
||||
self.cache_updated = false
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView:each_item()
|
||||
self:check_cache()
|
||||
if not self.updating_cache and not self.cache_updated then
|
||||
self:refresh_cache()
|
||||
end
|
||||
|
||||
return coroutine.wrap(function()
|
||||
local ox, oy = self:get_content_offset()
|
||||
local y = oy + style.padding.y
|
||||
local w = self.size.x
|
||||
local h = self:get_item_height()
|
||||
|
||||
for filename, item in pairs(self.items) do
|
||||
local in_scope = item.type == "group" or self:is_file_in_scope(item.filename)
|
||||
if in_scope and #item.todos > 0 then
|
||||
coroutine.yield(item, ox, y, w, h)
|
||||
y = y + h
|
||||
|
||||
for _, todo in ipairs(item.todos) do
|
||||
if item.expanded then
|
||||
local in_todo = string.find(todo.text:lower(), self.filter:lower())
|
||||
local todo_in_scope = self:is_file_in_scope(todo.filename)
|
||||
if todo_in_scope and (#self.filter == 0 or in_todo) then
|
||||
coroutine.yield(todo, ox, y, w, h)
|
||||
y = y + h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
if in_scope and item.tags then
|
||||
local first_tag = true
|
||||
for _, tag in pairs(item.tags) do
|
||||
if first_tag then
|
||||
coroutine.yield(item, ox, y, w, h)
|
||||
y = y + h
|
||||
first_tag = false
|
||||
end
|
||||
if item.expanded then
|
||||
coroutine.yield(tag, ox, y, w, h)
|
||||
y = y + h
|
||||
|
||||
for _, todo in ipairs(tag.todos) do
|
||||
if item.expanded and tag.expanded then
|
||||
local in_todo = string.find(todo.text:lower(), self.filter:lower())
|
||||
local todo_in_scope = self:is_file_in_scope(todo.filename)
|
||||
if todo_in_scope and (#self.filter == 0 or in_todo) then
|
||||
coroutine.yield(todo, ox, y, w, h)
|
||||
y = y + h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:on_mouse_moved(px, py)
|
||||
self.hovered_item = nil
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
if px > x and py > y and px <= x + w and py <= y + h then
|
||||
self.hovered_item = item
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView:goto_hovered_item()
|
||||
if not self.hovered_item then
|
||||
return
|
||||
end
|
||||
|
||||
if self.hovered_item.type == "group" or self.hovered_item.type == "file" then
|
||||
return
|
||||
end
|
||||
|
||||
core.try(function()
|
||||
local i = self.hovered_item
|
||||
local dv = core.root_view:open_doc(core.open_doc(i.filename))
|
||||
core.root_view.root_node:update_layout()
|
||||
dv.doc:set_selection(i.line, i.col)
|
||||
dv:scroll_to_line(i.line, false, true)
|
||||
end)
|
||||
end
|
||||
|
||||
function TodoTreeView:on_mouse_pressed(button, x, y)
|
||||
if not self.hovered_item then
|
||||
return
|
||||
elseif self.hovered_item.type == "file"
|
||||
or self.hovered_item.type == "group" then
|
||||
self.hovered_item.expanded = not self.hovered_item.expanded
|
||||
else
|
||||
self:goto_hovered_item()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:update()
|
||||
-- Update focus
|
||||
if core.active_view:is(DocView) then
|
||||
self.previous_focused_file = core.active_view.doc.filename
|
||||
elseif core.active_view:is(CommandView) or core.active_view:is(TodoTreeView) then
|
||||
-- Do nothing
|
||||
else
|
||||
self.previous_focused_file = nil
|
||||
end
|
||||
|
||||
self.scroll.to.y = math.max(0, self.scroll.to.y)
|
||||
|
||||
-- update width
|
||||
local dest = self.visible and config.plugins.todotreeview.treeview_size or 0
|
||||
if self.init_size then
|
||||
self.size.x = dest
|
||||
self.init_size = false
|
||||
else
|
||||
self:move_towards(self.size, "x", dest)
|
||||
end
|
||||
|
||||
TodoTreeView.super.update(self)
|
||||
end
|
||||
|
||||
|
||||
function TodoTreeView:draw()
|
||||
self:draw_background(style.background2)
|
||||
|
||||
--local h = self:get_item_height()
|
||||
local icon_width = style.icon_font:get_width("D")
|
||||
local spacing = style.font:get_width(" ") * 2
|
||||
local root_depth = 0
|
||||
|
||||
for item, x,y,w,h in self:each_item() do
|
||||
local text_color = style.text
|
||||
local tag_color = style.text
|
||||
local file_color = config.plugins.todotreeview.todo_file_color.name or style.text
|
||||
if config.plugins.todotreeview.tag_colors[item.tag] then
|
||||
text_color = config.plugins.todotreeview.tag_colors[item.tag].text or style.text
|
||||
tag_color = config.plugins.todotreeview.tag_colors[item.tag].tag or style.text
|
||||
end
|
||||
|
||||
-- hovered item background
|
||||
if item == self.hovered_item then
|
||||
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
||||
text_color = style.accent
|
||||
tag_color = style.accent
|
||||
file_color = config.plugins.todotreeview.todo_file_color.hover or style.accent
|
||||
if config.plugins.todotreeview.tag_colors[item.tag] then
|
||||
text_color = config.plugins.todotreeview.tag_colors[item.tag].text_hover or style.accent
|
||||
tag_color = config.plugins.todotreeview.tag_colors[item.tag].tag_hover or style.accent
|
||||
end
|
||||
end
|
||||
|
||||
-- icons
|
||||
local item_depth = 0
|
||||
x = x + (item_depth - root_depth) * style.padding.x + style.padding.x
|
||||
if item.type == "file" then
|
||||
local icon1 = item.expanded and "-" or "+"
|
||||
common.draw_text(style.icon_font, file_color, icon1, nil, x, y, 0, h)
|
||||
x = x + style.padding.x
|
||||
common.draw_text(style.icon_font, file_color, "f", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
elseif item.type == "group" then
|
||||
if config.plugins.todotreeview.todo_mode == "file_tag" then
|
||||
x = x + style.padding.x * 0.75
|
||||
end
|
||||
|
||||
if item.expanded then
|
||||
common.draw_text(style.icon_font, tag_color, "-", nil, x, y, 0, h)
|
||||
else
|
||||
common.draw_text(icon_small_font, tag_color, ">", nil, x, y, 0, h)
|
||||
end
|
||||
x = x + icon_width / 2
|
||||
else
|
||||
if config.plugins.todotreeview.todo_mode == "tag" then
|
||||
x = x + style.padding.x
|
||||
else
|
||||
x = x + style.padding.x * 1.5
|
||||
end
|
||||
common.draw_text(style.icon_font, text_color, "i", nil, x, y, 0, h)
|
||||
x = x + icon_width
|
||||
end
|
||||
|
||||
-- text
|
||||
x = x + spacing
|
||||
if item.type == "file" then
|
||||
common.draw_text(style.font, file_color, item.filename, nil, x, y, 0, h)
|
||||
elseif item.type == "group" then
|
||||
common.draw_text(style.font, tag_color, item.tag, nil, x, y, 0, h)
|
||||
else
|
||||
if config.plugins.todotreeview.todo_mode == "file" then
|
||||
common.draw_text(style.font, tag_color, item.tag, nil, x, y, 0, h)
|
||||
x = x + style.font:get_width(item.tag)
|
||||
common.draw_text(style.font, text_color, config.plugins.todotreeview.todo_separator..item.text, nil, x, y, 0, h)
|
||||
else
|
||||
common.draw_text(style.font, text_color, item.text, nil, x, y, 0, h)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function TodoTreeView:get_item_by_index(index)
|
||||
local i = 0
|
||||
for item in self:each_item() do
|
||||
if index == i then
|
||||
return item
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function TodoTreeView:get_hovered_parent_file_tag()
|
||||
local file_parent = nil
|
||||
local file_parent_index = 0
|
||||
local group_parent = nil
|
||||
local group_parent_index = 0
|
||||
local i = 0
|
||||
for item in self:each_item() do
|
||||
if item.type == "file" then
|
||||
file_parent = item
|
||||
file_parent_index = i
|
||||
end
|
||||
if item.type == "group" then
|
||||
group_parent = item
|
||||
group_parent_index = i
|
||||
end
|
||||
if i == self.focus_index then
|
||||
if item.type == "file" or item.type == "group" then
|
||||
return file_parent, file_parent_index
|
||||
else
|
||||
return group_parent, group_parent_index
|
||||
end
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
function TodoTreeView:get_hovered_parent()
|
||||
local parent = nil
|
||||
local parent_index = 0
|
||||
local i = 0
|
||||
for item in self:each_item() do
|
||||
if item.type == "group" or item.type == "file" then
|
||||
parent = item
|
||||
parent_index = i
|
||||
end
|
||||
if i == self.focus_index then
|
||||
return parent, parent_index
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return nil, 0
|
||||
end
|
||||
|
||||
function TodoTreeView:update_scroll_position()
|
||||
local h = self:get_item_height()
|
||||
local _, min_y, _, max_y = self:get_content_bounds()
|
||||
local start_row = math.floor(min_y / h)
|
||||
local end_row = math.floor(max_y / h)
|
||||
if self.focus_index < start_row then
|
||||
self.scroll.to.y = self.focus_index * h
|
||||
end
|
||||
if self.focus_index + 1 > end_row then
|
||||
self.scroll.to.y = (self.focus_index * h) - self.size.y + h
|
||||
end
|
||||
end
|
||||
|
||||
-- init
|
||||
local view = TodoTreeView()
|
||||
local node = core.root_view:get_active_node()
|
||||
view.size.x = config.plugins.todotreeview.treeview_size
|
||||
node:split("right", view, {x=true}, true)
|
||||
|
||||
core.status_view:add_item({
|
||||
predicate = function()
|
||||
return #view.filter > 0 and core.active_view and not core.active_view:is(CommandView)
|
||||
end,
|
||||
name = "todotreeview:filter",
|
||||
alignment = core.status_view.Item.RIGHT,
|
||||
get_item = function()
|
||||
return {
|
||||
style.text,
|
||||
string.format("Filter: %s", view.filter)
|
||||
}
|
||||
end,
|
||||
position = 1,
|
||||
tooltip = "Todos filtered by",
|
||||
separator = core.status_view.separator2
|
||||
})
|
||||
|
||||
-- register commands and keymap
|
||||
local previous_view = nil
|
||||
command.add(nil, {
|
||||
["todotreeview:toggle"] = function()
|
||||
view.visible = not view.visible
|
||||
end,
|
||||
|
||||
["todotreeview:expand-items"] = function()
|
||||
for _, item in pairs(view.items) do
|
||||
item.expanded = true
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:hide-items"] = function()
|
||||
for _, item in pairs(view.items) do
|
||||
item.expanded = false
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:toggle-focus"] = function()
|
||||
if not core.active_view:is(TodoTreeView) then
|
||||
previous_view = core.active_view
|
||||
core.set_active_view(view)
|
||||
view.hovered_item = view:get_item_by_index(view.focus_index)
|
||||
else
|
||||
command.perform("todotreeview:release-focus")
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:filter-notes"] = function()
|
||||
local todo_view_focus = core.active_view:is(TodoTreeView)
|
||||
local previous_filter = view.filter
|
||||
local submit = function(text)
|
||||
view.filter = text
|
||||
if todo_view_focus then
|
||||
view.focus_index = 0
|
||||
view.hovered_item = view:get_item_by_index(view.focus_index)
|
||||
view:update_scroll_position()
|
||||
end
|
||||
end
|
||||
local suggest = function(text)
|
||||
view.filter = text
|
||||
end
|
||||
local cancel = function(explicit)
|
||||
view.filter = previous_filter
|
||||
end
|
||||
core.command_view:enter("Filter Notes", {
|
||||
text = view.filter,
|
||||
submit = submit,
|
||||
suggest = suggest,
|
||||
cancel = cancel
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
command.add(
|
||||
function()
|
||||
return core.active_view:is(TodoTreeView)
|
||||
end, {
|
||||
["todotreeview:previous"] = function()
|
||||
if view.focus_index > 0 then
|
||||
view.focus_index = view.focus_index - 1
|
||||
view.hovered_item = view:get_item_by_index(view.focus_index)
|
||||
view:update_scroll_position()
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:next"] = function()
|
||||
local next_index = view.focus_index + 1
|
||||
local next_item = view:get_item_by_index(next_index)
|
||||
if next_item then
|
||||
view.focus_index = next_index
|
||||
view.hovered_item = next_item
|
||||
view:update_scroll_position()
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:collapse"] = function()
|
||||
if not view.hovered_item then
|
||||
return
|
||||
end
|
||||
|
||||
if view.hovered_item.type == "file" then
|
||||
view.hovered_item.expanded = false
|
||||
else
|
||||
if view.hovered_item.type == "group" and view.hovered_item.expanded then
|
||||
view.hovered_item.expanded = false
|
||||
else
|
||||
if config.plugins.todotreeview.todo_mode == "file_tag" then
|
||||
view.hovered_item, view.focus_index = view:get_hovered_parent_file_tag()
|
||||
else
|
||||
view.hovered_item, view.focus_index = view:get_hovered_parent()
|
||||
end
|
||||
|
||||
view:update_scroll_position()
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:expand"] = function()
|
||||
if not view.hovered_item then
|
||||
return
|
||||
end
|
||||
|
||||
if view.hovered_item.type == "file" or view.hovered_item.type == "group" then
|
||||
if view.hovered_item.expanded then
|
||||
command.perform("todotreeview:next")
|
||||
else
|
||||
view.hovered_item.expanded = true
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
["todotreeview:open"] = function()
|
||||
if not view.hovered_item then
|
||||
return
|
||||
end
|
||||
|
||||
view:goto_hovered_item()
|
||||
view.hovered_item = nil
|
||||
end,
|
||||
|
||||
["todotreeview:release-focus"] = function()
|
||||
core.set_active_view(
|
||||
previous_view or core.root_view:get_primary_node().active_view
|
||||
)
|
||||
view.hovered_item = nil
|
||||
end,
|
||||
})
|
||||
|
||||
keymap.add { ["ctrl+shift+t"] = "todotreeview:toggle" }
|
||||
keymap.add { ["ctrl+shift+e"] = "todotreeview:expand-items" }
|
||||
keymap.add { ["ctrl+shift+h"] = "todotreeview:hide-items" }
|
||||
keymap.add { ["ctrl+shift+b"] = "todotreeview:filter-notes" }
|
||||
keymap.add { ["up"] = "todotreeview:previous" }
|
||||
keymap.add { ["down"] = "todotreeview:next" }
|
||||
keymap.add { ["left"] = "todotreeview:collapse" }
|
||||
keymap.add { ["right"] = "todotreeview:expand" }
|
||||
keymap.add { ["return"] = "todotreeview:open" }
|
||||
keymap.add { ["escape"] = "todotreeview:release-focus" }
|
||||
|
Loading…
Reference in New Issue