aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/align_carets.lua2
-rw-r--r--plugins/autoinsert.lua7
-rw-r--r--plugins/autosave.lua33
-rw-r--r--plugins/autosaveonfocuslost.lua2
-rw-r--r--plugins/autowrap.lua42
-rw-r--r--plugins/bigclock.lua47
-rw-r--r--plugins/bracketmatch.lua78
-rw-r--r--plugins/centerdoc.lua103
-rw-r--r--plugins/colorpreview.lua48
-rw-r--r--plugins/copyfilelocation.lua2
-rw-r--r--plugins/datetimestamps.lua47
-rw-r--r--plugins/dragdropselected.lua242
-rw-r--r--plugins/ephemeral_tabs.lua2
-rw-r--r--plugins/eval.lua18
-rw-r--r--plugins/exec.lua28
-rw-r--r--plugins/extend_selection_line.lua11
-rw-r--r--plugins/fontconfig.lua5
-rw-r--r--plugins/force_syntax.lua57
-rw-r--r--plugins/ghmarkdown.lua2
-rw-r--r--plugins/gitstatus.lua93
-rw-r--r--plugins/gofmt.lua2
-rw-r--r--plugins/hidelinenumbers.lua6
-rw-r--r--plugins/hidestatus.lua19
-rw-r--r--plugins/indent_convert.lua21
-rw-r--r--plugins/indentguide.lua79
-rw-r--r--plugins/ipc.lua1043
-rw-r--r--plugins/language_R.lua71
-rw-r--r--plugins/language_angelscript.lua2
-rw-r--r--plugins/language_assembly_x86.lua2
-rw-r--r--plugins/language_batch.lua2
-rw-r--r--plugins/language_bib.lua2
-rw-r--r--plugins/language_caddyfile.lua104
-rw-r--r--plugins/language_cmake.lua2
-rw-r--r--plugins/language_cpp.lua203
-rw-r--r--plugins/language_csharp.lua2
-rw-r--r--plugins/language_d.lua2
-rw-r--r--plugins/language_dart.lua2
-rw-r--r--plugins/language_diff.lua2
-rw-r--r--plugins/language_elixir.lua2
-rw-r--r--plugins/language_elm.lua2
-rw-r--r--plugins/language_erb.lua2
-rw-r--r--plugins/language_fe.lua2
-rw-r--r--plugins/language_fennel.lua354
-rw-r--r--plugins/language_fstab.lua6
-rw-r--r--plugins/language_gdscript.lua2
-rw-r--r--plugins/language_glsl.lua2
-rw-r--r--plugins/language_gmi.lua4
-rw-r--r--plugins/language_go.lua150
-rw-r--r--plugins/language_hlsl.lua2
-rw-r--r--plugins/language_hs.lua2
-rw-r--r--plugins/language_htaccess.lua2
-rw-r--r--plugins/language_ini.lua2
-rw-r--r--plugins/language_java.lua2
-rw-r--r--plugins/language_jiyu.lua2
-rw-r--r--plugins/language_jsx.lua2
-rw-r--r--plugins/language_julia.lua2
-rw-r--r--plugins/language_liquid.lua28
-rw-r--r--plugins/language_lobster.lua6
-rw-r--r--plugins/language_make.lua2
-rw-r--r--plugins/language_meson.lua4
-rw-r--r--plugins/language_miniscript.lua102
-rw-r--r--plugins/language_moon.lua2
-rw-r--r--plugins/language_nginx.lua2
-rw-r--r--plugins/language_nim.lua2
-rw-r--r--plugins/language_objc.lua2
-rw-r--r--plugins/language_odin.lua2
-rw-r--r--plugins/language_perl.lua2
-rw-r--r--plugins/language_php.lua194
-rw-r--r--plugins/language_pico8.lua2
-rw-r--r--plugins/language_pkgbuild.lua2
-rw-r--r--plugins/language_po.lua2
-rw-r--r--plugins/language_powershell.lua143
-rw-r--r--plugins/language_psql.lua2
-rw-r--r--plugins/language_rescript.lua2
-rw-r--r--plugins/language_rivet.lua6
-rw-r--r--plugins/language_ruby.lua4
-rw-r--r--plugins/language_rust.lua124
-rw-r--r--plugins/language_sass.lua2
-rw-r--r--plugins/language_scala.lua114
-rw-r--r--plugins/language_sh.lua2
-rw-r--r--plugins/language_ssh_config.lua2
-rw-r--r--plugins/language_tcl.lua2
-rw-r--r--plugins/language_teal.lua2
-rw-r--r--plugins/language_tex.lua2
-rw-r--r--plugins/language_toml.lua2
-rw-r--r--plugins/language_ts.lua2
-rw-r--r--plugins/language_tsx.lua5
-rw-r--r--plugins/language_v.lua2
-rw-r--r--plugins/language_wren.lua6
-rw-r--r--plugins/language_yaml.lua144
-rw-r--r--plugins/language_zig.lua2
-rw-r--r--plugins/lfautoinsert.lua10
-rw-r--r--plugins/linecopypaste.lua50
-rw-r--r--plugins/linenumbers.lua105
-rw-r--r--plugins/macmodkeys.lua2
-rw-r--r--plugins/markers.lua8
-rw-r--r--plugins/memoryusage.lua56
-rw-r--r--plugins/minimap.lua960
-rw-r--r--plugins/motiontrail.lua35
-rw-r--r--plugins/navigate.lua2
-rw-r--r--plugins/nonicons.lua160
-rw-r--r--plugins/opacity.lua16
-rw-r--r--plugins/open_ext.lua2
-rw-r--r--plugins/openfilelocation.lua26
-rw-r--r--plugins/openselected.lua36
-rw-r--r--plugins/pdfview.lua2
-rw-r--r--plugins/primary_selection.lua187
-rw-r--r--plugins/rainbowparen.lua61
-rw-r--r--plugins/regexreplacepreview.lua222
-rw-r--r--plugins/restoretabs.lua6
-rw-r--r--plugins/scalestatus.lua62
-rw-r--r--plugins/select_colorscheme.lua20
-rw-r--r--plugins/selectionhighlight.lua15
-rw-r--r--plugins/settings.lua1858
-rw-r--r--plugins/smallclock.lua79
-rw-r--r--plugins/smoothcaret.lua43
-rw-r--r--plugins/sort.lua2
-rw-r--r--plugins/spellcheck.lua133
-rw-r--r--plugins/statusclock.lua100
-rw-r--r--plugins/tabnumbers.lua58
-rw-r--r--plugins/texcompile.lua2
-rw-r--r--plugins/themeselect.lua7
-rw-r--r--plugins/titleize.lua3
-rw-r--r--plugins/togglesnakecamel.lua2
-rw-r--r--plugins/typingspeed.lua137
-rw-r--r--plugins/unboundedscroll.lua20
-rw-r--r--plugins/wordcount.lua90
127 files changed, 6600 insertions, 1878 deletions
diff --git a/plugins/align_carets.lua b/plugins/align_carets.lua
index 95d64d5..2a1db2a 100644
--- a/plugins/align_carets.lua
+++ b/plugins/align_carets.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local DocView = require "core.docview"
diff --git a/plugins/autoinsert.lua b/plugins/autoinsert.lua
index 94bcc74..c49887a 100644
--- a/plugins/autoinsert.lua
+++ b/plugins/autoinsert.lua
@@ -1,20 +1,21 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local translate = require "core.doc.translate"
local config = require "core.config"
+local common = require "core.common"
local DocView = require "core.docview"
local command = require "core.command"
local keymap = require "core.keymap"
-config.plugins.autoinsert = { map = {
+config.plugins.autoinsert = common.merge({ map = {
["["] = "]",
["{"] = "}",
["("] = ")",
['"'] = '"',
["'"] = "'",
["`"] = "`",
-} }
+} }, config.plugins.autoinsert)
local function is_closer(chr)
diff --git a/plugins/autosave.lua b/plugins/autosave.lua
index 9518adf..b63a6f6 100644
--- a/plugins/autosave.lua
+++ b/plugins/autosave.lua
@@ -1,15 +1,40 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
local Doc = require "core.doc"
local command = require "core.command"
+local common = require "core.common"
-- this is used to detect the wait time
local last_keypress = os.time()
-- this exists so that we don't end up with multiple copies of the loop running at once
local looping = false
local on_text_change = Doc.on_text_change
--- the approximate amount of time, in seconds, that it takes to trigger an autosave
-config.plugins.autosave = { timeout = 1 }
+
+config.plugins.autosave = common.merge({
+ enabled = true,
+ -- the approximate amount of time, in seconds, that it takes to trigger an autosave
+ timeout = 1,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Auto Save",
+ {
+ label = "Enable",
+ description = "Enable or disable the auto save feature.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Timeout",
+ description = "Approximate amount of time in seconds it takes to trigger an autosave.",
+ path = "timeout",
+ type = "number",
+ default = 1,
+ min = 1,
+ max = 30
+ }
+ }
+}, config.plugins.autosave)
local function loop_for_save()
@@ -38,7 +63,7 @@ end
function Doc:on_text_change(type)
-- check if file is saved
- if self.filename then
+ if config.plugins.autosave.enabled and self.filename then
updatepress()
end
return on_text_change(self, type)
diff --git a/plugins/autosaveonfocuslost.lua b/plugins/autosaveonfocuslost.lua
index dea1e7c..1a9bd0c 100644
--- a/plugins/autosaveonfocuslost.lua
+++ b/plugins/autosaveonfocuslost.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local CommandView = require "core.commandview"
local DocView = require "core.docview"
diff --git a/plugins/autowrap.lua b/plugins/autowrap.lua
index c9dde6d..a7a9765 100644
--- a/plugins/autowrap.lua
+++ b/plugins/autowrap.lua
@@ -1,10 +1,32 @@
--- mod-version:2 -- lite-xl 2.0
-require "plugins.reflow"
+-- mod-version:3
+local core = require "core"
local config = require "core.config"
local command = require "core.command"
+local common = require "core.common"
local DocView = require "core.docview"
-config.plugins.autowrap = { files = { "%.md$", "%.txt$" } }
+config.plugins.autowrap = common.merge({
+ enabled = false,
+ files = { "%.md$", "%.txt$" },
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Auto Wrap",
+ {
+ label = "Enable",
+ description = "Activates text auto wrapping by default.",
+ path = "enabled",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Files",
+ description = "List of Lua patterns matching files to auto wrap.",
+ path = "files",
+ type = "list_strings",
+ default = { "%.md$", "%.txt$" },
+ }
+ }
+}, config.plugins.autowrap)
local on_text_input = DocView.on_text_input
@@ -12,6 +34,8 @@ local on_text_input = DocView.on_text_input
DocView.on_text_input = function(self, ...)
on_text_input(self, ...)
+ if not config.plugins.autowrap.enabled then return end
+
-- early-exit if the filename does not match a file type pattern
local filename = self.doc.filename or ""
local matched = false
@@ -31,7 +55,17 @@ DocView.on_text_input = function(self, ...)
command.perform("doc:select-lines")
command.perform("reflow:reflow")
command.perform("doc:move-to-next-char")
- command.perform("doc:move-to-previous-char")
command.perform("doc:move-to-end-of-line")
end
end
+
+command.add(nil, {
+ ["auto-wrap:toggle"] = function()
+ config.plugins.autowrap.enabled = not config.plugins.autowrap.enabled
+ if config.plugins.autowrap.enabled then
+ core.log("Auto wrap: on")
+ else
+ core.log("Auto wrap: off")
+ end
+ end
+})
diff --git a/plugins/bigclock.lua b/plugins/bigclock.lua
index c246df5..f3554ac 100644
--- a/plugins/bigclock.lua
+++ b/plugins/bigclock.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local style = require "core.style"
local command = require "core.command"
@@ -7,11 +7,39 @@ local config = require "core.config"
local View = require "core.view"
-config.plugins.bigclock = {
+config.plugins.bigclock = common.merge({
time_format = "%H:%M:%S",
date_format = "%A, %d %B %Y",
- scale = 1
-}
+ scale = 1,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Big Clock",
+ {
+ label = "Time Format",
+ description = "Time specification defined with Lua date/time place holders.",
+ path = "time_format",
+ type = "string",
+ default = "%H:%M:%S"
+ },
+ {
+ label = "Date Format",
+ description = "Date specification defined with Lua date/time place holders.",
+ path = "date_format",
+ type = "string",
+ default = "%A, %d %B %Y",
+ },
+ {
+ label = "Scale",
+ description = "Size of the clock relative to screen.",
+ path = "scale",
+ type = "number",
+ default = 1,
+ min = 0.5,
+ max = 3.0,
+ step = 0.1
+ }
+ }
+}, config.plugins.bigclock)
local ClockView = View:extend()
@@ -21,6 +49,7 @@ function ClockView:new()
ClockView.super.new(self)
self.time_text = ""
self.date_text = ""
+ self.last_scale = 0
end
@@ -30,14 +59,18 @@ end
function ClockView:update_fonts()
+ if self.last_scale ~= config.plugins.bigclock.scale then
+ self.last_scale = config.plugins.bigclock.scale
+ else
+ return
+ end
local size = math.floor(self.size.x * 0.15 / 15) * 15 * config.plugins.bigclock.scale
if self.font_size ~= size then
- self.time_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", size)
- self.date_font = renderer.font.load(DATADIR .. "/fonts/font.ttf", size * 0.3)
+ self.time_font = renderer.font.copy(style["font"], size)
+ self.date_font = renderer.font.copy(style["font"], size * 0.3)
self.font_size = size
collectgarbage()
end
- return self.font
end
diff --git a/plugins/bracketmatch.lua b/plugins/bracketmatch.lua
index cd9fc2f..6119330 100644
--- a/plugins/bracketmatch.lua
+++ b/plugins/bracketmatch.lua
@@ -1,10 +1,11 @@
--- mod-version:2 -- lite-xl 2.0
+--- mod-version:3
local core = require "core"
local style = require "core.style"
local command = require "core.command"
local keymap = require "core.keymap"
local DocView = require "core.docview"
local config = require "core.config"
+local common = require "core.common"
-- Colors can be configured as follows:
-- underline color = `style.bracketmatch_color`
@@ -12,12 +13,62 @@ local config = require "core.config"
-- background color = `style.bracketmatch_block_color`
-- frame color = `style.bracketmatch_frame_color`
-config.plugins.bracketmatch = {
- highligh_both = true, -- highlight the current bracket too
- style = "underline", -- can be "underline", "block", "frame", "none"
- color_char = false, -- color the bracket
- line_size = math.ceil(1 * SCALE), -- the size of the lines used in "underline" and "frame"
-}
+config.plugins.bracketmatch = common.merge({
+ -- highlight the current bracket too
+ highlight_both = true,
+ -- can be "underline", "block", "frame", "none"
+ style = "underline",
+ -- color the bracket
+ color_char = false,
+ -- the size of the lines used in "underline" and "frame"
+ line_size = math.ceil(1 * SCALE),
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Bracket Match",
+ {
+ label = "Highlight Both",
+ description = "Highlight the current bracket too.",
+ path = "highlight_both",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Style",
+ description = "The visual indicator for pair brackets.",
+ path = "style",
+ type = "selection",
+ default = "underline",
+ values = {
+ {"Underline", "underline"},
+ {"Block", "block"},
+ {"Frame", "frame"},
+ {"None", "none"}
+ }
+ },
+ {
+ label = "Colorize Bracket",
+ description = "Change the color of the matching brackets.",
+ path = "color_char",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Line Size",
+ description = "Height of the underline on matching brackets.",
+ path = "line_size",
+ type = "number",
+ default = 1,
+ min = 1,
+ step = 1,
+ get_value = function(value)
+ return math.floor(value / SCALE)
+ end,
+ set_value = function(value)
+ return math.ceil(value * SCALE)
+ end
+ }
+ }
+}, config.plugins.bracketmatch)
local bracket_maps = {
@@ -179,16 +230,17 @@ end
local draw_line_text = DocView.draw_line_text
-function DocView:draw_line_text(idx, x, y)
- draw_line_text(self, idx, x, y)
+function DocView:draw_line_text(line, x, y)
+ local lh = draw_line_text(self, line, x, y)
if self.doc == state.doc and state.line2 then
- if idx == state.line2 then
- draw_decoration(self, x, y, idx, state.col2)
+ if line == state.line2 then
+ draw_decoration(self, x, y, line, state.col2)
end
- if idx == state.line and config.plugins.bracketmatch.highligh_both then
- draw_decoration(self, x, y, idx, state.col + select_adj - 1)
+ if line == state.line and config.plugins.bracketmatch.highlight_both then
+ draw_decoration(self, x, y, line, state.col + select_adj - 1)
end
end
+ return lh
end
diff --git a/plugins/centerdoc.lua b/plugins/centerdoc.lua
index 8e4f8a4..980cbed 100644
--- a/plugins/centerdoc.lua
+++ b/plugins/centerdoc.lua
@@ -1,21 +1,108 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+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 treeview = require "plugins.treeview"
local DocView = require "core.docview"
+config.plugins.centerdoc = common.merge({
+ enabled = true,
+ zen_mode = false
+}, config.plugins.centerdoc)
local draw_line_gutter = DocView.draw_line_gutter
local get_gutter_width = DocView.get_gutter_width
-function DocView:draw_line_gutter(idx, x, y, width)
- local real_gutter_width = get_gutter_width(self)
- local offset = self:get_gutter_width() - real_gutter_width * 2
- draw_line_gutter(self, idx, x + offset, y, real_gutter_width)
+function DocView:draw_line_gutter(line, x, y, width)
+ local lh
+ if not config.plugins.centerdoc.enabled then
+ lh = draw_line_gutter(self, line, x, y, width)
+ else
+ local real_gutter_width = self:get_font():get_width(#self.doc.lines)
+ local offset = self:get_gutter_width() - real_gutter_width * 2
+ lh = draw_line_gutter(self, line, x + offset, y, real_gutter_width)
+ end
+ return lh
end
function DocView:get_gutter_width()
- local real_gutter_width = get_gutter_width(self)
- local width = real_gutter_width + self:get_font():get_width("n") * config.line_limit
- return math.max((self.size.x - width) / 2, real_gutter_width)
+ if not config.plugins.centerdoc.enabled then
+ return get_gutter_width(self)
+ else
+ local real_gutter_width, gutter_padding = get_gutter_width(self)
+ local width = real_gutter_width + self:get_font():get_width("n") * config.line_limit
+ return math.max((self.size.x - width) / 2, real_gutter_width), gutter_padding
+ end
end
+
+
+local previous_win_status = system.get_window_mode()
+local previous_treeview_status = treeview.visible
+local previous_statusbar_status = core.status_view.visible
+
+local function toggle_zen_mode(enabled)
+ config.plugins.centerdoc.zen_mode = enabled
+
+ if config.plugins.centerdoc.zen_mode then
+ previous_win_status = system.get_window_mode()
+ previous_treeview_status = treeview.visible
+ previous_statusbar_status = core.status_view.visible
+
+ config.plugins.centerdoc.enabled = true
+ system.set_window_mode("fullscreen")
+ treeview.visible = false
+ command.perform "status-bar:hide"
+ else
+ config.plugins.centerdoc.enabled = false
+ system.set_window_mode(previous_win_status)
+ treeview.visible = previous_treeview_status
+ core.status_view.visible = previous_statusbar_status
+ end
+end
+
+local on_startup = true
+
+-- The config specification used by the settings gui
+config.plugins.centerdoc.config_spec = {
+ name = "Center Document",
+ {
+ label = "Enable",
+ description = "Activates document centering by default.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Zen Mode",
+ description = "Activates zen mode by default.",
+ path = "zen_mode",
+ type = "toggle",
+ default = false,
+ on_apply = function(enabled)
+ if on_startup then
+ core.add_thread(function()
+ toggle_zen_mode(enabled)
+ end)
+ on_startup = false
+ else
+ toggle_zen_mode(enabled)
+ end
+ end
+ }
+}
+
+
+command.add(nil, {
+ ["center-doc:toggle"] = function()
+ config.plugins.centerdoc.enabled = not config.plugins.centerdoc.enabled
+ end,
+ ["center-doc:zen-mode-toggle"] = function()
+ toggle_zen_mode(not config.plugins.centerdoc.zen_mode)
+ end,
+})
+
+keymap.add { ["ctrl+alt+z"] = "center-doc:zen-mode-toggle" }
diff --git a/plugins/colorpreview.lua b/plugins/colorpreview.lua
index c552f07..0aa7663 100644
--- a/plugins/colorpreview.lua
+++ b/plugins/colorpreview.lua
@@ -1,15 +1,31 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+local config = require "core.config"
local common = require "core.common"
local DocView = require "core.docview"
+config.plugins.colorpreview = common.merge({
+ enabled = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Color Preview",
+ {
+ label = "Enable",
+ description = "Enable or disable the color preview feature.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.colorpreview)
+
local white = { common.color "#ffffff" }
local black = { common.color "#000000" }
local tmp = {}
-local function draw_color_previews(self, idx, x, y, ptn, base, nibbles)
- local text = self.doc.lines[idx]
+local function draw_color_previews(self, line, x, y, ptn, base, nibbles)
+ local text = self.doc.lines[line]
local s, e = 0, 0
while true do
@@ -35,8 +51,8 @@ local function draw_color_previews(self, idx, x, y, ptn, base, nibbles)
b = b * 16
end
- local x1 = x + self:get_col_x_offset(idx, s)
- local x2 = x + self:get_col_x_offset(idx, e + 1)
+ local x1 = x + self:get_col_x_offset(line, s)
+ local x2 = x + self:get_col_x_offset(line, e + 1)
local oy = self:get_line_text_y_offset()
local text_color = math.max(r, g, b) < 128 and white or black
@@ -44,7 +60,7 @@ local function draw_color_previews(self, idx, x, y, ptn, base, nibbles)
local l1, _, l2, _ = self.doc:get_selection(true)
- if not (self.doc:has_selection() and idx >= l1 and idx <= l2) then
+ if not (self.doc:has_selection() and line >= l1 and line <= l2) then
renderer.draw_rect(x1, y, x2 - x1, self:get_line_height(), tmp)
renderer.draw_text(self:get_font(), str, x1, y + oy, text_color)
end
@@ -54,9 +70,19 @@ end
local draw_line_text = DocView.draw_line_text
-function DocView:draw_line_text(idx, x, y)
- draw_line_text(self, idx, x, y)
- draw_color_previews(self, idx, x, y, "#(%x%x)(%x%x)(%x%x)(%x?%x?)%f[%W]", 16)
- draw_color_previews(self, idx, x, y, "#(%x)(%x)(%x)%f[%W]", 16, true) -- support #fff css format
- draw_color_previews(self, idx, x, y, "rgba?%((%d+)%D+(%d+)%D+(%d+)[%s,]-([%.%d]-)%s-%)", nil)
+function DocView:draw_line_text(line, x, y)
+ local lh = draw_line_text(self, line, x, y)
+ if config.plugins.colorpreview.enabled then
+ draw_color_previews(self, line, x, y,
+ "#(%x%x)(%x%x)(%x%x)(%x?%x?)%f[%W]",
+ 16
+ )
+ -- support #fff css format
+ draw_color_previews(self, line, x, y, "#(%x)(%x)(%x)%f[%W]", 16, true)
+ draw_color_previews(self, line, x, y,
+ "rgba?%((%d+)%D+(%d+)%D+(%d+)[%s,]-([%.%d]-)%s-%)",
+ nil
+ )
+ end
+ return lh
end
diff --git a/plugins/copyfilelocation.lua b/plugins/copyfilelocation.lua
index dedc188..eb7b1a9 100644
--- a/plugins/copyfilelocation.lua
+++ b/plugins/copyfilelocation.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
diff --git a/plugins/datetimestamps.lua b/plugins/datetimestamps.lua
index 51d698e..f16af83 100644
--- a/plugins/datetimestamps.lua
+++ b/plugins/datetimestamps.lua
@@ -1,7 +1,8 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
local command = require "core.command"
+local common = require "core.common"
--[[
Date and time format placeholders
@@ -25,11 +26,36 @@ from https://www.lua.org/pil/22.1.html
%y two-digit year (98) [00-99]
%% the character `%´
--]]
-config.plugins.datetimestamps = {
- format_datestamp = "%Y%m%d"
- format_datetimestamp = "%Y%m%d_%H%M%S"
- format_timestamp = "%H%M%S"
-}
+config.plugins.datetimestamps = common.merge({
+ format_datestamp = "%Y%m%d",
+ format_datetimestamp = "%Y%m%d_%H%M%S",
+ format_timestamp = "%H%M%S",
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Date and Time Stamps",
+ {
+ label = "Date",
+ description = "Date specification defined with Lua date/time place holders.",
+ path = "format_datestamp",
+ type = "string",
+ default = "%Y%m%d"
+ },
+ {
+ label = "Time",
+ description = "Time specification defined with Lua date/time place holders.",
+ path = "format_timestamp",
+ type = "string",
+ default = "%H%M%S"
+ },
+ {
+ label = "Date and Time",
+ description = "Date and time specification defined with Lua date/time place holders.",
+ path = "format_datetimestamp",
+ type = "string",
+ default = "%Y%m%d_%H%M%S"
+ }
+ }
+}, config.plugins.datetimestamps)
local function datestamp()
local sOut = os.date(config.plugins.datetimestamps.format_datestamp)
@@ -49,6 +75,13 @@ end
command.add("core.docview", {
["datetimestamps:insert-datestamp"] = datestamp,
["datetimestamps:insert-timestamp"] = timestamp,
- ["datetimestamps:insert-datetimestamp"] = datetimestamp
+ ["datetimestamps:insert-datetimestamp"] = datetimestamp,
+ ["datetimestamps:insert-custom"] = function()
+ core.command_view:enter("Date format eg: %H:%M:%S", {
+ submit = function(cmd)
+ core.active_view.doc:text_input(os.date(cmd) or "")
+ end
+ })
+ end,
})
diff --git a/plugins/dragdropselected.lua b/plugins/dragdropselected.lua
index 6bdb101..3c6583b 100644
--- a/plugins/dragdropselected.lua
+++ b/plugins/dragdropselected.lua
@@ -1,13 +1,13 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
--[[
- dragdropselected.lua
- provides basic drag and drop of selected text (in same document)
- version: 20200627_133351
- originally by SwissalpS
-
- TODO: use OS drag and drop events
- TODO: change mouse cursor when duplicating
- TODO: add dragging image
+ dragdropselected.lua
+ provides basic drag and drop of selected text (in same document)
+ version: 20200627_133351
+ originally by SwissalpS
+
+ TODO: use OS drag and drop events
+ TODO: change mouse cursor when duplicating
+ TODO: add dragging image
--]]
local DocView = require "core.docview"
local core = require "core"
@@ -22,16 +22,16 @@ local style = require "core.style"
-- iSelLine2 is line number where selection ends
-- iSelCol2 is column where selection ends
local function isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2)
- if iLine < iSelLine1 then return false end
- if iLine > iSelLine2 then return false end
- if (iLine == iSelLine1) and (iCol < iSelCol1) then return false end
- if (iLine == iSelLine2) and (iCol > iSelCol2) then return false end
- return true
+ if iLine < iSelLine1 then return false end
+ if iLine > iSelLine2 then return false end
+ if (iLine == iSelLine1) and (iCol < iSelCol1) then return false end
+ if (iLine == iSelLine2) and (iCol > iSelCol2) then return false end
+ return true
end -- isInSelection
-- distance between two points
local function distance(x1, y1, x2, y2)
- return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
+ return math.sqrt(math.pow(x2-x1, 2)+math.pow(y2-y1, 2))
end
local min_drag = style.code_font:get_width(" ")
@@ -40,126 +40,138 @@ local min_drag = style.code_font:get_width(" ")
local on_mouse_moved = DocView.on_mouse_moved
function DocView:on_mouse_moved(x, y, ...)
- local sCursor = nil
-
- -- make sure we only act if previously on_mouse_pressed was in selection
- if self.bClickedIntoSelection and
- ( -- we are already dragging or we moved enough to start dragging
- not self.drag_start_loc or
- distance(self.drag_start_loc[1],self.drag_start_loc[2], x, y) > min_drag
- ) then
- self.drag_start_loc = nil
-
- -- show that we are dragging something
- sCursor = 'hand'
-
- -- calculate line and column for current mouse position
- local iLine, iCol = self:resolve_screen_position(x, y)
- local iSelLine1 = self.dragged_selection[1]
- local iSelCol1 = self.dragged_selection[2]
- local iSelLine2 = self.dragged_selection[3]
- local iSelCol2 = self.dragged_selection[4]
- self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
- if not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) then
- -- show cursor only if outside selection
- self.doc:add_selection(iLine, iCol)
- end
- -- update scroll position
- self:scroll_to_line(iLine, true)
- end -- if previously clicked into selection
+ local sCursor = nil
+
+ -- make sure we only act if previously on_mouse_pressed was in selection
+ if
+ self.bClickedIntoSelection
+ and
+ ( -- we are already dragging or we moved enough to start dragging
+ not self.drag_start_loc or
+ distance(self.drag_start_loc[1],self.drag_start_loc[2], x, y) > min_drag
+ )
+ then
+ self.drag_start_loc = nil
- -- hand off to 'old' on_mouse_moved()
- on_mouse_moved(self, x, y, ...)
- -- override cursor as needed
- if sCursor then self.cursor = sCursor end
+ -- show that we are dragging something
+ sCursor = 'hand'
+
+ -- calculate line and column for current mouse position
+ local iLine, iCol = self:resolve_screen_position(x, y)
+ local iSelLine1 = self.dragged_selection[1]
+ local iSelCol1 = self.dragged_selection[2]
+ local iSelLine2 = self.dragged_selection[3]
+ local iSelCol2 = self.dragged_selection[4]
+ self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
+ if not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) then
+ -- show cursor only if outside selection
+ self.doc:add_selection(iLine, iCol)
+ end
+ -- update scroll position
+ self:scroll_to_line(iLine, true)
+ end -- if previously clicked into selection
+
+ -- hand off to 'old' on_mouse_moved()
+ on_mouse_moved(self, x, y, ...)
+ -- override cursor as needed
+ if sCursor then self.cursor = sCursor end
end -- DocView:on_mouse_moved
-- override DocView:on_mouse_pressed
local on_mouse_pressed = DocView.on_mouse_pressed
function DocView:on_mouse_pressed(button, x, y, clicks)
- local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
- if caught then
- return caught
- end
- -- no need to proceed if not left button or has no selection
- if ('left' ~= button)
- or (not self.doc:has_selection())
- or (1 < clicks) then
- return on_mouse_pressed(self, button, x, y, clicks)
- end
- -- convert pixel coordinates to line and column coordinates
- local iLine, iCol = self:resolve_screen_position(x, y)
- -- get selection coordinates
- local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = self.doc:get_selection(true)
- -- set flag for on_mouse_released and on_mouse_moved() methods to detect dragging
- self.bClickedIntoSelection = isInSelection(iLine, iCol, iSelLine1, iSelCol1,
- iSelLine2, iSelCol2)
- if self.bClickedIntoSelection then
- self.drag_start_loc = { x, y }
- -- stash selection for inserting later
- self.sDraggedText = self.doc:get_text(self.doc:get_selection())
- self.dragged_selection = { iSelLine1, iSelCol1, iSelLine2, iSelCol2 }
- else
- self.bClickedIntoSelection = nil
- self.dragged_selection = nil
- -- let 'old' on_mouse_pressed() do whatever it needs to do
- on_mouse_pressed(self, button, x, y, clicks)
- end
+ local caught = DocView.super.on_mouse_pressed(self, button, x, y, clicks)
+ if caught then
+ return caught
+ end
+ -- no need to proceed if not left button or has no selection
+ if
+ ('left' ~= button)
+ or (not self.doc:has_selection())
+ or (1 < clicks)
+ then
+ return on_mouse_pressed(self, button, x, y, clicks)
+ end
+ -- convert pixel coordinates to line and column coordinates
+ local iLine, iCol = self:resolve_screen_position(x, y)
+ -- get selection coordinates
+ local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = self.doc:get_selection(true)
+ -- set flag for on_mouse_released and on_mouse_moved() methods to detect dragging
+ self.bClickedIntoSelection = isInSelection(iLine, iCol, iSelLine1, iSelCol1,
+ iSelLine2, iSelCol2)
+ if self.bClickedIntoSelection then
+ self.drag_start_loc = { x, y }
+ -- stash selection for inserting later
+ self.sDraggedText = self.doc:get_text(self.doc:get_selection())
+ self.dragged_selection = { iSelLine1, iSelCol1, iSelLine2, iSelCol2 }
+ else
+ self.bClickedIntoSelection = nil
+ self.dragged_selection = nil
+ -- let 'old' on_mouse_pressed() do whatever it needs to do
+ on_mouse_pressed(self, button, x, y, clicks)
+ end
end -- DocView:on_mouse_pressed
-- override DocView:on_mouse_released()
local on_mouse_released = DocView.on_mouse_released
function DocView:on_mouse_released(button, x, y)
- local iLine, iCol = self:resolve_screen_position(x, y)
- if self.bClickedIntoSelection then
- local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = table.unpack(self.dragged_selection)
- if not self.drag_start_loc
- and not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2) then
- -- insert stashed selected text at current position
- if iLine < iSelLine1 or (iLine == iSelLine1 and iCol < iSelCol1) then
- -- delete first
- self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
- if not keymap.modkeys['ctrl'] then
- self.doc:delete_to(0)
- end
- self.doc:set_selection(iLine, iCol)
- self.doc:text_input(self.sDraggedText)
- else
- -- insert first
- self.doc:set_selection(iLine, iCol)
- self.doc:text_input(self.sDraggedText)
- self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
- if not keymap.modkeys['ctrl'] then
- self.doc:delete_to(0)
- end
- self.doc:set_selection(iLine, iCol)
- end
- elseif self.drag_start_loc then
- -- deselect only if the drag never happened
- self.doc:set_selection(iLine, iCol)
+ local iLine, iCol = self:resolve_screen_position(x, y)
+ if self.bClickedIntoSelection then
+ local iSelLine1, iSelCol1, iSelLine2, iSelCol2 = table.unpack(self.dragged_selection)
+ if
+ not self.drag_start_loc
+ and
+ not isInSelection(iLine, iCol, iSelLine1, iSelCol1, iSelLine2, iSelCol2)
+ then
+ -- insert stashed selected text at current position
+ if iLine < iSelLine1 or (iLine == iSelLine1 and iCol < iSelCol1) then
+ -- delete first
+ self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
+ if not keymap.modkeys['ctrl'] then
+ self.doc:delete_to(0)
end
- -- unset stash and flag(s) TODO:
- self.sDraggedText = ''
- self.bClickedIntoSelection = nil
+ self.doc:set_selection(iLine, iCol)
+ self.doc:text_input(self.sDraggedText)
+ else
+ -- insert first
+ self.doc:set_selection(iLine, iCol)
+ self.doc:text_input(self.sDraggedText)
+ self.doc:set_selection(iSelLine1, iSelCol1, iSelLine2, iSelCol2)
+ if not keymap.modkeys['ctrl'] then
+ self.doc:delete_to(0)
+ end
+ self.doc:set_selection(iLine, iCol)
+ end
+ elseif self.drag_start_loc then
+ -- deselect only if the drag never happened
+ self.doc:set_selection(iLine, iCol)
end
+ -- unset stash and flag(s) TODO:
+ self.sDraggedText = ''
+ self.bClickedIntoSelection = nil
+ end
- -- hand over to old handler
- on_mouse_released(self, button, x, y)
+ -- hand over to old handler
+ on_mouse_released(self, button, x, y)
end -- DocView:on_mouse_released
-- override DocView:draw_caret()
local draw_caret = DocView.draw_caret
function DocView:draw_caret(x, y)
- if self.bClickedIntoSelection then
- local iLine, iCol = self:resolve_screen_position(x, y)
- -- don't show carets inside selections
- if isInSelection(iLine, iCol,
- self.dragged_selection[1], self.dragged_selection[2],
- self.dragged_selection[3], self.dragged_selection[4]) then
- return
- end
+ if self.bClickedIntoSelection then
+ local iLine, iCol = self:resolve_screen_position(x, y)
+ -- don't show carets inside selections
+ if
+ isInSelection(
+ iLine, iCol,
+ self.dragged_selection[1], self.dragged_selection[2],
+ self.dragged_selection[3], self.dragged_selection[4]
+ )
+ then
+ return
end
- draw_caret(self, x, y)
+ end
+ draw_caret(self, x, y)
end -- DocView:draw_caret()
diff --git a/plugins/ephemeral_tabs.lua b/plugins/ephemeral_tabs.lua
index dea4261..f61c493 100644
--- a/plugins/ephemeral_tabs.lua
+++ b/plugins/ephemeral_tabs.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local RootView = require "core.rootview"
diff --git a/plugins/eval.lua b/plugins/eval.lua
index bd1ff56..c2eb19e 100644
--- a/plugins/eval.lua
+++ b/plugins/eval.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
@@ -13,12 +13,20 @@ end
command.add("core.docview", {
["eval:insert"] = function()
- core.command_view:enter("Evaluate And Insert Result", function(cmd)
- core.active_view.doc:text_input(eval(cmd))
- end)
+ core.command_view:enter("Evaluate And Insert Result", {
+ submit = function(cmd)
+ core.active_view.doc:text_input(eval(cmd))
+ end
+ })
end,
["eval:replace"] = function()
- core.active_view.doc:replace(eval)
+ core.command_view:enter("Evaluate And Replace With Result", {
+ submit = function(cmd)
+ core.active_view.doc:replace(function(str)
+ return eval(cmd)
+ end)
+ end
+ })
end,
})
diff --git a/plugins/exec.lua b/plugins/exec.lua
index cf2d8f2..b8f61c5 100644
--- a/plugins/exec.lua
+++ b/plugins/exec.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
@@ -28,19 +28,23 @@ end
command.add("core.docview", {
["exec:insert"] = function()
- core.command_view:enter("Insert Result Of Command", function(cmd)
- core.active_view.doc:text_input(exec(cmd))
- end)
+ core.command_view:enter("Insert Result Of Command", {
+ submit = function(cmd)
+ core.active_view.doc:text_input(exec(cmd))
+ end
+ })
end,
["exec:replace"] = function()
- core.command_view:enter("Replace With Result Of Command", function(cmd)
- core.active_view.doc:replace(function(str)
- return exec(
- "printf %b " .. printfb_quote(str:gsub("%\n$", "") .. "\n") .. " | eval '' " .. shell_quote(cmd),
- str:find("%\n$")
- )
- end)
- end)
+ core.command_view:enter("Replace With Result Of Command", {
+ submit = function(cmd)
+ core.active_view.doc:replace(function(str)
+ return exec(
+ "printf %b " .. printfb_quote(str:gsub("%\n$", "") .. "\n") .. " | eval '' " .. shell_quote(cmd),
+ str:find("%\n$")
+ )
+ end)
+ end
+ })
end,
})
diff --git a/plugins/extend_selection_line.lua b/plugins/extend_selection_line.lua
index e986597..e002674 100644
--- a/plugins/extend_selection_line.lua
+++ b/plugins/extend_selection_line.lua
@@ -1,19 +1,20 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local DocView = require "core.docview"
local style = require "core.style"
local draw_line_body = DocView.draw_line_body
-function DocView:draw_line_body(idx, x, y, ...)
- draw_line_body(self, idx, x, y, ...)
+function DocView:draw_line_body(line, x, y)
+ local line_height = draw_line_body(self, line, x, y)
local lh = self:get_line_height()
for _, line1, _, line2, _ in self.doc:get_selections(true) do
- if idx >= line1 and idx < line2 and line1 ~= line2 then
+ if line >= line1 and line < line2 and line1 ~= line2 then
-- draw selection from the end of the line to the end of the available space
- local x1 = x + self:get_col_x_offset(idx, #self.doc.lines[idx])
+ local x1 = x + self:get_col_x_offset(line, #self.doc.lines[line])
local x2 = x + self.scroll.x + self.size.x
if x2 > x1 then
renderer.draw_rect(x1, y, x2 - x1, lh, style.selection)
end
end
end
+ return line_height
end
diff --git a/plugins/fontconfig.lua b/plugins/fontconfig.lua
index 657e364..8b713b5 100644
--- a/plugins/fontconfig.lua
+++ b/plugins/fontconfig.lua
@@ -1,11 +1,12 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local subprocess = require "process"
local core = require "core"
local style = require "core.style"
local config = require "core.config"
+local common = require "core.common"
-config.plugins.fontconfig = { prefix = "" }
+config.plugins.fontconfig = common.merge({ prefix = "" }, config.plugins.fontconfig)
--[[
Example config (put it in user module):
diff --git a/plugins/force_syntax.lua b/plugins/force_syntax.lua
index dce4abc..ae5a138 100644
--- a/plugins/force_syntax.lua
+++ b/plugins/force_syntax.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local Doc = require "core.doc"
local syntax = require "core.syntax"
@@ -39,30 +39,24 @@ local function get_syntax_name(s)
return name or "Undefined"
end
-local statusview_get_items = StatusView.get_items
-function StatusView:get_items()
- local left, right = statusview_get_items(self)
-
- local is_dv = core.active_view and getmetatable(core.active_view) == DocView
- if not is_dv then return left, right end
-
- local syntax_name = get_syntax_name(doc().syntax)
-
- local ins = {
- style.dim,
- self.separator2,
- style.text,
- syntax_name
- }
-
- if syntax_name then
- for _,item in pairs(ins) do
- table.insert(right, item)
- end
- end
-
- return left, right
-end
+core.status_view:add_item({
+ predicate = function()
+ return core.active_view and getmetatable(core.active_view) == DocView
+ end,
+ name = "doc:syntax",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function()
+ local syntax_name = get_syntax_name(doc().syntax)
+ return {
+ style.text,
+ syntax_name
+ }
+ end,
+ command = "force-syntax:select-file-syntax",
+ position = -1,
+ tooltip = "file syntax",
+ separator = core.status_view.separator2
+})
local function get_syntax_list()
local pt_name = plain_text_syntax.name
@@ -110,23 +104,20 @@ end
command.add("core.docview", {
["force-syntax:select-file-syntax"] =
function()
- core.command_view:enter(
- "Set syntax for this file",
- function(text, item) -- submit
+ core.command_view:enter("Set syntax for this file", {
+ submit = function(text, item)
local list, _ = get_syntax_list()
doc().force_syntax = list[item.text]
doc():reset_syntax()
end,
- function(text) -- suggest
+ suggest = function(text)
local _, keylist = get_syntax_list()
local res = common.fuzzy_match(keylist, text)
-- Force Current and Auto detect syntax to the bottom
-- if the text is empty
table.sort(res, #text == 0 and bias_sorter or sorter)
return res
- end,
- nil, -- cancel
- nil -- validate
- )
+ end
+ })
end
})
diff --git a/plugins/ghmarkdown.lua b/plugins/ghmarkdown.lua
index 532be57..244b144 100644
--- a/plugins/ghmarkdown.lua
+++ b/plugins/ghmarkdown.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
diff --git a/plugins/gitstatus.lua b/plugins/gitstatus.lua
index de5c74b..9a36142 100644
--- a/plugins/gitstatus.lua
+++ b/plugins/gitstatus.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local common = require "core.common"
local config = require "core.config"
@@ -6,15 +6,37 @@ local style = require "core.style"
local StatusView = require "core.statusview"
local TreeView = require "plugins.treeview"
+config.plugins.gitstatus = common.merge({
+ recurse_submodules = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Git Status",
+ {
+ label = "Recurse Submodules",
+ description = "Also retrieve git stats from submodules.",
+ path = "recurse_submodules",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.gitstatus)
+
+style.gitstatus_addition = {common.color "#587c0c"}
+style.gitstatus_modification = {common.color "#0c7d9d"}
+style.gitstatus_deletion = {common.color "#94151b"}
+
local scan_rate = config.project_scan_rate or 5
local cached_color_for_item = {}
--- Override TreeView's color_for_item, but first
--- stash the old one (using [] in case it is not there at all)
-local old_color_for_item = TreeView["color_for_item"]
-function TreeView:color_for_item(abs_path)
- return cached_color_for_item[abs_path] or old_color_for_item(abs_path)
+-- Override TreeView's get_item_text to add modification color
+local treeview_get_item_text = TreeView.get_item_text
+function TreeView:get_item_text(item, active, hovered)
+ local text, font, color = treeview_get_item_text(self, item, active, hovered)
+ if cached_color_for_item[item.abs_filename] then
+ color = cached_color_for_item[item.abs_filename]
+ end
+ return text, font, color
end
@@ -24,15 +46,6 @@ local git = {
deletes = 0,
}
-
-config.gitstatus = {
- recurse_submodules = true
-}
-style.gitstatus_addition = {common.color "#587c0c"}
-style.gitstatus_modification = {common.color "#0c7d9d"}
-style.gitstatus_deletion = {common.color "#94151b"}
-
-
local function exec(cmd)
local proc = process.start(cmd)
-- Don't use proc:wait() here - that will freeze the app.
@@ -57,7 +70,11 @@ core.add_thread(function()
-- get diff
local diff = exec({"git", "diff", "--numstat"})
- if config.gitstatus.recurse_submodules and system.get_file_info(".gitmodules") then
+ if
+ config.plugins.gitstatus.recurse_submodules
+ and
+ system.get_file_info(".gitmodules")
+ then
local diff2 = exec({"git", "submodule", "foreach", "git diff --numstat"})
diff = diff .. diff2
end
@@ -99,27 +116,23 @@ core.add_thread(function()
end)
-local get_items = StatusView.get_items
-
-function StatusView:get_items()
- if not git.branch then
- return get_items(self)
- end
- local left, right = get_items(self)
-
- local t = {
- style.dim, self.separator,
- (git.inserts ~= 0 or git.deletes ~= 0) and style.accent or style.text,
- git.branch,
- style.dim, " ",
- git.inserts ~= 0 and style.accent or style.text, "+", git.inserts,
- style.dim, " / ",
- git.deletes ~= 0 and style.accent or style.text, "-", git.deletes,
- }
- for _, item in ipairs(t) do
- table.insert(right, item)
- end
-
- return left, right
-end
-
+core.status_view:add_item({
+ name = "status:git",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function()
+ if not git.branch then
+ return {}
+ end
+ return {
+ (git.inserts ~= 0 or git.deletes ~= 0) and style.accent or style.text,
+ git.branch,
+ style.dim, " ",
+ git.inserts ~= 0 and style.accent or style.text, "+", git.inserts,
+ style.dim, " / ",
+ git.deletes ~= 0 and style.accent or style.text, "-", git.deletes,
+ }
+ end,
+ position = -1,
+ tooltip = "branch and changes",
+ separator = core.status_view.separator2
+})
diff --git a/plugins/gofmt.lua b/plugins/gofmt.lua
index 02c817b..fec95e4 100644
--- a/plugins/gofmt.lua
+++ b/plugins/gofmt.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
diff --git a/plugins/hidelinenumbers.lua b/plugins/hidelinenumbers.lua
deleted file mode 100644
index 4494670..0000000
--- a/plugins/hidelinenumbers.lua
+++ /dev/null
@@ -1,6 +0,0 @@
--- mod-version:2 -- lite-xl 2.0
-local style = require "core.style"
-local DocView = require "core.docview"
-
-DocView.draw_line_gutter = function() end
-DocView.get_gutter_width = function() return style.padding.x end
diff --git a/plugins/hidestatus.lua b/plugins/hidestatus.lua
deleted file mode 100644
index 6e63a81..0000000
--- a/plugins/hidestatus.lua
+++ /dev/null
@@ -1,19 +0,0 @@
--- mod-version:2 -- lite-xl 2.0
-local command = require "core.command"
-local StatusView = require "core.statusview"
-
-local visible = false
-local funcs = {
- [true] = StatusView.update,
- [false] = function(self) self.size.y = 0 end,
-}
-
-function StatusView:update(...)
- funcs[visible](self, ...)
-end
-
-command.add(nil, {
- ["hide-status:toggle"] = function() visible = not visible end,
- ["hide-status:hide"] = function() visible = false end,
- ["hide-status:show"] = function() visible = true end,
-})
diff --git a/plugins/indent_convert.lua b/plugins/indent_convert.lua
index c86686d..3a950f5 100644
--- a/plugins/indent_convert.lua
+++ b/plugins/indent_convert.lua
@@ -1,11 +1,24 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
+local common = require "core.common"
local config = require "core.config"
local command = require "core.command"
-config.plugins.indent_convert = {
- update_indent_type = true -- set to false to avoid updating the document indent type
-}
+config.plugins.indent_convert = common.merge({
+ -- set to false to avoid updating the document indent type
+ update_indent_type = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Indent Convert",
+ {
+ label = "Update Indent Type",
+ description = "Disable to avoid updating the document indent type.",
+ path = "update_indent_type",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.indent_convert)
local zero_pattern = _VERSION == "Lua 5.1" and "%z" or "\0"
diff --git a/plugins/indentguide.lua b/plugins/indentguide.lua
index 99b1311..42eb3a6 100644
--- a/plugins/indentguide.lua
+++ b/plugins/indentguide.lua
@@ -1,8 +1,23 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local style = require "core.style"
local config = require "core.config"
+local common = require "core.common"
local DocView = require "core.docview"
+config.plugins.indentguide = common.merge({
+ enabled = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Indent Guide",
+ {
+ label = "Enable",
+ description = "Toggle the drawing of indentation indicator lines.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.indentguide)
-- TODO: replace with `doc:get_indent_info()` when 2.1 releases
local function get_indent_info(doc)
@@ -13,15 +28,15 @@ local function get_indent_info(doc)
end
-local function get_line_spaces(doc, idx, dir)
+local function get_line_spaces(doc, line, dir)
local _, indent_size = get_indent_info(doc)
- local text = doc.lines[idx]
+ 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, idx + dir, dir)
+ return get_line_spaces(doc, line + dir, dir)
end
local n = 0
for _,b in pairs({text:byte(s, e)}) do
@@ -31,25 +46,29 @@ local function get_line_spaces(doc, idx, dir)
end
-local function get_line_indent_guide_spaces(doc, idx)
- if doc.lines[idx]:find("^%s*\n") then
+local function get_line_indent_guide_spaces(doc, line)
+ if doc.lines[line]:find("^%s*\n") then
return math.max(
- get_line_spaces(doc, idx - 1, -1),
- get_line_spaces(doc, idx + 1, 1))
+ get_line_spaces(doc, line - 1, -1),
+ get_line_spaces(doc, line + 1, 1))
end
- return get_line_spaces(doc, idx)
+ return get_line_spaces(doc, line)
end
local docview_update = DocView.update
function DocView:update()
docview_update(self)
- local function get_indent(idx)
- if idx < 1 or idx > #self.doc.lines then return -1 end
- if not self.indentguide_indents[idx] then
- self.indentguide_indents[idx] = get_line_indent_guide_spaces(self.doc, idx)
+ if not config.plugins.indentguide.enabled or not self:is(DocView) then
+ return
+ end
+
+ 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)
end
- return self.indentguide_indents[idx]
+ return self.indentguide_indents[line]
end
self.indentguide_indents = {}
@@ -103,21 +122,23 @@ end
local draw_line_text = DocView.draw_line_text
-function DocView:draw_line_text(idx, x, y)
- local spaces = self.indentguide_indents[idx] or -1
- local _, indent_size = get_indent_info(self.doc)
- local w = math.max(1, SCALE)
- 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[idx] or -1
- if i < active_lvl and i + indent_size >= active_lvl then
- color = style.guide_highlight or style.accent
+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 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
+ color = style.guide_highlight or style.accent
+ end
+ local sw = space_sz * i
+ renderer.draw_rect(math.ceil(x + sw), y, w, h, color)
end
- local sw = space_sz * i
- renderer.draw_rect(math.ceil(x + sw), y, w, h, color)
end
- draw_line_text(self, idx, x, y)
+ return draw_line_text(self, line, x, y)
end
diff --git a/plugins/ipc.lua b/plugins/ipc.lua
new file mode 100644
index 0000000..8a68fc2
--- /dev/null
+++ b/plugins/ipc.lua
@@ -0,0 +1,1043 @@
+-- mod-version:3
+--
+-- Crossplatform file based IPC system for lite-xl.
+-- @copyright Jefferson Gonzalez <jgmdev@gmail.com>
+-- @license MIT
+--
+local core = require "core"
+local config = require "core.config"
+local common = require "core.common"
+local command = require "core.command"
+local Object = require "core.object"
+local RootView = require "core.rootview"
+local settings_found, settings = pcall(require, "plugins.settings")
+
+---The maximum amount of seconds a message will be broadcasted.
+---@type integer
+local MESSAGE_EXPIRATION=3
+
+---@class config.plugins.ipc
+---@field single_instance boolean
+---@field dirs_instance string
+config.plugins.ipc = common.merge({
+ single_instance = true,
+ dirs_instance = "new",
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Inter-process communication",
+ {
+ label = "Single Instance",
+ description = "Run a single instance of lite-xl.",
+ path = "single_instance",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Directories Instance",
+ description = "Control how to open directories in single instance mode.",
+ path = "dirs_instance",
+ type = "selection",
+ default = "new",
+ values = {
+ {"Create a New Instance", "new"},
+ {"Add to Current Instance", "add"},
+ {"Change Current Instance Project Directory", "change"}
+ }
+ }
+ }
+}, config.plugins.ipc)
+
+---@alias plugins.ipc.onmessageread fun(message: plugins.ipc.message) | nil
+---@alias plugins.ipc.onreplyread fun(reply: plugins.ipc.reply) | nil
+---@alias plugins.ipc.onmessage fun(message: plugins.ipc.message, reply: plugins.ipc.reply) | nil
+---@alias plugins.ipc.onreply fun(reply: plugins.ipc.reply) | nil
+---@alias plugins.ipc.function fun(...)
+
+---@alias plugins.ipc.messagetype
+---| '"message"'
+---| '"method"'
+---| '"signal"'
+
+---@class plugins.ipc.message
+---@field id string
+---@field sender string
+---@field name string
+---@field type plugins.ipc.messagetype | string
+---@field destinations table<integer,string>
+---@field data table<string,any>
+---@field timestamp number
+---@field on_read plugins.ipc.onmessageread
+---@field on_reply plugins.ipc.onreply
+---@field replies plugins.ipc.reply[]
+local IPCMessage = {
+ ---Id of the message
+ id = "",
+ ---The id of process that sent the message
+ sender = "",
+ ---Name of the message
+ name = "",
+ ---Type of message.
+ type = "",
+ ---List with id of the instance that should receive the message.
+ destinations = {},
+ ---A list of named values sent to receivers.
+ data = {},
+ ---Time in seconds when the message was sent for automatic expiration purposes.
+ timestamp = 0,
+ ---Optional callback executed by the receiver when the message is read.
+ on_read = function(message) end,
+ ---Optional callback executed when a reply to the message is received.
+ on_reply = function(reply) end,
+ ---The received replies for the message.
+ replies = {}
+}
+
+---@class plugins.ipc.reply
+---@field id string
+---@field sender string
+---@field replier string
+---@field data table<string,any>
+---@field timestamp number
+---@field on_read plugins.ipc.onreplyread
+local IPCReply = {
+ ---Id of the message
+ id = "",
+ ---The id of process that sent the message
+ sender = "",
+ ---The id of the replier
+ replier = "",
+ ---A list of named values sent back to sender.
+ data = {},
+ ---Time in seconds when the reply was sent for automatic expiration purposes.
+ timestamp = 0,
+ ---Optional callback executed by the sender when the reply is read.
+ on_read = function(reply) end
+}
+
+---@class plugins.ipc.instance
+---@field id string
+---@field position integer
+---@field last_update integer
+---@field messages plugins.ipc.message[]
+---@field replies plugins.ipc.reply[]
+---@field properties table
+local IPCInstance = {
+ ---Process id of the instance.
+ id = "",
+ ---The position in which the instance was launched.
+ position = 0,
+ ---Flag that indicates if this instance was the first started.
+ primary = false,
+ ---Indicates the last time this instance updated its session file.
+ last_update = 0,
+ ---The messages been broadcasted.
+ messages = {},
+ ---The replies been broadcasted.
+ replies = {},
+ ---Table of properties associated with the instance.
+ properties = {}
+}
+
+---@class core.ipc : core.object
+---@field private id string
+---@field private user_dir string
+---@field private running boolean
+---@field private file string
+---@field private primary boolean
+---@field private position integer
+---@field private messages plugins.ipc.message[]
+---@field private replies plugins.ipc.reply[]
+---@field private listeners table<string,table<integer,plugins.ipc.onmessage>>
+---@field private signals table<string,integer>
+---@field private methods table<string,integer>
+---@field private signal_definitions table<integer,string>
+---@field private method_definitions table<integer,string>
+local IPC = Object:extend()
+
+---@class plugins.ipc.threads
+---@field cr thread
+---@field wake number
+
+---List of threads belonging to all instantiated IPC objects.
+---@type plugins.ipc.threads[]
+local threads = {}
+
+---Register a new thread to be run on the background.
+---@param f function
+local function add_thread(f)
+ local key = #threads + 1
+ threads[key] = { cr = coroutine.create(f), wake = 0 }
+ return key
+end
+
+---Updates the session file of an IPC object.
+---@param self core.ipc
+local function update_file(self)
+ local file, errmsg = io.open(self.file, "w+")
+
+ if file then
+ local output = "-- Warning: Generated by IPC system do not edit manually!\n"
+ .. "return " .. common.serialize(
+ {
+ id = self.id,
+ primary = self.primary,
+ position = self.position,
+ last_update = os.time(),
+ messages = self.messages,
+ replies = self.replies,
+ signals = self.signal_definitions,
+ methods = self.method_definitions
+ },
+ {
+ pretty = true
+ }
+ )
+
+ output = output:gsub("%s+%[\"on_reply\"%].-\n", "")
+
+ file:write(output)
+ file:close()
+ else
+ core.error("IPC Error: failed updating status (%s)", errmsg)
+ end
+end
+
+---Constructor
+---@param id? string Defaults to current lite-xl process id.
+function IPC:new(id)
+ self.id = id or tostring(system.get_process_id())
+ self.user_dir = USERDIR .. "/ipc"
+ self.file = self.user_dir .. "/" .. self.id .. ".lua"
+ self.primary = false
+ self.running = false
+ self.messages = {}
+ self.replies = {}
+ self.listeners = {}
+ self.signals = {}
+ self.methods = {}
+ self.signal_definitions = {}
+ self.method_definitions = {}
+
+ local ipc_dir_status = system.get_file_info(self.user_dir)
+
+ if not ipc_dir_status then
+ local created, errmsg = common.mkdirp(self.user_dir)
+ if not created then
+ core.error("Error initializing IPC system: %s", errmsg)
+ return
+ end
+ end
+
+ local file, errmsg = io.open(self.file, "w+")
+
+ if not file then
+ core.error("Error initializing IPC system: %s", errmsg)
+ return
+ else
+ file:close()
+ os.remove(self.file)
+ end
+
+ -- Execute to set the instance position and primary attribute if no other running.
+ local instances = self:get_instances()
+ self.primary = #instances == 0 and true or false
+ self.position = #instances + 1
+
+ self:start()
+end
+
+---Starts and registers the ipc session and monitoring.
+function IPC:start()
+ if not self.running then
+ self.running = true
+
+ update_file(self)
+
+ local wait_time = 0.3
+
+ local this = self
+ self.coroutine_key = add_thread(function()
+ coroutine.yield(wait_time)
+ while(true) do
+ this:read_messages()
+ this:read_replies()
+ update_file(this)
+ coroutine.yield(wait_time)
+ end
+ end)
+ end
+end
+
+---Stop and unregister the ipc session and monitoring.
+function IPC:stop()
+ self.running = false
+ table.remove(threads, self.coroutine_key)
+ os.remove(self.file)
+end
+
+---Get a list of running lite-xl instances.
+---@return plugins.ipc.instance[]
+function IPC:get_instances()
+ ---@type plugins.ipc.instance[]
+ local instances = {}
+
+ local files, errmsg = system.list_dir(self.user_dir)
+
+ if files then
+ for _, file in ipairs(files) do
+ if string.match(file, "^%d+%.lua$") then
+ local path = self.user_dir .. "/" .. file
+ local file_info = system.get_file_info(path)
+ if file_info and file_info.type == "file" then
+ ::read_instance_file::
+ ---@type plugins.ipc.instance
+ local instance = dofile(path)
+ if instance and instance.id ~= self.id then
+ if instance.last_update + 2 > os.time() then
+ table.insert(instances, instance)
+ else
+ -- Delete expired instance session maybe result of a crash
+ os.remove(path)
+ end
+ elseif not instance and path ~= self.file then
+ --We retry reading the file since it was been modified
+ --by its owner instance.
+ goto read_instance_file
+ end
+ end
+ end
+ end
+ else
+ core.error("IPC Error: failed getting running instances (%s)", errmsg)
+ end
+
+ local instances_count = #instances
+
+ if instances_count > 0 then
+ table.sort(instances, function(ia, ib)
+ return ia.position < ib.position
+ end)
+ end
+
+ if not self.primary and self.position then
+ if instances_count == 0 or instances[1].position > self.position then
+ self.primary = true
+ end
+ end
+
+ return instances
+end
+
+---@class plugins.ipc.vardecl
+---@field name string
+---@field type string
+---@field optional boolean
+
+---Generate a string representation of a function
+---@param name string
+---@param params? plugins.ipc.vardecl[]
+---@param returns? plugins.ipc.vardecl[]
+---@return string function_definition
+local function generate_definition(name, params, returns)
+ local declaration = name .. "("
+
+ if params and #params > 0 then
+ local params_string = ""
+ for _, param in ipairs(params) do
+ params_string = params_string .. param.name
+ if param.optional then
+ params_string = params_string .. "?: "
+ else
+ params_string = params_string .. ": "
+ end
+ params_string = params_string .. param.type .. ", "
+ end
+ local params_stripped = params_string:gsub(", $", "")
+ declaration = declaration .. params_stripped
+ end
+
+ declaration = declaration .. ")"
+
+ if returns and #returns > 0 then
+ declaration = declaration .. " -> "
+ local returns_string = ""
+ for _, ret in ipairs(returns) do
+ if ret.name then
+ returns_string = returns_string .. ret.name .. ": "
+ end
+ returns_string = returns_string .. ret.type
+ if ret.optional then
+ returns_string = returns_string .. "?, "
+ else
+ returns_string = returns_string .. ", "
+ end
+ end
+ local returns_stripped = returns_string:gsub(", $", "")
+ declaration = declaration .. returns_stripped
+ end
+
+ return declaration
+end
+
+---Retrieve the id of the primary instance if found.
+---@return string | nil
+function IPC:get_primary_instance()
+ local instances = self:get_instances()
+ for _, instance in ipairs(instances) do
+ if instance.primary then
+ return instance.id
+ end
+ end
+ return nil
+end
+
+---Get a queued message.
+---@param message_id string
+---@return plugins.ipc.message | nil
+function IPC:get_message(message_id)
+ for _, message in ipairs(self.messages) do
+ if message.id == message_id then
+ return message
+ end
+ end
+ return nil
+end
+
+---Remove a message from the queue.
+---@param message_id string
+function IPC:remove_message(message_id)
+ for m, message in ipairs(self.messages) do
+ if message.id == message_id then
+ table.remove(self.messages, m)
+ break
+ end
+ end
+end
+
+---Get the reply sent to a specific message.
+---@param message_id string
+---@return plugins.ipc.reply | nil
+function IPC:get_reply(message_id)
+ for _, reply in ipairs(self.replies) do
+ if reply.id == message_id then
+ return reply
+ end
+ end
+ return nil
+end
+
+---Verify all the messages sent by running instances, read those directed
+---to the currently running instance and reply to them.
+function IPC:read_messages()
+ local instances = self:get_instances()
+
+ local awaiting_replies = {}
+
+ for _, instance in ipairs(instances) do
+ for _, message in ipairs(instance.messages) do
+ for _, destination in ipairs(message.destinations) do
+ if destination == self.id then
+ local reply = self:get_reply(message.id)
+
+ if not reply then
+ if message.on_read then
+ local on_read, errmsg = load(message.on_read)
+ if on_read then
+ local executed = core.try(function() on_read(message) end)
+ if not executed then
+ core.error(
+ "IPC Error: could not run message on_read\n"
+ .. "Message: %s\n",
+ common.serialize(message, {pretty = true})
+ )
+ end
+ else
+ core.error(
+ "IPC Error: could not run message on_read (%s)\n"
+ .. "Message: %s\n",
+ errmsg,
+ common.serialize(message, {pretty = true})
+ )
+ end
+ end
+
+ ---@type plugins.ipc.reply
+ reply = {}
+ reply.id = message.id
+ reply.sender = message.sender
+ reply.replier = self.id
+ reply.data = {}
+ reply.on_read = nil
+
+ local type_name = message.type .. "." .. message.name
+
+ -- Allow listeners to react to message and modify reply
+ if self.listeners[type_name] and #self.listeners[type_name] > 0 then
+ for _, on_message in ipairs(self.listeners[type_name]) do
+ on_message(message, reply)
+ end
+ end
+
+ if reply.on_read then
+ reply.on_read = string.dump(reply.on_read)
+ end
+
+ reply.timestamp = os.time()
+ end
+
+ table.insert(awaiting_replies, reply)
+ break
+ end
+ end
+ end
+ end
+
+ self.replies = awaiting_replies
+end
+
+---Reads replies directed to messages sent by the currently running instance
+---and if any returns them.
+---@return plugins.ipc.reply[] | nil
+function IPC:read_replies()
+ if #self.messages == 0 then
+ return
+ end
+
+ local instances = self:get_instances()
+
+ local replies = {}
+
+ local messages_removed = 0;
+ for m=1, #self.messages do
+ local message = self.messages[m-messages_removed]
+ local message_removed = false
+
+ local destinations_removed = 0
+ for d=1, #message.destinations do
+ local destination = message.destinations[d-destinations_removed]
+
+ local found = false
+ for _, instance in ipairs(instances) do
+ if instance.id == destination then
+ found = true
+ for _, reply in ipairs(instance.replies) do
+ if reply.id == message.id then
+ local reply_registered = false
+ for _, message_reply in ipairs(message.replies) do
+ if message_reply.replier == instance.id then
+ reply_registered = true
+ break
+ end
+ end
+ if not reply_registered then
+ if message.on_reply then
+ message.on_reply(reply)
+ end
+
+ if reply.on_read then
+ local on_read, errmsg = load(reply.on_read)
+ if on_read then
+ local executed = core.try(function() on_read(reply) end)
+ if not executed then
+ core.error(
+ "IPC Error: could not run reply on_read\n"
+ .. "Message: %s\n"
+ .. "Reply: %s",
+ common.serialize(message, {pretty = true}),
+ common.serialize(reply, {pretty = true})
+ )
+ end
+ else
+ core.error(
+ "IPC Error: could not run reply on_read (%s)\n"
+ .. "Message: %s\n"
+ .. "Reply: %s",
+ errmsg,
+ common.serialize(message, {pretty = true}),
+ common.serialize(reply, {pretty = true})
+ )
+ end
+ end
+
+ table.insert(replies, reply)
+ table.insert(message.replies, reply)
+ end
+ end
+ end
+ break
+ end
+ end
+ if not found then
+ table.remove(message.destinations, d-destinations_removed)
+ destinations_removed = destinations_removed + 1
+ if #message.destinations == 0 then
+ table.remove(self.messages, m-messages_removed)
+ messages_removed = messages_removed + 1
+ message_removed = true
+ end
+ end
+ end
+ if
+ not message_removed
+ and
+ (
+ #message.replies == #message.destinations
+ or
+ message.timestamp + MESSAGE_EXPIRATION < os.time()
+ )
+ then
+ table.remove(self.messages, m-messages_removed)
+ messages_removed = messages_removed + 1
+ end
+ end
+
+ return replies
+end
+
+---Blocks execution of current instance to wait for all replies by the
+---specified message and when finished returns them.
+---@param message_id string
+---@return plugins.ipc.reply[] | nil
+function IPC:wait_for_replies(message_id)
+ local message_data = self:get_message(message_id)
+
+ update_file(self)
+
+ if message_data then
+ self:read_replies()
+ while true do
+ if
+ message_data.replies
+ and
+ #message_data.replies == #message_data.destinations
+ then
+ return message_data.replies
+ elseif not self:get_message(message_id) then
+ return message_data.replies
+ end
+ self:read_replies()
+ end
+ end
+ return nil
+end
+
+---Blocks execution of current instance to wait for all messages to
+---be replied to.
+function IPC:wait_for_messages()
+ update_file(self)
+ while #self.messages > 0 do
+ self:read_replies()
+ system.sleep(0.1)
+ end
+end
+
+---@class plugins.ipc.sendmessageoptions
+---@field data table<string,any> @Optional data given to the receiver.
+---@field on_reply plugins.ipc.onreply @Callback that allows monitoring all the replies received for this message.
+---@field on_read plugins.ipc.onmessage @Function executed by the message receiver.
+---@field destinations string | table<integer,string> | nil @Id of the running instances to receive the message, if not set all running instances will receive the message.
+
+---Queue a new message to be sent to other lite-xl instances.
+---@param name string
+---@param options? plugins.ipc.sendmessageoptions
+---@param message_type? plugins.ipc.messagetype
+---@return string | nil message_id
+function IPC:send_message(name, options, message_type)
+ options = options or {}
+
+ local found_destinations = {}
+ local instances = self:get_instances()
+ local destinations = options.destinations
+
+ if type(destinations) == "string" then
+ destinations = { destinations }
+ end
+
+ if not destinations then
+ for _, instance in ipairs(instances) do
+ table.insert(found_destinations, instance.id)
+ end
+ else
+ for _, destination in ipairs(destinations) do
+ for _, instance in ipairs(instances) do
+ if instance.id == destination then
+ table.insert(found_destinations, destination)
+ end
+ end
+ end
+ end
+
+ if #found_destinations <= 0 then
+ return nil
+ end
+
+ ---@type plugins.ipc.message
+ local message = {}
+ message.id = self.id .. "." .. tostring(system.get_time())
+ message.name = name
+ message.type = message_type or "message"
+ message.sender = self.id
+ message.data = options.data or {}
+ message.destinations = found_destinations
+ message.timestamp = os.time()
+ message.on_reply = options.on_reply or nil
+ message.on_read = options.on_read and string.dump(options.on_read) or nil
+ message.replies = {}
+
+ table.insert(self.messages, message)
+
+ update_file(self)
+
+ return message.id
+end
+
+---Add a listener for a given type of message.
+---@param name string
+---@param callback plugins.ipc.onmessage
+---@param message_type? plugins.ipc.messagetype
+---@return integer listener_position
+function IPC:listen_message(name, callback, message_type)
+ message_type = message_type or "message"
+
+ local type_name = message_type .. "." .. name
+ if not self.listeners[type_name] then
+ self.listeners[type_name] = {}
+ end
+
+ table.insert(self.listeners[type_name], callback)
+
+ return #self.listeners[type_name]
+end
+
+---Listen for a given signal.
+---@param name string
+---@param callback plugins.ipc.function
+---@return integer listener_position
+function IPC:listen_signal(name, callback)
+ local signal_cb = function(message)
+ callback(table.unpack(message.data))
+ end
+ return self:listen_message(name, signal_cb, "signal")
+end
+
+---Add a new signal that can be sent to other instances.
+---@param name string A unique name for the signal.
+---@param params? plugins.ipc.vardecl[] Parameters that are going to be passed into callback.
+function IPC:register_signal(name, params)
+ if self.signals[name] then
+ core.log_quiet("IPC: Overriding signal '%s'", name)
+ table.remove(self.signal_definitions, self.signals[name])
+ end
+
+ self.signals[name] = table.insert(
+ self.signal_definitions,
+ generate_definition(name, params)
+ )
+
+ table.sort(self.signal_definitions)
+end
+
+---Add a new method that can be invoked from other instances.
+---@param name string A unique name for the method.
+---@param method fun(...) Function invoked when the method is called.
+---@param params? plugins.ipc.vardecl[] Parameters that are going to be passed into method.
+---@param returns? plugins.ipc.vardecl[] Return values of the method.
+function IPC:register_method(name, method, params, returns)
+ if self.methods[name] then
+ core.log_quiet("IPC: Overriding method '%s'", name)
+ table.remove(self.method_definitions, self.methods[name])
+ end
+
+ self.methods[name] = table.insert(
+ self.method_definitions,
+ generate_definition(name, params, returns)
+ )
+
+ table.sort(self.method_definitions)
+
+ self:listen_message(name, function(message, reply)
+ local ret = table.pack(method(table.unpack(message.data)))
+ reply.data = ret
+ end, "method")
+end
+
+---Broadcast a signal to running instances.
+---@param destinations string | table<integer, string> | nil
+---@param name string
+---@vararg any signal_parameters
+function IPC:signal(destinations, name, ...)
+ self:send_message(name, {
+ destinations = destinations,
+ data = table.pack(self.id, ...)
+ }, "signal")
+end
+
+---Call a method on another instance and wait for reply.
+---@param destinations string | table<integer, string> | nil
+---@param name string
+---@return any | table<string,table> return_of_called_method
+function IPC:call(destinations, name, ...)
+ local message_id = self:send_message(name, {
+ destinations = destinations,
+ data = table.pack(...)
+ }, "method")
+
+ local ret = nil
+
+ if message_id then
+ local replies = self:wait_for_replies(message_id)
+ if replies and #replies > 1 then
+ ret = {}
+ for _, reply in ipairs(replies) do
+ ret[reply.replier] = reply.data
+ end
+ elseif replies and #replies > 0 then
+ return table.unpack(replies[1].data)
+ end
+ else
+ core.error("IPC Error: could not make call to '%s'", name)
+ end
+
+ return ret
+end
+
+---Call a method on another instance asynchronously waiting for the replies.
+---@param destinations string | table<integer, string> | nil
+---@param name string
+---@param callback fun(id: string, ret: table) | nil Called with the returned values
+---@return string | nil message_id
+function IPC:call_async(destinations, name, callback, ...)
+ return self:send_message(name, {
+ destinations = destinations,
+ data = table.pack(...),
+ on_reply = callback and function(reply)
+ callback(reply.replier, reply.data)
+ end or nil
+ }, "method")
+end
+
+---Main ipc session for current instance.
+---@type core.ipc
+local ipc = IPC()
+
+---Get the IPC session for the running lite-xl instance.
+---@return core.ipc
+function IPC.current()
+ return ipc
+end
+
+--------------------------------------------------------------------------------
+-- Override system.wait_event to allow ipc monitoring on the background.
+--------------------------------------------------------------------------------
+local system_wait_event = system.wait_event
+
+local run_threads = coroutine.wrap(function()
+ while true do
+ for k, thread in pairs(threads) do
+ if thread.wake < system.get_time() then
+ local _, wait = assert(coroutine.resume(thread.cr))
+ if coroutine.status(thread.cr) == "dead" then
+ table.remove(threads, k)
+ elseif wait then
+ thread.wake = system.get_time() + wait
+ end
+ end
+ coroutine.yield()
+ end
+ end
+end)
+
+system.wait_event = function(timeout)
+ run_threads()
+
+ if not timeout then
+ if not system.window_has_focus() then
+ local t = system.get_time()
+ local h = 0.5 / 2
+ local dt = math.ceil(t / h) * h - t
+
+ system_wait_event(dt + 1 / config.fps)
+ else
+ system_wait_event()
+ end
+ else
+ system_wait_event(timeout)
+ end
+end
+
+--------------------------------------------------------------------------------
+-- Override system.show_fatal_error to be able and destroy session file on crash.
+--------------------------------------------------------------------------------
+local system_show_fatal_error = system.show_fatal_error
+
+system.show_fatal_error = function(title, message)
+ if title == "Lite XL internal error" then
+ ipc:stop()
+ end
+ system_show_fatal_error(title, message)
+end
+
+--------------------------------------------------------------------------------
+-- Override core.run to destroy ipc session file on exit.
+--------------------------------------------------------------------------------
+local core_run = core.run
+
+core.run = function()
+ core_run()
+ ipc:stop()
+end
+
+--------------------------------------------------------------------------------
+-- Override system.get_time temporarily as first function called on core.run
+-- to allow settings gui to properly load ipc config options.
+--------------------------------------------------------------------------------
+local system_get_time = system.get_time
+
+system.get_time = function()
+ if settings_found and not settings.ui then
+ return system_get_time()
+ end
+
+ if config.plugins.ipc.single_instance then
+ system.get_time = system_get_time
+
+ local primary_instance = ipc:get_primary_instance()
+ if primary_instance and ARGS[2] then
+ local open_directory = false
+ for i=2, #ARGS do
+ local path = system.absolute_path(ARGS[i])
+
+ if path then
+ local path_info = system.get_file_info(path)
+ if path_info then
+ if path_info.type == "file" then
+ ipc:call_async(primary_instance, "core.open_file", nil, path)
+ else
+ if config.plugins.ipc.dirs_instance == "add" then
+ ipc:call_async(primary_instance, "core.open_directory", nil, path)
+ elseif config.plugins.ipc.dirs_instance == "change" then
+ ipc:call_async(primary_instance, "core.change_directory", nil, path)
+ else
+ if #ARGS > 2 then
+ system.exec(string.format("%q %q", EXEFILE, path))
+ else
+ open_directory = true
+ end
+ end
+ end
+ end
+ end
+ end
+ ipc:wait_for_messages()
+ if not open_directory then
+ os.exit()
+ end
+ end
+ else
+ system.get_time = system_get_time
+ end
+
+ return system_get_time()
+end
+
+--------------------------------------------------------------------------------
+-- Register methods for opening files and directories.
+--------------------------------------------------------------------------------
+ipc:register_method("core.open_file", function(file)
+ if system.get_file_info(file) then
+ if system.raise_window then system.raise_window() end
+ core.root_view:open_doc(core.open_doc(file))
+ end
+end, {{name = "file", type = "string"}})
+
+ipc:register_method("core.open_directory", function(directory)
+ if system.get_file_info(directory) then
+ if system.raise_window then system.raise_window() end
+ core.add_project_directory(directory)
+ end
+end, {{name = "directory", type = "string"}})
+
+ipc:register_method("core.change_directory", function(directory)
+ if system.get_file_info(directory) then
+ if system.raise_window then system.raise_window() end
+ if directory == core.project_dir then return end
+ core.confirm_close_docs(core.docs, function(dirpath)
+ core.open_folder_project(dirpath)
+ end, directory)
+ end
+end, {{name = "directory", type = "string"}})
+
+--------------------------------------------------------------------------------
+-- Register file dragging signals from instance to instance
+--------------------------------------------------------------------------------
+ipc:register_signal("core.tab_drag_start", {{name = "file", type = "string"}})
+ipc:register_signal("core.tab_drag_stop")
+ipc:register_signal("core.tab_drag_received", {{name = "file", type = "string"}})
+
+local rootview_tab_dragging = false
+local rootview_dragged_node = nil
+local rootview_waiting_drop_file = ""
+local rootview_waiting_drop_instance = ""
+
+local rootview_on_mouse_moved = RootView.on_mouse_moved
+function RootView:on_mouse_moved(x, y, dx, dy)
+ rootview_on_mouse_moved(self, x, y, dx, dy)
+ if
+ self.dragged_node and self.dragged_node.dragging
+ and
+ not rootview_tab_dragging
+ then
+ ---@type core.doc
+ local doc = core.active_view.doc
+ if doc and doc.abs_filename then
+ rootview_tab_dragging = true
+ ipc:signal(nil, "core.tab_drag_start", doc.abs_filename)
+ rootview_dragged_node = self.dragged_node
+ end
+ elseif rootview_dragged_node then
+ local w, h, wx, wy = system.get_window_size()
+ if x < 0 or x > w or y < 0 or y > h then
+ self.dragged_node = nil
+ self:set_show_overlay(self.drag_overlay, false)
+ elseif not self.dragged_node then
+ self.dragged_node = rootview_dragged_node
+ self:set_show_overlay(self.drag_overlay, true)
+ end
+ core.request_cursor("hand")
+ elseif rootview_waiting_drop_file ~= "" then
+ ipc:signal(
+ rootview_waiting_drop_instance,
+ "core.tab_drag_received",
+ rootview_waiting_drop_file
+ )
+ core.root_view:open_doc(core.open_doc(rootview_waiting_drop_file))
+ rootview_waiting_drop_file = ""
+ end
+end
+
+local rootview_on_mouse_released = RootView.on_mouse_released
+function RootView:on_mouse_released(button, x, y, ...)
+ rootview_on_mouse_released(self, button, x, y, ...)
+ if rootview_tab_dragging then
+ rootview_tab_dragging = false
+ rootview_dragged_node = nil
+ ipc:signal(nil, "core.tab_drag_stop")
+ end
+end
+
+ipc:listen_signal("core.tab_drag_start", function(instance, file)
+ rootview_waiting_drop_instance = instance
+ rootview_waiting_drop_file = file
+end)
+
+ipc:listen_signal("core.tab_drag_stop", function()
+ rootview_waiting_drop_instance = ""
+ rootview_waiting_drop_file = ""
+end)
+
+ipc:listen_signal("core.tab_drag_received", function()
+ command.perform("root:close")
+end)
+
+
+return IPC
diff --git a/plugins/language_R.lua b/plugins/language_R.lua
index ad3b483..afe3d1e 100644
--- a/plugins/language_R.lua
+++ b/plugins/language_R.lua
@@ -1,40 +1,39 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add{
- name = "R",
- files = {"%.r$", "%.rds$", "%.rda$", "%.rdata$", "%.R$"},
- comment = "#",
- patterns = {
- {pattern = {"#", "\n"}, type = "comment"},
- {pattern = {'"', '"'}, type = "string"},
- {pattern = {"'", "'"}, type = "string"},
- {pattern = "[%a_][%w_]*%f[(]", type = "function"},
- {pattern = "[%a_][%w_]*", type = "symbol"},
- {pattern = "[%+%-=/%*%^%%<>!|&]", type = "operator"},
- {pattern = "0x[%da-fA-F]+", type = "number"},
- {pattern = "-?%d+[%d%.eE]*", type = "number"},
- {pattern = "-?%.?%d+", type = "number"},
-
- },
- symbols = {
- ["TRUE"] = "literal",
- ["FALSE"] = "literal",
- ["NA"] = "literal",
- ["NULL"] = "literal",
- ["Inf"] = "literal",
- ["if"] = "keyword",
- ["else"] = "keyword",
- ["while"] = "keyword",
- ["function"] = "keyword",
- ["break"] = "keyword",
- ["next"] = "keyword",
- ["repeat"] = "keyword",
- ["in"] = "keyword",
- ["for"] = "keyword",
- ["NA_integer"] = "keyword",
- ["NA_complex"] = "keyword",
- ["NA_character"] = "keyword",
- ["NA_real"] = "keyword"
- }
+ name = "R",
+ files = {"%.r$", "%.rds$", "%.rda$", "%.rdata$", "%.R$"},
+ comment = "#",
+ patterns = {
+ {pattern = {"#", "\n"}, type = "comment"},
+ {pattern = {'"', '"'}, type = "string"},
+ {pattern = {"'", "'"}, type = "string"},
+ {pattern = "[%a_][%w_]*%f[(]", type = "function"},
+ {pattern = "[%a_][%w_]*", type = "symbol"},
+ {pattern = "[%+%-=/%*%^%%<>!|&]", type = "operator"},
+ {pattern = "0x[%da-fA-F]+", type = "number"},
+ {pattern = "-?%d+[%d%.eE]*", type = "number"},
+ {pattern = "-?%.?%d+", type = "number"},
+ },
+ symbols = {
+ ["TRUE"] = "literal",
+ ["FALSE"] = "literal",
+ ["NA"] = "literal",
+ ["NULL"] = "literal",
+ ["Inf"] = "literal",
+ ["if"] = "keyword",
+ ["else"] = "keyword",
+ ["while"] = "keyword",
+ ["function"] = "keyword",
+ ["break"] = "keyword",
+ ["next"] = "keyword",
+ ["repeat"] = "keyword",
+ ["in"] = "keyword",
+ ["for"] = "keyword",
+ ["NA_integer"] = "keyword",
+ ["NA_complex"] = "keyword",
+ ["NA_character"] = "keyword",
+ ["NA_real"] = "keyword"
+ }
}
diff --git a/plugins/language_angelscript.lua b/plugins/language_angelscript.lua
index e62c1da..4e003ea 100644
--- a/plugins/language_angelscript.lua
+++ b/plugins/language_angelscript.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_assembly_x86.lua b/plugins/language_assembly_x86.lua
index baae3c4..e6d218b 100644
--- a/plugins/language_assembly_x86.lua
+++ b/plugins/language_assembly_x86.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Support for the intel syntax x86-64 assembly
-- Simply add it to lite-xl's plugins folder (located somewhere at home/.config/lite-xl/plugins)
-- https://github.com/DMClVG
diff --git a/plugins/language_batch.lua b/plugins/language_batch.lua
index 13753cf..32510c8 100644
--- a/plugins/language_batch.lua
+++ b/plugins/language_batch.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
-- batch syntax for lite <liqube>
diff --git a/plugins/language_bib.lua b/plugins/language_bib.lua
index cfde8da..1850ec6 100644
--- a/plugins/language_bib.lua
+++ b/plugins/language_bib.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_caddyfile.lua b/plugins/language_caddyfile.lua
new file mode 100644
index 0000000..f918c16
--- /dev/null
+++ b/plugins/language_caddyfile.lua
@@ -0,0 +1,104 @@
+-- mod-version:3
+local syntax = require "core.syntax"
+
+syntax.add {
+ files = { "Caddyfile" },
+ comment = "#",
+ patterns = {
+ { pattern = { "#", "\n"}, type = "comment" },
+ { pattern = { '"', '"', '\\' }, type = "string" },
+ -- Matcher definition
+ { pattern = "@[%w_]+", type = "operator" },
+ -- Snippet
+ { pattern = "%(%g+%)", type = "operator" },
+ -- Properties
+ { pattern = "^[%a_][%w_]*()%s+%f[%g]",
+ type = { "function", "normal" }
+ },
+ { pattern = "^[%a_][%w_]*()%s+$",
+ type = { "function", "normal" }
+ },
+ { pattern = "^%s*()[%a_][%w_]*()%s+$",
+ type = { "normal", "function", "normal" }
+ },
+ { pattern = "^%s*()[%a_][%w_]*()%s+%f[%g]",
+ type = { "normal", "function", "normal" }
+ },
+ -- Environment variables
+ { pattern = "{()%$[%w_]+():()[%w_]+()}",
+ type = { "operator", "keyword2", "operator", "keyword2", "operator" }
+ },
+ { pattern = "{()%$[%w_]+()}",
+ type = { "operator", "keyword2", "operator" }
+ },
+ -- Place holder
+ { pattern = "{%g-}", type = "keyword2" },
+ -- Operators
+ { pattern = "[+%-,:]", type = "operator" },
+ -- IP Address
+ { pattern = "%d+%.%d+%.%d+%.%d+", type = "literal" },
+ -- Path /path/subpath
+ { pattern = "/[%w%./]+", type = "literal" },
+ -- Wildcard domain *.levels
+ { pattern = "%*()[%w.]+",
+ type = { "operator", "literal" }
+ },
+ -- Match Operator
+ { pattern = "%*+", type = "operator" },
+ -- Domain leve1.level2
+ { pattern = "https?://[%w%./%*]+", type = "literal" },
+ -- Domain leve1.level2
+ { pattern = "%w+%.[%w%.]+", type = "literal" },
+ -- Number
+ { pattern = "%d+[mhskbi]*", type = "number" },
+ -- Everything else for symbols to work
+ { pattern = "[%a_][%w_]*", type = "symbol" },
+ },
+ symbols = {
+ ["true"] = "literal",
+ ["false"] = "literal",
+ ["localhost"] = "literal",
+
+ -- built-in directives
+ ["abort"] = "keyword",
+ ["acme_server"] = "keyword",
+ ["basicauth"] = "keyword",
+ ["bind"] = "keyword",
+ ["encode"] = "keyword",
+ ["error"] = "keyword",
+ ["file_server"] = "keyword",
+ ["forward_auth"] = "keyword",
+ ["handle"] = "keyword",
+ ["handle_errors"] = "keyword",
+ ["handle_path"] = "keyword",
+ ["header"] = "keyword",
+ ["import"] = "keyword",
+ ["log"] = "keyword",
+ ["method"] = "keyword",
+ ["map"] = "keyword",
+ ["metrics"] = "keyword",
+ ["php_fastcgi"] = "keyword",
+ ["push"] = "keyword",
+ ["redir"] = "keyword",
+ ["request_body"] = "keyword",
+ ["request_header"] = "keyword",
+ ["respond"] = "keyword",
+ ["reverse_proxy"] = "keyword",
+ ["rewrite"] = "keyword",
+ ["root"] = "keyword",
+ ["route"] = "keyword",
+ ["templates"] = "keyword",
+ ["tls"] = "keyword",
+ ["tracing"] = "keyword",
+ ["try_files"] = "keyword",
+ ["uri"] = "keyword",
+ ["vars"] = "keyword",
+
+ -- Module directives
+ ["cgi"] = "keyword",
+ ["ssh"] = "keyword",
+ ["exec"] = "keyword",
+ ["supervisor"] = "keyword",
+ ["layer4"] = "keyword",
+ },
+}
diff --git a/plugins/language_cmake.lua b/plugins/language_cmake.lua
index 8103632..19f1aa5 100644
--- a/plugins/language_cmake.lua
+++ b/plugins/language_cmake.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_cpp.lua b/plugins/language_cpp.lua
deleted file mode 100644
index b1afa0f..0000000
--- a/plugins/language_cpp.lua
+++ /dev/null
@@ -1,203 +0,0 @@
--- mod-version:2 -- lite-xl 2.0
-local syntax = require "core.syntax"
-
-syntax.add {
- name = "C++",
- files = {
- "%.h$", "%.inl$", "%.cpp$", "%.cc$", "%.C$", "%.cxx$",
- "%.c++$", "%.hh$", "%.H$", "%.hxx$", "%.hpp$", "%.h++$"
- },
- comment = "//",
- block_comment = { "/*", "*/" },
- patterns = {
- { pattern = "//.-\n", type = "comment" },
- { pattern = { "/%*", "%*/" }, type = "comment" },
- { pattern = { '"', '"', '\\' }, type = "string" },
- { pattern = { "'", "'", '\\' }, type = "string" },
- { pattern = "0x%x+", type = "number" },
- { pattern = "%d+[%d%.'eE]*f?", type = "number" },
- { pattern = "%.?%d+f?", type = "number" },
- { pattern = "[%+%-=/%*%^%%<>!~|:&]", type = "operator" },
- { pattern = "##", type = "operator" },
- { pattern = "struct%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
- { pattern = "class%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
- { pattern = "union%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
- { pattern = "namespace%s()[%a_][%w_]*", type = {"keyword", "keyword2"} },
- -- static declarations
- { pattern = "static()%s+()inline",
- type = { "keyword", "normal", "keyword" }
- },
- { pattern = "static()%s+()const",
- type = { "keyword", "normal", "keyword" }
- },
- { pattern = "static()%s+()[%a_][%w_]*",
- type = { "keyword", "normal", "literal" }
- },
- -- match method type declarations
- { pattern = "[%a_][%w_]*()%s*()%**()%s*()[%a_][%w_]*()%s*()::",
- type = {
- "literal", "normal", "operator", "normal",
- "literal", "normal", "operator"
- }
- },
- -- match function type declarations
- { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*%f[%(]",
- type = { "literal", "operator", "normal", "function" }
- },
- { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*%f[%(]",
- type = { "literal", "normal", "operator", "function" }
- },
- { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*%f[%(]",
- type = { "literal", "normal", "function" }
- },
- -- match variable type declarations
- { pattern = "[%a_][%w_]*()%*+()%s+()[%a_][%w_]*",
- type = { "literal", "operator", "normal", "normal" }
- },
- { pattern = "[%a_][%w_]*()%s+()%*+()[%a_][%w_]*",
- type = { "literal", "normal", "operator", "normal" }
- },
- { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()[;,%[%)]",
- type = { "literal", "normal", "normal", "normal", "normal" }
- },
- { pattern = "[%a_][%w_]*()%s+()[%a_][%w_]*()%s*()=",
- type = { "literal", "normal", "normal", "normal", "operator" }
- },
- { pattern = "[%a_][%w_]*()&()%s+()[%a_][%w_]*",
- type = { "literal", "operator", "normal", "normal" }
- },
- { pattern = "[%a_][%w_]*()%s+()&()[%a_][%w_]*",
- type = { "literal", "normal", "operator", "normal" }
- },
- -- Match scope operator element access
- { pattern = "[%a_][%w_]*()%s*()::",
- type = { "literal", "normal", "operator" }
- },
- -- Uppercase constants of at least 2 chars in len
- { pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
- type = "number"
- },
- -- Magic constants
- { pattern = "__[%u%l]+__", type = "number" },
- -- all other functions
- { pattern = "[%a_][%w_]*%f[(]", type = "function" },
- -- Macros
- { pattern = "^%s*#%s*define%s+()[%a_][%a%d_]*",
- type = { "keyword", "symbol" }
- },
- { pattern = "#%s*include%s+()<.->",
- type = { "keyword", "string" }
- },
- { pattern = "%f[#]#%s*[%a_][%w_]*", type = "keyword" },
- -- Everything else to make the tokenizer work properly
- { pattern = "[%a_][%w_]*", type = "symbol" },
- },
- symbols = {
- ["alignof"] = "keyword",
- ["alignas"] = "keyword",
- ["and"] = "keyword",
- ["and_eq"] = "keyword",
- ["not"] = "keyword",
- ["not_eq"] = "keyword",
- ["or"] = "keyword",
- ["or_eq"] = "keyword",
- ["xor"] = "keyword",
- ["xor_eq"] = "keyword",
- ["private"] = "keyword",
- ["protected"] = "keyword",
- ["public"] = "keyword",
- ["register"] = "keyword",
- ["nullptr"] = "keyword",
- ["operator"] = "keyword",
- ["asm"] = "keyword",
- ["bitand"] = "keyword",
- ["bitor"] = "keyword",
- ["catch"] = "keyword",
- ["throw"] = "keyword",
- ["try"] = "keyword",
- ["class"] = "keyword",
- ["compl"] = "keyword",
- ["explicit"] = "keyword",
- ["export"] = "keyword",
- ["concept"] = "keyword",
- ["consteval"] = "keyword",
- ["constexpr"] = "keyword",
- ["constinit"] = "keyword",
- ["const_cast"] = "keyword",
- ["dynamic_cast"] = "keyword",
- ["reinterpret_cast"] = "keyword",
- ["static_cast"] = "keyword",
- ["static_assert"] = "keyword",
- ["template"] = "keyword",
- ["this"] = "keyword",
- ["thread_local"] = "keyword",
- ["requires"] = "keyword",
- ["co_wait"] = "keyword",
- ["co_return"] = "keyword",
- ["co_yield"] = "keyword",
- ["decltype"] = "keyword",
- ["delete"] = "keyword",
- ["friend"] = "keyword",
- ["typeid"] = "keyword",
- ["typename"] = "keyword",
- ["mutable"] = "keyword",
- ["override"] = "keyword",
- ["virtual"] = "keyword",
- ["using"] = "keyword",
- ["namespace"] = "keyword",
- ["new"] = "keyword",
- ["noexcept"] = "keyword",
- ["if"] = "keyword",
- ["then"] = "keyword",
- ["else"] = "keyword",
- ["elseif"] = "keyword",
- ["do"] = "keyword",
- ["while"] = "keyword",
- ["for"] = "keyword",
- ["break"] = "keyword",
- ["continue"] = "keyword",
- ["return"] = "keyword",
- ["goto"] = "keyword",
- ["struct"] = "keyword",
- ["union"] = "keyword",
- ["typedef"] = "keyword",
- ["enum"] = "keyword",
- ["extern"] = "keyword",
- ["static"] = "keyword",
- ["volatile"] = "keyword",
- ["const"] = "keyword",
- ["inline"] = "keyword",
- ["switch"] = "keyword",
- ["case"] = "keyword",
- ["default"] = "keyword",
- ["auto"] = "keyword",
- ["void"] = "keyword2",
- ["int"] = "keyword2",
- ["short"] = "keyword2",
- ["long"] = "keyword2",
- ["float"] = "keyword2",
- ["double"] = "keyword2",
- ["char"] = "keyword2",
- ["unsigned"] = "keyword2",
- ["bool"] = "keyword2",
- ["true"] = "literal",
- ["false"] = "literal",
- ["NULL"] = "literal",
- ["wchar_t"] = "keyword2",
- ["char8_t"] = "keyword2",
- ["char16_t"] = "keyword2",
- ["char32_t"] = "keyword2",
- ["#include"] = "keyword",
- ["#if"] = "keyword",
- ["#ifdef"] = "keyword",
- ["#ifndef"] = "keyword",
- ["#elif"] = "keyword",
- ["#else"] = "keyword",
- ["#elseif"] = "keyword",
- ["#endif"] = "keyword",
- ["#define"] = "keyword",
- ["#warning"] = "keyword",
- ["#error"] = "keyword",
- ["#pragma"] = "keyword",
- },
-}
diff --git a/plugins/language_csharp.lua b/plugins/language_csharp.lua
index 5e1e81a..c137b63 100644
--- a/plugins/language_csharp.lua
+++ b/plugins/language_csharp.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_d.lua b/plugins/language_d.lua
index e59916e..6788f0b 100644
--- a/plugins/language_d.lua
+++ b/plugins/language_d.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_dart.lua b/plugins/language_dart.lua
index 97aa375..03274b2 100644
--- a/plugins/language_dart.lua
+++ b/plugins/language_dart.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_diff.lua b/plugins/language_diff.lua
index 4376b26..c4c5a90 100644
--- a/plugins/language_diff.lua
+++ b/plugins/language_diff.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
local style = require "core.style"
local common = require "core.common"
diff --git a/plugins/language_elixir.lua b/plugins/language_elixir.lua
index f414bd4..8f47770 100644
--- a/plugins/language_elixir.lua
+++ b/plugins/language_elixir.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_elm.lua b/plugins/language_elm.lua
index 65ddc1f..2d33813 100644
--- a/plugins/language_elm.lua
+++ b/plugins/language_elm.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_erb.lua b/plugins/language_erb.lua
index e63c7b0..d55b48c 100644
--- a/plugins/language_erb.lua
+++ b/plugins/language_erb.lua
@@ -1,4 +1,4 @@
--- mod-version:2
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_fe.lua b/plugins/language_fe.lua
index 18400ac..aee9b85 100644
--- a/plugins/language_fe.lua
+++ b/plugins/language_fe.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_fennel.lua b/plugins/language_fennel.lua
index d2fa7f0..dcfd245 100644
--- a/plugins/language_fennel.lua
+++ b/plugins/language_fennel.lua
@@ -1,102 +1,268 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Support for the Fennel programming language: https://fennel-lang.org
--- Covers all the keywords up to Fennel version 0.4.0
+-- Covers all the keywords up to Fennel version 1.2.0
-- Currently only covers highlighting, not indentation, delimiter
-- matching, or evaluation.
-local syntax = require "core.syntax"
+local syntax = require("core.syntax")
-syntax.add {
- name = "Fennel",
- files = "%.fnl$",
- comment = ";",
- patterns = {
- { pattern = ";.-\n", type = "comment" },
- { pattern = { '"', '"', '\\' }, type = "string" },
- { pattern = "0x[%da-fA-F]+", type = "number" },
- { pattern = "-?%d+[%d%.]*", type = "number" },
- { pattern = "-?%.?%d+", type = "number" },
- { pattern = "%f[^(][^()'%s\"]+", type = "function" },
- { pattern = "[^()'%s\"]+", type = "symbol" },
- },
+syntax.add({
+ comment = ";",
+ files = "%.fnl$",
+ name = "Fennel",
+ patterns = {
+ { pattern = ";.-\n", type = "comment" },
+ { pattern = { '"', '"', "\\" }, type = "string" },
+ { pattern = "0x[%da-fA-F]+", type = "number" },
+ { pattern = "-?%d+[%d%.]*", type = "number" },
+ { pattern = "-?%.?%d+", type = "number" },
+ { pattern = "%f[^(][^()'%s\"]+", type = "function" },
+ { pattern = "[^()'%s\"]+", type = "symbol" },
+ },
+ symbols = {
+ ["#"] = "keyword",
+ ["%"] = "keyword",
+ ["*"] = "keyword",
+ ["+"] = "keyword",
+ ["-"] = "keyword",
+ ["->"] = "keyword",
+ ["->>"] = "keyword",
+ ["-?>"] = "keyword",
+ ["-?>>"] = "keyword",
+ ["."] = "keyword",
+ [".."] = "keyword",
+ ["/"] = "keyword",
+ ["//"] = "keyword",
+ [":"] = "keyword",
+ ["<"] = "keyword",
+ ["<="] = "keyword",
+ ["="] = "keyword",
+ [">"] = "keyword",
+ [">="] = "keyword",
+ ["?."] = "keyword",
+ ["^"] = "keyword",
+ _G = "keyword",
+ accumulate = "keyword2",
+ ["and"] = "keyword2",
+ arg = "keyword2",
+ assert = "keyword2",
+ band = "keyword2",
+ bit32 = "keyword2",
+ ["bit32.arshift"] = "keyword2",
+ ["bit32.band"] = "keyword2",
+ ["bit32.bnot"] = "keyword2",
+ ["bit32.bor"] = "keyword2",
+ ["bit32.btest"] = "keyword2",
+ ["bit32.bxor"] = "keyword2",
+ ["bit32.extract"] = "keyword2",
+ ["bit32.lrotate"] = "keyword2",
+ ["bit32.lshift"] = "keyword2",
+ ["bit32.replace"] = "keyword2",
+ ["bit32.rrotate"] = "keyword2",
+ ["bit32.rshift"] = "keyword2",
+ bnot = "keyword2",
+ bor = "keyword2",
+ bxor = "keyword2",
+ collect = "keyword2",
+ collectgarbage = "keyword2",
+ comment = "keyword2",
+ coroutine = "keyword2",
+ ["coroutine.create"] = "keyword2",
+ ["coroutine.resume"] = "keyword2",
+ ["coroutine.running"] = "keyword2",
+ ["coroutine.status"] = "keyword2",
+ ["coroutine.wrap"] = "keyword2",
+ ["coroutine.yield"] = "keyword2",
+ debug = "keyword2",
+ ["debug.debug"] = "keyword2",
+ ["debug.gethook"] = "keyword2",
+ ["debug.getinfo"] = "keyword2",
+ ["debug.getlocal"] = "keyword2",
+ ["debug.getmetatable"] = "keyword2",
+ ["debug.getregistry"] = "keyword2",
+ ["debug.getupvalue"] = "keyword2",
+ ["debug.getuservalue"] = "keyword2",
+ ["debug.sethook"] = "keyword2",
+ ["debug.setlocal"] = "keyword2",
+ ["debug.setmetatable"] = "keyword2",
+ ["debug.setupvalue"] = "keyword2",
+ ["debug.setuservalue"] = "keyword2",
+ ["debug.traceback"] = "keyword2",
+ ["debug.upvalueid"] = "keyword2",
+ ["debug.upvaluejoin"] = "keyword2",
+ ["do"] = "keyword2",
+ dofile = "keyword2",
+ doto = "keyword2",
+ each = "keyword2",
+ error = "keyword2",
+ ["eval-compiler"] = "keyword2",
+ ["false"] = "literal",
+ fcollect = "keyword2",
+ fn = "keyword2",
+ ["for"] = "keyword2",
+ getmetatable = "keyword2",
+ global = "keyword2",
+ hashfn = "keyword2",
+ icollect = "keyword2",
+ ["if"] = "keyword2",
+ ["import-macros"] = "keyword2",
+ include = "keyword2",
+ io = "keyword2",
+ ["io.close"] = "keyword2",
+ ["io.flush"] = "keyword2",
+ ["io.input"] = "keyword2",
+ ["io.lines"] = "keyword2",
+ ["io.open"] = "keyword2",
+ ["io.output"] = "keyword2",
+ ["io.popen"] = "keyword2",
+ ["io.read"] = "keyword2",
+ ["io.tmpfile"] = "keyword2",
+ ["io.type"] = "keyword2",
+ ["io.write"] = "keyword2",
+ ipairs = "keyword2",
+ lambda = "keyword2",
+ length = "keyword2",
+ let = "keyword2",
+ load = "keyword2",
+ loadfile = "keyword2",
+ loadstring = "keyword2",
+ ["local"] = "keyword2",
+ lshift = "keyword2",
+ lua = "keyword2",
+ macro = "keyword2",
+ macrodebug = "keyword2",
+ macros = "keyword2",
+ match = "keyword2",
+ ["match-try"] = "keyword2",
+ math = "keyword2",
+ ["math.abs"] = "keyword2",
+ ["math.acos"] = "keyword2",
+ ["math.asin"] = "keyword2",
+ ["math.atan"] = "keyword2",
+ ["math.atan2"] = "keyword2",
+ ["math.ceil"] = "keyword2",
+ ["math.cos"] = "keyword2",
+ ["math.cosh"] = "keyword2",
+ ["math.deg"] = "keyword2",
+ ["math.exp"] = "keyword2",
+ ["math.floor"] = "keyword2",
+ ["math.fmod"] = "keyword2",
+ ["math.frexp"] = "keyword2",
+ ["math.ldexp"] = "keyword2",
+ ["math.log"] = "keyword2",
+ ["math.log10"] = "keyword2",
+ ["math.max"] = "keyword2",
+ ["math.min"] = "keyword2",
+ ["math.modf"] = "keyword2",
+ ["math.pow"] = "keyword2",
+ ["math.rad"] = "keyword2",
+ ["math.random"] = "keyword2",
+ ["math.randomseed"] = "keyword2",
+ ["math.sin"] = "keyword2",
+ ["math.sinh"] = "keyword2",
+ ["math.sqrt"] = "keyword2",
+ ["math.tan"] = "keyword2",
+ ["math.tanh"] = "keyword2",
+ module = "keyword2",
+ next = "keyword2",
+ ["nil"] = "literal",
+ ["not"] = "keyword2",
+ ["not="] = "keyword2",
+ ["or"] = "keyword2",
+ os = "keyword2",
+ ["os.clock"] = "keyword2",
+ ["os.date"] = "keyword2",
+ ["os.difftime"] = "keyword2",
+ ["os.execute"] = "keyword2",
+ ["os.exit"] = "keyword2",
+ ["os.getenv"] = "keyword2",
+ ["os.remove"] = "keyword2",
+ ["os.rename"] = "keyword2",
+ ["os.setlocale"] = "keyword2",
+ ["os.time"] = "keyword2",
+ ["os.tmpname"] = "keyword2",
+ package = "keyword2",
+ ["package.loadlib"] = "keyword2",
+ ["package.searchpath"] = "keyword2",
+ ["package.seeall"] = "keyword2",
+ pairs = "keyword2",
+ partial = "keyword2",
+ pcall = "keyword2",
+ ["pick-args"] = "keyword2",
+ ["pick-values"] = "keyword2",
+ print = "keyword2",
+ quote = "keyword2",
+ rawequal = "keyword2",
+ rawget = "keyword2",
+ rawlen = "keyword2",
+ rawset = "keyword2",
+ require = "keyword2",
+ ["require-macros"] = "keyword2",
+ rshift = "keyword2",
+ select = "keyword2",
+ set = "keyword2",
+ ["set-forcibly!"] = "keyword2",
+ setmetatable = "keyword2",
+ string = "keyword2",
+ ["string.byte"] = "keyword2",
+ ["string.char"] = "keyword2",
+ ["string.dump"] = "keyword2",
+ ["string.find"] = "keyword2",
+ ["string.format"] = "keyword2",
+ ["string.gmatch"] = "keyword2",
+ ["string.gsub"] = "keyword2",
+ ["string.len"] = "keyword2",
+ ["string.lower"] = "keyword2",
+ ["string.match"] = "keyword2",
+ ["string.rep"] = "keyword2",
+ ["string.reverse"] = "keyword2",
+ ["string.sub"] = "keyword2",
+ ["string.upper"] = "keyword2",
+ table = "keyword2",
+ ["table.concat"] = "keyword2",
+ ["table.insert"] = "keyword2",
+ ["table.maxn"] = "keyword2",
+ ["table.pack"] = "keyword2",
+ ["table.remove"] = "keyword2",
+ ["table.sort"] = "keyword2",
+ ["table.unpack"] = "keyword2",
+ tonumber = "keyword2",
+ tostring = "keyword2",
+ ["true"] = "literal",
+ tset = "keyword2",
+ type = "keyword2",
+ unpack = "keyword2",
+ values = "keyword2",
+ var = "keyword2",
+ when = "keyword2",
+ ["while"] = "keyword2",
+ ["with-open"] = "keyword2",
+ xpcall = "keyword2",
+ ["~="] = "keyword",
+ ["λ"] = "keyword",
+ },
+})
- symbols = {
- ["eval-compiler"] = "keyword2",
- ["doc"] = "keyword2",
- ["lua"] = "keyword2",
- ["hashfn"] = "keyword2",
- ["macro"] = "keyword2",
- ["macros"] = "keyword2",
- ["import-macros"] = "keyword2",
- ["do"] = "keyword2",
- ["values"] = "keyword2",
- ["if"] = "keyword2",
- ["when"] = "keyword2",
- ["each"] = "keyword2",
- ["for"] = "keyword2",
- ["fn"] = "keyword2",
- ["lambda"] = "keyword2",
- ["λ"] = "keyword2",
- ["partial"] = "keyword2",
- ["while"] = "keyword2",
- ["set"] = "keyword2",
- ["global"] = "keyword2",
- ["var"] = "keyword2",
- ["local"] = "keyword2",
- ["let"] = "keyword2",
- ["tset"] = "keyword2",
- ["set-forcibly!"] = "keyword2",
- ["doto"] = "keyword2",
- ["match"] = "keyword2",
- ["or"] = "keyword2",
- ["and"] = "keyword2",
- ["not"] = "keyword2",
- ["not="] = "keyword2",
- ["pick-args"] = "keyword2",
- ["pick-values"] = "keyword2",
- ["macrodebug"] = "keyword2",
+-- To regenerate the syntax from the compiler:
+-- (macro s []
+-- (let [{: syntax} (require :fennel)
+-- symbols {:nil :literal
+-- :true :literal
+-- :false :literal}]
+-- `(syntax.add {:name "Fennel"
+-- :files "%.fnl$"
+-- :comment ";"
+-- :patterns [{:type :comment :pattern ";.-\n"}
+-- {:type :string :pattern {1 "\"" 2 "\"" 3 "\\"}}
+-- {:type :number :pattern "0x[%da-fA-F]+"}
+-- {:type :number :pattern "-?%d+[%d%.]*"}
+-- {:type :number :pattern "-?%.?%d+"}
+-- {:type :function :pattern "%f[^(][^()'%s\"]+"}
+-- {:type :symbol :pattern "[^()'%s\"]+"}]
+-- :symbols ,(collect [name (pairs (syntax)) :into symbols]
+-- (values name
+-- (if (name:find "[a-z]")
+-- :keyword2 :keyword)))}))) (s)
- ["."] = "keyword",
- ["+"] = "keyword",
- [".."] = "keyword",
- ["^"] = "keyword",
- ["-"] = "keyword",
- ["*"] = "keyword",
- ["%"] = "keyword",
- ["/"] = "keyword",
- [">"] = "keyword",
- ["<"] = "keyword",
- [">="] = "keyword",
- ["<="] = "keyword",
- ["="] = "keyword",
- ["#"] = "keyword",
- ["..."] = "keyword",
- [":"] = "keyword",
- ["->"] = "keyword",
- ["->>"] = "keyword",
- ["-?>"] = "keyword",
- ["-?>>"] = "keyword",
- ["$"] = "keyword",
- ["$1"] = "keyword",
- ["$2"] = "keyword",
- ["$3"] = "keyword",
- ["$4"] = "keyword",
- ["$5"] = "keyword",
- ["$6"] = "keyword",
- ["$7"] = "keyword",
- ["$8"] = "keyword",
- ["$9"] = "keyword",
-
- ["lshift"] = "keyword",
- ["rshift"] = "keyword",
- ["bor"] = "keyword",
- ["band"] = "keyword",
- ["bnot"] = "keyword",
- ["bxor"] = "keyword",
-
- ["nil"] = "literal",
- ["true"] = "literal",
- ["false"] = "literal",
- }
-}
+-- and reformat the output, of course
diff --git a/plugins/language_fstab.lua b/plugins/language_fstab.lua
index 4198516..bbe418e 100644
--- a/plugins/language_fstab.lua
+++ b/plugins/language_fstab.lua
@@ -1,4 +1,4 @@
--- mod-version:2
+-- mod-version:3
local syntax = require "core.syntax"
@@ -7,7 +7,7 @@ syntax.add {
files = { "fstab" },
comment = '#',
patterns = {
- -- Only lines that start with a # are comments; you can have #'s in fuse
+ -- Only lines that start with a # are comments; you can have #'s in fuse
-- filesystem strings that aren't comments, so shouldn't be highlighted as such.
{ regex = "^#.*$", type = "comment" },
{ pattern = "[=/:.,]+", type = "operator" },
@@ -45,7 +45,7 @@ syntax.add {
["LABEL"] = "keyword",
["UUID"] = "keyword",
-
+
-- filesystems
["aufs"] = "keyword2",
["autofs"] = "keyword2",
diff --git a/plugins/language_gdscript.lua b/plugins/language_gdscript.lua
index 168fc44..d03dce8 100644
--- a/plugins/language_gdscript.lua
+++ b/plugins/language_gdscript.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Support for the GDScript programming language: https://godotengine.org/
-- Covers the most used keywords up to Godot version 3.2.x
diff --git a/plugins/language_glsl.lua b/plugins/language_glsl.lua
index f8b2782..360e11f 100644
--- a/plugins/language_glsl.lua
+++ b/plugins/language_glsl.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local style = require "core.style"
local common = require "core.common"
diff --git a/plugins/language_gmi.lua b/plugins/language_gmi.lua
index a6ef4d8..c5d819a 100644
--- a/plugins/language_gmi.lua
+++ b/plugins/language_gmi.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
@@ -10,7 +10,7 @@ syntax.add {
{ pattern = { "```", "```" }, type = "string" },
{ pattern = "#.*", type = "keyword" },
{ pattern = "%*%s", type = "keyword2" },
- { pattern = "=>", type = "function" },
+ { pattern = "=>", type = "function" },
{ pattern = "https?://%S+", type = "literal" },
{ pattern = "gemini?://%S+", type = "literal" },
{ pattern = ">.*", type = "comment" },
diff --git a/plugins/language_go.lua b/plugins/language_go.lua
index 58c38c2..93db0e4 100644
--- a/plugins/language_go.lua
+++ b/plugins/language_go.lua
@@ -1,26 +1,143 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
name = "Go",
files = { "%.go$" },
comment = "//",
+ block_comment = {"/*", "*/"},
patterns = {
- { pattern = "//.-\n", type = "comment" },
- { pattern = { "/%*", "%*/" }, type = "comment" },
- { pattern = { '"', '"', '\\' }, type = "string" },
- { pattern = { "`", "`", '\\' }, type = "string" },
- { pattern = { "'", "'", '\\' }, type = "string" },
- { pattern = "0[oO_][0-7]+", type = "number" },
- { pattern = "-?0x[%x_]+", type = "number" },
- { pattern = "-?%d+_%d", type = "number" },
- { pattern = "-?%d+[%d%.eE]*f?", type = "number" },
- { pattern = "-?%.?%d+f?", type = "number" },
- { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
- { pattern = ":=", type = "operator" },
- { pattern = "[%a_][%w_]*%f[(]", type = "function" }, -- function call
- { pattern = "func()%s*[%a_][%w_]*()%f[%[(]", type = {"keyword", "function", "normal"} }, -- function statement
- { pattern = "[%a_][%w_]*", type = "symbol" },
+ { pattern = "//.-\n", type = "comment" },
+ { pattern = { "/%*", "%*/" }, type = "comment" },
+ { pattern = { '"', '"', '\\' }, type = "string" },
+ { pattern = { "`", "`", '\\' }, type = "string" },
+ { pattern = { "'", "'", '\\' }, type = "string" },
+ { pattern = "0[oO_][0-7]+i?", type = "number" },
+ { pattern = "-?0x[%x_]+i?", type = "number" },
+ { pattern = "-?%d+_%di?", type = "number" },
+ { pattern = "-?%d+[%d%.eE]*f?i?", type = "number" },
+ { pattern = "-?%.?%d+f?i?", type = "number" },
+ -- goto label
+ { pattern = "^%s+()[%a_][%w%_]*()%s*:%s$", -- this is to fix `default:`
+ type = { "normal", "function", "normal" }
+ },
+ { pattern = "^%s*[%a_][%w%_]*()%s*:%s$",
+ type = { "function", "normal" }
+ },
+ -- pointer, generic and reference type
+ { pattern = "[%*~&]()[%a_][%w%_]*",
+ type = { "operator", "keyword2" }
+ },
+ -- slice type
+ { pattern = "%[%]()[%a_][%w%_]*",
+ type = { "operator", "keyword2" }
+ },
+ -- type coerce
+ {
+ pattern = "%.%(()[%a_][%w_]*()%)",
+ type = { "normal", "keyword2", "normal" }
+ },
+ -- struct literal
+ { pattern = "[%a_][%w%_]*()%s*{%s*",
+ type = { "keyword2", "normal" }
+ },
+ -- operators
+ { pattern = "[%+%-=/%*%^%%<>!~|&]", type = "operator" },
+ { pattern = ":=", type = "operator" },
+ -- function calls
+ { pattern = "func()%s*[%a_][%w_]*()%f[%[(]", -- function statement
+ type = {"keyword", "function", "normal"}
+ },
+ { pattern = "[%a_][%w_]*%f[(]", type = "function" },
+ { pattern = "%.()[%a_][%w_]*%f[(]",
+ type = { "normal", "function" }
+ },
+ -- type declaration
+ { pattern = "type()%s+()[%a_][%w%_]*",
+ type = { "keyword", "normal", "keyword2" }
+ },
+ -- variable declaration
+ { pattern = "var()%s+()[%a_][%w%_]*",
+ type = { "keyword", "normal", "symbol" }
+ },
+ -- goto
+ { pattern = "goto()%s+()[%a_][%w%_]*",
+ type = { "keyword", "normal", "function" }
+ },
+ -- if fix
+ { pattern = "if()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- for fix
+ { pattern = "for()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- return fix
+ { pattern = "return()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- range fix
+ { pattern = "range()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- func fix
+ { pattern = "func()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- switch fix
+ { pattern = "switch()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- case fix
+ { pattern = "case()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- break fix
+ { pattern = "break()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- continue fix
+ { pattern = "continue()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- package fix
+ { pattern = "package()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- go fix
+ { pattern = "go()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- chan fix
+ { pattern = "chan()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- defer fix
+ { pattern = "defer()%s+%f[%a_]",
+ type = { "keyword", "normal" }
+ },
+ -- field declaration
+ { pattern = "[%a_][%w%_]*()%s*():%s*%f[%w%p]",
+ type = { "function", "normal", "operator" }
+ },
+ -- parameters or declarations
+ { pattern = "[%a_][%w%_]*()%s+()[%*~&]?()[%a_][%w%_]*",
+ type = { "literal", "normal", "operator", "keyword2" }
+ },
+ { pattern = "[%a_][%w_]*()%s+()%[%]()[%a_][%w%_]*",
+ type = { "literal", "normal", "normal", "keyword2" }
+ },
+ -- single return type
+ {
+ pattern = "%)%s+%(?()[%a_][%w%_]*()%)?%s+%{",
+ type = { "normal", "keyword2", "normal" }
+ },
+ -- sub fields
+ { pattern = "%.()[%a_][%w_]*",
+ type = { "normal", "literal" }
+ },
+ -- every other symbol
+ { pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
["if"] = "keyword",
@@ -48,6 +165,7 @@ syntax.add {
["go"] = "keyword",
["fallthrough"] = "keyword",
["goto"] = "keyword",
+ ["iota"] = "keyword2",
["int"] = "keyword2",
["int64"] = "keyword2",
["int32"] = "keyword2",
diff --git a/plugins/language_hlsl.lua b/plugins/language_hlsl.lua
index 696e9b2..e1d5570 100644
--- a/plugins/language_hlsl.lua
+++ b/plugins/language_hlsl.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local style = require "core.style"
local common = require "core.common"
diff --git a/plugins/language_hs.lua b/plugins/language_hs.lua
index a60ff75..4271210 100644
--- a/plugins/language_hs.lua
+++ b/plugins/language_hs.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_htaccess.lua b/plugins/language_htaccess.lua
index 2cb71ca..acfa419 100644
--- a/plugins/language_htaccess.lua
+++ b/plugins/language_htaccess.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
local keywords = {
diff --git a/plugins/language_ini.lua b/plugins/language_ini.lua
index 706cc80..2f5d3b5 100644
--- a/plugins/language_ini.lua
+++ b/plugins/language_ini.lua
@@ -1,4 +1,4 @@
--- mod-version:2
+-- mod-version:3
local syntax = require "core.syntax"
diff --git a/plugins/language_java.lua b/plugins/language_java.lua
index 810ffd2..30c1249 100644
--- a/plugins/language_java.lua
+++ b/plugins/language_java.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_jiyu.lua b/plugins/language_jiyu.lua
index 78cc377..dbf9d0e 100644
--- a/plugins/language_jiyu.lua
+++ b/plugins/language_jiyu.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_jsx.lua b/plugins/language_jsx.lua
index 1cbc4ac..04e846b 100644
--- a/plugins/language_jsx.lua
+++ b/plugins/language_jsx.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Almost identical to JS, with the exception that / shouldn't denote a regex. Current JS syntax highlighter will highlight half the document due to closing tags.
local syntax = require "core.syntax"
diff --git a/plugins/language_julia.lua b/plugins/language_julia.lua
index e62a9b2..347592f 100644
--- a/plugins/language_julia.lua
+++ b/plugins/language_julia.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Support for the Julia programming language:
-- Covers the most used keywords up to Julia version 1.6.4
diff --git a/plugins/language_liquid.lua b/plugins/language_liquid.lua
index 58688c8..ad40a79 100644
--- a/plugins/language_liquid.lua
+++ b/plugins/language_liquid.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
local liquid_syntax = {
@@ -92,7 +92,7 @@ local liquid_syntax = {
["upcase"] = "keyword2",
["when"] = "keyword",
["where"] = "keyword2"
-
+
},
}
@@ -102,27 +102,27 @@ syntax.add {
patterns = {
{ pattern = { "{%%", "%%}" }, syntax = liquid_syntax, type = "function" },
{ pattern = { "{{", "}}" }, syntax = liquid_syntax, type = "function" },
- {
- pattern = {
+ {
+ pattern = {
"<%s*[sS][cC][rR][iI][pP][tT]%s+[tT][yY][pP][eE]%s*=%s*" ..
"['\"]%a+/[jJ][aA][vV][aA][sS][cC][rR][iI][pP][tT]['\"]%s*>",
- "<%s*/[sS][cC][rR][iI][pP][tT]>"
+ "<%s*/[sS][cC][rR][iI][pP][tT]>"
},
- syntax = ".js",
- type = "function"
+ syntax = ".js",
+ type = "function"
},
- {
- pattern = {
+ {
+ pattern = {
"<%s*[sS][cC][rR][iI][pP][tT]%s*>",
- "<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
+ "<%s*/%s*[sS][cC][rR][iI][pP][tT]>"
},
syntax = ".js",
type = "function"
},
- {
- pattern = {
- "<%s*[sS][tT][yY][lL][eE][^>]*>",
- "<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
+ {
+ pattern = {
+ "<%s*[sS][tT][yY][lL][eE][^>]*>",
+ "<%s*/%s*[sS][tT][yY][lL][eE]%s*>"
},
syntax = ".css",
type = "function"
diff --git a/plugins/language_lobster.lua b/plugins/language_lobster.lua
index b6ab143..24f4251 100644
--- a/plugins/language_lobster.lua
+++ b/plugins/language_lobster.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
@@ -46,7 +46,7 @@ syntax.add {
["string"] = "keyword2",
["any"] = "keyword2",
["void"] = "keyword2",
-
+
["is"] = "keyword",
["typeof"] = "keyword",
["var"] = "keyword",
@@ -59,7 +59,7 @@ syntax.add {
["not"] = "operator",
["and"] = "operator",
- ["or"] = "operator",
+ ["or"] = "operator",
["nil"] = "literal",
},
diff --git a/plugins/language_make.lua b/plugins/language_make.lua
index 687b827..4ad3521 100644
--- a/plugins/language_make.lua
+++ b/plugins/language_make.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_meson.lua b/plugins/language_meson.lua
index f5c55d7..be10d66 100644
--- a/plugins/language_meson.lua
+++ b/plugins/language_meson.lua
@@ -1,9 +1,9 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
name = "Meson",
- files = "meson.build$",
+ files = { "^meson%.build$", "^meson_options%.txt$" },
comment = "#",
patterns = {
{ pattern = { "#", "\n" }, type = "comment" },
diff --git a/plugins/language_miniscript.lua b/plugins/language_miniscript.lua
new file mode 100644
index 0000000..afe0a2b
--- /dev/null
+++ b/plugins/language_miniscript.lua
@@ -0,0 +1,102 @@
+-- mod-version:3
+local syntax = require 'core.syntax'
+
+syntax.add {
+ name = "MiniScript",
+ files = { "%.ms$" },
+ comment = "//",
+ patterns = {
+ { pattern = "//.*", type = "comment" },
+ { pattern = { '"', '"' }, type = "string" },
+ { pattern = "[<>!=]=", type = "operator" },
+ { pattern = "[%+%-%*%/%^@<>:]", type = "operator" },
+ { pattern = "%d%.%d*[eE][-+]?%d+", type = "number" },
+ { pattern = "%d%.%d*", type = "number" },
+ { pattern = "%.?%d*[eE][-+]?%d+", type = "number" },
+ { pattern = "%.?%d+", type = "number" },
+ { pattern = "[%a_][%w_]*", type = "symbol" },
+ },
+ symbols = {
+ ["if"] = "keyword",
+ ["not"] = "keyword",
+ ["and"] = "keyword",
+ ["or"] = "keyword",
+ ["else"] = "keyword",
+ ["then"] = "keyword",
+ ["for"] = "keyword",
+ ["in"] = "keyword",
+ ["while"] = "keyword",
+ ["break"] = "keyword",
+ ["continue"] = "keyword",
+ ["function"] = "keyword",
+ ["end"] = "keyword",
+ ["return"] = "keyword",
+ ["new"] = "keyword",
+ ["isa"] = "keyword",
+ ["self"] = "keyword2",
+
+ ["true"] = "literal",
+ ["false"] = "literal",
+ ["null"] = "literal",
+ ["globals"] = "literal",
+ ["locals"] = "literal",
+ ["outer"] = "literal",
+
+ -- Built-in types's classes
+ ["number"] = "literal",
+ ["string"] = "literal",
+ ["list"] = "literal",
+ ["map"] = "literal",
+ ["funcRef"] = "literal",
+
+ -- Intrinsic functions
+ ["abs"] = "function",
+ ["acos"] = "function",
+ ["asin"] = "function",
+ ["atan"] = "function",
+ ["bitAnd"] = "function",
+ ["bitOr"] = "function",
+ ["bitXor"] = "function",
+ ["ceil"] = "function",
+ ["char"] = "function",
+ ["code"] = "function",
+ ["cos"] = "function",
+ ["floor"] = "function",
+ ["hash"] = "function",
+ ["hasIndex"] = "function",
+ ["indexes"] = "function",
+ ["indexOf"] = "function",
+ ["insert"] = "function",
+ ["join"] = "function",
+ ["len"] = "function",
+ ["log"] = "function",
+ ["lower"] = "function",
+ ["pi"] = "function",
+ ["pop"] = "function",
+ ["print"] = "function",
+ ["pull"] = "function",
+ ["push"] = "function",
+ ["range"] = "function",
+ ["remove"] = "function",
+ ["replace"] = "function",
+ ["rnd"] = "function",
+ ["round"] = "function",
+ ["shuffle"] = "function",
+ ["sign"] = "function",
+ ["sin"] = "function",
+ ["slice"] = "function",
+ ["sort"] = "function",
+ ["split"] = "function",
+ ["sqrt"] = "function",
+ ["str"] = "function",
+ ["sum"] = "function",
+ ["tan"] = "function",
+ ["time"] = "function",
+ ["upper"] = "function",
+ ["val"] = "function",
+ ["values"] = "function",
+ ["version"] = "function",
+ ["wait"] = "function",
+ ["yield"] = "function",
+ },
+}
diff --git a/plugins/language_moon.lua b/plugins/language_moon.lua
index 59eea37..8042b7d 100644
--- a/plugins/language_moon.lua
+++ b/plugins/language_moon.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_nginx.lua b/plugins/language_nginx.lua
index 73cf979..7124c1a 100644
--- a/plugins/language_nginx.lua
+++ b/plugins/language_nginx.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
-- Copied from https://github.com/shanoor/vscode-nginx/blob/master/syntaxes/nginx.tmLanguage
diff --git a/plugins/language_nim.lua b/plugins/language_nim.lua
index 5f00365..d7726d1 100644
--- a/plugins/language_nim.lua
+++ b/plugins/language_nim.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
local patterns = {}
diff --git a/plugins/language_objc.lua b/plugins/language_objc.lua
index e0945d2..8d1210b 100644
--- a/plugins/language_objc.lua
+++ b/plugins/language_objc.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_odin.lua b/plugins/language_odin.lua
index ff75700..a5dfa45 100644
--- a/plugins/language_odin.lua
+++ b/plugins/language_odin.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_perl.lua b/plugins/language_perl.lua
index 1938b1b..0e057e2 100644
--- a/plugins/language_perl.lua
+++ b/plugins/language_perl.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_php.lua b/plugins/language_php.lua
index 5ef4f22..2820862 100644
--- a/plugins/language_php.lua
+++ b/plugins/language_php.lua
@@ -1,23 +1,152 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
--[[
language_php.lua
provides php syntax support allowing mixed html, css and js
- version: 20210902_1
+ version: 20220614_1
--]]
local syntax = require "core.syntax"
+local common = require "core.common"
+local config = require "core.config"
-- load syntax dependencies to add additional rules
require "plugins.language_css"
require "plugins.language_js"
--- generate SQL string marker regex
-local sql_markers = { 'create', 'select', 'insert', 'update', 'replace', 'delete', 'drop', 'alter' }
-local sql_regex = {}
-for _,marker in ipairs(sql_markers) do
- table.insert(sql_regex, marker)
- table.insert(sql_regex, string.upper(marker))
+local psql_found = pcall(require, "plugins.language_psql")
+local sql_strings = {}
+
+config.plugins.language_php = common.merge({
+ sql_strings = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Language PHP",
+ {
+ label = "SQL Strings",
+ description = "Highlight as SQL, strings starting with sql statements, "
+ .. "depends on language_psql.",
+ path = "sql_strings",
+ type = "toggle",
+ default = true,
+ on_apply = function(enabled)
+ local syntax_php = syntax.get("file.phps")
+ if enabled and psql_found then
+ if
+ not syntax_php.patterns[6].syntax
+ or
+ syntax_php.patterns[6].syntax ~= '.sql'
+ then
+ table.insert(syntax_php.patterns, 5, sql_strings[1])
+ table.insert(syntax_php.patterns, 6, sql_strings[2])
+ end
+ elseif
+ syntax_php.patterns[6].syntax
+ and
+ syntax_php.patterns[6].syntax == '.sql'
+ then
+ table.remove(syntax_php.patterns, 5)
+ table.remove(syntax_php.patterns, 5)
+ end
+ end
+ }
+ }
+}, config.plugins.language_php)
+
+-- Patterns to match some of the string inline variables
+local inline_variables = {
+ { pattern = "%s+", type = "string" },
+ { pattern = "\\%$", type = "string" },
+ { pattern = "%{[%$%s]*%}", type = "string" },
+ -- matches {$varname[index]}
+ { pattern = "{"
+ .. "()%$[%a_][%w_]*"
+ .. "()%["
+ .. "()[%w%s_%-\"\'%(%)|;:,%.#@%%!%^&%*%+=%[%]<>~`%?\\/]*"
+ .. "()%]"
+ .. "}",
+ type = {
+ "keyword", "keyword2", "keyword", "string", "keyword"
+ }
+ },
+ { pattern = "{"
+ .. "()%$[%a_][%w_]*"
+ .. "()%->"
+ .. "()[%a_][%w_]*"
+ .. "()}",
+ type = {
+ "keyword", "keyword2", "keyword", "symbol", "keyword"
+ }
+ },
+ { pattern = "{()%$[%a_][%w_]*()}",
+ type = { "keyword", "keyword2", "keyword" }
+ },
+ { pattern = "%$[%a_][%w_]*()%[()%w*()%]",
+ type = { "keyword2", "keyword", "string", "keyword" }
+ },
+ { pattern = "%$[%a_][%w_]*()%->()%w+",
+ type = { "keyword2", "keyword", "symbol" }
+ },
+ { pattern = "%$[%a_][%w_]*", type = "keyword2" },
+ { pattern = "%w+", type = "string" },
+ { pattern = "[^\"]", type = "string" },
+}
+
+local function combine_patterns(t1, t2)
+ local temp = { table.unpack(t1) }
+ for _, t in ipairs(t2) do
+ table.insert(temp, t)
+ end
+ return temp
+end
+
+local function clone(tbl)
+ local t = {}
+ if tbl then
+ for k, v in pairs(tbl) do
+ if type(v) == "table" then
+ t[k] = clone(v)
+ else
+ t[k] = v
+ end
+ end
+ end
+ return t
+end
+
+-- optionally allow sql syntax on strings
+if psql_found then
+ -- generate SQL string marker regex
+ local sql_markers = { 'create', 'select', 'insert', 'update', 'replace', 'delete', 'drop', 'alter' }
+ local sql_regex = {}
+ for _, marker in ipairs(sql_markers) do
+ table.insert(sql_regex, marker)
+ table.insert(sql_regex, string.upper(marker))
+ end
+ sql_regex = table.concat(sql_regex, '|')
+
+ -- inject inline variable rules to cloned psql syntax
+ local syntax_phpsql = clone(syntax.get("file.sql"))
+ syntax_phpsql.name = "PHP SQL"
+ syntax_phpsql.files = "%.phpsql$"
+ table.insert(syntax_phpsql.patterns, 2, { pattern = "\\%$", type = "symbol" })
+ table.insert(syntax_phpsql.patterns, 3, { pattern = "%{[%$%s]*%}", type = "symbol" })
+ for i=4, 9 do
+ table.insert(syntax_phpsql.patterns, i, inline_variables[i])
+ end
+
+ -- SQL strings
+ sql_strings = {
+ {
+ regex = { '"(?=(?:'..sql_regex..')\\s+)', '"', '\\' },
+ syntax = syntax_phpsql,
+ type = "string"
+ },
+ {
+ regex = { '\'(?=(?:'..sql_regex..')\\s+)', '\'', '\\' },
+ syntax = '.sql',
+ type = "string"
+ },
+ }
end
-sql_regex = table.concat(sql_regex, '|')
-- define the core php syntax coloring
syntax.add {
@@ -33,20 +162,36 @@ syntax.add {
{ pattern = "//.-\n", type = "comment" },
{ pattern = "#.-\n", type = "comment" },
{ pattern = { "/%*", "%*/" }, type = "comment" },
- -- SQL strings
- {
- regex = { '"(?=(?:'..sql_regex..')\\s+)', '"', '\\' },
- syntax = '.sql',
- type = "string"
+ -- Single quote string
+ { pattern = { "'", "'", '\\' }, type = "string" },
+ { pattern = { "<<<'%a%w*'\n", "^%s*%a%w*%f[;]", '\\' },
+ type = "string"
},
- {
- regex = { '\'(?=(?:'..sql_regex..')\\s+)', '\'', '\\' },
- syntax = '.sql',
- type = "string"
+ -- Strings with support for some inline variables syntax
+ { pattern = { "<<<%a%w*\n", "^%s*%a%w*%f[;]", '\\' },
+ syntax = {
+ patterns = combine_patterns(inline_variables, {
+ -- prevent matching outside of the parent string
+ { pattern = "^%s*%a%w*();$",
+ type = { "string", "normal" }
+ },
+ { pattern = "%p", type = "string" },
+ }),
+ symbols = {}
+ },
+ type = "string"
+ },
+ { pattern = { '"', '"', '\\' },
+ syntax = {
+ patterns = combine_patterns(inline_variables, {
+ -- prevent matching outside of the parent string
+ { pattern = "%p+%f[\"]", type = "string" },
+ { pattern = "%p", type = "string" },
+ }),
+ symbols = {}
+ },
+ type = "string"
},
- -- The '\\' is for escaping to work on " or '
- { pattern = { '"', '"', '\\' }, type = "string" },
- { pattern = { "'", "'", '\\' }, type = "string" },
{ pattern = "0[bB][%d]+", type = "number" },
{ pattern = "0[xX][%da-fA-F]+", type = "number" },
{ pattern = "-?%d[%d_%.eE]*", type = "number" },
@@ -171,6 +316,13 @@ syntax.add {
},
}
+-- insert sql string rules after the "/%*", "%*/" pattern
+if psql_found and config.plugins.language_php.sql_strings then
+ local syntax_php = syntax.get("file.phps")
+ table.insert(syntax_php.patterns, 5, sql_strings[1])
+ table.insert(syntax_php.patterns, 6, sql_strings[2])
+end
+
-- allows html, css and js coloring on php files
syntax.add {
name = "PHP",
diff --git a/plugins/language_pico8.lua b/plugins/language_pico8.lua
index 40c9772..aefd6f6 100644
--- a/plugins/language_pico8.lua
+++ b/plugins/language_pico8.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_pkgbuild.lua b/plugins/language_pkgbuild.lua
index de38d5c..049a5d1 100644
--- a/plugins/language_pkgbuild.lua
+++ b/plugins/language_pkgbuild.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_po.lua b/plugins/language_po.lua
index db060b1..a1b098c 100644
--- a/plugins/language_po.lua
+++ b/plugins/language_po.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_powershell.lua b/plugins/language_powershell.lua
index 63de2f3..fdba844 100644
--- a/plugins/language_powershell.lua
+++ b/plugins/language_powershell.lua
@@ -1,74 +1,77 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
- name = "PowerShell",
- files = {"%.ps1$", "%.psm1$", "%.psd1$", "%.ps1xml$", "%.pssc$", "%.psrc$", "%.cdxml$"},
- comment = "#",
- patterns = {
- {pattern = "#.*\n", type = "comment"},
- {pattern = [[\.]], type = "normal"},
- {pattern = {'"', '"'}, type = "string"},
- {pattern = {"'", "'"}, type = "string"},
- {pattern = "%f[%w_][%d%.]+%f[^%w_]", type = "number"},
- {pattern = "[%+=/%*%^%%<>!~|&,:]+", type = "operator"},
- {pattern = "%f[%S]%-[%w%-_]+", type = "function"},
- {pattern = "[%u][%a]+[%-][%u][%a]+", type = "function"},
- {pattern = "${.*}", type = "symbol"},
- {pattern = "$[%a_@*][%w_]*", type = "keyword2"},
- {pattern = "$[%$][%a]+", type = "keyword2"},
- {pattern = "[%a_][%w_]*", type = "symbol"}
- },
- symbols = {
- ["if"] = "keyword",
- ["else"] = "keyword",
- ["elseif"] = "keyword",
- ["switch"] = "keyword",
- ["default"] = "keyword",
- ["function"] = "keyword",
- ["filter"] = "keyword",
- ["workflow"] = "keyword",
- ["configuration"] = "keyword",
- ["class"] = "keyword",
- ["enum"] = "keyword",
- ["Parameter"] = "keyword",
- ["ValidateScript"] = "keyword",
- ["CmdletBinding"] = "keyword",
- ["try"] = "keyword",
- ["catch"] = "keyword",
- ["finally"] = "keyword",
- ["throw"] = "keyword",
- ["while"] = "keyword",
- ["for"] = "keyword",
- ["do"] = "keyword",
- ["until"] = "keyword",
- ["break"] = "keyword",
- ["continue"] = "keyword",
- ["foreach"] = "keyword",
- ["in"] = "keyword",
- ["return"] = "keyword",
- ["where"] = "function",
- ["select"] = "function",
- ["filter"] = "keyword",
- ["in"] = "keyword",
- ["trap"] = "keyword",
- ["param"] = "keyword",
- ["data"] = "keyword",
- ["dynamicparam"] = "keyword",
- ["begin"] = "function",
- ["process"] = "function",
- ["end"] = "function",
- ["exit"] = "function",
- ["inlinescript"] = "function",
- ["parallel"] = "function",
- ["sequence"] = "function",
- ["true"] = "literal",
- ["false"] = "literal",
- ["TODO"] = "comment",
- ["FIXME"] = "comment",
- ["XXX"] = "comment",
- ["TBD"] = "comment",
- ["HACK"] = "comment",
- ["NOTE"] = "comment"
- }
+ name = "PowerShell",
+ files = {
+ "%.ps1$", "%.psm1$", "%.psd1$", "%.ps1xml$",
+ "%.pssc$", "%.psrc$", "%.cdxml$"
+ },
+ comment = "#",
+ patterns = {
+ {pattern = "#.*\n", type = "comment"},
+ {pattern = [[\.]], type = "normal"},
+ {pattern = {'"', '"'}, type = "string"},
+ {pattern = {"'", "'"}, type = "string"},
+ {pattern = "%f[%w_][%d%.]+%f[^%w_]", type = "number"},
+ {pattern = "[%+=/%*%^%%<>!~|&,:]+", type = "operator"},
+ {pattern = "%f[%S]%-[%w%-_]+", type = "function"},
+ {pattern = "[%u][%a]+[%-][%u][%a]+", type = "function"},
+ {pattern = "${.*}", type = "symbol"},
+ {pattern = "$[%a_@*][%w_]*", type = "keyword2"},
+ {pattern = "$[%$][%a]+", type = "keyword2"},
+ {pattern = "[%a_][%w_]*", type = "symbol"}
+ },
+ symbols = {
+ ["if"] = "keyword",
+ ["else"] = "keyword",
+ ["elseif"] = "keyword",
+ ["switch"] = "keyword",
+ ["default"] = "keyword",
+ ["function"] = "keyword",
+ ["filter"] = "keyword",
+ ["workflow"] = "keyword",
+ ["configuration"] = "keyword",
+ ["class"] = "keyword",
+ ["enum"] = "keyword",
+ ["Parameter"] = "keyword",
+ ["ValidateScript"] = "keyword",
+ ["CmdletBinding"] = "keyword",
+ ["try"] = "keyword",
+ ["catch"] = "keyword",
+ ["finally"] = "keyword",
+ ["throw"] = "keyword",
+ ["while"] = "keyword",
+ ["for"] = "keyword",
+ ["do"] = "keyword",
+ ["until"] = "keyword",
+ ["break"] = "keyword",
+ ["continue"] = "keyword",
+ ["foreach"] = "keyword",
+ ["in"] = "keyword",
+ ["return"] = "keyword",
+ ["where"] = "function",
+ ["select"] = "function",
+ ["filter"] = "keyword",
+ ["in"] = "keyword",
+ ["trap"] = "keyword",
+ ["param"] = "keyword",
+ ["data"] = "keyword",
+ ["dynamicparam"] = "keyword",
+ ["begin"] = "function",
+ ["process"] = "function",
+ ["end"] = "function",
+ ["exit"] = "function",
+ ["inlinescript"] = "function",
+ ["parallel"] = "function",
+ ["sequence"] = "function",
+ ["true"] = "literal",
+ ["false"] = "literal",
+ ["TODO"] = "comment",
+ ["FIXME"] = "comment",
+ ["XXX"] = "comment",
+ ["TBD"] = "comment",
+ ["HACK"] = "comment",
+ ["NOTE"] = "comment"
+ }
}
diff --git a/plugins/language_psql.lua b/plugins/language_psql.lua
index 80bc4cd..b891dae 100644
--- a/plugins/language_psql.lua
+++ b/plugins/language_psql.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
-- In sql symbols can be lower case and upper case
diff --git a/plugins/language_rescript.lua b/plugins/language_rescript.lua
index 9007dc1..3d3bca0 100644
--- a/plugins/language_rescript.lua
+++ b/plugins/language_rescript.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_rivet.lua b/plugins/language_rivet.lua
index 61b11bc..214d76b 100644
--- a/plugins/language_rivet.lua
+++ b/plugins/language_rivet.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Syntax highlighting for the Rivet programming language.
-- by StunxFS :)
@@ -24,7 +24,7 @@ syntax.add {
{pattern = "-?%.?%d+", type = "number"},
{pattern = "[%+%-=/%*%^%%<>!~|&%.%?]", type = "operator"},
-- Uppercase constants of at least 2 chars in length
- {
+ {
pattern = "_?%u[%u_][%u%d_]*%f[%s%+%*%-%.%)%]}%?%^%%=/<>~|&;:,!]",
type = "number"
},
@@ -80,7 +80,7 @@ syntax.add {
["is"] = "keyword",
["in"] = "keyword",
["as"] = "keyword",
-
+
-- types
["no_return"] = "keyword2",
["bool"] = "keyword2",
diff --git a/plugins/language_ruby.lua b/plugins/language_ruby.lua
index 85717fe..c46d558 100644
--- a/plugins/language_ruby.lua
+++ b/plugins/language_ruby.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
@@ -23,7 +23,6 @@ syntax.add {
},
symbols = {
["nil"] = "literal",
- ["end"] = "literal",
["true"] = "literal",
["false"] = "literal",
["private"] = "keyword",
@@ -63,7 +62,6 @@ syntax.add {
["self"] = "keyword",
["super"] = "keyword",
["then"] = "keyword",
- ["true"] = "keyword",
["undef"] = "keyword",
["unless"] = "keyword",
["until"] = "keyword",
diff --git a/plugins/language_rust.lua b/plugins/language_rust.lua
index f20d35e..848e8b1 100644
--- a/plugins/language_rust.lua
+++ b/plugins/language_rust.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
@@ -23,68 +23,66 @@ syntax.add {
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
- ["as"] = "keyword",
- ["async"] = "keyword",
- ["await"] = "keyword",
- ["break"] = "keyword",
- ["const"] = "keyword",
- ["continue"] = "keyword",
- ["crate"] = "keyword",
- ["dyn"] = "keyword",
- ["else"] = "keyword",
- ["enum"] = "keyword",
- ["extern"] = "keyword",
- ["false"] = "keyword",
- ["fn"] = "keyword",
- ["for"] = "keyword",
- ["if"] = "keyword",
- ["impl"] = "keyword",
- ["in"] = "keyword",
- ["let"] = "keyword",
- ["loop"] = "keyword",
- ["match"] = "keyword",
- ["mod"] = "keyword",
- ["move"] = "keyword",
- ["mut"] = "keyword",
- ["pub"] = "keyword",
- ["ref"] = "keyword",
- ["return"] = "keyword",
- ["Self"] = "keyword",
- ["self"] = "keyword",
- ["static"] = "keyword",
- ["struct"] = "keyword",
- ["super"] = "keyword",
- ["trait"] = "keyword",
- ["true"] = "keyword",
- ["type"] = "keyword",
- ["unsafe"] = "keyword",
- ["use"] = "keyword",
- ["where"] = "keyword",
- ["while"] = "keyword",
- ["i32"] = "keyword2",
- ["i64"] = "keyword2",
- ["i128"] = "keyword2",
- ["i16"] = "keyword2",
- ["i8"] = "keyword2",
- ["u8"] = "keyword2",
- ["u16"] = "keyword2",
- ["u32"] = "keyword2",
- ["u64"] = "keyword2",
- ["usize"] = "keyword2",
- ["isize"] = "keyword2",
- ["f32"] = "keyword2",
- ["f64"] = "keyword2",
- ["f128"] = "keyword2",
- ["String"] = "keyword2",
- ["char"] = "keyword2",
- ["str"] = "keyword2",
- ["bool"] = "keyword2",
- ["true"] = "literal",
- ["false"] = "literal",
- ["None"] = "literal",
- ["Some"] = "literal",
- ["Option"] = "literal",
- ["Result"] = "literal",
+ ["as"] = "keyword",
+ ["async"] = "keyword",
+ ["await"] = "keyword",
+ ["break"] = "keyword",
+ ["const"] = "keyword",
+ ["continue"] = "keyword",
+ ["crate"] = "keyword",
+ ["dyn"] = "keyword",
+ ["else"] = "keyword",
+ ["enum"] = "keyword",
+ ["extern"] = "keyword",
+ ["fn"] = "keyword",
+ ["for"] = "keyword",
+ ["if"] = "keyword",
+ ["impl"] = "keyword",
+ ["in"] = "keyword",
+ ["let"] = "keyword",
+ ["loop"] = "keyword",
+ ["match"] = "keyword",
+ ["mod"] = "keyword",
+ ["move"] = "keyword",
+ ["mut"] = "keyword",
+ ["pub"] = "keyword",
+ ["ref"] = "keyword",
+ ["return"] = "keyword",
+ ["Self"] = "keyword",
+ ["self"] = "keyword",
+ ["static"] = "keyword",
+ ["struct"] = "keyword",
+ ["super"] = "keyword",
+ ["trait"] = "keyword",
+ ["type"] = "keyword",
+ ["unsafe"] = "keyword",
+ ["use"] = "keyword",
+ ["where"] = "keyword",
+ ["while"] = "keyword",
+ ["i32"] = "keyword2",
+ ["i64"] = "keyword2",
+ ["i128"] = "keyword2",
+ ["i16"] = "keyword2",
+ ["i8"] = "keyword2",
+ ["u8"] = "keyword2",
+ ["u16"] = "keyword2",
+ ["u32"] = "keyword2",
+ ["u64"] = "keyword2",
+ ["usize"] = "keyword2",
+ ["isize"] = "keyword2",
+ ["f32"] = "keyword2",
+ ["f64"] = "keyword2",
+ ["f128"] = "keyword2",
+ ["String"] = "keyword2",
+ ["char"] = "keyword2",
+ ["str"] = "keyword2",
+ ["bool"] = "keyword2",
+ ["true"] = "literal",
+ ["false"] = "literal",
+ ["None"] = "literal",
+ ["Some"] = "literal",
+ ["Option"] = "literal",
+ ["Result"] = "literal",
},
}
diff --git a/plugins/language_sass.lua b/plugins/language_sass.lua
index 723d975..f440927 100644
--- a/plugins/language_sass.lua
+++ b/plugins/language_sass.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_scala.lua b/plugins/language_scala.lua
index fddadc7..6c5d7a7 100644
--- a/plugins/language_scala.lua
+++ b/plugins/language_scala.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
@@ -19,62 +19,62 @@ syntax.add {
{ pattern = "[%a_][%w_]*", type = "symbol" },
},
symbols = {
- ["abstract"] = "keyword",
- ["case"] = "keyword",
- ["catch"] = "keyword",
- ["class"] = "keyword",
- ["finally"] = "keyword",
- ["final"] = "keyword",
- ["do"] = "keyword",
- ["extends"] = "keyword",
- ["forSome"] = "keyword",
- ["implicit"] = "keyword",
- ["lazy"] = "keyword",
- ["match"] = "keyword",
- ["new"] = "keyword",
- ["override"] = "keyword",
- ["package"] = "keyword",
- ["throw"] = "keyword",
- ["trait"] = "keyword",
- ["type"] = "keyword",
- ["var"] = "keyword",
- ["val"] = "keyword",
+ ["abstract"] = "keyword",
+ ["case"] = "keyword",
+ ["catch"] = "keyword",
+ ["class"] = "keyword",
+ ["finally"] = "keyword",
+ ["final"] = "keyword",
+ ["do"] = "keyword",
+ ["extends"] = "keyword",
+ ["forSome"] = "keyword",
+ ["implicit"] = "keyword",
+ ["lazy"] = "keyword",
+ ["match"] = "keyword",
+ ["new"] = "keyword",
+ ["override"] = "keyword",
+ ["package"] = "keyword",
+ ["throw"] = "keyword",
+ ["trait"] = "keyword",
+ ["type"] = "keyword",
+ ["var"] = "keyword",
+ ["val"] = "keyword",
["println"] = "keyword",
- ["return"] = "keyword",
- ["for"] = "keyword",
- ["Try"] = "keyword",
- ["def"] = "keyword",
- ["while"] = "keyword",
- ["with"] = "keyword",
- ["if"] = "keyword",
- ["else"] = "keyword",
- ["import"] = "keyword",
- ["object"] = "keyword",
- ["yield"] = "keyword",
-
- ["private"] = "keyword2",
- ["protected"] = "keyword2",
- ["sealed"] = "keyword2",
- ["super"] = "keyword2",
- ["this"] = "keyword2",
- ["Byte"] = "keyword2",
- ["Short"] = "keyword2",
- ["Int"] = "keyword2",
- ["Long"] = "keyword2",
- ["Float"] = "keyword2",
- ["Double"] = "keyword2",
- ["Char"] = "keyword2",
- ["String"] = "keyword2",
- ["List"] = "keyword2",
- ["Array"] = "keyword2",
- ["Boolean"] = "keyword2",
-
- ["Null"] = "literal",
- ["Any"] = "literal",
- ["AnyRef"] = "literal",
- ["Nothing"] = "literal",
- ["Unit"] = "literal",
- ["true"] = "literal",
- ["false"] = "literal",
+ ["return"] = "keyword",
+ ["for"] = "keyword",
+ ["Try"] = "keyword",
+ ["def"] = "keyword",
+ ["while"] = "keyword",
+ ["with"] = "keyword",
+ ["if"] = "keyword",
+ ["else"] = "keyword",
+ ["import"] = "keyword",
+ ["object"] = "keyword",
+ ["yield"] = "keyword",
+
+ ["private"] = "keyword2",
+ ["protected"] = "keyword2",
+ ["sealed"] = "keyword2",
+ ["super"] = "keyword2",
+ ["this"] = "keyword2",
+ ["Byte"] = "keyword2",
+ ["Short"] = "keyword2",
+ ["Int"] = "keyword2",
+ ["Long"] = "keyword2",
+ ["Float"] = "keyword2",
+ ["Double"] = "keyword2",
+ ["Char"] = "keyword2",
+ ["String"] = "keyword2",
+ ["List"] = "keyword2",
+ ["Array"] = "keyword2",
+ ["Boolean"] = "keyword2",
+
+ ["Null"] = "literal",
+ ["Any"] = "literal",
+ ["AnyRef"] = "literal",
+ ["Nothing"] = "literal",
+ ["Unit"] = "literal",
+ ["true"] = "literal",
+ ["false"] = "literal",
}
}
diff --git a/plugins/language_sh.lua b/plugins/language_sh.lua
index fdfe867..5d7b987 100644
--- a/plugins/language_sh.lua
+++ b/plugins/language_sh.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_ssh_config.lua b/plugins/language_ssh_config.lua
index c105082..894ce21 100644
--- a/plugins/language_ssh_config.lua
+++ b/plugins/language_ssh_config.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_tcl.lua b/plugins/language_tcl.lua
index 1fb672e..7a6e13f 100644
--- a/plugins/language_tcl.lua
+++ b/plugins/language_tcl.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_teal.lua b/plugins/language_teal.lua
index a974fdd..0b0e35d 100644
--- a/plugins/language_teal.lua
+++ b/plugins/language_teal.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_tex.lua b/plugins/language_tex.lua
index 264c5ca..f8e69e2 100644
--- a/plugins/language_tex.lua
+++ b/plugins/language_tex.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_toml.lua b/plugins/language_toml.lua
index 2b7fd01..02e5b58 100644
--- a/plugins/language_toml.lua
+++ b/plugins/language_toml.lua
@@ -1,4 +1,4 @@
--- mod-version:2
+-- mod-version:3
local syntax = require "core.syntax"
diff --git a/plugins/language_ts.lua b/plugins/language_ts.lua
index 13c6ac2..f28261d 100644
--- a/plugins/language_ts.lua
+++ b/plugins/language_ts.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- copied from language_js, but added regex highlighting back
local syntax = require "core.syntax"
diff --git a/plugins/language_tsx.lua b/plugins/language_tsx.lua
index 473f808..beedbc6 100644
--- a/plugins/language_tsx.lua
+++ b/plugins/language_tsx.lua
@@ -1,5 +1,6 @@
--- mod-version:2 -- lite-xl 2.0
--- Almost identical to JS, with the exception that / shouldn't denote a regex. Current JS syntax highlighter will highlight half the document due to closing tags.
+-- mod-version:3
+-- Almost identical to JS, with the exception that / shouldn't denote a regex.
+-- Current JS syntax highlighter will highlight half the document due to closing tags.
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_v.lua b/plugins/language_v.lua
index 4afd3fd..d5b9764 100644
--- a/plugins/language_v.lua
+++ b/plugins/language_v.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/language_wren.lua b/plugins/language_wren.lua
index 9bd6c82..2022dbf 100644
--- a/plugins/language_wren.lua
+++ b/plugins/language_wren.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
@@ -22,19 +22,15 @@ syntax.add {
["class"] = "keyword",
["construct"] = "keyword",
["else"] = "keyword",
- ["false"] = "keyword",
["for"] = "keyword",
["foreign"] = "keyword",
["if"] = "keyword",
["import"] = "keyword",
["in"] = "keyword",
["is"] = "keyword",
- ["null"] = "keyword",
["return"] = "keyword",
["static"] = "keyword",
["super"] = "keyword",
- ["this"] = "keyword",
- ["true"] = "keyword",
["var"] = "keyword",
["while"] = "keyword",
["this"] = "keyword2",
diff --git a/plugins/language_yaml.lua b/plugins/language_yaml.lua
index a83e89f..8b7f634 100644
--- a/plugins/language_yaml.lua
+++ b/plugins/language_yaml.lua
@@ -1,53 +1,149 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
+local yaml_bracket_list = {
+ patterns = {
+ -- comments
+ { pattern = { "#", "\n"}, type = "comment" },
+ -- strings
+ { pattern = { '"', '"', '\\' }, type = "string" },
+ { pattern = { "'", "'", '\\' }, type = "string" },
+ -- keys
+ {
+ pattern = "[%w%d]+%g+()%s*():()%s",
+ type = { "keyword2", "normal", "operator", "normal" }
+ },
+ -- variables
+ { pattern = "%$%a%w+", type = "keyword" },
+ { pattern = "%$%{%{.-%}%}", type = "keyword" },
+ -- numeric place holders
+ { pattern = "%-?%.inf", type = "number" },
+ { pattern = "%.NaN", type = "number" },
+ -- numbers
+ { pattern = "[%+%-]?0%d+", type = "number" },
+ { pattern = "[%+%-]?0x%x+", type = "number" },
+ { pattern = "[%+%-]?%d+[,%.eE:%+%d]*%d+", type = "number" },
+ { pattern = "[%+%-]?%d+", type = "number" },
+ -- others
+ { pattern = ",", type = "operator" },
+ { pattern = "%w+", type = "string" },
+ {
+ pattern = "[_%(%)%*@~`!%%%^&=%+%-\\;%.><%?/%s]+",
+ type = "string"
+ }
+ },
+ symbols = {}
+}
+
syntax.add {
name = "YAML",
files = { "%.yml$", "%.yaml$" },
comment = "#",
+ space_handling = false,
patterns = {
+ --- rules that start with spaces first and those taking precedence
+ -- parent and child keys
+ {
+ pattern = "^[%w%d]+%g+%s*%f[:]",
+ type = "literal"
+ },
+ {
+ pattern = "^%s+[%w%d]+%g+%s*%f[:]",
+ type = "keyword2"
+ },
+ -- bracket lists after key declaration
+ {
+ pattern = { ":%s+%[", "%]" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ {
+ pattern = { ":%s+{", "}" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ -- child key
+ {
+ pattern = "^%s+()[%w%d]+%g+()%s*():()%s",
+ type = { "normal", "keyword2", "normal", "operator", "normal" }
+ },
+ -- child list element
+ {
+ pattern = "^%s+()%-()%s+()[%w%d]+%g+()%s*():()%s",
+ type = { "normal", "operator", "normal", "keyword2", "normal", "operator", "normal" }
+ },
+ -- unkeyed bracket lists
+ {
+ pattern = { "^%s*%[", "%]" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ {
+ pattern = { "^%s*{", "}" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ {
+ pattern = { "^%s*%-%s*%[", "%]" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ {
+ pattern = { "^%s*%-%s*{", "}" },
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ -- rule to optimize space handling
+ { pattern = "%s+", type = "normal" },
+ --- all the other rules
+ -- comments
{ pattern = { "#", "\n"}, type = "comment" },
+ -- strings
{ pattern = { '"', '"', '\\' }, type = "string" },
{ pattern = { "'", "'", '\\' }, type = "string" },
+ -- extra bracket lists rules on explicit type
+ {
+ pattern = { "!!%w+%s+%[", "%]"},
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ {
+ pattern = { "!!%w+%s+{", "}"},
+ syntax = yaml_bracket_list, type = "operator"
+ },
+ -- numeric place holders
{ pattern = "%-?%.inf", type = "number" },
{ pattern = "%.NaN", type = "number" },
+ -- parent list element
+ {
+ pattern = "^%-()%s+()[%w%d]+%g+()%s*():()%s",
+ type = { "operator", "normal", "keyword2", "normal", "operator", "normal" }
+ },
+ -- key label
{
pattern = "%&()%g+",
type = { "keyword", "literal" }
},
- { pattern = "!%g+", type = "keyword" },
+ -- key elements expansion
{ pattern = "<<", type = "literal" },
{
- pattern = "[%s]%*()[%w%d_]+",
- type = { "keyword", "keyword2" }
- },
- {
pattern = "%*()[%w%d_]+",
type = { "keyword", "literal" }
},
+ -- explicit data types
+ { pattern = "!!%g+", type = "keyword" },
+ -- parent key
{
- pattern = "[%[%{]()%s*[%w%d]+%g+%s*():%s",
- type = { "operator", "keyword2", "operator" }
+ pattern = "^[%w%d]+%g+()%s*():()%s",
+ type = { "literal", "normal", "operator", "normal" }
},
- {
- pattern = "[%s][%w%d]+%g+%s*():%s",
- type = { "keyword2", "operator" }
- },
- {
- pattern = "[%w%d]+%g+%s*():%s",
- type = { "literal", "operator" }
- },
- { pattern = "0%d+", type = "number" },
- { pattern = "0x%x+", type = "number" },
+ -- variables
+ { pattern = "%$%a%w+", type = "keyword" },
+ { pattern = "%$%{%{.-%}%}", type = "keyword" },
+ -- numbers
+ { pattern = "[%+%-]?0%d+", type = "number" },
+ { pattern = "[%+%-]?0x%x+", type = "number" },
{ pattern = "[%+%-]?%d+[,%.eE:%+%d]*%d+", type = "number" },
+ { pattern = "[%+%-]?%d+", type = "number" },
+ -- special operators
{ pattern = "[%*%|%!>%%]", type = "keyword" },
- { pattern = "[%-:%?%*%{%}%[%]]", type = "operator" },
- -- Everything else for keywords to work
- {
- pattern = "[%d%a_][%g_]*()[%]%},]",
- type = { "string", "operator" }
- },
+ { pattern = "[%-%$:%?]+", type = "operator" },
+ -- Everything else as a string
{ pattern = "[%d%a_][%g_]*", type = "string" },
+ { pattern = "%p+", type = "string" }
},
symbols = {
["true"] = "number",
diff --git a/plugins/language_zig.lua b/plugins/language_zig.lua
index 192b114..f2c32d5 100644
--- a/plugins/language_zig.lua
+++ b/plugins/language_zig.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local syntax = require "core.syntax"
syntax.add {
diff --git a/plugins/lfautoinsert.lua b/plugins/lfautoinsert.lua
index a156964..ae3da75 100644
--- a/plugins/lfautoinsert.lua
+++ b/plugins/lfautoinsert.lua
@@ -1,11 +1,11 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local keymap = require "core.keymap"
-config.plugins.lfautoinsert = { map = {
+config.plugins.lfautoinsert = common.merge({ map = {
["{%s*\n"] = "}",
["%(%s*\n"] = ")",
["%f[[]%[%s*\n"] = "]",
@@ -37,7 +37,7 @@ config.plugins.lfautoinsert = { map = {
["%[%[%s*\n"] = "]]"
}
},
-} }
+} }, config.plugins.lfautoinsert)
local function get_autoinsert_map(filename)
local map = {}
@@ -64,7 +64,7 @@ local function indent_size(doc, line)
return e - s
end
-command.add("core.docview", {
+command.add("core.docview!", {
["autoinsert:newline"] = function()
command.perform("doc:newline")
@@ -97,7 +97,7 @@ command.add("core.docview", {
})
keymap.add {
- ["return"] = { "command:submit", "autoinsert:newline" }
+ ["return"] = { "autoinsert:newline" }
}
return {
diff --git a/plugins/linecopypaste.lua b/plugins/linecopypaste.lua
deleted file mode 100644
index ea2a84c..0000000
--- a/plugins/linecopypaste.lua
+++ /dev/null
@@ -1,50 +0,0 @@
--- mod-version:2 -- lite-xl 2.0
-local core = require "core"
-local command = require "core.command"
-
-local function doc()
- return core.active_view.doc
-end
-
-local line_in_clipboard = false
-
-local doc_copy = command.map["doc:copy"].perform
-command.map["doc:copy"].perform = function()
- if doc():has_selection() then
- doc_copy()
- line_in_clipboard = false
- else
- local line = doc():get_selection()
- system.set_clipboard(doc().lines[line])
- line_in_clipboard = true
- end
-end
-
-local doc_cut = command.map["doc:cut"].perform
-command.map["doc:cut"].perform = function()
- if doc():has_selection() then
- doc_cut()
- line_in_clipboard = false
- else
- local line = doc():get_selection()
- system.set_clipboard(doc().lines[line])
- if line < #(doc().lines) then
- doc():remove(line, 1, line+1, 1)
- else -- last line in file
- doc():remove(line, 1, line, #(doc().lines[line]))
- end
- doc():set_selection(line, 1)
- line_in_clipboard = true
- end
-end
-
-local doc_paste = command.map["doc:paste"].perform
-command.map["doc:paste"].perform = function()
- if line_in_clipboard == false then
- doc_paste()
- else
- local line, col = doc():get_selection()
- doc():insert(line, 1, system.get_clipboard():gsub("\r", ""))
- doc():set_selection(line+1, col)
- end
-end
diff --git a/plugins/linenumbers.lua b/plugins/linenumbers.lua
index 1b0bdc6..c075776 100644
--- a/plugins/linenumbers.lua
+++ b/plugins/linenumbers.lua
@@ -1,85 +1,116 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
local common = require "core.common"
local command = require "core.command"
-local draw = DocView.draw_line_gutter
-local get_width = DocView.get_gutter_width
+config.plugins.linenumbers = common.merge({
+ show = true,
+ relative = false,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Line Numbers",
+ {
+ label = "Show Numbers",
+ description = "Display or hide the line numbers.",
+ path = "show",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Relative Line Numbers",
+ description = "Display relative line numbers starting from active line.",
+ path = "relative",
+ type = "toggle",
+ default = false
+ }
+ }
+}, config.plugins.linenumbers)
-function DocView:draw_line_gutter(idx, x, y, width)
- if not config.line_numbers and not config.relative_line_numbers then
- return
- end
+local draw_line_gutter = DocView.draw_line_gutter
+local get_gutter_width = DocView.get_gutter_width
- if config.relative_line_numbers then
+function DocView:draw_line_gutter(line, x, y, width)
+ local lh = self:get_line_height()
+ if not config.plugins.linenumbers.show then
+ return lh
+ end
+ if config.plugins.linenumbers.relative then
local color = style.line_number
- local local_idx = idx
- local align = "right"
+ local local_idx = line
- local l1 = self.doc:get_selection(false)
- if idx == l1 then
- color = style.line_number2
- if config.line_numbers then
- align = "center"
- else
- local_idx = 0
+ for _, line1, _, line2 in self.doc:get_selections(true) do
+ if line >= line1 and line <= line2 then
+ color = style.line_number2
+ break
end
- else
- local_idx = math.abs(idx - l1)
end
- -- Fix for old version (<=1.16)
- if width == nil then
- local gpad = style.padding.x * 2
- local gw = self:get_font():get_width(#self.doc.lines) + gpad
- width = gpad and gw - gpad or gw
+ local l1 = self.doc:get_selection(false)
+ if line == l1 then
+ color = style.line_number2
+ local_idx = 0
+ else
+ local_idx = math.abs(line - l1)
end
common.draw_text(
self:get_font(),
- color, local_idx, align,
+ color, local_idx, "right",
x + style.padding.x,
- y + self:get_line_text_y_offset(),
- width, self:get_line_height()
+ y,
+ width, lh
)
else
- draw(self, idx, x, y, width)
+ return draw_line_gutter(self, line, x, y, width)
end
+ return lh
end
function DocView:get_gutter_width(...)
- if not config.line_numbers and not config.relative_line_numbers then
- return style.padding.x
+ if
+ not config.plugins.linenumbers.show
+ then
+ local width = get_gutter_width(self, ...)
+
+ local correct_width = self:get_font():get_width(#self.doc.lines)
+ + (style.padding.x * 2)
+
+ -- compatibility with center doc
+ if width <= correct_width then
+ width = style.padding.x
+ end
+
+ return width, 0
else
- return get_width(self, ...)
+ return get_gutter_width(self, ...)
end
end
command.add(nil, {
["line-numbers:toggle"] = function()
- config.line_numbers = not config.line_numbers
+ config.plugins.linenumbers.show = not config.plugins.linenumbers.show
end,
["line-numbers:disable"] = function()
- config.line_numbers = false
+ config.plugins.linenumbers.show = false
end,
["line-numbers:enable"] = function()
- config.line_numbers = true
+ config.plugins.linenumbers.show = true
end,
["relative-line-numbers:toggle"] = function()
- config.relative_line_numbers = not config.relative_line_numbers
+ config.plugins.linenumbers.relative = not config.plugins.linenumbers.relative
end,
["relative-line-numbers:enable"] = function()
- config.relative_line_numbers = true
+ config.plugins.linenumbers.relative = true
end,
["relative-line-numbers:disable"] = function()
- config.relative_line_numbers = false
+ config.plugins.linenumbers.relative = false
end
})
diff --git a/plugins/macmodkeys.lua b/plugins/macmodkeys.lua
index d8c0d05..e4656b9 100644
--- a/plugins/macmodkeys.lua
+++ b/plugins/macmodkeys.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local keymap = require "core.keymap"
local on_key_pressed = keymap.on_key_pressed
diff --git a/plugins/markers.lua b/plugins/markers.lua
index ad89fad..e2ec68e 100644
--- a/plugins/markers.lua
+++ b/plugins/markers.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Markers plugin for lite text editor
-- original implementation by Petri Häkkinen
@@ -52,12 +52,12 @@ end
local draw_line_gutter = DocView.draw_line_gutter
-function DocView:draw_line_gutter(idx, x, y, width)
- if cache[self.doc] and cache[self.doc][idx] then
+function DocView:draw_line_gutter(line, x, y, width)
+ if cache[self.doc] and cache[self.doc][line] then
local h = self:get_line_height()
renderer.draw_rect(x, y, style.padding.x * 0.4, h, style.selection)
end
- draw_line_gutter(self, idx, x, y, width)
+ return draw_line_gutter(self, line, x, y, width)
end
diff --git a/plugins/memoryusage.lua b/plugins/memoryusage.lua
index fcdcc29..290b4e1 100644
--- a/plugins/memoryusage.lua
+++ b/plugins/memoryusage.lua
@@ -1,19 +1,49 @@
--- mod-version:2 -- lite-xl 2.0
+-- 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"
-local get_items = StatusView.get_items
-
-function StatusView:get_items()
- local left, right = get_items(self)
- local t = {
- style.text, (math.floor(collectgarbage("count") / 10.24) / 100) .. " MB",
- style.dim, self.separator2,
+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
+ }
}
- for i, item in ipairs(t) do
- table.insert(right, i, item)
- end
- return left, right
-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
+})
diff --git a/plugins/minimap.lua b/plugins/minimap.lua
index 4bf4fca..c2dd8f0 100644
--- a/plugins/minimap.lua
+++ b/plugins/minimap.lua
@@ -1,382 +1,666 @@
--- mod-version:2
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local common = require "core.common"
local config = require "core.config"
local style = require "core.style"
local DocView = require "core.docview"
+local Highlighter = require "core.doc.highlighter"
local Object = require "core.object"
+local Scrollbar = require "core.scrollbar"
+
+-- Sample configurations:
+-- full width:
+-- config.plugins.minimap.highlight_width = 100
+-- config.plugins.minimap.gutter_width = 0
+-- left side:
+-- config.plugins.minimap.highlight_align = 'left'
+-- config.plugins.minimap.highlight_width = 3
+-- config.plugins.minimap.gutter_width = 4
+-- right side:
+-- config.plugins.minimap.highlight_align = 'right'
+-- config.plugins.minimap.highlight_width = 5
+-- config.plugins.minimap.gutter_width = 0
-- General plugin settings
-config.plugins.minimap = {
- enabled = true,
- width = 100,
- instant_scroll = false,
- syntax_highlight = true,
- scale = 1,
- -- how many spaces one tab is equivalent to
- tab_width = 4,
- draw_background = true,
-
- -- you can override these colors
- selection_color = nil,
- caret_color = nil,
-
- -- If other plugins provide per-line highlights,
- -- this controls the placement. (e.g. gitdiff_highlight)
- highlight_align = 'left',
- highlight_width = 3,
- gutter_width = 5,
- -- try these values:
- -- full width:
- -- config.plugins.minimap.highlight_width = 100
- -- config.plugins.minimap.gutter_width = 0
- -- left side:
- -- config.plugins.minimap.highlight_align = 'left'
- -- config.plugins.minimap.highlight_width = 3
- -- config.plugins.minimap.gutter_width = 4
- -- right side:
- -- config.plugins.minimap.highlight_align = 'right'
- -- config.plugins.minimap.highlight_width = 5
- -- config.plugins.minimap.gutter_width = 0
+config.plugins.minimap = common.merge({
+ enabled = true,
+ width = 100,
+ instant_scroll = false,
+ syntax_highlight = true,
+ scale = 1,
+ -- number of spaces needed to split a token
+ spaces_to_split = 2,
+ -- hide on small docs (can be true, false or min number of lines)
+ avoid_small_docs = false,
+ -- how many spaces one tab is equivalent to
+ tab_width = 4,
+ draw_background = true,
+ -- you can override these colors
+ selection_color = nil,
+ caret_color = nil,
+ -- If other plugins provide per-line highlights,
+ -- this controls the placement. (e.g. gitdiff_highlight)
+ highlight_align = 'left',
+ highlight_width = 3,
+ gutter_width = 5,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Mini Map",
+ {
+ label = "Enabled",
+ description = "Activate the minimap by default.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Width",
+ description = "Width of the minimap in pixels.",
+ path = "width",
+ type = "number",
+ default = 100,
+ min = 50,
+ max = 1000
+ },
+ {
+ label = "Instant Scroll",
+ description = "When enabled disables the scrolling animation.",
+ path = "instant_scroll",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Syntax Highlighting",
+ description = "Disable to improve performance.",
+ path = "syntax_highlight",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Scale",
+ description = "Size of the minimap using a scaling factor.",
+ path = "scale",
+ type = "number",
+ default = 1,
+ min = 0.5,
+ max = 10,
+ step = 0.1
+ },
+ {
+ label = "Spaces to split",
+ description = "Number of spaces needed to split a token.",
+ path = "spaces_to_split",
+ type = "number",
+ default = 2,
+ min = 1
+ },
+ {
+ label = "Hide for small Docs",
+ description = "Hide the minimap when a Doc is small enough.",
+ path = "avoid_small_docs",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Small Docs definition",
+ description = "Size of a Doc to be considered small. Use 0 to automatically decide.",
+ path = "avoid_small_docs_len",
+ type = "number",
+ default = 0,
+ min = 0,
+ on_apply = function(value)
+ if value == 0 then
+ config.plugins.minimap.avoid_small_docs = true
+ else
+ config.plugins.minimap.avoid_small_docs = value
+ end
+ end
+ },
+ {
+ label = "Tabs Width",
+ description = "The amount of spaces that represent a tab.",
+ path = "tab_width",
+ type = "number",
+ default = 4,
+ min = 1,
+ max = 8
+ },
+ {
+ label = "Draw Background",
+ description = "When disabled makes the minimap transparent.",
+ path = "draw_background",
+ type = "toggle",
+ default = true
+ },
+ {
+ 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
+ },
+ {
+ 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
+ },
+ {
+ label = "Highlight Alignment",
+ path = "highlight_align",
+ type = "selection",
+ default = "left",
+ values = {
+ {"Left", "left"},
+ {"Right", "right"}
+ }
+ },
+ {
+ label = "Highlight Width",
+ path = "highlight_width",
+ type = "number",
+ default = 3,
+ min = 0,
+ max = 50
+ },
+ {
+ label = "Gutter Width",
+ description = "Left padding of the minimap.",
+ path = "gutter_width",
+ type = "number",
+ default = 5,
+ min = 0,
+ max = 50
+ },
+ }
+}, config.plugins.minimap)
+
+
+-- contains the settings values that require a cache reset if changed
+local cached_settings = {
+ color_scheme_canary = nil,
+ syntax_highlight = nil,
+ spaces_to_split = nil,
+ scale = nil,
+ width = nil,
}
-- Configure size for rendering each char in the minimap
-local char_height = 1 * SCALE * config.plugins.minimap.scale
-local char_spacing = 0.8 * SCALE * config.plugins.minimap.scale
-local line_spacing = 2 * SCALE * config.plugins.minimap.scale
+local char_spacing
+local char_height
+local line_spacing
+
+-- cache for the location of the rects for each Doc
+local highlighter_cache
+local function reset_cache()
+ highlighter_cache = setmetatable({}, { __mode = "k" })
+ cached_settings = {
+ color_scheme_canary = style.syntax["normal"],
+ syntax_highlight = config.plugins.minimap.syntax_highlight,
+ spaces_to_split = config.plugins.minimap.spaces_to_split,
+ scale = config.plugins.minimap.scale,
+ width = config.plugins.minimap.width,
+ }
+ char_spacing = 0.8 * SCALE * config.plugins.minimap.scale
+ -- keep y aligned to pixels
+ char_height = math.max(1, math.floor(1 * SCALE * config.plugins.minimap.scale + 0.5))
+ line_spacing = math.max(1, math.floor(2 * SCALE * config.plugins.minimap.scale + 0.5))
+end
+reset_cache()
+
+
+local function reset_cache_if_needed()
+ if
+ cached_settings.color_scheme_canary ~= style.syntax["normal"]
+ or cached_settings.syntax_highlight ~= config.plugins.minimap.syntax_highlight
+ or cached_settings.spaces_to_split ~= config.plugins.minimap.spaces_to_split
+ or cached_settings.scale ~= config.plugins.minimap.scale
+ or cached_settings.width ~= config.plugins.minimap.width
+ then
+ reset_cache()
+ end
+end
+
+
+
+
+-- Move cache to make space for new lines
+local prev_insert_notify = Highlighter.insert_notify
+function Highlighter:insert_notify(line, n, ...)
+ prev_insert_notify(self, line, n, ...)
+ local blanks = { }
+ 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
+ end
+ end
+end
+
+
+-- Close the cache gap created by removed lines
+local prev_remove_notify = Highlighter.remove_notify
+function Highlighter:remove_notify(line, n, ...)
+ prev_remove_notify(self, 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
+ end
+end
+
+
+-- Remove changed lines from the cache
+local prev_tokenize_line = Highlighter.tokenize_line
+function Highlighter:tokenize_line(idx, state, ...)
+ local res = prev_tokenize_line(self, idx, state, ...)
+ if not highlighter_cache[self] then
+ highlighter_cache[self] = {}
+ end
+ highlighter_cache[self][idx] = nil
+ return res
+end
+
+-- Ask the Highlighter to retokenize the lines we have in cache
+local prev_invalidate = Highlighter.invalidate
+function Highlighter:invalidate(idx, ...)
+ local cache = highlighter_cache[self]
+ if cache then
+ self.max_wanted_line = math.max(self.max_wanted_line, #cache)
+ end
+ return prev_invalidate(self, idx, ...)
+end
+
+
+-- Remove cache on Highlighter reset (for example on syntax change)
+local prev_soft_reset = Highlighter.soft_reset
+function Highlighter:soft_reset(...)
+ prev_soft_reset(self, ...)
+ highlighter_cache[self] = {}
+end
+
+
+local MiniMap = Scrollbar:extend()
-local MiniMap = Object:extend()
-function MiniMap:new()
+function MiniMap:new(dv)
+ MiniMap.super.new(self, { direction = "v", alignment = "e" })
+ self.dv = dv
+ self.enabled = nil
end
+
function MiniMap:line_highlight_color(line_index)
- -- other plugins can override this, and return a color
+ -- other plugins can override this, and return a color
end
-local minimap = MiniMap()
-local function show_minimap()
- return config.plugins.minimap.enabled
- and getmetatable(core.active_view) == DocView
- and core.active_view ~= core.command_view
- and core.active_view.doc
+function MiniMap:is_minimap_enabled()
+ if self.enabled ~= nil then return self.enabled end
+ if not config.plugins.minimap.enabled then return false end
+ if config.plugins.minimap.avoid_small_docs then
+ local last_line = #self.dv.doc.lines
+ if type(config.plugins.minimap.avoid_small_docs) == "number" then
+ return last_line > config.plugins.minimap.avoid_small_docs
+ else
+ local docview = self.dv
+ local _, y = docview:get_line_screen_position(last_line, docview.doc.lines[last_line])
+ y = y + docview.scroll.y - docview.position.y + docview:get_line_height()
+ return y > docview.size.y
+ end
+ end
+ return true
+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)
+ end
+ return visible_lines_start, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large
end
--- Overloaded since the default implementation adds a extra x3 size of hotspot for the mouse to hit the scrollbar.
-local prev_scrollbar_overlaps_point = DocView.scrollbar_overlaps_point
-DocView.scrollbar_overlaps_point = function(self, x, y)
- if not show_minimap() then
- return prev_scrollbar_overlaps_point(self, x, y)
- end
- local sx, sy, sw, sh = self:get_scrollbar_rect()
- return x >= sx and x < sx + sw and y >= sy and y < sy + sh
+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
end
--- Helper function to determine if current file is too large to be shown fully inside the minimap area.
-local function is_file_too_large(self)
- local line_count = #self.doc.lines
- local _, _, _, sh = self:get_scrollbar_rect()
- -- check if line count is too large to fit inside the minimap area
- local max_minmap_lines = math.floor(sh / line_spacing)
- return line_count > 1 and line_count > max_minmap_lines
+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_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
+ 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
end
--- Overloaded with an extra check if the user clicked inside the minimap to automatically scroll to that line.
-local prev_on_mouse_pressed = DocView.on_mouse_pressed
-DocView.on_mouse_pressed = function(self, button, x, y, clicks)
- if not show_minimap() then
- return prev_on_mouse_pressed(self, button, x, y, clicks)
- end
-
- -- check if user clicked in the minimap area and jump directly to that line
- -- unless they are actually trying to perform a drag
- local minimap_hit = self:scrollbar_overlaps_point(x, y)
- if minimap_hit then
- local line_count = #self.doc.lines
- local minimap_height = line_count * line_spacing
-
- -- check if line count is too large to fit inside the minimap area
- local is_too_large = is_file_too_large(self)
- if is_too_large then
- local _, _, _, sh = self:get_scrollbar_rect()
- minimap_height = sh
- end
-
- -- calc which line to jump to
- local dy = y - self.position.y
- local jump_to_line = math.floor((dy / minimap_height) * line_count) + 1
-
- local _, cy, _, cy2 = self:get_content_bounds()
- local lh = self:get_line_height()
- local visible_lines_count = math.max(1, (cy2 - cy) / lh)
- local visible_lines_start = math.max(1, math.floor(cy / lh))
-
- -- calc if user hit the currently visible area
- local hit_visible_area = true
- if is_too_large then
-
- local visible_height = visible_lines_count * line_spacing
- local scroll_pos = (visible_lines_start - 1) /
- (line_count - visible_lines_count - 1)
- scroll_pos = math.min(1.0, scroll_pos) -- 0..1
- local visible_y = self.position.y + scroll_pos *
- (minimap_height - visible_height)
-
- local t = (line_count - visible_lines_start) / visible_lines_count
- if t <= 1 then visible_y = visible_y + visible_height * (1.0 - t) end
-
- if y < visible_y or y > visible_y + visible_height then
- hit_visible_area = false
- end
- else
-
- -- If the click is on the currently visible line numbers,
- -- ignore it since then they probably want to initiate a drag instead.
- if jump_to_line < visible_lines_start or jump_to_line > visible_lines_start +
- visible_lines_count then hit_visible_area = false end
- end
-
- -- if user didn't click on the visible area (ie not dragging), scroll accordingly
- if not hit_visible_area then
- self:scroll_to_line(jump_to_line, false, config.plugins.minimap.instant_scroll)
- end
-
- end
-
- return prev_on_mouse_pressed(self, button, x, y, clicks)
+
+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)
end
--- Overloaded with pretty much the same logic as original DocView implementation,
--- with the exception of the dragging scrollbar delta. We want it to behave a bit snappier
--- since the "scrollbar" essentially represents the lines visible in the content view.
-local prev_on_mouse_moved = DocView.on_mouse_moved
-DocView.on_mouse_moved = function(self, x, y, dx, dy)
- if not show_minimap() then
- return prev_on_mouse_moved(self, x, y, dx, dy)
- end
-
- if self.dragging_scrollbar then
- local line_count = #self.doc.lines
- local lh = self:get_line_height()
- local delta = lh / line_spacing * dy
-
- if is_file_too_large(self) then
- local _, sy, _, sh = self:get_scrollbar_rect()
- delta = (line_count * lh) / sh * dy
- end
-
- self.scroll.to.y = self.scroll.to.y + delta
- end
-
- -- we need to "hide" that the scrollbar is dragging so that View doesnt does its own scrolling logic
- local t = self.dragging_scrollbar
- self.dragging_scrollbar = false
- local r = prev_on_mouse_moved(self, x, y, dx, dy)
- self.dragging_scrollbar = t
- return r
+
+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
--- Overloaded since we want the mouse to interact with the full size of the minimap area,
--- not juse the scrollbar.
-local prev_get_scrollbar_rect = DocView.get_scrollbar_rect
-DocView.get_scrollbar_rect = function(self)
- if not show_minimap() then return prev_get_scrollbar_rect(self) 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
- return self.position.x + self.size.x - config.plugins.minimap.width * SCALE,
- self.position.y, config.plugins.minimap.width * SCALE, self.size.y
+function MiniMap:draw()
+ if not self:is_minimap_enabled() then return MiniMap.super.draw(self) end
+ local dv = self.dv
+ local x, y, w, h = self:get_track_rect()
+
+ 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)
+ end
+ self:draw_thumb()
+
+ -- 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
+ local selection_min_y = math.min(selection1_y, selection2_y)
+ local selection_h = math.abs(selection2_y - selection1_y)+1
+ renderer.draw_rect(x, selection_min_y, w, selection_h, selection_color)
+ renderer.draw_rect(x, selection1_y, w, line_spacing, caret_color)
+ end
+
+ local highlight_align = config.plugins.minimap.highlight_align
+ local highlight_width = config.plugins.minimap.highlight_width
+ local gutter_width = config.plugins.minimap.gutter_width
+
+ -- time to draw the actual code, setup some local vars that are used in both highlighted and plain rendering.
+ local line_y = y
+
+ -- when not using syntax highlighted rendering, just use the normal color but dim it 50%.
+ local color = style.syntax["normal"]
+ color = {color[1], color[2], color[3], color[4] * 0.5}
+
+ -- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
+ local batch_width = 0
+ local batch_start = x
+ local last_batch_end = -1
+ local minimap_cutoff_x = config.plugins.minimap.width * SCALE
+ local batch_syntax_type = nil
+ local function flush_batch(type, cache)
+ if batch_width > 0 then
+ local lastidx = #cache
+ local old_color = color
+ color = style.syntax[type]
+ if config.plugins.minimap.syntax_highlight and color ~= nil then
+ -- fetch and dim colors
+ color = {color[1], color[2], color[3], (color[4] or 255) * 0.5}
+ else
+ color = old_color
+ end
+ if #cache >= 3 then
+ local last_color = cache[lastidx]
+ if
+ last_batch_end == batch_start -- no space skipped
+ and (
+ batch_syntax_type == type -- and same syntax
+ or ( -- or same color
+ last_color[1] == color[1]
+ and last_color[2] == color[2]
+ and last_color[3] == color[3]
+ and last_color[4] == color[4]
+ )
+ )
+ then
+ batch_start = cache[lastidx - 2]
+ batch_width = cache[lastidx - 1] + batch_width
+ lastidx = lastidx - 3
+ end
+ end
+ cache[lastidx + 1] = batch_start
+ cache[lastidx + 2] = batch_width
+ cache[lastidx + 3] = color
+ end
+ batch_syntax_type = type
+ batch_start = batch_start + batch_width
+ last_batch_end = batch_start
+ batch_width = 0
+ end
+
+ local highlight_x
+ if highlight_align == 'left' then
+ highlight_x = x
+ else
+ highlight_x = x + w - highlight_width
+ end
+ local function render_highlight(idx, line_y)
+ local highlight_color = self:line_highlight_color(idx)
+ if highlight_color then
+ renderer.draw_rect(highlight_x, line_y, highlight_width, line_spacing, 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
+
+ -- per line
+ for idx = minimap_lines_start, endidx do
+ batch_syntax_type = nil
+ batch_start = 0
+ batch_width = 0
+ last_batch_end = -1
+
+ render_highlight(idx, line_y)
+ local cache = highlighter_cache[dv.doc.highlighter][idx]
+ if not highlighter_cache[dv.doc.highlighter][idx] then -- need to cache
+ highlighter_cache[dv.doc.highlighter][idx] = {}
+ cache = highlighter_cache[dv.doc.highlighter][idx]
+ -- per token
+ for _, type, text in dv.doc.highlighter:each_token(idx) do
+ if not config.plugins.minimap.syntax_highlight then
+ type = nil
+ end
+ local start = 1
+ while true do
+ -- find text followed spaces followed by newline
+ local s, e, w, eol = string.ufind(text, "[^%s]*()[ \t]*()\n?", start)
+ if not s then break end
+ local nchars = w - s
+ start = e + 1
+ batch_width = batch_width + char_spacing * nchars
+
+ local nspaces = 0
+ for i=w,e do
+ local whitespace = string.sub(text, i, i)
+ if whitespace == "\t" then
+ nspaces = nspaces + config.plugins.minimap.tab_width
+ elseif whitespace == " " then
+ nspaces = nspaces + 1
+ end
+ end
+ -- not enough spaces; consider them part of the batch
+ if nspaces < config.plugins.minimap.spaces_to_split then
+ batch_width = batch_width + nspaces * char_spacing
+ end
+ -- line has ended or no more space in the minimap;
+ -- we can go to the next line
+ if eol <= w or batch_start + batch_width > minimap_cutoff_x then
+ if batch_width > 0 then
+ flush_batch(type, cache)
+ end
+ break
+ end
+ -- enough spaces to split the batch
+ if nspaces >= config.plugins.minimap.spaces_to_split then
+ flush_batch(type, cache)
+ batch_start = batch_start + nspaces * char_spacing
+ end
+ end
+ end
+ end
+ -- draw from cache
+ for i=1,#cache,3 do
+ local batch_start = cache[i ] + x + gutter_width
+ local batch_width = cache[i + 1]
+ local color = cache[i + 2]
+ renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
+ end
+ line_y = line_y + line_spacing
+ end
end
--- Overloaded so we can render the minimap in the "scrollbar area".
-local prev_draw_scrollbar = DocView.draw_scrollbar
-DocView.draw_scrollbar = function(self)
- if not show_minimap() then return prev_draw_scrollbar(self) end
-
- local x, y, w, h = self:get_scrollbar_rect()
-
- local highlight = self.hovered_scrollbar or self.dragging_scrollbar
- local visual_color = highlight and style.scrollbar2 or style.scrollbar
-
- local _, cy, _, cy2 = self:get_content_bounds()
- local lh = self:get_line_height()
- local visible_lines_count = math.max(1, (cy2 - cy) / lh)
- local visible_lines_start = math.max(1, math.floor(cy / lh))
- local scroller_height = visible_lines_count * line_spacing
- local line_count = #self.doc.lines
-
- local visible_y = self.position.y + (visible_lines_start - 1) * line_spacing
-
- -- check if file is too large to fit inside the minimap area
- local max_minmap_lines = math.floor(h / line_spacing)
- local minimap_start_line = 1
- if is_file_too_large(self) 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 scroll_pos_pixels = scroll_pos * (h - scroller_height)
- visible_y = self.position.y + scroll_pos_pixels
-
- -- offset visible area if user is scrolling past end
- local t = (line_count - visible_lines_start) / visible_lines_count
- if t <= 1 then visible_y = visible_y + scroller_height * (1.0 - t) end
-
- minimap_start_line = visible_lines_start -
- math.floor(scroll_pos_pixels / line_spacing)
- minimap_start_line = math.max(1, math.min(minimap_start_line,
- line_count - max_minmap_lines))
- end
-
- if config.plugins.minimap.draw_background then
- renderer.draw_rect(x, y, w, h, style.minimap_background or style.background)
- end
- -- draw visual rect
- renderer.draw_rect(x, visible_y, w, scroller_height, visual_color)
-
- -- 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
- local selection_line, selection_col, selection_line2, selection_col2 = self.doc:get_selection()
- local selection_y = y + (selection_line - minimap_start_line) * line_spacing
- local selection2_y = y + (selection_line2 - minimap_start_line) * line_spacing
- local selection_min_y = math.min(selection_y, selection2_y)
- local selection_h = math.abs(selection2_y - selection_y)+1
- renderer.draw_rect(x, selection_min_y, w, selection_h, selection_color)
- renderer.draw_rect(x, selection_y, w, line_spacing, caret_color)
-
- local highlight_align = config.plugins.minimap.highlight_align
- local highlight_width = config.plugins.minimap.highlight_width
- local gutter_width = config.plugins.minimap.gutter_width
-
- -- time to draw the actual code, setup some local vars that are used in both highlighted and plain renderind.
- local line_y = y
-
- -- when not using syntax highlighted rendering, just use the normal color but dim it 50%.
- local color = style.syntax["normal"]
- color = {color[1], color[2], color[3], color[4] * 0.5}
-
- -- we try to "batch" characters so that they can be rendered as just one rectangle instead of one for each.
- local batch_width = 0
- local batch_start = x
- local minimap_cutoff_x = x + config.plugins.minimap.width * SCALE
- local batch_syntax_type = nil
- local function flush_batch(type)
- local old_color = color
- color = style.syntax[batch_syntax_type]
- if config.plugins.minimap.syntax_highlight and color ~= nil then
- -- fetch and dim colors
- color = {color[1], color[2], color[3], color[4] * 0.5}
- else
- color = old_color
- end
- if batch_width > 0 then
- renderer.draw_rect(batch_start, line_y, batch_width, char_height, color)
- end
- batch_syntax_type = type
- batch_start = batch_start + batch_width
- batch_width = 0
- end
-
- local highlight_x
- if highlight_align == 'left' then
- highlight_x = x
- else
- highlight_x = x + w - highlight_width
- end
- local function render_highlight(idx, line_y)
- local highlight_color = minimap:line_highlight_color(idx)
- if highlight_color then
- renderer.draw_rect(highlight_x, line_y, highlight_width, line_spacing, highlight_color)
- end
- end
-
- -- render lines with syntax highlighting
- if config.plugins.minimap.syntax_highlight then
-
- -- keep track of the highlight type, since this needs to break batches as well
- batch_syntax_type = nil
-
- -- per line
- local endidx = minimap_start_line + max_minmap_lines
- endidx = math.min(endidx, line_count)
- for idx = minimap_start_line, endidx do
- batch_syntax_type = nil
- batch_start = x + gutter_width
- batch_width = 0
-
- render_highlight(idx, line_y)
-
- -- per token
- for _, type, text in self.doc.highlighter:each_token(idx) do
- -- flush prev batch
- if not batch_syntax_type then batch_syntax_type = type end
- if batch_syntax_type ~= type then flush_batch(type) end
-
- -- per character
- for char in common.utf8_chars(text) do
- if char == " " or char == "\n" then
- flush_batch(type)
- batch_start = batch_start + char_spacing
- elseif char == " " then
- flush_batch(type)
- batch_start = batch_start + (char_spacing * config.plugins.minimap.tab_width)
- elseif batch_start + batch_width > minimap_cutoff_x then
- flush_batch(type)
- break
- else
- batch_width = batch_width + char_spacing
- end
-
- end
- end
- flush_batch(nil)
- line_y = line_y + line_spacing
- end
-
- else -- render lines without syntax highlighting
- for idx = 1, line_count - 1 do
- batch_start = x + gutter_width
- batch_width = 0
-
- render_highlight(idx, line_y)
-
- for char in common.utf8_chars(self.doc.lines[idx]) do
- if char == " " or char == "\n" then
- flush_batch()
- batch_start = batch_start + char_spacing
- elseif batch_start + batch_width > minimap_cutoff_x then
- flush_batch()
- else
- batch_width = batch_width + char_spacing
- end
- end
- flush_batch()
- line_y = line_y + line_spacing
- end
-
- 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
+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
-local prev_update = DocView.update
-DocView.update = function (self)
- if not show_minimap() then return prev_update(self) end
- self.size.x = self.size.x - config.plugins.minimap.width * SCALE
- return prev_update(self)
+
+local function get_all_docviews(node, t)
+ t = t or {}
+ if not node then return end
+ if node.type == "leaf" then
+ for i,v in ipairs(node.views) do
+ if v:is(DocView) then
+ table.insert(t, v)
+ end
+ end
+ end
+ get_all_docviews(node.a, t)
+ get_all_docviews(node.b, t)
+ return t
end
+
command.add(nil, {
- ["minimap:toggle-visibility"] = function()
- config.plugins.minimap.enabled = not config.plugins.minimap.enabled
- end,
- ["minimap:toggle-syntax-highlighting"] = function()
- config.plugins.minimap.syntax_highlight = not config.plugins.minimap.syntax_highlight
- end
+ ["minimap:toggle-visibility"] = function()
+ config.plugins.minimap.enabled = not config.plugins.minimap.enabled
+ for i,v in ipairs(get_all_docviews(core.root_view.root_node)) do
+ v.v_scrollbar.enabled = nil
+ end
+ end,
+ ["minimap:toggle-syntax-highlighting"] = function()
+ config.plugins.minimap.syntax_highlight = not config.plugins.minimap.syntax_highlight
+ end
+})
+
+command.add("core.docview!", {
+ ["minimap:toggle-visibility-for-current-view"] = function()
+ local sb = core.active_view.v_scrollbar
+ if sb.enabled ~= nil then
+ sb.enabled = not sb.enabled
+ else
+ sb.enabled = not config.plugins.minimap.enabled
+ end
+ end
})
-return minimap
+return MiniMap
diff --git a/plugins/motiontrail.lua b/plugins/motiontrail.lua
index 1359c90..16e7307 100644
--- a/plugins/motiontrail.lua
+++ b/plugins/motiontrail.lua
@@ -1,10 +1,34 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
+local common = require "core.common"
local style = require "core.style"
local DocView = require "core.docview"
-config.plugins.motiontrail = { steps = 50 }
+config.plugins.motiontrail = common.merge({
+ enabled = true,
+ steps = 50,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Motion Trail",
+ {
+ label = "Enabled",
+ description = "Disable or enable the caret motion trail effect.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Steps",
+ description = "Amount of trail steps to generate on caret movement.",
+ path = "steps",
+ type = "number",
+ default = 50,
+ min = 10,
+ max = 100
+ },
+ }
+}, config.plugins.motiontrail)
local function lerp(a, b, t)
@@ -14,8 +38,7 @@ end
local function get_caret_rect(dv)
local line, col = dv.doc:get_selection()
- local x, y = dv:get_line_screen_position(line)
- x = x + dv:get_col_x_offset(line, col)
+ local x, y = dv:get_line_screen_position(line, col)
return x, y, style.caret_width, dv:get_line_height()
end
@@ -26,7 +49,9 @@ local draw = DocView.draw
function DocView:draw(...)
draw(self, ...)
- if self ~= core.active_view then return end
+ if not config.plugins.motiontrail.enabled or self ~= core.active_view then
+ return
+ end
local x, y, w, h = get_caret_rect(self)
diff --git a/plugins/navigate.lua b/plugins/navigate.lua
index 4e6092f..d83c02f 100644
--- a/plugins/navigate.lua
+++ b/plugins/navigate.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local common = require "core.common"
diff --git a/plugins/nonicons.lua b/plugins/nonicons.lua
index 9a1e963..b8b01bc 100644
--- a/plugins/nonicons.lua
+++ b/plugins/nonicons.lua
@@ -1,11 +1,57 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+-- Author: Jipok
+-- Doesn't work well with scaling mode == "ui"
+
local core = require "core"
local common = require "core.common"
+local config = require "core.config"
local style = require "core.style"
local TreeView = require "plugins.treeview"
+local Node = require "core.node"
+-- Config
+config.plugins.nonicons = common.merge({
+ use_default_dir_icons = false,
+ use_default_chevrons = false,
+ draw_treeview_icons = true,
+ draw_tab_icons = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Nonicons",
+ {
+ label = "Use Default Directory Icons",
+ description = "When enabled does not use nonicon directory icons.",
+ path = "use_default_dir_icons",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Use Default Chevrons",
+ description = "When enabled does not use nonicon expand/collapse arrow icons.",
+ path = "use_default_chevrons",
+ type = "toggle",
+ default = false
+ },
+ {
+ label = "Draw Treeview Icons",
+ description = "Enables file related icons on the treeview.",
+ path = "draw_treeview_icons",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Draw Tab Icons",
+ description = "Adds file related icons to tabs.",
+ path = "draw_tab_icons",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.nonicons)
local icon_font = renderer.font.load(USERDIR.."/fonts/nonicons.ttf", 15 * SCALE)
+local chevron_width = icon_font:get_width("")
+local previous_scale = SCALE
local extension_icons = {
[".lua"] = { "#51a0cf", "" },
[".md"] = { "#519aba", "" }, -- Markdown
@@ -39,16 +85,16 @@ local extension_icons = {
[".swift"] = { "#e37933", "" },
[".ts"] = { "#519aba", "" }, -- TypeScript
[".elm"] = { "#519aba", "" },
- [".diff"] = { "#41535b", "" }, [".patch"] = { "#41535b", "" },
+ [".diff"] = { "#41535b", "" },
[".ex"] = { "#a074c4", "" }, [".exs"] = { "#a074c4", "" }, -- Elixir
-- Following without special icon:
[".awk"] = { "#4d5a5e", "" },
[".nim"] = { "#F88A02", "" },
[".zig"] = { "#cbcb41", "" },
-
}
local known_names_icons = {
["changelog"] = { "#657175", "" }, ["changelog.txt"] = { "#4d5a5e", "" },
+ ["changelog.md"] = { "#519aba", "" },
["makefile"] = { "#6d8086", "" },
["dockerfile"] = { "#296478", "" },
["docker-compose.yml"] = { "#4289a1", "" },
@@ -68,66 +114,68 @@ for k, v in pairs(known_names_icons) do
v[1] = { common.color(v[1]) }
end
--- Replace original draw
-function TreeView:draw()
- if not self.visible then return end
- self:draw_background(style.background2)
-
- local icon_width = icon_font:get_width("")
- local spacing = icon_font:get_width("") / 2
-
- local doc = core.active_view.doc
- local active_filename = doc and system.absolute_path(doc.filename or "")
-
- for item, x,y,w,h in self:each_item() do
- local color = style.text
-
- -- highlight active_view doc
- if item.abs_filename == active_filename then
- color = style.accent
+-- Override function to change default icons for dirs, special extensions and names
+local TreeView_get_item_icon = TreeView.get_item_icon
+function TreeView:get_item_icon(item, active, hovered)
+ local icon, font, color = TreeView_get_item_icon(self, item, active, hovered)
+ if previous_scale ~= SCALE then
+ icon_font:set_size(
+ icon_font:get_size() * (SCALE / previous_scale)
+ )
+ chevron_width = icon_font:get_width("")
+ previous_scale = SCALE
+ end
+ if not config.plugins.nonicons.use_default_dir_icons then
+ icon = "" -- unicode 61766
+ font = icon_font
+ color = style.text
+ if item.type == "dir" then
+ icon = item.expanded and "" or "" -- unicode U+F23C and U+F23B
end
-
- -- hovered item background
- if item == self.hovered_item then
- renderer.draw_rect(x, y, w, h, style.line_highlight)
+ end
+ if config.plugins.nonicons.draw_treeview_icons then
+ local custom_icon = known_names_icons[item.name:lower()]
+ if custom_icon == nil then
+ custom_icon = extension_icons[item.name:match("^.+(%..+)$")]
+ end
+ if custom_icon ~= nil then
+ color = custom_icon[1]
+ icon = custom_icon[2]
+ font = icon_font
+ end
+ if active or hovered then
color = style.accent
end
+ end
+ return icon, font, color
+end
- -- icons
- x = x + item.depth * style.padding.x + style.padding.x
+-- Override function to draw chevrons if setting is disabled
+local TreeView_draw_item_chevron = TreeView.draw_item_chevron
+function TreeView:draw_item_chevron(item, active, hovered, x, y, w, h)
+ if not config.plugins.nonicons.use_default_chevrons then
if item.type == "dir" then
- local icon1 = item.expanded and "" or "" -- unicode 61726 and 61728
- local icon2 = item.expanded and "" or "" -- unicode U+F23C and U+F23B
- x = x - spacing
- common.draw_text(icon_font, color, icon1, nil, x, y, 0, h)
- x = x + style.padding.x + spacing
- common.draw_text(icon_font, color, icon2, nil, x, y, 0, h)
- x = x + icon_width
- else
- x = x + style.padding.x
- -- default icon
- local icon = "" -- unicode 61766
- local icon_color = color
- -- icon depending on the file extension or full name
- local custom_icon = known_names_icons[item.name:lower()]
- if custom_icon == nil then
- custom_icon = extension_icons[item.name:match("^.+(%..+)$")]
- end
- if custom_icon ~= nil then
- icon_color = custom_icon[1]
- icon = custom_icon[2]
- end
- common.draw_text(icon_font, icon_color, icon, nil, x, y, 0, h)
- x = x + icon_width
+ local chevron_icon = item.expanded and "" or ""
+ local chevron_color = hovered and style.accent or style.text
+ common.draw_text(icon_font, chevron_color, chevron_icon, nil, x, y, 0, h)
end
-
- -- text
- x = x + spacing
- x = common.draw_text(style.font, color, item.name, nil, x, y, 0, h)
+ return chevron_width + style.padding.x/4
end
+ return TreeView_draw_item_chevron(self, item, active, hovered, x, y, w, h)
+end
- self:draw_scrollbar()
- if self.hovered_item and self.tooltip.alpha > 0 then
- core.root_view:defer_draw(self.draw_tooltip, self)
+-- Override function to draw icons in tabs titles if setting is enabled
+local Node_draw_tab_title = Node.draw_tab_title
+function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
+ if config.plugins.nonicons.draw_tab_icons then
+ local padx = chevron_width + style.padding.x/2
+ local tx = x + padx -- Space for icon
+ w = w - padx
+ Node_draw_tab_title(self, view, font, is_active, is_hovered, tx, y, w, h)
+ if (view == nil) or (view.doc == nil) then return end
+ local item = { type = "file", name = view.doc:get_name() }
+ TreeView:draw_item_icon(item, false, is_hovered, x, y, w, h)
+ else
+ Node_draw_tab_title(self, view, font, is_active, is_hovered, x, y, w, h)
end
end
diff --git a/plugins/opacity.lua b/plugins/opacity.lua
index 8dd0d9a..a97cae9 100644
--- a/plugins/opacity.lua
+++ b/plugins/opacity.lua
@@ -1,4 +1,5 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+local core = require "core"
local common = require "core.common"
local command = require "core.command"
local keymap = require "core.keymap"
@@ -11,7 +12,7 @@ local default_opacity = 1
local current_opacity = default_opacity
local function set_opacity(opacity)
- if not opacity_on then opacity_on = true end
+ if not opacity_on then return end
current_opacity = common.clamp(opacity, 0.2, 1)
system.set_window_opacity(current_opacity)
end
@@ -30,8 +31,10 @@ end
local function tog_opacity()
opacity_on = not opacity_on
if opacity_on then
+ core.log("Opacity: on")
system.set_window_opacity(current_opacity)
else
+ core.log("Opacity: off")
system.set_window_opacity(default_opacity)
end
end
@@ -53,7 +56,14 @@ command.add(nil, {
["opacity:reset" ] = function() res_opacity() end,
["opacity:decrease"] = function() dec_opacity() end,
["opacity:increase"] = function() inc_opacity() end,
- ["opacity:toggle mouse wheel use"] = function() use_mousewheel = not use_mousewheel end,
+ ["opacity:toggle mouse wheel use"] = function()
+ use_mousewheel = not use_mousewheel
+ if use_mousewheel then
+ core.log("Opacity (shift + mouse wheel): on")
+ else
+ core.log("Opacity (shift + mouse wheel): off")
+ end
+ end,
})
keymap.add {
diff --git a/plugins/open_ext.lua b/plugins/open_ext.lua
index 8a98516..4c57d68 100644
--- a/plugins/open_ext.lua
+++ b/plugins/open_ext.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- The general idea is to check if the file opened is valid utf-8
-- since lite-xl only supports UTF8 text, others can be safely assumed
diff --git a/plugins/openfilelocation.lua b/plugins/openfilelocation.lua
index 4b89815..603c7b6 100644
--- a/plugins/openfilelocation.lua
+++ b/plugins/openfilelocation.lua
@@ -1,18 +1,32 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
+local common = require "core.common"
local command = require "core.command"
local config = require "core.config"
-
-config.plugins.openfilelocation = {}
+local platform_filemanager
if PLATFORM == "Windows" then
- config.plugins.openfilelocation.filemanager = "explorer"
+ platform_filemanager = "explorer"
elseif PLATFORM == "Mac OS X" then
- config.plugins.openfilelocation.filemanager = "open"
+ platform_filemanager = "open"
else
- config.plugins.openfilelocation.filemanager = "xdg-open"
+ platform_filemanager = "xdg-open"
end
+config.plugins.openfilelocation = common.merge({
+ filemanager = platform_filemanager,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Open File Location",
+ {
+ label = "File Manager",
+ description = "Command of the file browser.",
+ path = "filemanager",
+ type = "string",
+ default = platform_filemanager
+ }
+ }
+}, config.plugins.openfilelocation)
command.add("core.docview", {
["open-file-location:open-file-location"] = function()
diff --git a/plugins/openselected.lua b/plugins/openselected.lua
index af00194..6333da9 100644
--- a/plugins/openselected.lua
+++ b/plugins/openselected.lua
@@ -1,19 +1,35 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
+local common = require "core.common"
local config = require "core.config"
+local contextmenu = require "plugins.contextmenu"
-config.plugins.openselected = {}
+local platform_filelauncher
if PLATFORM == "Windows" then
- config.plugins.openselected.filemanager = "start"
+ platform_filelauncher = "start"
elseif PLATFORM == "Mac OS X" then
- config.plugins.openselected.filemanager = "open"
+ platform_filelauncher = "open"
else
- config.plugins.openselected.filemanager = "xdg-open"
+ platform_filelauncher = "xdg-open"
end
+config.plugins.openselected = common.merge({
+ filelauncher = platform_filelauncher,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Open Selected Text",
+ {
+ label = "File Launcher",
+ description = "Command used to open the selected path or link externally.",
+ path = "filelauncher",
+ type = "string",
+ default = platform_filelauncher
+ }
+ }
+}, config.plugins.openselected)
command.add("core.docview", {
["open-selected:open-selected"] = function()
@@ -35,10 +51,16 @@ command.add("core.docview", {
core.log("Opening %s...", text)
- system.exec(config.plugins.openselected.filemanager .. " " .. text)
+ system.exec(config.plugins.openselected.filelauncher .. " " .. text)
end,
})
-keymap.add { ["ctrl+shift+o"] = "open-selected:open-selected" }
+contextmenu:register("core.docview", {
+ contextmenu.DIVIDER,
+ { text = "Open Selection", command = "open-selected:open-selected" }
+})
+
+
+keymap.add { ["ctrl+alt+o"] = "open-selected:open-selected" }
diff --git a/plugins/pdfview.lua b/plugins/pdfview.lua
index d5d749a..199584e 100644
--- a/plugins/pdfview.lua
+++ b/plugins/pdfview.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
diff --git a/plugins/primary_selection.lua b/plugins/primary_selection.lua
new file mode 100644
index 0000000..388caf0
--- /dev/null
+++ b/plugins/primary_selection.lua
@@ -0,0 +1,187 @@
+-- mod-version:3
+local core = require "core"
+local Doc = require "core.doc"
+local command = require "core.command"
+local keymap = require "core.keymap"
+local config = require "core.config"
+local common = require "core.common"
+
+local function string_to_cmd(s)
+ local result = {}
+ for match in s:gmatch("%g+") do
+ table.insert(result, match)
+ end
+ return result
+end
+
+config.plugins.primary_selection = common.merge({
+ command_in = { "xclip", "-in", "-selection", "primary" }, -- Command to use to copy the selection
+ command_out = { "xclip", "-out", "-selection", "primary" }, -- Command to use to obtain the selection
+ set_cursor = true, -- Set cursor on middle mouse click
+ min_copy_time = 0.150, -- How much time to delay setting the selection; in seconds
+ config_spec = {
+ name = "Primary selection",
+ {
+ label = "Command copy",
+ description = "Command to use to copy the selection.",
+ path = "_command_in",
+ type = "string",
+ default = "xclip -in -selection primary",
+ on_apply = function(value)
+ config.plugins.primary_selection.command_in = string_to_cmd(value)
+ end,
+ },
+ {
+ label = "Command paste",
+ description = "Command to use to obtain the selection.",
+ path = "_command_out",
+ type = "string",
+ default = "xclip -out -selection primary",
+ on_apply = function(value)
+ config.plugins.primary_selection.command_out = string_to_cmd(value)
+ end,
+ },
+ {
+ label = "Set cursor",
+ description = "Set cursor on middle mouse click.",
+ path = "set_cursor",
+ type = "toggle",
+ default = true,
+ },
+ {
+ label = "Copy timeout",
+ description = "How much time to delay setting the selection; in milliseconds.",
+ path = "min_copy_time_ms",
+ type = "number",
+ default = 150,
+ min = 0,
+ step = 50,
+ on_apply = function(value)
+ config.plugins.primary_selection.min_copy_time = value / 1000
+ end
+ },
+ }
+}, config.plugins.primary_selection)
+
+
+local last_selection_data
+--[[
+ = {
+ time = nil,
+ line1 = nil,
+ col1 = nil,
+ line2 = nil,
+ col2 = nil,
+ doc = nil,
+}
+]]
+
+local xclip_copy
+local function delayed_copy()
+ while true do
+ local data = last_selection_data
+ if not data then return end
+ local current_time = system.get_time()
+ local diff_time = current_time - data.time
+ -- Check if enough time has passed since last selection change
+ if diff_time >= config.plugins.primary_selection.min_copy_time then
+ if xclip_copy then xclip_copy:terminate() end
+ if not config.plugins.primary_selection.command_in
+ or #config.plugins.primary_selection.command_in == 0 then
+ core.warn("No primary selection copy command set")
+ break
+ end
+ xclip_copy = process.start(config.plugins.primary_selection.command_in)
+ if not xclip_copy then
+ core.warn("Unable to start copy command")
+ break
+ end
+ local text = data.doc:get_text(data.line1, data.col1, data.line2, data.col2)
+ local nbytes = #text
+ local total_written = 0
+ -- In some rare cases xclip isn't fast enough so we need to retry sending the data
+ local retry = 3
+ repeat
+ local written, err = xclip_copy:write(text)
+ if written == 0 or not written then
+ if retry > 0 then
+ retry = retry - 1
+ else
+ core.error("Error while setting primary selection. "..(err or ""))
+ break
+ end
+ else
+ retry = 3
+ end
+ total_written = total_written + written
+ text = string.sub(text, written + 1)
+ until total_written >= nbytes
+ xclip_copy:close_stream(process.STREAM_STDIN)
+ -- We need to leave the process running as killing it would destroy the copied buffer
+ break
+ end
+ coroutine.yield()
+ end
+ last_selection_data = nil
+end
+
+
+local doc_set_selections = Doc.set_selections
+function Doc:set_selections(...)
+ local result = doc_set_selections(self, ...)
+ local line1, col1, line2, col2
+ line1, col1, line2, col2 = self:get_selection()
+ if line1 ~= line2 or col1 ~= col2 then
+ if not last_selection_data then
+ -- Start "timer" to confirm the selection only after `min_copy_time` has passed
+ core.add_thread(delayed_copy)
+ last_selection_data = { }
+ end
+ -- We could extract the text here, but it is a potentially heavy operation,
+ -- so we do it only when we're actually confirming the selection.
+ -- The drawback is that if the selection is overwritten/deleted,
+ -- it is either never sent, or is different than expected.
+ -- TODO: Confirm the selection on text change.
+ last_selection_data.time = system.get_time()
+ last_selection_data.line1 = line1
+ last_selection_data.col1 = col1
+ last_selection_data.line2 = line2
+ last_selection_data.col2 = col2
+ last_selection_data.doc = self
+ end
+ return result
+end
+
+
+command.add("core.docview", {
+ ["primary-selection:paste"] = function(x, y, clicks, ...)
+ if not config.plugins.primary_selection.command_out
+ or #config.plugins.primary_selection.command_out == 0 then
+ core.warn("No primary selection paste command set")
+ return
+ end
+ if x and config.plugins.primary_selection.set_cursor then
+ -- TODO: There must be a better way to do this
+ core.on_event("mousepressed", "left", x, y, clicks, ...)
+ core.on_event("mousereleased", "left", x, y, clicks, ...)
+ end
+ local xclip = process.start(config.plugins.primary_selection.command_out)
+ if not xclip then
+ core.warn("Unable to start paste command")
+ return
+ end
+ local text = {}
+ repeat
+ local buffer = xclip:read_stdout()
+ table.insert(text, buffer or "")
+ until not buffer
+ if #text > 0 then
+ core.active_view.doc:text_input(table.concat(text))
+ end
+ end
+})
+
+keymap.add({
+ ["1mclick"] = "primary-selection:paste"
+})
+
diff --git a/plugins/rainbowparen.lua b/plugins/rainbowparen.lua
index 52e9d50..6ca4cb4 100644
--- a/plugins/rainbowparen.lua
+++ b/plugins/rainbowparen.lua
@@ -1,7 +1,23 @@
--- mod-version:2 -- lite-xl 2.0
-local tokenizer = require "core.tokenizer"
+-- mod-version:3
+local core = require "core"
local style = require "core.style"
+local config = require "core.config"
local common = require "core.common"
+local command = require "core.command"
+local tokenizer = require "core.tokenizer"
+local Highlighter = require "core.doc.highlighter"
+
+config.plugins.rainbowparen = common.merge({
+ enabled = true,
+ parens = 5
+}, config.plugins.rainbowparen)
+
+style.syntax.paren_unbalanced = style.syntax.paren_unbalanced or { common.color "#DC0408" }
+style.syntax.paren1 = style.syntax.paren1 or { common.color "#FC6F71"}
+style.syntax.paren2 = style.syntax.paren2 or { common.color "#fcb053"}
+style.syntax.paren3 = style.syntax.paren3 or { common.color "#fcd476"}
+style.syntax.paren4 = style.syntax.paren4 or { common.color "#52dab2"}
+style.syntax.paren5 = style.syntax.paren5 or { common.color "#5a98cf"}
local tokenize = tokenizer.tokenize
local closers = {
@@ -9,10 +25,15 @@ local closers = {
["["] = "]",
["{"] = "}"
}
+
local function parenstyle(parenstack)
- return "paren" .. ((#parenstack % 5) + 1)
+ return "paren" .. ((#parenstack % config.plugins.rainbowparen.parens) + 1)
end
+
function tokenizer.tokenize(syntax, text, state)
+ if not config.plugins.rainbowparen.enabled then
+ return tokenize(syntax, text, state)
+ end
state = state or {}
local res, istate = tokenize(syntax, text, state.istate)
local parenstack = state.parenstack or ""
@@ -51,9 +72,31 @@ function tokenizer.tokenize(syntax, text, state)
return newres, { parenstack = parenstack, istate = istate }
end
-style.syntax.paren_unbalanced = style.syntax.paren_unbalanced or { common.color "#DC0408" }
-style.syntax.paren1 = style.syntax.paren1 or { common.color "#FC6F71"}
-style.syntax.paren2 = style.syntax.paren2 or { common.color "#fcb053"}
-style.syntax.paren3 = style.syntax.paren3 or { common.color "#fcd476"}
-style.syntax.paren4 = style.syntax.paren4 or { common.color "#52dab2"}
-style.syntax.paren5 = style.syntax.paren5 or { common.color "#5a98cf"}
+local function toggle_rainbowparen(enabled)
+ config.plugins.rainbowparen.enabled = enabled
+ for _, doc in ipairs(core.docs) do
+ doc.highlighter = Highlighter(doc)
+ doc:reset_syntax()
+ end
+end
+
+-- The config specification used by the settings gui
+config.plugins.rainbowparen.config_spec = {
+ name = "Rainbow Parentheses",
+ {
+ label = "Enable",
+ description = "Activates rainbow parenthesis coloring by default.",
+ path = "enabled",
+ type = "toggle",
+ default = true,
+ on_apply = function(enabled)
+ toggle_rainbowparen(enabled)
+ end
+ }
+}
+
+command.add(nil, {
+ ["rainbow-parentheses:toggle"] = function()
+ toggle_rainbowparen(not config.plugins.rainbowparen.enabled)
+ end
+})
diff --git a/plugins/regexreplacepreview.lua b/plugins/regexreplacepreview.lua
index 1c8b845..18d692b 100644
--- a/plugins/regexreplacepreview.lua
+++ b/plugins/regexreplacepreview.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local keymap = require "core.keymap"
local command = require "core.command"
@@ -6,121 +6,125 @@ local command = require "core.command"
-- Takes the following pattern: /pattern/replace/
-- Capture groupings can be replaced using \1 through \9
local function regex_replace_file(view, pattern, old_lines, raw, start_line, end_line)
- local doc = view.doc
- local start_pattern, end_pattern, end_replacement, start_replacement = 2, 2;
- repeat
- end_pattern = string.find(pattern, "/", end_pattern)
- until end_pattern == nil or pattern[end_pattern-1] ~= "\\"
- if end_pattern == nil then
- end_pattern = #pattern + 1
- else
- end_pattern = end_pattern - 1
- start_replacement = end_pattern+2;
- end_replacement = end_pattern+2;
- repeat
- end_replacement = string.find(pattern, "/", end_replacement)
- until end_replacement == nil or pattern[end_replacement-1] ~= "\\"
- end
- end_replacement = end_replacement and (end_replacement - 1)
-
- local re = start_pattern ~= end_pattern and regex.compile(pattern:sub(start_pattern, end_pattern))
-
- local replacement = end_replacement and pattern:sub(start_replacement, end_replacement)
- local replace_line = raw and function(line, new_text)
- if line == #doc.lines then
- doc:raw_remove(line, 1, line, #doc.lines[line], { idx = 1 }, 0)
- else
- doc:raw_remove(line, 1, line+1, 1, { idx = 1 }, 0)
- end
- doc:raw_insert(line, 1, new_text, { idx = 1 }, 0)
- end or function(line, new_text)
- if line == #doc.lines then
- doc:remove(line, 1, line, #doc.lines[line])
- else
- doc:remove(line, 1, line+1, 1)
- end
- doc:insert(line, 1, new_text)
- end
-
- local line_scroll = nil
- if re then
- for i = (start_line or 1), (end_line or #doc.lines) do
- local new_text, matches, rmatches
- local old_text = old_lines[i] or doc.lines[i]
- local old_length = #old_text
- if replacement then
- new_text, matches, rmatches = regex.gsub(re, old_text, replacement)
- end
- if matches and #matches > 0 then
- old_lines[i] = old_text
- replace_line(i, new_text)
- if line_scroll == nil then
- line_scroll = i
- doc:set_selection(i, rmatches[1][1], i, rmatches[1][2])
- end
- elseif old_lines[i] then
- replace_line(i, old_lines[i])
- old_lines[i] = nil
- end
- if not replacement then
- local s,e = regex.match(re, old_text)
- if s then
- line_scroll = i
- doc:set_selection(i, s, i, e)
- break
- end
- end
+ local doc = view.doc
+ local start_pattern, end_pattern, end_replacement, start_replacement = 2, 2;
+ repeat
+ end_pattern = string.find(pattern, "/", end_pattern)
+ until end_pattern == nil or pattern[end_pattern-1] ~= "\\"
+ if end_pattern == nil then
+ end_pattern = #pattern + 1
+ else
+ end_pattern = end_pattern - 1
+ start_replacement = end_pattern+2;
+ end_replacement = end_pattern+2;
+ repeat
+ end_replacement = string.find(pattern, "/", end_replacement)
+ until end_replacement == nil or pattern[end_replacement-1] ~= "\\"
+ end
+ end_replacement = end_replacement and (end_replacement - 1)
+
+ local re = start_pattern ~= end_pattern
+ and regex.compile(pattern:sub(start_pattern, end_pattern))
+
+ local replacement = end_replacement and pattern:sub(
+ start_replacement, end_replacement
+ )
+ local replace_line = raw and function(line, new_text)
+ if line == #doc.lines then
+ doc:raw_remove(line, 1, line, #doc.lines[line], { idx = 1 }, 0)
+ else
+ doc:raw_remove(line, 1, line+1, 1, { idx = 1 }, 0)
+ end
+ doc:raw_insert(line, 1, new_text, { idx = 1 }, 0)
+ end or function(line, new_text)
+ if line == #doc.lines then
+ doc:remove(line, 1, line, #doc.lines[line])
+ else
+ doc:remove(line, 1, line+1, 1)
+ end
+ doc:insert(line, 1, new_text)
+ end
+
+ local line_scroll = nil
+ if re then
+ for i = (start_line or 1), (end_line or #doc.lines) do
+ local new_text, matches, rmatches
+ local old_text = old_lines[i] or doc.lines[i]
+ local old_length = #old_text
+ if replacement then
+ new_text, matches, rmatches = regex.gsub(re, old_text, replacement)
end
- if line_scroll then
- view:scroll_to_line(line_scroll, true)
+ if matches and #matches > 0 then
+ old_lines[i] = old_text
+ replace_line(i, new_text)
+ if line_scroll == nil then
+ line_scroll = i
+ doc:set_selection(i, rmatches[1][1], i, rmatches[1][2])
+ end
+ elseif old_lines[i] then
+ replace_line(i, old_lines[i])
+ old_lines[i] = nil
end
- end
- if replacement == nil then
- for k,v in pairs(old_lines) do
- replace_line(k, v)
+ if not replacement then
+ local s,e = regex.match(re, old_text)
+ if s then
+ line_scroll = i
+ doc:set_selection(i, s, i, e)
+ break
+ end
end
- old_lines = {}
- end
- return old_lines, line_scroll ~= nil
+ end
+ if line_scroll then
+ view:scroll_to_line(line_scroll, true)
+ end
+ end
+ if replacement == nil then
+ for k,v in pairs(old_lines) do
+ replace_line(k, v)
+ end
+ old_lines = {}
+ end
+ return old_lines, line_scroll ~= nil
end
command.add("core.docview", {
- ["regex-replace-preview:find-replace-regex"] = function()
- core.command_view:set_text("/")
- local old_lines = {}
- local view = core.active_view
- local doc = view.doc
- local original_selection = { doc:get_selection(true) }
- local selection = doc:has_selection() and { doc:get_selection(true) } or {}
- core.command_view:enter(
- "Regex Replace (enter pattern as /old/new/)",
- function(pattern)
- regex_replace_file(view, pattern, {}, false, selection[1], selection[3])
- end,
- function(pattern)
- local incremental, has_replacement = regex_replace_file(view, pattern, old_lines, true, selection[1], selection[3])
- if incremental then
- old_lines = incremental
- end
- if not has_replacement then
- doc:set_selection(unpack(original_selection))
+ ["regex-replace-preview:find-replace-regex"] = function()
+ local old_lines = {}
+ local view = core.active_view
+ local doc = view.doc
+ local original_selection = { doc:get_selection(true) }
+ local selection = doc:has_selection() and { doc:get_selection(true) } or {}
+ core.command_view:enter("Regex Replace (enter pattern as /old/new/)", {
+ text = "/",
+ submit = function(pattern)
+ regex_replace_file(view, pattern, {}, false, selection[1], selection[3])
+ end,
+ suggest = function(pattern)
+ local incremental, has_replacement = regex_replace_file(
+ view, pattern, old_lines, true, selection[1], selection[3]
+ )
+ if incremental then
+ old_lines = incremental
+ end
+ if not has_replacement then
+ doc:set_selection(table.unpack(original_selection))
+ end
+ end,
+ cancel = function(pattern)
+ for k,v in pairs(old_lines) do
+ if v then
+ if k == #doc.lines then
+ doc:raw_remove(k, 1, k, #doc.lines[k], { idx = 1 }, 0)
+ else
+ doc:raw_remove(k, 1, k+1, 1, { idx = 1 }, 0)
end
- end,
- function(pattern)
- for k,v in pairs(old_lines) do
- if v then
- if k == #doc.lines then
- doc:raw_remove(k, 1, k, #doc.lines[k], { idx = 1 }, 0)
- else
- doc:raw_remove(k, 1, k+1, 1, { idx = 1 }, 0)
- end
- doc:raw_insert(k, 1, v, { idx = 1 }, 0)
- end
- end
- doc:set_selection(unpack(original_selection))
- end
- )
- end
+ doc:raw_insert(k, 1, v, { idx = 1 }, 0)
+ end
+ end
+ doc:set_selection(table.unpack(original_selection))
+ end
+ })
+ end
})
keymap.add { ["ctrl+shift+r"] = "regex-replace-preview:find-replace-regex" }
diff --git a/plugins/restoretabs.lua b/plugins/restoretabs.lua
index 5bcd977..4c33304 100644
--- a/plugins/restoretabs.lua
+++ b/plugins/restoretabs.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
-- Not perfect, because we can't actually figure out when something closes, but should be good enough, so long as we check the list of open views.
-- Maybe find a better way to get at "Node"?
local core = require "core"
@@ -17,7 +17,7 @@ RootView.update = function(self)
if not initialized_tab_system then
local Node = getmetatable(self.root_node)
local old_close = Node.close_view
-
+
Node.close_view = function(self, root, view)
if view.doc and view.doc.abs_filename then
local closing_filename = view.doc.abs_filename
@@ -40,7 +40,7 @@ RootView.update = function(self)
end
-command.add("core.docview", {
+command.add(nil, {
["restore-tabs:restore-tab"] = function()
if #tab_history > 0 then
local file = tab_history[#tab_history]
diff --git a/plugins/scalestatus.lua b/plugins/scalestatus.lua
index 8f3ef68..a7623b1 100644
--- a/plugins/scalestatus.lua
+++ b/plugins/scalestatus.lua
@@ -1,34 +1,54 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
--[[
scalestatus.lua
displays current scale (zoom) in status view
version: 20200628_155804
originally by SwissalpS
--]]
-local scale = require "plugins.scale"
-
+local core = require "core"
+local common = require "core.common"
local config = require "core.config"
+local scale = require "plugins.scale"
local StatusView = require "core.statusview"
-config.plugins.scalestatus = { format = '%.0f%%' }
-
-local get_items = StatusView.get_items
-function StatusView:get_items()
-
- local left, right = get_items(self)
-
- local t = {
- self.separator,
- string.format(config.plugins.scalestatus.format, scale.get() * 100),
+config.plugins.scalestatus = common.merge({
+ enabled = true,
+ format = '%.0f%%',
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Scale Status",
+ {
+ label = "Enabled",
+ description = "Show or hide the scale status 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:scale"):show()
+ else
+ core.status_view:get_item("status:scale"):hide()
+ end
+ end)
+ end
+ }
}
-
- for _, item in ipairs(t) do
- table.insert(right, item)
- end
-
- return left, right
-
-end
+}, config.plugins.scalestatus)
+
+core.status_view:add_item({
+ name = "status:scale",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function()
+ return {string.format(
+ config.plugins.scalestatus.format,
+ scale.get() * 100
+ )}
+ end,
+ position = 1,
+ tooltip = "scale",
+ separator = core.status_view.separator2
+})
return true
diff --git a/plugins/select_colorscheme.lua b/plugins/select_colorscheme.lua
index 1e25bc4..6fa45d4 100644
--- a/plugins/select_colorscheme.lua
+++ b/plugins/select_colorscheme.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local common = require "core.common"
@@ -65,10 +65,10 @@ local function make_color_module_name(name)
end
function Settings:change_color(name)
- if self:is_change_color(name) then
- core.reload_module(make_color_module_name(name))
- self.color_scheme = name
- end
+ if self:is_change_color(name) then
+ core.reload_module(make_color_module_name(name))
+ self.color_scheme = name
+ end
end
function Settings:save_settings()
@@ -121,10 +121,12 @@ local color_scheme_suggest = function(text)
end
command.add(nil, {
- ["ui:color scheme"] = function()
- core.command_view:enter("Select color scheme", color_scheme_submit, color_scheme_suggest)
- end,
- })
+ ["ui:color scheme"] = function()
+ core.command_view:enter("Select color scheme", {
+ submit = color_scheme_submit, suggest = color_scheme_suggest
+ })
+ end,
+})
-- ----------------------------------------------------------------
Settings:init()
diff --git a/plugins/selectionhighlight.lua b/plugins/selectionhighlight.lua
index 19bc475..133dced 100644
--- a/plugins/selectionhighlight.lua
+++ b/plugins/selectionhighlight.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local style = require "core.style"
local DocView = require "core.docview"
@@ -16,15 +16,15 @@ end
local draw_line_body = DocView.draw_line_body
-function DocView:draw_line_body(idx, x, y)
- draw_line_body(self, idx, x, y)
+function DocView:draw_line_body(line, x, y)
+ local line_height = draw_line_body(self, line, x, y)
local line1, col1, line2, col2 = self.doc:get_selection(true)
if line1 == line2 and col1 ~= col2 then
local selection = self.doc:get_text(line1, col1, line2, col2)
if not selection:match("^%s+$") then
local lh = self:get_line_height()
local selected_text = self.doc.lines[line1]:sub(col1, col2 - 1)
- local current_line_text = self.doc.lines[idx]
+ local current_line_text = self.doc.lines[line]
local last_col = 1
while true do
local start_col, end_col = current_line_text:find(
@@ -32,9 +32,9 @@ function DocView:draw_line_body(idx, x, y)
)
if start_col == nil then break end
-- don't draw box around the selection
- if idx ~= line1 or start_col ~= col1 then
- local x1 = x + self:get_col_x_offset(idx, start_col)
- local x2 = x + self:get_col_x_offset(idx, end_col + 1)
+ if line ~= line1 or start_col ~= col1 then
+ local x1 = x + self:get_col_x_offset(line, start_col)
+ local x2 = x + self:get_col_x_offset(line, end_col + 1)
local color = style.selectionhighlight or style.syntax.comment
draw_box(x1, y, x2 - x1, lh, color)
end
@@ -42,5 +42,6 @@ function DocView:draw_line_body(idx, x, y)
end
end
end
+ return line_height
end
diff --git a/plugins/settings.lua b/plugins/settings.lua
new file mode 100644
index 0000000..62d1592
--- /dev/null
+++ b/plugins/settings.lua
@@ -0,0 +1,1858 @@
+-- mod-version:3 --priority:0
+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 style = require "core.style"
+
+-- check if widget is installed before proceeding
+local widget_found, Widget = pcall(require, "widget")
+if not widget_found then
+ core.error("Widget library not found: https://github.com/lite-xl/lite-xl-widgets")
+ return
+end
+
+local Label = require "widget.label"
+local Line = require "widget.line"
+local NoteBook = require "widget.notebook"
+local Button = require "widget.button"
+local TextBox = require "widget.textbox"
+local SelectBox = require "widget.selectbox"
+local NumberBox = require "widget.numberbox"
+local Toggle = require "widget.toggle"
+local ListBox = require "widget.listbox"
+local FoldingBook = require "widget.foldingbook"
+local FontsList = require "widget.fontslist"
+local ItemsList = require "widget.itemslist"
+local KeybindingDialog = require "widget.keybinddialog"
+local Fonts = require "widget.fonts"
+local FilePicker = require "widget.filepicker"
+
+local settings = {}
+
+settings.core = {}
+settings.plugins = {}
+settings.sections = {}
+settings.plugin_sections = {}
+settings.config = {}
+settings.default_keybindings = {}
+
+---Enumeration for the different types of settings.
+---@type table<string, integer>
+settings.type = {
+ STRING = 1,
+ NUMBER = 2,
+ TOGGLE = 3,
+ SELECTION = 4,
+ LIST_STRINGS = 5,
+ BUTTON = 6,
+ FONT = 7,
+ FILE = 8,
+ DIRECTORY = 9
+}
+
+---@alias settings.types
+---| `settings.type.STRING`
+---| `settings.type.NUMBER`
+---| `settings.type.TOGGLE`
+---| `settings.type.SELECTION`
+---| `settings.type.LIST_STRINGS`
+---| `settings.type.BUTTON`
+---| `settings.type.FONT`
+---| `settings.type.FILE`
+
+---Represents a setting to render on a settings pane.
+---@class settings.option
+---@field public label string
+---@field public description string
+---@field public path string
+---@field public type settings.types | integer
+---@field public default string | number | boolean | table<integer, string> | table<integer, integer>
+---@field public min number
+---@field public max number
+---@field public step number
+---@field public values table
+---@field public fonts_list table<string, renderer.font>
+---@field public font_error boolean
+---@field public get_value nil | fun(value:any):any
+---@field public set_value nil | fun(value:any):any
+---@field public icon string
+---@field public on_click nil | string | fun(button:string, x:integer, y:integer)
+---@field public on_apply nil | fun(value:any)
+---@field public exists boolean
+---@field public filters table<integer,string>
+settings.option = {
+ ---Title displayed to the user eg: "My Option"
+ label = "",
+ ---Description of the option eg: "Modifies the document indentation"
+ description = "",
+ ---Config path in the config table, eg: section.myoption, myoption, etc...
+ path = "",
+ ---Type of option that will be used to render an appropriate control
+ type = "",
+ ---Default value of the option
+ default = "",
+ ---Used for NUMBER to indiciate the minimum number allowed
+ min = 0,
+ ---Used for NUMBER to indiciate the maximum number allowed
+ max = 0,
+ ---Used for NUMBER to indiciate the increment/decrement amount
+ step = 0,
+ ---Used in a SELECTION to provide the list of valid options
+ values = {},
+ ---Optionally used for FONT to store the generated font group.
+ fonts_list = {},
+ ---Flag set to true when loading user defined fonts fail
+ font_error = false,
+ ---Optional function that is used to manipulate the current value on retrieval.
+ get_value = nil,
+ ---Optional function that is used to manipulate the saved value on save.
+ set_value = nil,
+ ---The icon set for a BUTTON
+ icon = "",
+ ---Command or function executed when a BUTTON is clicked
+ on_click = nil,
+ ---Optional function executed when the option value is applied.
+ on_apply = nil,
+ ---When FILE or DIRECTORY this flag tells the path should exist.
+ exists = false,
+ ---Lua patterns used on FILE or DIRECTORY to filter browser results and
+ ---also force the selection to match one of the filters.
+ filters = {}
+}
+
+---Add a new settings section to the settings UI
+---@param section string
+---@param options settings.option[]
+---@param plugin_name? string Optional name of plugin
+---@param overwrite? boolean Overwrite previous section options
+function settings.add(section, options, plugin_name, overwrite)
+ local category = ""
+ if plugin_name ~= nil then
+ category = "plugins"
+ else
+ category = "core"
+ end
+
+ if overwrite and settings[category][section] then
+ settings[category][section] = {}
+ end
+
+ if not settings[category][section] then
+ settings[category][section] = {}
+ if category ~= "plugins" then
+ table.insert(settings.sections, section)
+ else
+ table.insert(settings.plugin_sections, section)
+ end
+ end
+
+ if plugin_name ~= nil then
+ if not settings[category][section][plugin_name] then
+ settings[category][section][plugin_name] = {}
+ end
+ for _, option in ipairs(options) do
+ table.insert(settings[category][section][plugin_name], option)
+ end
+ else
+ for _, option in ipairs(options) do
+ table.insert(settings[category][section], option)
+ end
+ end
+end
+
+--------------------------------------------------------------------------------
+-- Add Core Settings
+--------------------------------------------------------------------------------
+
+settings.add("General",
+ {
+ {
+ label = "User Module",
+ description = "Open your init.lua for customizations.",
+ type = settings.type.BUTTON,
+ icon = "P",
+ on_click = "core:open-user-module"
+ },
+ {
+ label = "Clear Fonts Cache",
+ description = "Delete current font cache and regenerate a fresh one.",
+ type = settings.type.BUTTON,
+ icon = "C",
+ on_click = function()
+ Fonts.clean_cache()
+ end
+ },
+ {
+ label = "Maximum Project Files",
+ description = "The maximum amount of project files to register.",
+ path = "max_project_files",
+ type = settings.type.NUMBER,
+ default = 2000,
+ min = 1,
+ max = 100000,
+ on_apply = function(button, x, y)
+ if button == "left" then
+ core.rescan_project_directories()
+ end
+ end
+ },
+ {
+ label = "File Size Limit",
+ description = "The maximum file size in megabytes allowed for editing.",
+ path = "file_size_limit",
+ type = settings.type.NUMBER,
+ default = 10,
+ min = 1,
+ max = 50
+ },
+ {
+ label = "Ignore Files",
+ description = "List of lua patterns matching files to be ignored by the editor.",
+ path = "ignore_files",
+ type = settings.type.LIST_STRINGS,
+ default = { "^%." },
+ on_apply = function()
+ core.rescan_project_directories()
+ end
+ },
+ {
+ label = "Maximum Clicks",
+ description = "The maximum amount of consecutive clicks that are registered by the editor.",
+ path = "max_clicks",
+ type = settings.type.NUMBER,
+ default = 3,
+ min = 1,
+ max = 10
+ },
+ }
+)
+
+settings.add("Graphics",
+ {
+ {
+ label = "Frames Per Second",
+ description = "Lower value for low end machines and higher for a smoother experience.",
+ path = "fps",
+ type = settings.type.NUMBER,
+ default = 60,
+ min = 10,
+ max = 300
+ },
+ {
+ label = "Transitions",
+ description = "If disabled turns off all transitions but improves rendering performance.",
+ path = "transitions",
+ type = settings.type.TOGGLE,
+ default = true
+ },
+ {
+ label = "Animation Rate",
+ description = "The amount of time it takes for a transition to finish.",
+ path = "animation_rate",
+ type = settings.type.NUMBER,
+ default = 1.0,
+ min = 0.5,
+ max = 3.0,
+ step = 0.1
+ },
+ {
+ label = "Animate Mouse Drag Scroll",
+ description = "Causes higher cpu usage but smoother scroll transition.",
+ path = "animate_drag_scroll",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Scrolling Transitions",
+ path = "disabled_transitions.scroll",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Command View Transitions",
+ path = "disabled_transitions.commandview",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Context Menu Transitions",
+ path = "disabled_transitions.contextmenu",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Log View Transitions",
+ path = "disabled_transitions.logview",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Nag Bar Transitions",
+ path = "disabled_transitions.nagbar",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Tab Transitions",
+ path = "disabled_transitions.tabs",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Tab Drag Transitions",
+ path = "disabled_transitions.tab_drag",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Disable Status Bar Transitions",
+ path = "disabled_transitions.statusbar",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ }
+)
+
+settings.add("User Interface",
+ {
+ {
+ label = "Font",
+ description = "The font and fallbacks used on non code text.",
+ path = "font",
+ type = settings.type.FONT,
+ fonts_list = style,
+ default = {
+ fonts = {
+ {
+ name = "Fira Sans Regular",
+ path = DATADIR .. "/fonts/FiraSans-Regular.ttf"
+ }
+ },
+ options = {
+ size = 15,
+ antialiasing = "subpixel",
+ hinting = "slight"
+ }
+ }
+ },
+ {
+ label = "Borderless",
+ description = "Use built-in window decorations.",
+ path = "borderless",
+ type = settings.type.TOGGLE,
+ default = false,
+ on_apply = function()
+ core.configure_borderless_window()
+ end
+ },
+ {
+ label = "Always Show Tabs",
+ description = "Shows tabs even if a single document is opened.",
+ path = "always_show_tabs",
+ type = settings.type.TOGGLE,
+ default = true
+ },
+ {
+ label = "Maximum Tabs",
+ description = "The maximum amount of visible document tabs.",
+ path = "max_tabs",
+ type = settings.type.NUMBER,
+ default = 8,
+ min = 1,
+ max = 100
+ },
+ {
+ label = "Close Button on Tabs",
+ description = "Display the close button on tabs.",
+ path = "tab_close_button",
+ type = settings.type.TOGGLE,
+ default = true
+ },
+ {
+ label = "Mouse wheel scroll rate",
+ description = "The amount to scroll when using the mouse wheel.",
+ path = "mouse_wheel_scroll",
+ type = settings.type.NUMBER,
+ default = 50,
+ min = 10,
+ max = 200,
+ get_value = function(value)
+ return value / SCALE
+ end,
+ set_value = function(value)
+ return value * SCALE
+ end
+ },
+ {
+ label = "Disable Cursor Blinking",
+ description = "Disables cursor blinking on text input elements.",
+ path = "disable_blink",
+ type = settings.type.TOGGLE,
+ default = false
+ },
+ {
+ label = "Cursor Blinking Period",
+ description = "Interval in seconds in which the cursor blinks.",
+ path = "blink_period",
+ type = settings.type.NUMBER,
+ default = 0.8,
+ min = 0.3,
+ max = 2.0,
+ step = 0.1
+ }
+ }
+)
+
+settings.add("Editor",
+ {
+ {
+ label = "Code Font",
+ description = "The font and fallbacks used on the code editor.",
+ path = "code_font",
+ type = settings.type.FONT,
+ fonts_list = style,
+ default = {
+ fonts = {
+ {
+ name = "JetBrains Mono Regular",
+ path = DATADIR .. "/fonts/JetBrainsMono-Regular.ttf"
+ }
+ },
+ options = {
+ size = 15,
+ antialiasing = "subpixel",
+ hinting = "slight"
+ }
+ }
+ },
+ {
+ label = "Indentation Type",
+ description = "The character inserted when pressing the tab key.",
+ path = "tab_type",
+ type = settings.type.SELECTION,
+ default = "soft",
+ values = {
+ {"Space", "soft"},
+ {"Tab", "hard"}
+ }
+ },
+ {
+ label = "Indentation Size",
+ description = "Amount of spaces shown per indentation.",
+ path = "indent_size",
+ type = settings.type.NUMBER,
+ default = 2,
+ min = 1,
+ max = 10
+ },
+ {
+ label = "Line Limit",
+ description = "Amount of characters at which the line breaking column will be drawn.",
+ path = "line_limit",
+ type = settings.type.NUMBER,
+ default = 80,
+ min = 1
+ },
+ {
+ label = "Line Height",
+ description = "The amount of spacing between lines.",
+ path = "line_height",
+ type = settings.type.NUMBER,
+ default = 1.2,
+ min = 1.0,
+ max = 3.0,
+ step = 0.1
+ },
+ {
+ label = "Highlight Line",
+ description = "Highlight the current line.",
+ path = "highlight_current_line",
+ type = settings.type.SELECTION,
+ default = true,
+ values = {
+ {"Yes", true},
+ {"No", false},
+ {"No Selection", "no_selection"}
+ },
+ set_value = function(value)
+ if type(value) == "nil" then return false end
+ return value
+ end
+ },
+ {
+ label = "Maximum Undo History",
+ description = "The amount of undo elements to keep.",
+ path = "max_undos",
+ type = settings.type.NUMBER,
+ default = 10000,
+ min = 100,
+ max = 100000
+ },
+ {
+ label = "Undo Merge Timeout",
+ description = "Time in seconds before applying an undo action.",
+ path = "undo_merge_timeout",
+ type = settings.type.NUMBER,
+ default = 0.3,
+ min = 0.1,
+ max = 1.0,
+ step = 0.1
+ },
+ {
+ label = "Symbol Pattern",
+ description = "A lua pattern used to match symbols in the document.",
+ path = "symbol_pattern",
+ type = settings.type.STRING,
+ default = "[%a_][%w_]*"
+ },
+ {
+ label = "Non Word Characters",
+ description = "A string of characters that do not belong to a word.",
+ path = "non_word_chars",
+ type = settings.type.STRING,
+ default = " \\t\\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-",
+ get_value = function(value)
+ return value:gsub("\n", "\\n"):gsub("\t", "\\t")
+ end,
+ set_value = function(value)
+ return value:gsub("\\n", "\n"):gsub("\\t", "\t")
+ end
+ },
+ {
+ label = "Scroll Past the End",
+ description = "Allow scrolling beyond the document ending.",
+ path = "scroll_past_end",
+ type = settings.type.TOGGLE,
+ default = true
+ }
+ }
+)
+
+settings.add("Development",
+ {
+ {
+ label = "Core Log",
+ description = "Open the list of logged messages.",
+ type = settings.type.BUTTON,
+ icon = "f",
+ on_click = "core:open-log"
+ },
+ {
+ label = "Log Items",
+ description = "The maximum amount of entries to keep on the log UI.",
+ path = "max_log_items",
+ type = settings.type.NUMBER,
+ default = 80,
+ min = 50,
+ max = 2000
+ },
+ {
+ label = "Skip Plugins Version",
+ description = "Do not verify the plugins required versions at startup.",
+ path = "skip_plugins_version",
+ type = settings.type.TOGGLE,
+ default = false
+ }
+ }
+)
+
+settings.add("Status Bar",
+ {
+ {
+ label = "Enabled",
+ description = "Toggle the default visibility of the status bar.",
+ path = "statusbar.enabled",
+ type = settings.type.TOGGLE,
+ default = true,
+ on_apply = function(enabled)
+ if enabled then
+ core.status_view:show()
+ else
+ core.status_view:hide()
+ end
+ end
+ },
+ {
+ label = "Show Notifications",
+ description = "Toggle the visibility of status messages.",
+ path = "statusbar.messages",
+ type = settings.type.TOGGLE,
+ default = true,
+ on_apply = function(enabled)
+ core.status_view:display_messages(enabled)
+ end
+ },
+ {
+ label = "Messages Timeout",
+ description = "The amount in seconds before a notification dissapears.",
+ path = "message_timeout",
+ type = settings.type.NUMBER,
+ default = 5,
+ min = 1,
+ max = 30
+ }
+ }
+)
+
+---Retrieve from given config the associated value using the given path.
+---@param conf table
+---@param path string
+---@param default any
+---@return any | nil
+local function get_config_value(conf, path, default)
+ local sections = {};
+ for match in (path.."."):gmatch("(.-)%.") do
+ table.insert(sections, match);
+ end
+
+ local element = conf
+ for _, section in ipairs(sections) do
+ if type(element[section]) ~= "nil" then
+ element = element[section]
+ else
+ return default
+ end
+ end
+
+ if type(element) == "nil" then
+ return default
+ end
+
+ return element
+end
+
+---Loops the given config table using the given path and store the value.
+---@param conf table
+---@param path string
+---@param value any
+local function set_config_value(conf, path, value)
+ local sections = {};
+ for match in (path.."."):gmatch("(.-)%.") do
+ table.insert(sections, match);
+ end
+
+ local sections_count = #sections
+
+ if sections_count == 1 then
+ conf[sections[1]] = value
+ return
+ elseif type(conf[sections[1]]) ~= "table" then
+ conf[sections[1]] = {}
+ end
+
+ local element = conf
+ for idx, section in ipairs(sections) do
+ if type(element[section]) ~= "table" then
+ element[section] = {}
+ element = element[section]
+ else
+ element = element[section]
+ end
+ if idx + 1 == sections_count then break end
+ end
+
+ element[sections[sections_count]] = value
+end
+
+---Get a list of system and user installed plugins.
+---@return table<integer, string>
+local function get_installed_plugins()
+ local files, ordered = {}, {}
+
+ for _, root_dir in ipairs {DATADIR, USERDIR} do
+ local plugin_dir = root_dir .. "/plugins"
+ for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
+ local valid = false
+ local file_info = system.get_file_info(plugin_dir .. "/" .. filename)
+ if file_info then
+ if
+ file_info.type == "file"
+ and
+ filename:match("%.lua$")
+ and
+ not filename:match("^language_")
+ then
+ valid = true
+ filename = filename:gsub("%.lua$", "")
+ elseif file_info.type == "dir" then
+ if system.get_file_info(plugin_dir .. "/" .. filename .. "/init.lua") then
+ valid = true
+ end
+ end
+ end
+ if valid then
+ if not files[filename] then table.insert(ordered, filename) end
+ files[filename] = true
+ end
+ end
+ end
+
+ table.sort(ordered)
+
+ return ordered
+end
+
+---Get a list of system and user installed colors.
+---@return table<integer, table>
+local function get_installed_colors()
+ local files, ordered = {}, {}
+
+ for _, root_dir in ipairs {DATADIR, USERDIR} do
+ local dir = root_dir .. "/colors"
+ for _, filename in ipairs(system.list_dir(dir) or {}) do
+ local file_info = system.get_file_info(dir .. "/" .. filename)
+ if
+ file_info and file_info.type == "file"
+ and
+ filename:match("%.lua$")
+ then
+ -- read colors
+ local contents = io.open(dir .. "/" .. filename):read("*a")
+ local colors = {}
+ for r, g, b in contents:gmatch("#(%x%x)(%x%x)(%x%x)") do
+ r = tonumber(r, 16)
+ g = tonumber(g, 16)
+ b = tonumber(b, 16)
+ table.insert(colors, { r, g, b, 0xff })
+ end
+ -- sort colors from darker to lighter
+ table.sort(colors, function(a, b)
+ return a[1] + a[2] + a[3] < b[1] + b[2] + b[3]
+ end)
+ -- remove duplicate colors
+ local b = {}
+ for i = #colors, 1, -1 do
+ local a = colors[i]
+ if a[1] == b[1] and a[2] == b[2] and a[3] == b[3] then
+ table.remove(colors, i)
+ else
+ b = colors[i]
+ end
+ end
+ -- insert color to ordered table if not duplicate
+ filename = filename:gsub("%.lua$", "")
+ if not files[filename] then
+ table.insert(ordered, {name = filename, colors = colors})
+ end
+ files[filename] = true
+ end
+ end
+ end
+
+ table.sort(ordered, function(a, b) return a.name < b.name end)
+
+ return ordered
+end
+
+---Capitalize first letter of every word.
+---Taken from core.command.
+---@param words string
+---@return string
+local function capitalize_first(words)
+ return words:sub(1, 1):upper() .. words:sub(2)
+end
+
+---Similar to command prettify_name but also takes care of underscores.
+---@param name string
+---@return string
+local function prettify_name(name)
+ name = name:gsub("[%-_]", " "):gsub("%S+", capitalize_first)
+ return name
+end
+
+---Load config options from the USERDIR user_settings.lua and store them on
+---settings.config for later usage.
+local function load_settings()
+ local ok, t = pcall(dofile, USERDIR .. "/user_settings.lua")
+ settings.config = ok and t.config or {}
+end
+
+---Save current config options into the USERDIR user_settings.lua
+local function save_settings()
+ local fp = io.open(USERDIR .. "/user_settings.lua", "w")
+ if fp then
+ local output = "{\n [\"config\"] = "
+ .. common.serialize(
+ settings.config,
+ { pretty = true, escape = true, sort = true, initial_indent = 1 }
+ ):gsub("^%s+", "")
+ .. "\n}\n"
+ fp:write("return ", output)
+ fp:close()
+ end
+end
+
+---Apply a keybinding and optionally save it.
+---@param cmd string
+---@param bindings table<integer, string>
+---@param skip_save? boolean
+---@return table | nil
+local function apply_keybinding(cmd, bindings, skip_save)
+ local row_value = nil
+ local changed = false
+
+ local original_bindings = { keymap.get_binding(cmd) }
+ for _, binding in ipairs(original_bindings) do
+ keymap.unbind(binding, cmd)
+ end
+
+ if #bindings > 0 then
+ if
+ not skip_save
+ and
+ settings.config.custom_keybindings
+ and
+ settings.config.custom_keybindings[cmd]
+ then
+ settings.config.custom_keybindings[cmd] = {}
+ end
+ local shortcuts = ""
+ for _, binding in ipairs(bindings) do
+ if not binding:match("%+$") and binding ~= "" and binding ~= "none" then
+ keymap.add({[binding] = cmd})
+ shortcuts = shortcuts .. binding .. "\n"
+ if not skip_save then
+ if not settings.config.custom_keybindings then
+ settings.config.custom_keybindings = {}
+ settings.config.custom_keybindings[cmd] = {}
+ elseif not settings.config.custom_keybindings[cmd] then
+ settings.config.custom_keybindings[cmd] = {}
+ end
+ table.insert(settings.config.custom_keybindings[cmd], binding)
+ changed = true
+ end
+ end
+ end
+ if shortcuts ~= "" then
+ local bindings_list = shortcuts:gsub("\n$", "")
+ row_value = {
+ style.text, cmd, ListBox.COLEND, style.dim, bindings_list
+ }
+ end
+ elseif
+ not skip_save
+ and
+ settings.config.custom_keybindings
+ and
+ settings.config.custom_keybindings[cmd]
+ then
+ settings.config.custom_keybindings[cmd] = nil
+ changed = true
+ end
+
+ if changed then
+ save_settings()
+ end
+
+ if not row_value then
+ row_value = {
+ style.text, cmd, ListBox.COLEND, style.dim, "none"
+ }
+ end
+
+ return row_value
+end
+
+---Load the saved fonts into the config path or fonts_list table.
+---@param option settings.option
+---@param path string
+---@param saved_value any
+local function merge_font_settings(option, path, saved_value)
+ local font_options = saved_value.options or {
+ size = 15,
+ antialiasing = "supixel",
+ hinting = "slight"
+ }
+ font_options.size = font_options.size or 15
+ font_options.antialiasing = font_options.antialiasing or "subpixel"
+ font_options.hinting = font_options.hinting or "slight"
+
+ local fonts = {}
+ local font_loaded = true
+ for _, font in ipairs(saved_value.fonts) do
+ local font_data = nil
+ font_loaded = core.try(function()
+ font_data = renderer.font.load(
+ font.path, font_options.size * SCALE, font_options
+ )
+ end)
+ if font_loaded then
+ table.insert(fonts, font_data)
+ else
+ option.font_error = true
+ core.error("Settings: could not load %s\n'%s - %s'", path, font.name, font.path)
+ break
+ end
+ end
+
+ if font_loaded then
+ if option.fonts_list then
+ set_config_value(option.fonts_list, option.path, renderer.font.group(fonts))
+ else
+ set_config_value(config, path, renderer.font.group(fonts))
+ end
+ end
+end
+
+---Merge previously saved settings without destroying the config table.
+local function merge_settings()
+ if type(settings.config) ~= "table" then return end
+
+ -- merge core settings
+ for _, section in ipairs(settings.sections) do
+ local options = settings.core[section]
+ for _, option in ipairs(options) do
+ if type(option.path) == "string" then
+ local saved_value = get_config_value(settings.config, option.path)
+ if type(saved_value) ~= "nil" then
+ if option.type == settings.type.FONT or option.type == "font" then
+ merge_font_settings(option, option.path, saved_value)
+ else
+ set_config_value(config, option.path, saved_value)
+ end
+ if option.on_apply then
+ option.on_apply(saved_value)
+ end
+ end
+ end
+ end
+ end
+
+ -- merge plugin settings
+ table.sort(settings.plugin_sections)
+ for _, section in ipairs(settings.plugin_sections) do
+ local plugins = settings.plugins[section]
+ for plugin_name, options in pairs(plugins) do
+ for _, option in pairs(options) do
+ if type(option.path) == "string" then
+ local path = "plugins." .. plugin_name .. "." .. option.path
+ local saved_value = get_config_value(settings.config, path)
+ if type(saved_value) ~= "nil" then
+ if option.type == settings.type.FONT or option.type == "font" then
+ merge_font_settings(option, path, saved_value)
+ else
+ set_config_value(config, path, saved_value)
+ end
+ if option.on_apply then
+ option.on_apply(saved_value)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- apply custom keybindings
+ if settings.config.custom_keybindings then
+ for cmd, bindings in pairs(settings.config.custom_keybindings) do
+ apply_keybinding(cmd, bindings, true)
+ end
+ end
+end
+
+---Scan all plugins to check if they define a config_spec and load it.
+local function scan_plugins_spec()
+ for plugin, conf in pairs(config.plugins) do
+ if type(conf) == "table" and conf.config_spec then
+ settings.add(
+ conf.config_spec.name,
+ conf.config_spec,
+ plugin
+ )
+ end
+ end
+end
+
+---Called at core first run to store the default keybindings.
+local function store_default_keybindings()
+ for name, _ in pairs(command.map) do
+ local keys = { keymap.get_binding(name) }
+ if #keys > 0 then
+ settings.default_keybindings[name] = keys
+ end
+ end
+end
+
+---@class settings.ui : widget
+---@field private notebook widget.notebook
+---@field private core widget
+---@field private colors widget
+---@field private plugins widget
+---@field private keybinds widget
+---@field private about widget
+---@field private core_sections widget.foldingbook
+---@field private plugin_sections widget.foldingbook
+local Settings = Widget:extend()
+
+---Constructor
+function Settings:new()
+ Settings.super.new(self, nil, false)
+
+ self.name = "Settings"
+ self.defer_draw = false
+ self.border.width = 0
+ self.draggable = false
+ self.scrollable = false
+
+ ---@type widget.notebook
+ self.notebook = NoteBook(self)
+ self.notebook.size.x = 250
+ self.notebook.size.y = 300
+ self.notebook.border.width = 0
+
+ self.core = self.notebook:add_pane("core", "Core")
+ self.colors = self.notebook:add_pane("colors", "Colors")
+ self.plugins = self.notebook:add_pane("plugins", "Plugins")
+ self.keybinds = self.notebook:add_pane("keybindings", "Keybindings")
+ self.about = self.notebook:add_pane("about", "About")
+
+ self.notebook:set_pane_icon("core", "P")
+ self.notebook:set_pane_icon("colors", "W")
+ self.notebook:set_pane_icon("plugins", "B")
+ self.notebook:set_pane_icon("keybindings", "M")
+ self.notebook:set_pane_icon("about", "i")
+
+ self.core_sections = FoldingBook(self.core)
+ self.core_sections.border.width = 0
+ self.core_sections.scrollable = false
+
+ self.plugin_sections = FoldingBook(self.plugins)
+ self.plugin_sections.border.width = 0
+ self.plugin_sections.scrollable = false
+
+ self:load_core_settings()
+ self:load_color_settings()
+ self:load_plugin_settings()
+ self:load_keymap_settings()
+
+ self:setup_about()
+end
+
+---Helper function to add control for both core and plugin settings.
+---@oaram pane widget
+---@param option settings.option
+---@param plugin_name? string | nil
+local function add_control(pane, option, plugin_name)
+ local found = false
+ local path = type(plugin_name) ~= "nil" and
+ "plugins." .. plugin_name .. "." .. option.path or option.path
+ local option_value = nil
+ if type(path) ~= "nil" then
+ option_value = get_config_value(config, path, option.default)
+ end
+
+ if option.get_value then
+ option_value = option.get_value(option_value)
+ end
+
+ ---@type widget
+ local widget = nil
+
+ if type(option.type) == "string" then
+ option.type = settings.type[option.type:upper()]
+ end
+
+ if option.type == settings.type.NUMBER then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.numberbox
+ local number = NumberBox(pane, option_value, option.min, option.max, option.step)
+ widget = number
+ found = true
+
+ elseif option.type == settings.type.TOGGLE then
+ ---@type widget.toggle
+ local toggle = Toggle(pane, option.label, option_value)
+ widget = toggle
+ found = true
+
+ elseif option.type == settings.type.STRING then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.textbox
+ local string = TextBox(pane, option_value or "")
+ widget = string
+ found = true
+
+ elseif option.type == settings.type.SELECTION then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.selectbox
+ local select = SelectBox(pane)
+ for _, data in pairs(option.values) do
+ select:add_option(data[1], data[2])
+ end
+ for idx, _ in ipairs(select.list.rows) do
+ if select.list:get_row_data(idx) == option_value then
+ select:set_selected(idx-1)
+ break
+ end
+ end
+ widget = select
+ found = true
+
+ elseif option.type == settings.type.BUTTON then
+ ---@type widget.button
+ local button = Button(pane, option.label)
+ if option.icon then
+ button:set_icon(option.icon)
+ end
+ if option.on_click then
+ local command_type = type(option.on_click)
+ if command_type == "string" then
+ function button:on_click()
+ command.perform(option.on_click)
+ end
+ elseif command_type == "function" then
+ button.on_click = option.on_click
+ end
+ end
+ widget = button
+ found = true
+
+ elseif option.type == settings.type.LIST_STRINGS then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.itemslist
+ local list = ItemsList(pane)
+ if type(option_value) == "table" then
+ for _, value in ipairs(option_value) do
+ list:add_item(value)
+ end
+ end
+ widget = list
+ found = true
+
+ elseif option.type == settings.type.FONT then
+ --get fonts without conversion to renderer.font
+ if type(path) ~= "nil" then
+ if not option.font_error then
+ option_value = get_config_value(settings.config, path, option.default)
+ else
+ --fallback to default fonts if error loading user defined ones
+ option_value = option.default
+ end
+ end
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.fontslist
+ local fonts = FontsList(pane)
+ if type(option_value) == "table" then
+ for _, font in ipairs(option_value.fonts) do
+ fonts:add_font(font)
+ end
+
+ local font_options = option_value.options or {
+ size = 15,
+ antialiasing = "supixel",
+ hinting = "slight"
+ }
+ font_options.size = font_options.size or 15
+ font_options.antialiasing = font_options.antialiasing or "subpixel"
+ font_options.hinting = font_options.hinting or "slight"
+ fonts:set_options(font_options)
+ end
+ widget = fonts
+ found = true
+
+ elseif option.type == settings.type.FILE then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.filepicker
+ local file = FilePicker(pane, option_value or "")
+ if option.exists then
+ file:set_mode(FilePicker.mode.FILE_EXISTS)
+ else
+ file:set_mode(FilePicker.mode.FILE)
+ end
+ file.filters = option.filters or {}
+ widget = file
+ found = true
+
+ elseif option.type == settings.type.DIRECTORY then
+ ---@type widget.label
+ Label(pane, option.label .. ":")
+ ---@type widget.filepicker
+ local file = FilePicker(pane, option_value or "")
+ if option.exists then
+ file:set_mode(FilePicker.mode.DIRECTORY_EXISTS)
+ else
+ file:set_mode(FilePicker.mode.DIRECTORY)
+ end
+ file.filters = option.filters or {}
+ widget = file
+ found = true
+ end
+
+ if widget and type(path) ~= "nil" then
+ function widget:on_change(value)
+ if self:is(SelectBox) then
+ value = self:get_selected_data()
+ elseif self:is(ItemsList) then
+ value = self:get_items()
+ elseif self:is(FontsList) then
+ value = {
+ fonts = self:get_fonts(),
+ options = self:get_options()
+ }
+ end
+
+ if option.set_value then
+ value = option.set_value(value)
+ end
+
+ if self:is(FontsList) then
+ local fonts = {}
+ for _, font in ipairs(value.fonts) do
+ table.insert(fonts, renderer.font.load(
+ font.path, value.options.size * SCALE, value.options
+ ))
+ end
+ if option.fonts_list then
+ set_config_value(option.fonts_list, path, renderer.font.group(fonts))
+ else
+ set_config_value(config, path, renderer.font.group(fonts))
+ end
+ else
+ set_config_value(config, path, value)
+ end
+
+ set_config_value(settings.config, path, value)
+ save_settings()
+ if option.on_apply then
+ option.on_apply(value)
+ end
+ end
+ end
+
+ if (option.description or option.default) and found then
+ local text = option.description or ""
+ local default = ""
+ local default_type = type(option.default)
+ if default_type ~= "table" and default_type ~= "nil" then
+ if text ~= "" then
+ text = text .. " "
+ end
+ default = string.format("(default: %s)", option.default)
+ end
+ ---@type widget.label
+ local description = Label(pane, text .. default)
+ description.desc = true
+ end
+end
+
+---Generate all the widgets for core settings.
+function Settings:load_core_settings()
+ for _, section in ipairs(settings.sections) do
+ local options = settings.core[section]
+
+ ---@type widget|widget.foldingbook.pane
+ local pane = self.core_sections:get_pane(section)
+ if not pane then
+ pane = self.core_sections:add_pane(section, section)
+ else
+ pane = pane.container
+ end
+
+ for _, opt in ipairs(options) do
+ ---@type settings.option
+ local option = opt
+ add_control(pane, option)
+ end
+ end
+end
+
+---Function in charge of rendering the colors column of the color pane.
+---@param self widget.listbox
+---@oaram row integer
+---@param x integer
+---@param y integer
+---@param font renderer.font
+---@param color renderer.color
+---@param only_calc boolean
+---@return number width
+---@return number height
+local function on_color_draw(self, row, x, y, font, color, only_calc)
+ local w = self:get_width() - (x - self.position.x) - style.padding.x
+ local h = font:get_height()
+
+ if not only_calc then
+ local row_data = self:get_row_data(row)
+ local width = w/#row_data.colors
+
+ for i = 1, #row_data.colors do
+ renderer.draw_rect(x + ((i - 1) * width), y, width, h, row_data.colors[i])
+ end
+ end
+
+ return w, h
+end
+
+---Generate the list of all available colors with preview
+function Settings:load_color_settings()
+ self.colors.scrollable = false
+
+ local colors = get_installed_colors()
+
+ ---@type widget.listbox
+ local listbox = ListBox(self.colors)
+
+ listbox.border.width = 0
+ listbox:enable_expand(true)
+
+ listbox:add_column("Theme")
+ listbox:add_column("Colors")
+
+ for idx, details in ipairs(colors) do
+ local name = details.name
+ if settings.config.theme and settings.config.theme == name then
+ listbox:set_selected(idx)
+ end
+ listbox:add_row({
+ style.text, name, ListBox.COLEND, on_color_draw
+ }, {name = name, colors = details.colors})
+ end
+
+ function listbox:on_row_click(idx, data)
+ core.reload_module("colors." .. data.name)
+ settings.config.theme = data.name
+ save_settings()
+ end
+end
+
+---Unload a plugin settings from plugins section.
+---@param plugin string
+function Settings:disable_plugin(plugin)
+ for _, section in ipairs(settings.plugin_sections) do
+ local plugins = settings.plugins[section]
+
+ for plugin_name, options in pairs(plugins) do
+ if plugin_name == plugin then
+ self.plugin_sections:delete_pane(section)
+ end
+ end
+ end
+
+ if
+ type(settings.config.enabled_plugins) == "table"
+ and
+ settings.config.enabled_plugins[plugin]
+ then
+ settings.config.enabled_plugins[plugin] = nil
+ end
+ if type(settings.config.disabled_plugins) ~= "table" then
+ settings.config.disabled_plugins = {}
+ end
+
+ settings.config.disabled_plugins[plugin] = true
+ save_settings()
+end
+
+---Load plugin and append its settings to the plugins section.
+---@param plugin string
+function Settings:enable_plugin(plugin)
+ local loaded = false
+ local config_type = type(config.plugins[plugin])
+ if config_type == "boolean" or config_type == "nil" then
+ config.plugins[plugin] = {}
+ loaded = true
+ end
+
+ require("plugins." .. plugin)
+
+ if config.plugins[plugin] and config.plugins[plugin].config_spec then
+ local conf = config.plugins[plugin].config_spec
+ settings.add(conf.name, conf, plugin, true)
+ end
+
+ for _, section in ipairs(settings.plugin_sections) do
+ local plugins = settings.plugins[section]
+
+ for plugin_name, options in pairs(plugins) do
+ if plugin_name == plugin then
+ ---@type widget
+ local pane = self.plugin_sections:get_pane(section)
+ if not pane then
+ pane = self.plugin_sections:add_pane(section, section)
+ else
+ pane = pane.container
+ end
+
+ for _, opt in ipairs(options) do
+ ---@type settings.option
+ local option = opt
+ add_control(pane, option, plugin_name)
+ end
+ end
+ end
+ end
+
+ if
+ type(settings.config.disabled_plugins) == "table"
+ and
+ settings.config.disabled_plugins[plugin]
+ then
+ settings.config.disabled_plugins[plugin] = nil
+ end
+ if type(settings.config.enabled_plugins) ~= "table" then
+ settings.config.enabled_plugins = {}
+ end
+
+ settings.config.enabled_plugins[plugin] = true
+ save_settings()
+
+ if loaded then
+ core.log("Loaded '%s' plugin", plugin)
+ end
+end
+
+---Generate all the widgets for plugin settings.
+function Settings:load_plugin_settings()
+ ---@type widget
+ local pane = self.plugin_sections:get_pane("enable_disable")
+ if not pane then
+ pane = self.plugin_sections:add_pane("enable_disable", "Installed")
+ else
+ pane = pane.container
+ end
+
+ -- requires earlier access to startup process
+ Label(
+ pane,
+ "Notice: disabling plugins will not take effect until next restart"
+ )
+
+ Line(pane, 2, 10)
+
+ local plugins = get_installed_plugins()
+ for _, plugin in ipairs(plugins) do
+ if plugin ~= "settings" then
+ local enabled = false
+
+ if
+ (
+ type(config.plugins[plugin]) ~= "nil"
+ and
+ config.plugins[plugin] ~= false
+ )
+ or
+ (
+ settings.config.enabled_plugins
+ and
+ settings.config.enabled_plugins[plugin]
+ )
+ then
+ enabled = true
+ end
+
+ local this = self
+
+ ---@type widget.toggle
+ local toggle = Toggle(pane, prettify_name(plugin), enabled)
+ function toggle:on_change(value)
+ if value then
+ this:enable_plugin(plugin)
+ else
+ this:disable_plugin(plugin)
+ end
+ end
+ end
+ end
+
+ table.sort(settings.plugin_sections)
+
+ for _, section in ipairs(settings.plugin_sections) do
+ local plugins = settings.plugins[section]
+
+ for plugin_name, options in pairs(plugins) do
+ ---@type widget
+ local pane = self.plugin_sections:get_pane(section)
+ if not pane then
+ pane = self.plugin_sections:add_pane(section, section)
+ else
+ pane = pane.container
+ end
+
+ for _, opt in ipairs(options) do
+ ---@type settings.option
+ local option = opt
+ add_control(pane, option, plugin_name)
+ end
+ end
+ end
+end
+
+---@type widget.keybinddialog
+local keymap_dialog = KeybindingDialog()
+
+function keymap_dialog:on_save(bindings)
+ local row_value = apply_keybinding(self.command, bindings)
+ if row_value then
+ self.listbox:set_row(self.row_id, row_value)
+ end
+end
+
+function keymap_dialog:on_reset()
+ local default_keys = settings.default_keybindings[self.command]
+ local current_keys = { keymap.get_binding(self.command) }
+
+ for _, binding in ipairs(current_keys) do
+ keymap.unbind(binding, self.command)
+ end
+
+ if default_keys and #default_keys > 0 then
+ local cmd = self.command
+ if not settings.config.custom_keybindings then
+ settings.config.custom_keybindings = {}
+ settings.config.custom_keybindings[cmd] = {}
+ elseif not settings.config.custom_keybindings[cmd] then
+ settings.config.custom_keybindings[cmd] = {}
+ end
+ local shortcuts = ""
+ for _, binding in ipairs(default_keys) do
+ keymap.add({[binding] = cmd})
+ shortcuts = shortcuts .. binding .. "\n"
+ table.insert(settings.config.custom_keybindings[cmd], binding)
+ end
+ local bindings_list = shortcuts:gsub("\n$", "")
+ self.listbox:set_row(self.row_id, {
+ style.text, cmd, ListBox.COLEND, style.dim, bindings_list
+ })
+ else
+ self.listbox:set_row(self.row_id, {
+ style.text, self.command, ListBox.COLEND, style.dim, "none"
+ })
+ end
+ if
+ settings.config.custom_keybindings
+ and
+ settings.config.custom_keybindings[self.command]
+ then
+ settings.config.custom_keybindings[self.command] = nil
+ save_settings()
+ end
+end
+
+---Generate the list of all available commands and allow editing their keymaps.
+function Settings:load_keymap_settings()
+ self.keybinds.scrollable = false
+
+ local ordered = {}
+ for name, _ in pairs(command.map) do
+ table.insert(ordered, name)
+ end
+ table.sort(ordered)
+
+ ---@type widget.listbox
+ local listbox = ListBox(self.keybinds)
+
+ listbox.border.width = 0
+ listbox:enable_expand(true)
+
+ listbox:add_column("Command")
+ listbox:add_column("Bindings")
+
+ for _, name in ipairs(ordered) do
+ local keys = { keymap.get_binding(name) }
+ local binding = ""
+ if #keys == 1 then
+ binding = keys[1]
+ elseif #keys > 1 then
+ binding = keys[1]
+ for idx, key in ipairs(keys) do
+ if idx ~= 1 then
+ binding = binding .. "\n" .. key
+ end
+ end
+ elseif #keys < 1 then
+ binding = "none"
+ end
+ listbox:add_row({
+ style.text, name, ListBox.COLEND, style.dim, binding
+ }, name)
+ end
+
+ function listbox:on_row_click(idx, data)
+ if not keymap_dialog:is_visible() then
+ local bindings = { keymap.get_binding(data) }
+ keymap_dialog:set_bindings(bindings)
+ keymap_dialog.row_id = idx
+ keymap_dialog.command = data
+ keymap_dialog.listbox = self
+ keymap_dialog:show()
+ end
+ end
+end
+
+function Settings:setup_about()
+ ---@type widget.label
+ local title = Label(self.about, "Lite XL")
+ title.font = "big_font"
+ ---@type widget.label
+ local version = Label(self.about, "version " .. VERSION)
+ ---@type widget.label
+ local description = Label(
+ self.about,
+ "A lightweight text editor written in Lua, adapted from lite."
+ )
+
+ local function open_link(link)
+ local platform_filelauncher
+ if PLATFORM == "Windows" then
+ platform_filelauncher = "start"
+ elseif PLATFORM == "Mac OS X" then
+ platform_filelauncher = "open"
+ else
+ platform_filelauncher = "xdg-open"
+ end
+ system.exec(platform_filelauncher .. " " .. link)
+ end
+
+ ---@type widget.button
+ local button = Button(self.about, "Visit Website")
+ button:set_tooltip("Open https://lite-xl.com/")
+ function button:on_click() open_link("https://lite-xl.com/") end
+
+ ---@type widget.listbox
+ local contributors = ListBox(self.about)
+ contributors.scrollable = true
+ contributors:add_column("Contributors")
+ contributors:add_column("")
+ contributors:add_column("Website")
+ function contributors:on_row_click(_, data) open_link(data) end
+
+local contributors_list = {
+ { "Rxi", "Lite Founder", "https://github.com/rxi" },
+ { "Francesco Abbate", "Lite XL Founder", "https://github.com/franko" },
+ { "Adam Harrison", "Core", "https://github.com/adamharrison" },
+ { "Andrea Zanellato", "CI, Website", "https://github.com/redtide" },
+ { "Björn Buckwalter", "MacOS Support", "https://github.com/bjornbm" },
+ { "boppyt", "Contributor", "https://github.com/boppyt" },
+ { "Cukmekerb", "Contributor", "https://github.com/vincens2005" },
+ { "Daniel Rocha", "Contributor", "https://github.com/dannRocha" },
+ { "daubaris", "Contributor", "https://github.com/daubaris" },
+ { "Dheisom Gomes", "Contributor", "https://github.com/dheisom" },
+ { "Evgeny Petrovskiy", "Contributor", "https://github.com/eugenpt" },
+ { "Ferdinand Prantl", "Contributor", "https://github.com/prantlf" },
+ { "Jan", "Build System", "https://github.com/Jan200101" },
+ { "Janis-Leuenberger", "MacOS Support", "https://github.com/Janis-Leuenberger" },
+ { "Jefferson", "Contributor", "https://github.com/jgmdev" },
+ { "Jipok", "Contributor", "https://github.com/Jipok" },
+ { "Joshua Minor", "Contributor", "https://github.com/jminor" },
+ { "George Linkovsky", "Contributor", "https://github.com/Timofffee" },
+ { "Guldoman", "Core", "https://github.com/Guldoman" },
+ { "liquidev", "Contributor", "https://github.com/liquidev" },
+ { "Mat Mariani", "MacOS Support", "https://github.com/mathewmariani" },
+ { "Nightwing", "Contributor", "https://github.com/Nightwing13" },
+ { "Nils Kvist", "Contributor", "https://github.com/budRich" },
+ { "Not-a-web-Developer", "Contributor", "https://github.com/Not-a-web-Developer" },
+ { "Robert Štojs", "CI", "https://github.com/netrobert" },
+ { "sammyette", "Plugins", "https://github.com/TorchedSammy" },
+ { "Takase", "Core", "https://github.com/takase1121" },
+ { "xwii", "Contributor", "https://github.com/xcb-xwii" }
+}
+
+ for _, c in ipairs(contributors_list) do
+ contributors:add_row({
+ c[1], ListBox.COLEND, c[2], ListBox.COLEND, c[3]
+ }, c[3])
+ end
+
+ ---@param self widget
+ function self.about:update_positions()
+ local center = self:get_width() / 2
+
+ title:set_label("Lite XL")
+ title:set_position(
+ center - (title:get_width() / 2),
+ style.padding.y
+ )
+
+ version:set_position(
+ center - (version:get_width() / 2),
+ title:get_bottom() + (style.padding.y / 2)
+ )
+
+ description:set_position(
+ center - (description:get_width() / 2),
+ version:get_bottom() + (style.padding.y / 2)
+ )
+
+ button:set_position(
+ center - (button:get_width() / 2),
+ description:get_bottom() + style.padding.y
+ )
+
+ contributors:set_position(
+ style.padding.x,
+ button:get_bottom() + style.padding.y
+ )
+
+ contributors:set_size(
+ self:get_width() - (style.padding.x * 2),
+ self:get_height() - (button:get_bottom() + (style.padding.y * 2))
+ )
+
+ contributors:set_visible_rows()
+ end
+end
+
+---Reposition and resize core and plugin widgets.
+function Settings:update()
+ if not Settings.super.update(self) then return end
+
+ self.notebook:set_size(self.size.x, self.size.y)
+
+ for _, section in ipairs({self.core_sections, self.plugin_sections}) do
+ if section.parent:is_visible() then
+ section:set_size(
+ section.parent.size.x - (style.padding.x),
+ section:get_real_height()
+ )
+ section:set_position(style.padding.x / 2, 0)
+ for _, pane in ipairs(section.panes) do
+ local prev_child = nil
+ for pos=#pane.container.childs, 1, -1 do
+ local child = pane.container.childs[pos]
+ local x, y = 10, (10 * SCALE)
+ if prev_child then
+ if
+ (prev_child:is(Label) and not prev_child.desc)
+ or
+ (child:is(Label) and child.desc)
+ then
+ y = prev_child:get_bottom() + (10 * SCALE)
+ else
+ y = prev_child:get_bottom() + (30 * SCALE)
+ end
+ end
+ if child:is(Line) then
+ x = 0
+ elseif child:is(ItemsList) or child:is(FilePicker) or child:is(TextBox) then
+ child:set_size(pane.container:get_width() - 20, child.size.y)
+ end
+ child:set_position(x, y)
+ prev_child = child
+ end
+ end
+ end
+ end
+
+ if self.about:is_visible() then
+ self.about:update_positions()
+ end
+end
+
+--------------------------------------------------------------------------------
+-- overwrite core run to inject previously saved settings
+--------------------------------------------------------------------------------
+local core_run = core.run
+function core.run()
+ store_default_keybindings()
+
+ -- load plugins disabled by default and enabled by user
+ if settings.config.enabled_plugins then
+ for name, _ in pairs(settings.config.enabled_plugins) do
+ if
+ type(config.plugins[name]) == "boolean"
+ and
+ not config.plugins[name]
+ then
+ require("plugins." .. name)
+ end
+ end
+ end
+
+ -- append all settings defined in the plugins spec
+ scan_plugins_spec()
+
+ -- merge custom settings into config
+ merge_settings()
+
+ ---@type settings.ui
+ settings.ui = Settings()
+
+ -- apply user chosen color theme
+ if settings.config.theme and settings.config.theme ~= "default" then
+ core.try(function()
+ core.reload_module("colors." .. settings.config.theme)
+ end)
+ end
+
+ -- re-apply user settings
+ core.load_user_directory()
+ core.load_project_module()
+
+ core_run()
+end
+
+--------------------------------------------------------------------------------
+-- Disable plugins at startup, only works if this file is the first
+-- required on user module, or priority tag is obeyed by lite-xl.
+--------------------------------------------------------------------------------
+-- load custom user settings that include list of disabled plugins
+load_settings()
+
+-- only disable non already loaded plugins
+if settings.config.disabled_plugins then
+ for name, _ in pairs(settings.config.disabled_plugins) do
+ if type(rawget(config.plugins, name)) == "nil" then
+ config.plugins[name] = false
+ end
+ end
+end
+
+-- properly apply skip_plugins_version before other plugins are loaded
+if settings.config.skip_plugins_version then
+ config.skip_plugins_version = true
+else
+ config.skip_plugins_version = false
+end
+
+--------------------------------------------------------------------------------
+-- Add command and keymap to load settings view
+--------------------------------------------------------------------------------
+command.add(nil, {
+ ["ui:settings"] = function()
+ settings.ui:show()
+ local node = core.root_view:get_active_node_default()
+ local found = false
+ for _, view in ipairs(node.views) do
+ if view == settings.ui then
+ found = true
+ node:set_active_view(view)
+ break
+ end
+ end
+ if not found then
+ node:add_view(settings.ui)
+ end
+ end,
+})
+
+keymap.add {
+ ["ctrl+alt+p"] = "ui:settings"
+}
+
+--------------------------------------------------------------------------------
+-- Overwrite toolbar preferences command to open the settings gui
+--------------------------------------------------------------------------------
+if config.plugins.toolbarview ~= false then
+ local ToolbarView = require "plugins.toolbarview"
+ local toolbarview_on_mouse_moved = ToolbarView.on_mouse_moved
+ function ToolbarView:on_mouse_moved(px, py, ...)
+ toolbarview_on_mouse_moved(self, px, py, ...)
+ if
+ self.hovered_item
+ and
+ self.hovered_item.command == "core:open-user-module"
+ then
+ self.hovered_item.command = "ui:settings"
+ end
+ end
+end
+
+
+return settings;
diff --git a/plugins/smallclock.lua b/plugins/smallclock.lua
index f656a8f..e975152 100644
--- a/plugins/smallclock.lua
+++ b/plugins/smallclock.lua
@@ -1,27 +1,66 @@
--- mod-version:2 -- lite-xl 2.00
+-- mod-version:3
local core = require "core"
+local config = require "core.config"
+local common = require "core.common"
local style = require "core.style"
-local status_view = require "core.statusview"
+local StatusView = require "core.statusview"
-local time = ""
-
-core.add_thread(function()
- while true do
- local t = os.date("*t")
- time = string.format("%02d:%02d", t.hour, t.min)
- coroutine.yield(1)
- end
-end)
-
-local get_items = status_view.get_items
+config.plugins.smallclock = common.merge({
+ enabled = true,
+ clock_type = "24",
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Small Clock",
+ {
+ label = "Enabled",
+ description = "Show or hide the small clock 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:small-clock"):show()
+ else
+ core.status_view:get_item("status:small-clock"):hide()
+ end
+ end)
+ end
+ },
+ {
+ label = "Clock Type",
+ description = "Choose between 12 or 24 hours clock mode.",
+ path = "clock_type",
+ type = "selection",
+ default = "24",
+ values = {
+ {"24 Hours", "24"},
+ {"12 Hours", "12"}
+ }
+ }
+ }
+}, config.plugins.smallclock)
-function status_view:get_items()
- local left, right = get_items(self)
- local t = {style.dim, self.separator2, style.accent, time}
+local time = ""
- for _, item in ipairs(t) do
- table.insert(right, item)
+local last_time = os.time()
+local function update_time()
+ if os.time() > last_time then
+ local h = config.plugins.smallclock.clock_type == "24"
+ and os.date("%H") or os.date("%I")
+ local m = os.date("%M")
+ time = string.format("%02d:%02d", h, m)
+ last_time = os.time()
end
-
- return left, right
end
+
+core.status_view:add_item({
+ name = "status:small-clock",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function()
+ update_time()
+ return {style.accent, time}
+ end,
+ position = -1,
+ separator = core.status_view.separator2
+})
diff --git a/plugins/smoothcaret.lua b/plugins/smoothcaret.lua
index 40d852e..5639ff2 100644
--- a/plugins/smoothcaret.lua
+++ b/plugins/smoothcaret.lua
@@ -1,15 +1,42 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
local style = require "core.style"
+local common = require "core.common"
local DocView = require "core.docview"
-config.plugins.smoothcaret = { rate = 0.65 }
+config.plugins.smoothcaret = common.merge({
+ enabled = true,
+ rate = 0.65,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Smooth Caret",
+ {
+ label = "Enabled",
+ description = "Disable or enable the smooth caret animation.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Rate",
+ description = "Speed of the animation.",
+ path = "rate",
+ type = "number",
+ default = 0.65,
+ min = 0.2,
+ max = 1.0,
+ step = 0.05
+ },
+ }
+}, config.plugins.smoothcaret)
local docview_update = DocView.update
function DocView:update()
docview_update(self)
+ if not config.plugins.smoothcaret.enabled then return end
+
local minline, maxline = self:get_visible_line_range()
-- We need to keep track of all the carets
@@ -21,10 +48,10 @@ function DocView:update()
local idx, v_idx = 1, 1
for _, line, col in self.doc:get_selections() do
- local x, y = self:get_line_screen_position(line)
+ local x, y = self:get_line_screen_position(line, col)
-- Keep the position relative to the whole View
-- This way scrolling won't animate the caret
- x = x + self:get_col_x_offset(line, col) + self.scroll.x
+ x = x + self.scroll.x
y = y + self.scroll.y
if not self.carets[idx] then
@@ -56,7 +83,7 @@ function DocView:update()
-- Remove unused carets to avoid animating new ones when they are added
for i = idx, #self.carets do
- self.carets[idx] = nil
+ self.carets[i] = nil
end
if self.mouse_selecting ~= self.last_mouse_selecting then
@@ -72,7 +99,13 @@ function DocView:update()
self.caret_idx = 1
end
+local docview_draw_caret = DocView.draw_caret
function DocView:draw_caret(x, y)
+ if not config.plugins.smoothcaret.enabled then
+ docview_draw_caret(self, x, y)
+ return
+ end
+
local c = self.visible_carets[self.caret_idx] or { current = { x = x, y = y } }
local lh = self:get_line_height()
diff --git a/plugins/sort.lua b/plugins/sort.lua
index 6c65149..1ad4034 100644
--- a/plugins/sort.lua
+++ b/plugins/sort.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local translate = require "core.doc.translate"
diff --git a/plugins/spellcheck.lua b/plugins/spellcheck.lua
index 7b0ba2b..f55cf1f 100644
--- a/plugins/spellcheck.lua
+++ b/plugins/spellcheck.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local style = require "core.style"
local config = require "core.config"
@@ -7,33 +7,42 @@ local common = require "core.common"
local DocView = require "core.docview"
local Doc = require "core.doc"
-config.plugins.spellcheck = {}
-config.plugins.spellcheck.files = { "%.txt$", "%.md$", "%.markdown$" }
+local platform_dictionary_file
if PLATFORM == "Windows" then
- config.plugins.spellcheck.dictionary_file = EXEDIR .. "/words.txt"
+ platform_dictionary_file = EXEDIR .. "/words.txt"
else
- config.plugins.spellcheck.dictionary_file = "/usr/share/dict/words"
+ platform_dictionary_file = "/usr/share/dict/words"
end
+config.plugins.spellcheck = common.merge({
+ enabled = true,
+ files = { "%.txt$", "%.md$", "%.markdown$" },
+ dictionary_file = platform_dictionary_file
+}, config.plugins.spellcheck)
local last_input_time = 0
local word_pattern = "%a+"
local words
-core.add_thread(function()
- local t = {}
- local i = 0
- for line in io.lines(config.plugins.spellcheck.dictionary_file) do
- for word in line:gmatch(word_pattern) do
- t[word:lower()] = true
+local function load_dictionary()
+ core.add_thread(function()
+ local t = {}
+ local i = 0
+ for line in io.lines(config.plugins.spellcheck.dictionary_file) do
+ for word in line:gmatch(word_pattern) do
+ t[word:lower()] = true
+ end
+ i = i + 1
+ if i % 1000 == 0 then coroutine.yield() end
end
- i = i + 1
- if i % 1000 == 0 then coroutine.yield() end
- end
- words = t
- core.redraw = true
- core.log_quiet("Finished loading dictionary file: \"%s\"", config.plugins.spellcheck.dictionary_file)
-end)
+ words = t
+ core.redraw = true
+ core.log_quiet(
+ "Finished loading dictionary file: \"%s\"",
+ config.plugins.spellcheck.dictionary_file
+ )
+ end)
+end
local function matches_any(filename, ptns)
@@ -62,11 +71,16 @@ end
local draw_line_text = DocView.draw_line_text
function DocView:draw_line_text(idx, x, y)
- draw_line_text(self, idx, x, y)
-
- if not words
- or not matches_any(self.doc.filename or "", config.plugins.spellcheck.files) then
- return
+ local lh = draw_line_text(self, idx, x, y)
+
+ if
+ not config.plugins.spellcheck.enabled
+ or
+ not words
+ or
+ not matches_any(self.doc.filename or "", config.plugins.spellcheck.files)
+ then
+ return lh
end
local s, e = 0, 0
@@ -78,12 +92,13 @@ function DocView:draw_line_text(idx, x, y)
local word = text:sub(s, e):lower()
if not words[word] and not active_word(self.doc, idx, e + 1) then
local color = style.spellcheck_error or style.syntax.keyword2
- local x1 = x + self:get_col_x_offset(idx, s)
- local x2 = x + self:get_col_x_offset(idx, e + 1)
+ local x1, y1 = self:get_line_screen_position(idx, s)
+ local x2, y2 = self:get_line_screen_position(idx, e + 1)
local h = math.ceil(1 * SCALE)
- renderer.draw_rect(x1, y + self:get_line_height() - h, x2 - x1, h, color)
+ renderer.draw_rect(x1, y1 + self:get_line_height() - h, x2 - x1, h, color)
end
end
+ return lh
end
@@ -112,8 +127,44 @@ local function compare_words(word1, word2)
end
+-- The config specification used by the settings gui
+config.plugins.spellcheck.config_spec = {
+ name = "Spell Check",
+ {
+ label = "Enabled",
+ description = "Disable or enable spell checking.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ },
+ {
+ label = "Files",
+ description = "List of Lua patterns matching files to spell check.",
+ path = "files",
+ type = "list_strings",
+ default = { "%.txt$", "%.md$", "%.markdown$" }
+ },
+ {
+ label = "Dictionary File",
+ description = "Path to a text file that contains a list of dictionary words.",
+ path = "dictionary_file",
+ type = "file",
+ exists = true,
+ default = platform_dictionary_file,
+ on_apply = function()
+ load_dictionary()
+ end
+ }
+}
+
+load_dictionary()
+
command.add("core.docview", {
+ ["spell-check:toggle"] = function()
+ config.plugins.spellcheck.enabled = not config.plugins.spellcheck.enabled
+ end,
+
["spell-check:add-to-dictionary"] = function()
local word = get_word_at_caret()
if words[word] then
@@ -164,18 +215,28 @@ command.add("core.docview", {
-- select word and init replacement selector
local label = string.format("Replace \"%s\" With", word)
doc:set_selection(line, e + 1, line, s)
- core.command_view:enter(label, function(text, item)
- text = item and item.text or text
- doc:replace(function() return text end)
- end, function(text)
- local t = {}
- for _, w in ipairs(suggestions) do
- if w:lower():find(text:lower(), 1, true) then
- table.insert(t, w)
+ core.command_view:enter(label, {
+ submit = function(text, item)
+ text = item and item.text or text
+ doc:replace(function() return text end)
+ end,
+ suggest = function(text)
+ local t = {}
+ for _, w in ipairs(suggestions) do
+ if w:lower():find(text:lower(), 1, true) then
+ table.insert(t, w)
+ end
end
+ return t
end
- return t
- end)
+ })
end,
})
+
+local contextmenu = require "plugins.contextmenu"
+contextmenu:register("core.docview", {
+ contextmenu.DIVIDER,
+ { text = "View Suggestions", command = "spell-check:replace" },
+ { text = "Add to Dictionary", command = "spell-check:add-to-dictionary" }
+})
diff --git a/plugins/statusclock.lua b/plugins/statusclock.lua
index 8289502..aec58e3 100644
--- a/plugins/statusclock.lua
+++ b/plugins/statusclock.lua
@@ -1,54 +1,88 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
local style = require "core.style"
+local common = require "core.common"
local StatusView = require "core.statusview"
-local scan_rate = 1
-config.plugins.statusclock = {
+config.plugins.statusclock = common.merge({
+ enabled = true,
time_format = "%H:%M:%S",
- date_format = "%A, %d %B %Y"
-}
+ date_format = "%A, %d %B %Y",
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Status Clock",
+ {
+ label = "Enabled",
+ description = "Show or hide the clock 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:clock"):show()
+ else
+ core.status_view:get_item("status:clock"):hide()
+ end
+ end)
+ end
+ },
+ {
+ label = "Time Format",
+ description = "Time specification defined with Lua date/time place holders.",
+ path = "time_format",
+ type = "string",
+ default = "%H:%M:%S"
+ },
+ {
+ label = "Date Format",
+ description = "Date specification defined with Lua date/time place holders.",
+ path = "date_format",
+ type = "string",
+ default = "%A, %d %B %Y",
+ }
+ }
+}, config.plugins.statusclock)
local time_data = {
time_text = '',
date_text = '',
}
-core.add_thread(function()
- while true do
+local last_time = os.time()
+local function update_time()
+ if os.time() > last_time then
local time_text = os.date(config.plugins.statusclock.time_format)
local date_text = os.date(config.plugins.statusclock.date_format)
-
+
if time_data.time_text ~= time_text or time_data.time_text ~= date_text then
- core.redraw = true
time_data.time_text = time_text
time_data.date_text = date_text
end
-
- coroutine.yield(scan_rate)
- end
-end)
-
-local get_items = StatusView.get_items
-
-function StatusView:get_items()
- local left, right = get_items(self)
-
- local t = {
- style.dim,
- self.separator,
- style.dim and style.text,
- time_data.date_text,
- style.dim,
- self.separator,
- style.dim and style.text,
- time_data.time_text,
- }
- for _, item in ipairs(t) do
- table.insert(right, item)
+ -- only redraw if seconds enabled
+ if config.plugins.statusclock.time_format:find("%S", 1, true) then
+ core.redraw = true
+ end
+ last_time = os.time()
end
-
- return left, right
end
+core.status_view:add_item({
+ name = "status:clock",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function(self)
+ update_time()
+ return {
+ style.text,
+ time_data.date_text,
+ style.dim,
+ self.separator,
+ style.text,
+ time_data.time_text,
+ }
+ end,
+ position = -1,
+ separator = core.status_view.separator2
+})
+
diff --git a/plugins/tabnumbers.lua b/plugins/tabnumbers.lua
index 581a6b2..97e5d10 100644
--- a/plugins/tabnumbers.lua
+++ b/plugins/tabnumbers.lua
@@ -1,28 +1,44 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+local config = require "core.config"
local common = require "core.common"
-local core = require "core"
local style = require "core.style"
+local Node = require "core.node"
--- quite hackish, but Node isn't normally public
-local Node = getmetatable(core.root_view.root_node)
-local draw_tabs = Node.draw_tabs
+config.plugins.tabnumbers = common.merge({
+ enabled = true,
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Tab Numbers",
+ {
+ label = "Draw Tab Numbers",
+ description = "Show or hide numbers on the interface tabs.",
+ path = "enabled",
+ type = "toggle",
+ default = true
+ }
+ }
+}, config.plugins.tabnumbers)
-function Node:draw_tabs(...)
- draw_tabs(self, ...)
-
- for i, view in ipairs(self.views) do
- if i > 9 then break end
-
- local x, y, w, h = self:get_tab_rect(i)
- local number = tostring(i)
- local color = style.dim
- local title_width = style.font:get_width(view:get_name())
- local free_real_estate =
- math.min(math.max((w - title_width) / 2, style.padding.x), h)
- if view == self.active_view then
- color = style.accent
+-- Overwrite draw_tab_title to prepend tab number
+local Node_draw_tab_title = Node.draw_tab_title
+function Node:draw_tab_title(view, font, is_active, is_hovered, x, y, w, h)
+ if config.plugins.tabnumbers.enabled then
+ local number = ""
+ for i, v in ipairs(self.views) do
+ if view == v then
+ number = tostring(i)
+ end
+ end
+ local padx = 0
+ if number ~= "" then
+ padx = style.font:get_width(number) + (style.padding.x / 2)
+ w = w - padx
+ local color = is_active and style.text or style.dim
+ common.draw_text(style.font, color, number, nil, x, y, w, h)
end
- -- renderer.draw_rect(x, y + h - 1, free_real_estate, 1, color)
- common.draw_text(style.font, color, tostring(i), "center", x, y, free_real_estate, h)
+ local tx = x + padx -- Space for number
+ Node_draw_tab_title(self, view, font, is_active, is_hovered, tx, y, w, h)
+ else
+ Node_draw_tab_title(self, view, font, is_active, is_hovered, x, y, w, h)
end
end
diff --git a/plugins/texcompile.lua b/plugins/texcompile.lua
index 0c19d1b..af31aed 100644
--- a/plugins/texcompile.lua
+++ b/plugins/texcompile.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local config = require "core.config"
local command = require "core.command"
diff --git a/plugins/themeselect.lua b/plugins/themeselect.lua
index 70bd627..fe4ff34 100644
--- a/plugins/themeselect.lua
+++ b/plugins/themeselect.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
-- Load a specific theme when the filename of an active document does match
@@ -7,12 +7,11 @@ local core = require "core"
-- usage:
-- require("plugins.themeselect").add_pattern("%.md$", "summer")
-local theme_select = { }
+local theme_select = {}
local saved_colors_module = "core.style"
-local themes_patterns = {
-}
+local themes_patterns = {}
local reload_module = core.reload_module
local set_visited = core.set_visited
diff --git a/plugins/titleize.lua b/plugins/titleize.lua
index edb50d2..16d20b0 100644
--- a/plugins/titleize.lua
+++ b/plugins/titleize.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
@@ -9,4 +9,3 @@ command.add("core.docview", {
end)
end,
})
-
diff --git a/plugins/togglesnakecamel.lua b/plugins/togglesnakecamel.lua
index b5d20d9..c055933 100644
--- a/plugins/togglesnakecamel.lua
+++ b/plugins/togglesnakecamel.lua
@@ -1,4 +1,4 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
diff --git a/plugins/typingspeed.lua b/plugins/typingspeed.lua
index 62e44ef..0e28ec9 100644
--- a/plugins/typingspeed.lua
+++ b/plugins/typingspeed.lua
@@ -1,4 +1,4 @@
--- mod-version:2
+-- mod-version:3
local core = require "core"
local style = require "core.style"
@@ -6,17 +6,38 @@ local common = require "core.common"
local config = require "core.config"
local DocView = require "core.docview"
-if common["merge"] then
- config.plugins.typingspeed = common.merge({
- -- characters that should be counted as word boundary
- word_boundaries = "[%p%s]",
- }, config.plugins.keystats)
-else
- config.plugins.typingspeed = {
- -- characters that should be counted as word boundary
- word_boundaries = "[%p%s]",
- }
-end
+config.plugins.typingspeed = common.merge({
+ enabled = true,
+ -- characters that should be counted as word boundary
+ word_boundaries = "[%p%s]",
+ -- The config specification used by the settings gui
+ config_spec = {
+ name = "Typing Speed",
+ {
+ label = "Enabled",
+ description = "Show or hide the typing speed 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("typing-speed:stats"):show()
+ else
+ core.status_view:get_item("typing-speed:stats"):hide()
+ end
+ end)
+ end
+ },
+ {
+ label = "Word Boundaries",
+ description = "Lua pattern that matches characters to separate words.",
+ path = "word_boundaries",
+ type = "string",
+ default = "[%p%s]"
+ }
+ }
+}, config.plugins.typingspeed)
local chars = 0
local chars_last = 0
@@ -29,65 +50,51 @@ local wpm = 0
core.add_thread(function()
while true do
- local t = os.date("*t")
- if t.sec <= time_last then
- words_last = words
- words = 0
- chars_last = chars
- chars = 0
- time_last = t.sec
- end
- wpm = words_last * (1-(t.sec)/60) + words
- cpm = chars_last * (1-(t.sec)/60) + chars
+ if config.plugins.typingspeed.enabled then
+ local t = os.date("*t")
+ if t.sec <= time_last then
+ words_last = words
+ words = 0
+ chars_last = chars
+ chars = 0
+ time_last = t.sec
+ end
+ wpm = words_last * (1-(t.sec)/60) + words
+ cpm = chars_last * (1-(t.sec)/60) + chars
+ end
coroutine.yield(1)
end
end)
local on_text_input = DocView.on_text_input
function DocView:on_text_input(text, idx)
- chars = chars + 1
- if string.find(text, config.plugins.typingspeed.word_boundaries) then
- if started_word then
- words = words + 1
- started_word = false
- end
- else
- started_word = true
- end
+ if config.plugins.typingspeed.enabled then
+ chars = chars + 1
+ if string.find(text, config.plugins.typingspeed.word_boundaries) then
+ if started_word then
+ words = words + 1
+ started_word = false
+ end
+ else
+ started_word = true
+ end
+ end
on_text_input(self, text, idx)
end
-if core.status_view["add_item"] then
- core.status_view:add_item(
- function()
- return core.active_view and getmetatable(core.active_view) == DocView
- end,
- "keystats:stats",
- core.status_view.Item.RIGHT,
- function()
- return {
- style.text,
- string.format("%.0f CPM / %.0f WPM", cpm, wpm)
- }
- end,
- nil,
- 1,
- "characters / words per minute"
- ).separator = core.status_view.separator2
-else
- local get_items = core.status_view.get_items
- function core.status_view:get_items()
- local left, right = get_items(self)
-
- local t = {
- style.text, string.format("%.0f CPM / %.0f WPM", cpm, wpm),
- style.dim, self.separator2
- }
-
- for i, item in ipairs(t) do
- table.insert(right, i, item)
- end
-
- return left, right
- end
-end
+core.status_view:add_item({
+ predicate = function()
+ return core.active_view and getmetatable(core.active_view) == DocView
+ end,
+ name = "typing-speed:stats",
+ alignment = core.status_view.Item.RIGHT,
+ get_item = function()
+ return {
+ style.text,
+ string.format("%.0f CPM / %.0f WPM", cpm, wpm)
+ }
+ end,
+ position = 1,
+ tooltip = "characters / words per minute",
+ separator = core.status_view.separator2
+})
diff --git a/plugins/unboundedscroll.lua b/plugins/unboundedscroll.lua
index a27ab25..0793cfa 100644
--- a/plugins/unboundedscroll.lua
+++ b/plugins/unboundedscroll.lua
@@ -1,6 +1,18 @@
--- mod-version:2 -- lite-xl 2.0
+-- mod-version:3
+local command = require "core.command"
local DocView = require "core.docview"
-function DocView.clamp_scroll_position()
- -- do nothing
-end
+local doc_view_clamp_scroll_position = DocView.clamp_scroll_position
+local function clamp_scroll_noop() end
+
+DocView.clamp_scroll_position = clamp_scroll_noop
+
+command.add(nil, {
+ ["unbounded-scroll:toggle"] = function()
+ if DocView.clamp_scroll_position == clamp_scroll_noop then
+ DocView.clamp_scroll_position = doc_view_clamp_scroll_position
+ else
+ DocView.clamp_scroll_position = clamp_scroll_noop
+ end
+ end,
+})
diff --git a/plugins/wordcount.lua b/plugins/wordcount.lua
new file mode 100644
index 0000000..42224ac
--- /dev/null
+++ b/plugins/wordcount.lua
@@ -0,0 +1,90 @@
+-- mod-version:3
+local core = require "core"
+local style = require "core.style"
+local StatusView = require "core.statusview"
+local CommandView = require "core.commandview"
+local DocView = require "core.docview"
+local Doc = require "core.doc"
+local keymap = require "core.keymap"
+
+
+local words = setmetatable({}, { __mode = "k" })
+
+
+local function compute_line_words(line)
+ local s, total_words = 1, 0
+ while true do
+ local ns, e = line:find("%s+", s)
+ if ns == 1 and e == #line then break end
+ if not e then total_words = math.max(total_words, 1) break end
+ total_words = total_words + 1
+ s = e + 1
+ end
+ return total_words
+end
+
+
+local function compute_words(doc, start_line, end_line)
+ local total_words = 0
+ for i = start_line or 1, end_line or #doc.lines do
+ total_words = total_words + compute_line_words(doc.lines[i])
+ end
+ return total_words
+end
+
+
+local old_raw_insert = Doc.raw_insert
+function Doc:raw_insert(line, col, text, undo_stack, time)
+ if words[self] then
+ local old_count = compute_words(self, line, line)
+ old_raw_insert(self, line, col, text, undo_stack, time)
+ local total_lines, s = 0, 0
+ while true do
+ s = text:find("\n", s + 1, true)
+ if not s then break end
+ total_lines = total_lines + 1
+ end
+ words[self] = words[self] + compute_words(self, line, line + total_lines) - old_count
+ else
+ old_raw_insert(self, line, col, text, undo_stack, time)
+ end
+end
+
+
+local old_raw_remove = Doc.raw_remove
+function Doc:raw_remove(line1, col1, line2, col2, undo_stack, time)
+ if words[self] then
+ local old_count = compute_words(self, line1, line2)
+ old_raw_remove(self, line1, col1, line2, col2, undo_stack, time)
+ words[self] = words[self] + compute_words(self, line1, line1) - old_count
+ else
+ old_raw_remove(self, line1, col1, line2, col2, undo_stack, time)
+ end
+end
+
+
+local old_doc_new = Doc.new
+function Doc:new(...)
+ old_doc_new(self, ...)
+ words[self] = compute_words(self)
+end
+
+local cached_word_length, cached_word_count
+
+core.status_view:add_item({
+ predicate = function() return core.active_view:is(DocView) and not core.active_view:is(CommandView) and words[core.active_view.doc] end,
+ name = "status:word-count",
+ alignment = StatusView.Item.RIGHT,
+ get_item = function()
+ local selection_text = core.active_view.doc:get_selection_text()
+ if #selection_text ~= cached_word_length then
+ cached_word_count = compute_line_words(selection_text)
+ cached_word_length = #selection_text
+ end
+ if #selection_text > 0 then
+ return { style.text, cached_word_count .. " words" }
+ else
+ return { style.text, words[core.active_view.doc] .. " words" }
+ end
+ end
+})