- Adjusting gitdiff_highlight-{gitdiff,init}.lua to play nice with
lite-xl v2.1 plugin scheme (produces red notification bar and
disables plugin when lite-xl loads otherwise)
- Update to version git20221101.0971a7a:
* Changed minimap to accomodate new API.
* language_php: fix strings not getting terminated
* minimap: check docview has minimap scroll
* minimap: fixed small mistake
* Minimap Rework with Scrollbar (#134)
* language_meson: added meson_options.txt and fixed meson.build patter
* indentguide: do not apply on CommandView and allow toggling it.
* Map ruby syntax to Gemfile and Gemfile.lock (#132)
* Add `language_erb` to README
* Add language plugin for ERB files (#130)
* Removed language_cpp, as it's in the core, and not in this repo at all, in this branch.
- Renaming nonicons-userdir.patch to nonicons-userdir.diff
OBS-URL: https://build.opensuse.org/request/show/1033559
OBS-URL: https://build.opensuse.org/package/show/editors/lite-xl-plugins?expand=0&rev=7
425 lines
10 KiB
Lua
425 lines
10 KiB
Lua
-- mod-version:3
|
|
local core = require "core"
|
|
local keymap = require "core.keymap"
|
|
local command = require "core.command"
|
|
local common = require "core.common"
|
|
local config = require "core.config"
|
|
local style = require "core.style"
|
|
local View = require "core.view"
|
|
|
|
local console = {}
|
|
|
|
-- The main object is used to store the main console view and the function for
|
|
-- its activation, start_console.
|
|
-- The main console view is the one which is shown at the botton and whose visibility can
|
|
-- be toggled with the console:toggle command. It is created and added in the nodes'
|
|
-- hierarchy only when it is actually required. In this way we avoid plugging the console
|
|
-- view in a random location based on plugin load order.
|
|
local main = {}
|
|
local views = {}
|
|
local pending_threads = {}
|
|
local thread_active = false
|
|
local output = nil
|
|
local output_id = 0
|
|
local visible = false
|
|
|
|
config.plugins.console = common.merge({
|
|
size = 250 * SCALE,
|
|
max_lines = 200,
|
|
autoscroll = true,
|
|
config_spec = {
|
|
name = "Console",
|
|
{
|
|
label = "Size",
|
|
description = "Default height of the console.",
|
|
path = "size",
|
|
type = "number",
|
|
min = 100,
|
|
default = 250,
|
|
get_value = function(value)
|
|
return value / SCALE
|
|
end,
|
|
set_value = function(value)
|
|
return value * SCALE
|
|
end,
|
|
on_apply = function(value)
|
|
if main.view then
|
|
main.view:set_target_size("y", value)
|
|
end
|
|
end
|
|
},
|
|
{
|
|
label = "Maximum Lines",
|
|
description = "The maximum amount of output lines to keep on history.",
|
|
path = "max_lines",
|
|
type = "number",
|
|
min = 100,
|
|
default = 200
|
|
},
|
|
{
|
|
label = "Auto-scroll",
|
|
description = "Automatically scroll down when printing new output.",
|
|
path = "autoscroll",
|
|
type = "toggle",
|
|
default = true,
|
|
}
|
|
}
|
|
}, config.plugins.console)
|
|
|
|
function console.clear()
|
|
output = { { text = "", time = 0 } }
|
|
end
|
|
|
|
|
|
local function write_file(filename, text)
|
|
local fp = io.open(filename, "w")
|
|
fp:write(text)
|
|
fp:close()
|
|
end
|
|
|
|
|
|
local function lines(text)
|
|
return (text .. "\n"):gmatch("(.-)\n")
|
|
end
|
|
|
|
|
|
local function push_output(str, opt)
|
|
local first = true
|
|
for line in lines(str) do
|
|
if first then
|
|
line = table.remove(output).text .. line
|
|
end
|
|
line = line:gsub("\x1b%[[%d;]+m", "") -- strip ANSI colors
|
|
table.insert(output, {
|
|
text = line,
|
|
time = os.time(),
|
|
icon = line:find(opt.error_pattern) and "!"
|
|
or line:find(opt.warning_pattern) and "i",
|
|
file_pattern = opt.file_pattern,
|
|
file_prefix = opt.file_prefix,
|
|
})
|
|
if #output > config.plugins.console.max_lines then
|
|
table.remove(output, 1)
|
|
for view in pairs(views) do
|
|
view:on_line_removed()
|
|
end
|
|
end
|
|
first = false
|
|
end
|
|
output_id = output_id + 1
|
|
core.redraw = true
|
|
end
|
|
|
|
-- A file pattern to identify the line and column can be given like:
|
|
--
|
|
-- file_pattern = "([^?:%s]+%.[^?:%s]+):(%d+):(%d+):"
|
|
--
|
|
-- The 2nd and 3rd captures will be considered as line and column of the
|
|
-- position within the file.
|
|
local function init_opt(opt)
|
|
local res = {
|
|
command = "",
|
|
file_pattern = "([^?:%s]+%.[^?:%s]+):?(%d*):?(%d*)",
|
|
error_pattern = "error",
|
|
warning_pattern = "warning",
|
|
cwd = ".",
|
|
on_complete = function() end,
|
|
file_prefix = ".",
|
|
}
|
|
for k, v in pairs(res) do
|
|
res[k] = opt[k] or v
|
|
end
|
|
return res
|
|
end
|
|
|
|
|
|
function console.run(opt)
|
|
opt = init_opt(opt)
|
|
|
|
local function thread()
|
|
local command
|
|
if PLATFORM == "Windows" then
|
|
command = string.format("cmd /c (%s) 2>&1", opt.command)
|
|
else
|
|
command = { "bash", "-c", "--", string.format("(%s) 2>&1", opt.command) }
|
|
end
|
|
|
|
local proc, err = process.start(command, { cwd=opt.cwd, stdin = process.REDIRECT_DISCARD })
|
|
if proc then
|
|
local text = proc:read_stdout()
|
|
while text ~= nil do
|
|
push_output(text, opt)
|
|
coroutine.yield(0.1)
|
|
text = proc:read_stdout()
|
|
end
|
|
if output[#output].text ~= "" then
|
|
push_output("\n", opt)
|
|
end
|
|
push_output("!DIVIDER\n", opt)
|
|
|
|
opt.on_complete(proc:returncode())
|
|
else
|
|
core.error("Error while executing command: %q", err)
|
|
end
|
|
|
|
-- handle pending thread
|
|
local pending = table.remove(pending_threads, 1)
|
|
if pending then
|
|
core.add_thread(pending)
|
|
else
|
|
thread_active = false
|
|
end
|
|
end
|
|
|
|
-- push/init thread
|
|
if thread_active then
|
|
table.insert(pending_threads, thread)
|
|
else
|
|
core.add_thread(thread)
|
|
thread_active = true
|
|
end
|
|
|
|
-- make sure static console is visible if it's the only ConsoleView
|
|
if not main.view then main.start_console() end
|
|
local count = 0
|
|
for _ in pairs(views) do count = count + 1 end
|
|
if count == 1 then visible = true end
|
|
end
|
|
|
|
|
|
|
|
local ConsoleView = View:extend()
|
|
|
|
function ConsoleView:new()
|
|
ConsoleView.super.new(self)
|
|
self.target_size = config.plugins.console.size
|
|
self.scrollable = true
|
|
self.hovered_idx = -1
|
|
views[self] = true
|
|
end
|
|
|
|
|
|
function ConsoleView:set_target_size(axis, value)
|
|
if axis == "y" then
|
|
self.target_size = value
|
|
return true
|
|
end
|
|
end
|
|
|
|
|
|
function ConsoleView:try_close(...)
|
|
ConsoleView.super.try_close(self, ...)
|
|
views[self] = nil
|
|
end
|
|
|
|
|
|
function ConsoleView:get_name()
|
|
return "Console"
|
|
end
|
|
|
|
|
|
function ConsoleView:get_line_height()
|
|
return style.code_font:get_height() * config.line_height
|
|
end
|
|
|
|
|
|
function ConsoleView:get_line_count()
|
|
return #output - (output[#output].text == "" and 1 or 0)
|
|
end
|
|
|
|
|
|
function ConsoleView:get_scrollable_size()
|
|
return self:get_line_count() * self:get_line_height() + style.padding.y * 2
|
|
end
|
|
|
|
|
|
function ConsoleView:get_visible_line_range()
|
|
local lh = self:get_line_height()
|
|
local min = math.max(1, math.floor(self.scroll.y / lh))
|
|
return min, min + math.floor(self.size.y / lh) + 1
|
|
end
|
|
|
|
|
|
function ConsoleView:on_mouse_moved(mx, my, ...)
|
|
ConsoleView.super.on_mouse_moved(self, mx, my, ...)
|
|
self.hovered_idx = 0
|
|
for i, item, x,y,w,h in self:each_visible_line() do
|
|
if mx >= x and my >= y and mx < x + w and my < y + h then
|
|
if item.text:find(item.file_pattern) then
|
|
self.hovered_idx = i
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function resolve_file(file_prefix, name)
|
|
name = file_prefix .. PATHSEP .. name
|
|
if system.get_file_info(name) then
|
|
return name
|
|
end
|
|
local filenames = {}
|
|
for _, f in ipairs(core.project_files) do
|
|
table.insert(filenames, f.filename)
|
|
end
|
|
local t = common.fuzzy_match(filenames, name)
|
|
return t[1]
|
|
end
|
|
|
|
|
|
function ConsoleView:on_line_removed()
|
|
local diff = self:get_line_height()
|
|
self.scroll.y = self.scroll.y - diff
|
|
self.scroll.to.y = self.scroll.to.y - diff
|
|
end
|
|
|
|
|
|
function ConsoleView:on_mouse_pressed(...)
|
|
local caught = ConsoleView.super.on_mouse_pressed(self, ...)
|
|
if caught then
|
|
return
|
|
end
|
|
local item = output[self.hovered_idx]
|
|
if item then
|
|
local file, line, col = item.text:match(item.file_pattern)
|
|
local resolved_file = resolve_file(item.file_prefix, file)
|
|
if not resolved_file then
|
|
-- fixes meson output which adds ../ for build sub directories
|
|
resolved_file = resolve_file(
|
|
item.file_prefix,
|
|
file:gsub("%.%./", ""):gsub("^%./", "")
|
|
)
|
|
end
|
|
if not resolved_file then
|
|
core.error("Couldn't resolve file \"%s\"", file)
|
|
return
|
|
end
|
|
core.try(function()
|
|
core.root_view:open_doc(core.open_doc(resolved_file))
|
|
line = tonumber(line) or 1
|
|
col = tonumber(col) or 1
|
|
core.add_thread(function()
|
|
core.active_view.doc:set_selection(line, col)
|
|
end)
|
|
end)
|
|
end
|
|
end
|
|
|
|
|
|
function ConsoleView:each_visible_line()
|
|
return coroutine.wrap(function()
|
|
local x, y = self:get_content_offset()
|
|
local lh = self:get_line_height()
|
|
local min, max = self:get_visible_line_range()
|
|
y = y + lh * (min - 1) + style.padding.y
|
|
max = math.min(max, self:get_line_count())
|
|
|
|
for i = min, max do
|
|
local item = output[i]
|
|
if not item then break end
|
|
coroutine.yield(i, item, x, y, self.size.x, lh)
|
|
y = y + lh
|
|
end
|
|
end)
|
|
end
|
|
|
|
|
|
function ConsoleView:update(...)
|
|
if self.last_output_id ~= output_id then
|
|
if config.plugins.console.autoscroll then
|
|
self.scroll.to.y = self:get_scrollable_size()
|
|
end
|
|
self.last_output_id = output_id
|
|
end
|
|
ConsoleView.super.update(self, ...)
|
|
end
|
|
|
|
|
|
function ConsoleView:draw()
|
|
self:draw_background(style.background)
|
|
local icon_w = style.icon_font:get_width("!")
|
|
|
|
for i, item, x, y, w, h in self:each_visible_line() do
|
|
local tx = x + style.padding.x
|
|
local time = os.date("%H:%M:%S", item.time)
|
|
local color = style.text
|
|
if self.hovered_idx == i then
|
|
color = style.accent
|
|
renderer.draw_rect(x, y, w, h, style.line_highlight)
|
|
end
|
|
if item.text == "!DIVIDER" then
|
|
local w = style.font:get_width(time)
|
|
renderer.draw_rect(tx, y + h / 2, w, math.ceil(SCALE * 1), style.dim)
|
|
else
|
|
tx = common.draw_text(style.font, style.dim, time, "left", tx, y, w, h)
|
|
tx = tx + style.padding.x
|
|
if item.icon then
|
|
common.draw_text(style.icon_font, color, item.icon, "left", tx, y, w, h)
|
|
end
|
|
tx = tx + icon_w + style.padding.x
|
|
common.draw_text(style.code_font, color, item.text, "left", tx, y, w, h)
|
|
end
|
|
end
|
|
|
|
self:draw_scrollbar(self)
|
|
end
|
|
|
|
function main.start_console()
|
|
-- init static bottom-of-screen console
|
|
main.view = ConsoleView()
|
|
local node = core.root_view.root_node:get_node_for_view(core.command_view)
|
|
node:split("up", main.view, {y = true}, true)
|
|
|
|
function main.view:update(...)
|
|
local dest = visible and self.target_size or 0
|
|
self:move_towards(self.size, "y", dest)
|
|
ConsoleView.update(self, ...)
|
|
end
|
|
end
|
|
|
|
|
|
local last_command = ""
|
|
|
|
command.add(nil, {
|
|
["console:reset-output"] = function()
|
|
output = { { text = "", time = 0 } }
|
|
end,
|
|
|
|
["console:open-console"] = function()
|
|
local node = core.root_view:get_active_node()
|
|
node:add_view(ConsoleView())
|
|
end,
|
|
|
|
["console:toggle"] = function()
|
|
visible = not visible
|
|
if visible and not main.view then main.start_console() end
|
|
end,
|
|
|
|
["console:run"] = function()
|
|
core.command_view:enter("Run Console Command", {
|
|
submit = function(cmd)
|
|
if cmd == "clear" or cmd == "cls" then
|
|
console.clear()
|
|
else
|
|
console.run { command = cmd }
|
|
last_command = cmd
|
|
end
|
|
end,
|
|
text = last_command,
|
|
select_text = true
|
|
})
|
|
end
|
|
})
|
|
|
|
keymap.add {
|
|
["ctrl+."] = "console:toggle",
|
|
["ctrl+shift+."] = "console:run",
|
|
}
|
|
|
|
-- for `workspace` plugin:
|
|
package.loaded["plugins.console.view"] = ConsoleView
|
|
|
|
console.clear()
|
|
return console
|