Added the release_files folder
This commit is contained in:
parent
1b00045146
commit
0f7e075d6f
|
@ -22,7 +22,6 @@ LiteXL*
|
||||||
lite
|
lite
|
||||||
.config/
|
.config/
|
||||||
*.lha
|
*.lha
|
||||||
release_files
|
|
||||||
*.o
|
*.o
|
||||||
*.snalyzerinfo
|
*.snalyzerinfo
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#02103d" }
|
||||||
|
style.background2 = { common.color "#02103d" }
|
||||||
|
style.background3 = { common.color "#02103d" }
|
||||||
|
style.text = { common.color "#0f6773" }
|
||||||
|
style.caret = { common.color "#6a8ca8" }
|
||||||
|
style.accent = { common.color "#6a8ca8" }
|
||||||
|
style.dim = { common.color "#303030" }
|
||||||
|
style.divider = { common.color "#151515" }
|
||||||
|
style.selection = { common.color "#242424" }
|
||||||
|
style.line_number = { common.color "#252525" }
|
||||||
|
style.line_number2 = { common.color "#444444" }
|
||||||
|
style.line_highlight = { common.color "#101010" }
|
||||||
|
style.scrollbar = { common.color "#252525" }
|
||||||
|
style.scrollbar2 = { common.color "#444444" }
|
||||||
|
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["symbol"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["comment"] = { common.color "#404040" }
|
||||||
|
style.syntax["keyword"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["number"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["literal"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["string"] = { common.color "#132a52" }
|
||||||
|
style.syntax["operator"] = { common.color "#01A870" }
|
||||||
|
style.syntax["function"] = { common.color "#01A870" }
|
|
@ -0,0 +1,48 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
-- App --
|
||||||
|
style.background = { common.color "#222831" }
|
||||||
|
style.background2 = { common.color "#1e232b" }
|
||||||
|
style.background3 = { common.color "#1e232b" }
|
||||||
|
style.text = { common.color "#dfe2e7" }
|
||||||
|
style.caret = { common.color "#dfe2e7" }
|
||||||
|
style.accent = { common.color "#e2e4e9" }
|
||||||
|
style.dim = { common.color "#8893a5" }
|
||||||
|
style.divider = { common.color "#1e232b" }
|
||||||
|
style.selection = { common.color "#2c3440" }
|
||||||
|
style.line_number = { common.color "#8893a5" }
|
||||||
|
style.line_number2 = { common.color "#8893a5" }
|
||||||
|
style.line_highlight = { common.color "#242a34" }
|
||||||
|
style.scrollbar = { common.color "#2c3440" }
|
||||||
|
style.scrollbar2 = { common.color "#f5ad44" }
|
||||||
|
style.scrollbar_track = { common.color "#00000000" }
|
||||||
|
style.nagbar = { common.color "#db504a" }
|
||||||
|
style.nagbar_text = { common.color "#dfe2e7" }
|
||||||
|
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.45)" }
|
||||||
|
style.drag_overlay = { common.color "#dfe2e733" }
|
||||||
|
style.drag_overlay_tab = { common.color "#f5ad44" }
|
||||||
|
style.good = { common.color "#47e2b1" }
|
||||||
|
style.warn = { common.color "#f5ad44" }
|
||||||
|
style.error = { common.color "#db504a" }
|
||||||
|
style.modified = { common.color "#448bf5" }
|
||||||
|
|
||||||
|
-- Syntax --
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#dfe2e7" }
|
||||||
|
style.syntax["symbol"] = { common.color "#dfe2e7" }
|
||||||
|
style.syntax["comment"] = { common.color "#8893a5" }
|
||||||
|
style.syntax["keyword"] = { common.color "#448bf5" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#f5ad44" }
|
||||||
|
style.syntax["number"] = { common.color "#f5ad44" }
|
||||||
|
style.syntax["literal"] = { common.color "#45e1df" }
|
||||||
|
style.syntax["string"] = { common.color "#f5ad44" }
|
||||||
|
style.syntax["operator"] = { common.color "#dfe2e7" }
|
||||||
|
style.syntax["function"] = { common.color "#f786aa" }
|
||||||
|
|
||||||
|
-- Lint+ --
|
||||||
|
style.lint = {}
|
||||||
|
style.lint["info"] = { common.color "#448bf5" }
|
||||||
|
style.lint["hint"] = { common.color "#47e2b1" }
|
||||||
|
style.lint["warning"] = { common.color "#f5ad44" }
|
||||||
|
style.lint["error"] = { common.color "#db504a" }
|
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#03071e" }
|
||||||
|
style.background2 = { common.color "#03071e" }
|
||||||
|
style.background3 = { common.color "#03071e" }
|
||||||
|
style.text = { common.color "#ffa848" }
|
||||||
|
style.caret = { common.color "#ffa848" }
|
||||||
|
style.accent = { common.color "#ffb86c" }
|
||||||
|
style.dim = { common.color "#4f526b" }
|
||||||
|
style.divider = { common.color "#22242e" }
|
||||||
|
style.selection = { common.color "#4c5163" }
|
||||||
|
style.line_number = { common.color "#44475a" }
|
||||||
|
style.line_number2 = { common.color "#717796" }
|
||||||
|
style.line_highlight = { common.color "#2d303d" }
|
||||||
|
style.scrollbar = { common.color "#44475a" }
|
||||||
|
style.scrollbar2 = { common.color "#4c5163" }
|
||||||
|
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#f5faff" }
|
||||||
|
style.syntax["symbol"] = { common.color "#f5faff" }
|
||||||
|
style.syntax["comment"] = { common.color "#081355" }
|
||||||
|
style.syntax["keyword"] = { common.color "#fc0fc0" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#05e6fa" }
|
||||||
|
style.syntax["number"] = { common.color "#7612c5" }
|
||||||
|
style.syntax["literal"] = { common.color "#7612c5" }
|
||||||
|
style.syntax["string"] = { common.color "#fdd017" }
|
||||||
|
style.syntax["operator"] = { common.color "#fc0fc0" }
|
||||||
|
style.syntax["function"] = { common.color "#05e6fa" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#073642" }
|
||||||
|
style.background2 = { common.color "#073642" }
|
||||||
|
style.background3 = { common.color "#073642" }
|
||||||
|
style.text = { common.color "#00d1d1" }
|
||||||
|
style.caret = { common.color "#f053f3" }
|
||||||
|
style.accent = { common.color "#f053f3" }
|
||||||
|
style.dim = { common.color "#586e75" }
|
||||||
|
style.divider = { common.color "#6c71c4" }
|
||||||
|
style.selection = { common.color "#415256" }
|
||||||
|
style.line_number = { common.color "#586e75" }
|
||||||
|
style.line_number2 = { common.color "#f053f3" }
|
||||||
|
style.line_highlight = { common.color "#415256" }
|
||||||
|
style.scrollbar = { common.color "#6c71c4" }
|
||||||
|
style.scrollbar2 = { common.color "#6c71c4" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#00d1d1" }
|
||||||
|
style.syntax["symbol"] = { common.color "#00ff7f" }
|
||||||
|
style.syntax["comment"] = { common.color "#6c71c4" }
|
||||||
|
style.syntax["keyword"] = { common.color "#6c71c4" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#6c71c4" }
|
||||||
|
style.syntax["number"] = { common.color "#00ff7f" }
|
||||||
|
style.syntax["literal"] = { common.color "#1586d2" }
|
||||||
|
style.syntax["string"] = { common.color "#f7f97d" }
|
||||||
|
style.syntax["operator"] = { common.color "#00ff7f" }
|
||||||
|
style.syntax["function"] = { common.color "#55ffff" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#282a36" }
|
||||||
|
style.background2 = { common.color "#21222b" }
|
||||||
|
style.background3 = { common.color "#21222b" }
|
||||||
|
style.text = { common.color "#7b81a6" }
|
||||||
|
style.caret = { common.color "#f8f8f0" }
|
||||||
|
style.accent = { common.color "#8be9fd" }
|
||||||
|
style.dim = { common.color "#4f5873" }
|
||||||
|
style.divider = { common.color "#1f2029" }
|
||||||
|
style.selection = { common.color "#44475a" }
|
||||||
|
style.line_number = { common.color "#53576e" }
|
||||||
|
style.line_number2 = { common.color "#f8f8f0" }
|
||||||
|
style.line_highlight = { common.color "#313442" }
|
||||||
|
style.scrollbar = { common.color "#44475a" }
|
||||||
|
style.scrollbar2 = { common.color "#ff79c6" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#f8f8f2" }
|
||||||
|
style.syntax["symbol"] = { common.color "#f8f8f2" }
|
||||||
|
style.syntax["comment"] = { common.color "#6272a4" }
|
||||||
|
style.syntax["keyword"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["number"] = { common.color "#bd93f9" }
|
||||||
|
style.syntax["literal"] = { common.color "#f1fa8c" }
|
||||||
|
style.syntax["string"] = { common.color "#f1fa8c" }
|
||||||
|
style.syntax["operator"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["function"] = { common.color "#50fa7b" }
|
|
@ -0,0 +1,38 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
math.randomseed(os.time())
|
||||||
|
|
||||||
|
local color = {
|
||||||
|
math.random(90, 255),
|
||||||
|
math.random(90, 255),
|
||||||
|
math.random(90, 255),
|
||||||
|
255
|
||||||
|
}
|
||||||
|
|
||||||
|
style.background = { common.color "#151515" }
|
||||||
|
style.background2 = { common.color "#151515" }
|
||||||
|
style.background3 = { common.color "#151515" }
|
||||||
|
style.text = { common.color "#707070" }
|
||||||
|
style.caret = { common.color "#dfdfdf" }
|
||||||
|
style.accent = { common.color "#d0d0d0" }
|
||||||
|
style.dim = { common.color "#303030" }
|
||||||
|
style.divider = { common.color "#151515" }
|
||||||
|
style.selection = { common.color "#303030" }
|
||||||
|
style.line_number = { common.color "#252525" }
|
||||||
|
style.line_number2 = { common.color "#444444" }
|
||||||
|
style.line_highlight = { common.color "#101010" }
|
||||||
|
style.scrollbar = { common.color "#252525" }
|
||||||
|
style.scrollbar2 = { common.color "#444444" }
|
||||||
|
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["symbol"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["comment"] = { common.color "#404040" }
|
||||||
|
style.syntax["keyword"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["number"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["literal"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["string"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["operator"] = color
|
||||||
|
style.syntax["function"] = color
|
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#151515" }
|
||||||
|
style.background2 = { common.color "#151515" }
|
||||||
|
style.background3 = { common.color "#151515" }
|
||||||
|
style.text = { common.color "#707070" }
|
||||||
|
style.caret = { common.color "#dfdfdf" }
|
||||||
|
style.accent = { common.color "#d0d0d0" }
|
||||||
|
style.dim = { common.color "#303030" }
|
||||||
|
style.divider = { common.color "#151515" }
|
||||||
|
style.selection = { common.color "#242424" }
|
||||||
|
style.line_number = { common.color "#252525" }
|
||||||
|
style.line_number2 = { common.color "#444444" }
|
||||||
|
style.line_highlight = { common.color "#101010" }
|
||||||
|
style.scrollbar = { common.color "#252525" }
|
||||||
|
style.scrollbar2 = { common.color "#444444" }
|
||||||
|
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["symbol"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["comment"] = { common.color "#404040" }
|
||||||
|
style.syntax["keyword"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["number"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["literal"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["string"] = { common.color "#dfdfdf" }
|
||||||
|
style.syntax["operator"] = { common.color "#01A870" }
|
||||||
|
style.syntax["function"] = { common.color "#01A870" }
|
|
@ -0,0 +1,38 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
-- GitHub color palette
|
||||||
|
-- Ported by Andrey Proskurin (proskur1n)
|
||||||
|
local bg = { common.color "#22272e" }
|
||||||
|
local bg2 = { common.color "#2d333b" }
|
||||||
|
local fg = { common.color "#adbac7" }
|
||||||
|
local fgdim = { common.color "#768390" }
|
||||||
|
local red = { common.color "#f47067" }
|
||||||
|
local blue = { common.color "#6cb6ff" }
|
||||||
|
local purple = { common.color "#dcbdfb" }
|
||||||
|
|
||||||
|
style.background = bg
|
||||||
|
style.background2 = bg
|
||||||
|
style.background3 = bg
|
||||||
|
style.text = fg
|
||||||
|
style.caret = red
|
||||||
|
style.accent = blue
|
||||||
|
style.dim = fgdim
|
||||||
|
style.divider = { common.color "#444c56" }
|
||||||
|
style.selection = { common.color "#2e4c77" }
|
||||||
|
style.line_number = fgdim
|
||||||
|
style.line_number2 = fg
|
||||||
|
style.line_highlight = bg2
|
||||||
|
style.scrol = fgdim
|
||||||
|
style.scrollbar2 = fg
|
||||||
|
|
||||||
|
style.syntax["normal"] = fg
|
||||||
|
style.syntax["symbol"] = fg
|
||||||
|
style.syntax["comment"] = fgdim
|
||||||
|
style.syntax["keyword"] = red
|
||||||
|
style.syntax["keyword2"] = red
|
||||||
|
style.syntax["number"] = blue
|
||||||
|
style.syntax["literal"] = blue
|
||||||
|
style.syntax["string"] = { common.color "#96d0ff" }
|
||||||
|
style.syntax["operator"] = fg
|
||||||
|
style.syntax["function"] = blue
|
|
@ -0,0 +1,41 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
-- GitHub color palette
|
||||||
|
-- Ported by Andrey Proskurin (proskur1n)
|
||||||
|
local bg = { common.color "#0d1117" }
|
||||||
|
local bg2 = { common.color "#161925" }
|
||||||
|
local fg = { common.color "#adbac7" }
|
||||||
|
local fgdim = { common.color "#768390" }
|
||||||
|
local red = { common.color "#f47067" }
|
||||||
|
local blue = { common.color "#6cb6ff" }
|
||||||
|
local purple = { common.color "#dcbdfb" }
|
||||||
|
|
||||||
|
style.background = bg
|
||||||
|
style.background2 = bg
|
||||||
|
style.background3 = bg2
|
||||||
|
style.text = fg
|
||||||
|
style.caret = red
|
||||||
|
style.accent = blue
|
||||||
|
style.dim = fgdim
|
||||||
|
style.divider = { common.color "#444c56" }
|
||||||
|
style.selection = { common.color "#2e4c77" }
|
||||||
|
style.line_number = fgdim
|
||||||
|
style.line_number2 = fg
|
||||||
|
style.line_highlight = {common.color "#1e202e"}
|
||||||
|
style.scrollbar = fgdim
|
||||||
|
style.scrollbar2 = fg
|
||||||
|
|
||||||
|
style.syntax["normal"] = fg
|
||||||
|
style.syntax["symbol"] = fg
|
||||||
|
style.syntax["comment"] = fgdim
|
||||||
|
style.syntax["keyword"] = red
|
||||||
|
style.syntax["keyword2"] = red
|
||||||
|
style.syntax["number"] = blue
|
||||||
|
style.syntax["literal"] = blue
|
||||||
|
style.syntax["string"] = { common.color "#96d0ff" }
|
||||||
|
style.syntax["operator"] = fg
|
||||||
|
style.syntax["function"] = blue
|
||||||
|
|
||||||
|
style.guide = { common.color "#404040" } -- indentguide
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#282828" }
|
||||||
|
style.background2 = { common.color "#1d2021" }
|
||||||
|
style.background3 = { common.color "#1d2021" }
|
||||||
|
style.text = { common.color "#928374" }
|
||||||
|
style.caret = { common.color "#fbf1c7" }
|
||||||
|
style.accent = { common.color "#ebdbb2" }
|
||||||
|
style.dim = { common.color "#928374" }
|
||||||
|
style.divider = { common.color "#1d2021" }
|
||||||
|
style.selection = { common.color "#3c3836" }
|
||||||
|
style.line_number = { common.color "#928374" }
|
||||||
|
style.line_number2 = { common.color "#ebdbb2" }
|
||||||
|
style.line_highlight = { common.color "#32302f" }
|
||||||
|
style.scrollbar = { common.color "#928374" }
|
||||||
|
style.scrollbar2 = { common.color "#fbf1c7" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#ebdbb2" }
|
||||||
|
style.syntax["symbol"] = { common.color "#ebdbb2" }
|
||||||
|
style.syntax["comment"] = { common.color "#928374" }
|
||||||
|
style.syntax["keyword"] = { common.color "#fb4934" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#83a598" }
|
||||||
|
style.syntax["number"] = { common.color "#d3869b" }
|
||||||
|
style.syntax["literal"] = { common.color "#d3869b" }
|
||||||
|
style.syntax["string"] = { common.color "#b8bb26" }
|
||||||
|
style.syntax["operator"] = { common.color "#ebdbb2" }
|
||||||
|
style.syntax["function"] = { common.color "#8ec07c" }
|
|
@ -0,0 +1,37 @@
|
||||||
|
-- Colors from: https://github.com/nanotech/jellybeans.vim
|
||||||
|
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#151515" }
|
||||||
|
style.background2 = { common.color "#212121" }
|
||||||
|
style.background3 = { common.color "#212121" }
|
||||||
|
style.text = { common.color "#e8e8d3" }
|
||||||
|
style.caret = { common.color "#e8e8d3" }
|
||||||
|
style.accent = { common.color "#597bc5" } -- Text in autocomplete and command, col(>80) in satusbar
|
||||||
|
style.dim = { common.color "#888888" } -- Text of nonactive tabs, prefix in log
|
||||||
|
style.divider = { common.color "#151515" }
|
||||||
|
style.selection = { common.color "#404040" }
|
||||||
|
style.line_number = { common.color "#3b3b3b" }
|
||||||
|
style.line_number2 = { common.color "#888888" } -- Number on line with caret
|
||||||
|
style.line_highlight = { common.color "#191919"}
|
||||||
|
style.scrollbar = { common.color "#2e2e2e" }
|
||||||
|
style.scrollbar2 = { common.color "#3b3b3b" } -- Hovered
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#6b8b9b" }
|
||||||
|
style.syntax["symbol"] = { common.color "#e8e8d3" }
|
||||||
|
style.syntax["comment"] = { common.color "#888888" }
|
||||||
|
style.syntax["keyword"] = { common.color "#8197bf" } -- local function end, if case
|
||||||
|
style.syntax["keyword2"] = { common.color "#FFB964" } -- self, int float
|
||||||
|
style.syntax["number"] = { common.color "#cf6a4c" }
|
||||||
|
style.syntax["literal"] = { common.color "#8FBFDC" }
|
||||||
|
style.syntax["string"] = { common.color "#99ad6a" }
|
||||||
|
style.syntax["operator"] = { common.color "#8FBFDC"} -- = + - / < >
|
||||||
|
style.syntax["function"] = { common.color "#FAD07A" }
|
||||||
|
|
||||||
|
-- PLUGINS
|
||||||
|
style.linter_warning = { common.color "#d8ad4c" } -- linter
|
||||||
|
style.bracketmatch_color = { common.color "#8197bf" } -- bracketmatch
|
||||||
|
style.guide = { common.color "#3b3b3b" }
|
||||||
|
style.guide_highlight = { common.color "#5b5b5b" } -- indentguide
|
||||||
|
style.guide_width = 1 -- indentguide
|
|
@ -0,0 +1,32 @@
|
||||||
|
-- Liqube Dark Code for Lite <liqube.com>
|
||||||
|
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#13171e" }
|
||||||
|
style.background2 = { common.color "#21252b" }
|
||||||
|
style.background3 = { common.color "#21252b" }
|
||||||
|
style.text = { common.color "#abb2bf" }
|
||||||
|
style.caret = { common.color "#abb2bf" }
|
||||||
|
style.accent = { common.color "#ffffff" }
|
||||||
|
style.dim = { common.color "#545e70" }
|
||||||
|
style.divider = { common.color "#242223" }
|
||||||
|
style.selection = { common.color "#3e4451" }
|
||||||
|
style.line_number = { common.color "#323641" }
|
||||||
|
style.line_number2 = { common.color "#596275" }
|
||||||
|
style.line_highlight = { common.color "#1c1f25" }
|
||||||
|
style.scrollbar = { common.color "#3d3f43" }
|
||||||
|
style.scrollbar2 = { common.color "#595b5f" }
|
||||||
|
style.guide = { common.color "#1c1f25" } -- indentguide
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#abb2bf" }
|
||||||
|
style.syntax["symbol"] = { common.color "#71a9d7" }
|
||||||
|
style.syntax["comment"] = { common.color "#5c6370" }
|
||||||
|
style.syntax["keyword"] = { common.color "#98c875" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#ffffff" }
|
||||||
|
style.syntax["number"] = { common.color "#ffffff" }
|
||||||
|
style.syntax["literal"] = { common.color "#ea5964" }
|
||||||
|
style.syntax["string"] = { common.color "#ea5964" }
|
||||||
|
style.syntax["operator"] = { common.color "#657085" }
|
||||||
|
style.syntax["function"] = { common.color "#ffffff" }
|
||||||
|
style.syntax["preprocessor"] = { common.color "#98c875" } -- thinking ahead
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#303841" }
|
||||||
|
style.background2 = { common.color "#1d2227" }
|
||||||
|
style.background3 = { common.color "#1d2227" }
|
||||||
|
style.text = { common.color "#9ea191" }
|
||||||
|
style.caret = { common.color "#61efce" }
|
||||||
|
style.accent = { common.color "#ffd152" }
|
||||||
|
style.dim = { common.color "#4c5863" }
|
||||||
|
style.divider = { common.color "#242223" }
|
||||||
|
style.selection = { common.color "#4c5863" }
|
||||||
|
style.line_number = { common.color "#bfc5d0" }
|
||||||
|
style.line_number2 = { common.color "#848b95" }
|
||||||
|
style.line_highlight = { common.color "#303841" }
|
||||||
|
style.scrollbar = { common.color "#696f75" }
|
||||||
|
style.scrollbar2 = { common.color "#444b53" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#d7dde9" }
|
||||||
|
style.syntax["symbol"] = { common.color "#d8dee9" }
|
||||||
|
style.syntax["comment"] = { common.color "#a6acb9" }
|
||||||
|
style.syntax["keyword"] = { common.color "#e55e66" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#ef6179" }
|
||||||
|
style.syntax["number"] = { common.color "#ffd152" }
|
||||||
|
style.syntax["literal"] = { common.color "#e75550" }
|
||||||
|
style.syntax["string"] = { common.color "#939d5d" }
|
||||||
|
style.syntax["operator"] = { common.color "#c2674f" }
|
||||||
|
style.syntax["function"] = { common.color "#6699ca" }
|
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#080808" }
|
||||||
|
style.background2 = { common.color "#080808" }
|
||||||
|
style.background3 = { common.color "#101010" }
|
||||||
|
style.text = { common.color "#707070" }
|
||||||
|
style.caret = { common.color "#ffffff" }
|
||||||
|
style.accent = { common.color "#d0d0d0" }
|
||||||
|
style.dim = { common.color "#303030" }
|
||||||
|
style.divider = { common.color "#080808" }
|
||||||
|
style.selection = { common.color "#242424" }
|
||||||
|
style.line_number = { common.color "#202020" }
|
||||||
|
style.line_number2 = { common.color "#707070" }
|
||||||
|
style.line_highlight = { common.color "#101010" }
|
||||||
|
style.scrollbar = { common.color "#252525" }
|
||||||
|
style.scrollbar2 = { common.color "#303030" }
|
||||||
|
|
||||||
|
style.syntax = {}
|
||||||
|
style.syntax["normal"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["symbol"] = { common.color "#a0a0a0" }
|
||||||
|
style.syntax["comment"] = { common.color "#404040" }
|
||||||
|
style.syntax["keyword"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["number"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["literal"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["string"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["operator"] = { common.color "#f0f0f0" }
|
||||||
|
style.syntax["function"] = { common.color "#a0a0a0" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#272822" }
|
||||||
|
style.background2 = { common.color "#22231C" }
|
||||||
|
style.background3 = { common.color "#22231C" }
|
||||||
|
style.text = { common.color "#9ea191" }
|
||||||
|
style.caret = { common.color "#F8F8F0" }
|
||||||
|
style.accent = { common.color "#F8F8F2" }
|
||||||
|
style.dim = { common.color "#5e6052" }
|
||||||
|
style.divider = { common.color "#1b1c17" }
|
||||||
|
style.selection = { common.color "#49483E" }
|
||||||
|
style.line_number = { common.color "#75715E" }
|
||||||
|
style.line_number2 = { common.color "#d2d0c6" }
|
||||||
|
style.line_highlight = { common.color "#36372f" }
|
||||||
|
style.scrollbar = { common.color "#49483E" }
|
||||||
|
style.scrollbar2 = { common.color "#636254" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#F8F8F2" }
|
||||||
|
style.syntax["symbol"] = { common.color "#F8F8F2" }
|
||||||
|
style.syntax["comment"] = { common.color "#75715E" }
|
||||||
|
style.syntax["keyword"] = { common.color "#F92672" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#66DAEF" }
|
||||||
|
style.syntax["number"] = { common.color "#AE81FF" }
|
||||||
|
style.syntax["literal"] = { common.color "#AE81FF" }
|
||||||
|
style.syntax["string"] = { common.color "#E6DB74" }
|
||||||
|
style.syntax["operator"] = { common.color "#F8F8F2" }
|
||||||
|
style.syntax["function"] = { common.color "#A6E22E" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#282923" }
|
||||||
|
style.background2 = { common.color "#181915" }
|
||||||
|
style.background3 = { common.color "#181915" }
|
||||||
|
style.text = { common.color "#9ea191" }
|
||||||
|
style.caret = { common.color "#f8f8f2" }
|
||||||
|
style.accent = { common.color "#f8f8f2" }
|
||||||
|
style.dim = { common.color "#5e6052" }
|
||||||
|
style.divider = { common.color "#1b1c17" }
|
||||||
|
style.selection = { common.color "#3a3a32" }
|
||||||
|
style.line_number = { common.color "#90918b" }
|
||||||
|
style.line_number2 = { common.color "#d2d0c6" }
|
||||||
|
style.line_highlight = { common.color "#282923" }
|
||||||
|
style.scrollbar = { common.color "#63635f" }
|
||||||
|
style.scrollbar2 = { common.color "#3d3d38" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#f8f8f2" }
|
||||||
|
style.syntax["symbol"] = { common.color "#f8f8f2" }
|
||||||
|
style.syntax["comment"] = { common.color "#75715E" }
|
||||||
|
style.syntax["keyword"] = { common.color "#f92472" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#f92472" }
|
||||||
|
style.syntax["number"] = { common.color "#ac80ff" }
|
||||||
|
style.syntax["literal"] = { common.color "#e7db74" }
|
||||||
|
style.syntax["string"] = { common.color "#e7db74" }
|
||||||
|
style.syntax["operator"] = { common.color "#f92472" }
|
||||||
|
style.syntax["function"] = { common.color "#5cd5ef" }
|
|
@ -0,0 +1,39 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
|
||||||
|
style.background = { common.color "#2E3440" }
|
||||||
|
style.background2 = { common.color "#2E3440" }
|
||||||
|
style.background3 = { common.color "#3B4252" }
|
||||||
|
style.text = { common.color "#D8DEE9" }
|
||||||
|
style.caret = { common.color "#D8DEE9" }
|
||||||
|
style.accent = { common.color "#88C0D0" }
|
||||||
|
style.dim = { common.color "#d8dee966" }
|
||||||
|
style.divider = { common.color "#3B4252" }
|
||||||
|
style.selection = { common.color "#434C5ECC" }
|
||||||
|
style.line_number = { common.color "#4C566A" }
|
||||||
|
style.line_number2 = { common.color "#D8DEE9" }
|
||||||
|
style.line_highlight = { common.color "#3B4252" }
|
||||||
|
style.scrollbar = { common.color "#434c5eaa" }
|
||||||
|
style.scrollbar2 = { common.color "#434c5e" }
|
||||||
|
style.good = { common.color "#72b886cc" }
|
||||||
|
style.warn = { common.color "#d08770" }
|
||||||
|
style.error = { common.color "#bf616a" }
|
||||||
|
style.modified = { common.color "#ebcb8b" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#ECEFF4" }
|
||||||
|
style.syntax["symbol"] = { common.color "#D8DEE9" }
|
||||||
|
style.syntax["comment"] = { common.color "#616E88" }
|
||||||
|
style.syntax["keyword"] = { common.color "#81A1C1" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#81A1C1" }
|
||||||
|
style.syntax["number"] = { common.color "#B48EAD" }
|
||||||
|
style.syntax["literal"] = { common.color "#81A1C1" }
|
||||||
|
style.syntax["string"] = { common.color "#A3BE8C" }
|
||||||
|
style.syntax["operator"] = { common.color "#81A1C1" }
|
||||||
|
style.syntax["function"] = { common.color "#88C0D0" }
|
||||||
|
|
||||||
|
config.highlight_current_line = "no_selection"
|
||||||
|
|
||||||
|
style.guide = { common.color "#434c5eb3" }
|
||||||
|
style.bracketmatch_color = { common.color "#8fbcbb" }
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#282c34" }
|
||||||
|
style.background2 = { common.color "#21252B" }
|
||||||
|
style.background3 = { common.color "#21252B" }
|
||||||
|
style.text = { common.color "#abb2bf" }
|
||||||
|
style.caret = { common.color "#528bff" }
|
||||||
|
style.accent = { common.color "#ffffff" }
|
||||||
|
style.dim = { common.color "#4f5873" }
|
||||||
|
style.divider = { common.color "#181A1F" }
|
||||||
|
style.selection = { common.color "#383D49" }
|
||||||
|
style.line_number = { common.color "#53576e" }
|
||||||
|
style.line_number2 = { common.color "#666B76" }
|
||||||
|
style.line_highlight = { common.color "#2C333E" }
|
||||||
|
style.scrollbar = { common.color "#4f5873" }
|
||||||
|
style.scrollbar2 = { common.color "#3060C1" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#abb2bf" }
|
||||||
|
style.syntax["symbol"] = { common.color "#abb2bf" }
|
||||||
|
style.syntax["comment"] = { common.color "#5f697a" }
|
||||||
|
style.syntax["keyword"] = { common.color "#cd74e8" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#eb6772" }
|
||||||
|
style.syntax["number"] = { common.color "#db9d63" }
|
||||||
|
style.syntax["literal"] = { common.color "#e6c07b" }
|
||||||
|
style.syntax["string"] = { common.color "#9acc76" }
|
||||||
|
style.syntax["operator"] = { common.color "#56B6C2" }
|
||||||
|
style.syntax["function"] = { common.color "#5cb3fa" }
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#242424" }
|
||||||
|
style.background2 = { common.color "#252528" }
|
||||||
|
style.background3 = { common.color "#44475A" }
|
||||||
|
|
||||||
|
style.text = { common.color "#fffff0" }
|
||||||
|
style.caret = { common.color "#69FF94" }
|
||||||
|
style.accent = { common.color "#ff0fff" }
|
||||||
|
|
||||||
|
style.dim = { common.color "#0fffff" }
|
||||||
|
style.divider = { common.color "#7b7f8b" }
|
||||||
|
style.selection = { common.color "#48484f" }
|
||||||
|
style.selectionhighlight = { common.color "#dddeee" }
|
||||||
|
style.line_number = { common.color "#525259" }
|
||||||
|
style.line_number2 = { common.color "#f6f6e0" }
|
||||||
|
style.line_highlight = { common.color "#343438" }
|
||||||
|
style.scrollbar = { common.color "#414146" }
|
||||||
|
style.scrollbar2 = { common.color "#4b4bff" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#e1e1e6" }
|
||||||
|
style.syntax["symbol"] = { common.color "#97e1f1" }
|
||||||
|
style.syntax["comment"] = { common.color "#676b6f" }
|
||||||
|
style.syntax["keyword"] = { common.color "#E58AC9" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#F77483" }
|
||||||
|
style.syntax["number"] = { common.color "#FFA94D" }
|
||||||
|
style.syntax["literal"] = { common.color "#ee6666" }
|
||||||
|
style.syntax["string"] = { common.color "#f7c95c" }
|
||||||
|
style.syntax["operator"] = { common.color "#93DDFA" }
|
||||||
|
style.syntax["function"] = { common.color "#bf9eee" }
|
|
@ -0,0 +1,29 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#222226" }
|
||||||
|
style.background2 = { common.color "#252528" }
|
||||||
|
style.background3 = { common.color "#1e1e21" }
|
||||||
|
style.text = { common.color "#dddddd" }
|
||||||
|
style.caret = { common.color "#aeafad" }
|
||||||
|
style.accent = { common.color "#0097fb" }
|
||||||
|
style.dim = { common.color "#9393a5" }
|
||||||
|
style.divider = { common.color "#1E1E1E" }
|
||||||
|
style.selection = { common.color "#264f78" }
|
||||||
|
style.line_number = { common.color "#858585" }
|
||||||
|
style.line_number2 = { common.color "#c6c6c6" }
|
||||||
|
style.line_highlight = { common.color "#2b2b2f"}
|
||||||
|
style.scrollbar = { common.color "#313136" }
|
||||||
|
style.scrollbar2 = { common.color "#bfbfbf" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#dddddd" }
|
||||||
|
style.syntax["symbol"] = { common.color "#e06c75" }
|
||||||
|
style.syntax["comment"] = { common.color "#c5c5c5" }
|
||||||
|
style.syntax["keyword"] = { common.color "#61afef" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#56B6C2" }
|
||||||
|
style.syntax["number"] = { common.color "#d19a66" }
|
||||||
|
style.syntax["literal"] = { common.color "#61AFEF" }
|
||||||
|
style.syntax["string"] = { common.color "#98C379" }
|
||||||
|
style.syntax["operator"] = { common.color "#dddddd" }
|
||||||
|
style.syntax["function"] = { common.color "#c678dd" }
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#252336" }
|
||||||
|
style.background2 = { common.color "#171521" }
|
||||||
|
style.background3 = { common.color "#171521" }
|
||||||
|
style.text = { common.color "#8f94bf" }
|
||||||
|
style.caret = { common.color "#f17e6e" }
|
||||||
|
style.accent = { common.color "#ff79c6" }
|
||||||
|
style.dim = { common.color "#4f526b" }
|
||||||
|
style.divider = { common.color "#171521" }
|
||||||
|
style.selection = { common.color "#4a445a" }
|
||||||
|
style.line_number = { common.color "#4a445a" }
|
||||||
|
style.line_number2 = { common.color "#ff79c6" }
|
||||||
|
style.line_highlight = { common.color "rgba(0, 0, 0, 0.30)" }
|
||||||
|
style.scrollbar = { common.color "#4f526b" }
|
||||||
|
style.scrollbar2 = { common.color "#717382" }
|
||||||
|
style.nagbar = { common.color "#ff79c6" }
|
||||||
|
style.nagbar_text = { common.color "#FFFFFF" }
|
||||||
|
style.nagbar_dim = { common.color "rgba(0, 0, 0, 0.30)" }
|
||||||
|
style.drag_overlay = { common.color "rgba(0, 0, 0, 0.30)" }
|
||||||
|
style.drag_overlay_tab = { common.color "#f17e6e" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#FFFFFF" }
|
||||||
|
style.syntax["symbol"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["comment"] = { common.color "#9484bd" }
|
||||||
|
style.syntax["keyword"] = { common.color "#f5de4a" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#f73f51" }
|
||||||
|
style.syntax["number"] = { common.color "#bd93f9" }
|
||||||
|
style.syntax["literal"] = { common.color "#5afad2" }
|
||||||
|
style.syntax["string"] = { common.color "#ff8b39" }
|
||||||
|
style.syntax["operator"] = { common.color "#f5de4a" }
|
||||||
|
style.syntax["function"] = { common.color "#8be9fd" }
|
||||||
|
|
||||||
|
style.guide = { common.color "#4a445a" }
|
||||||
|
style.bracketmatch_color = { common.color "#f17e6e" }
|
|
@ -0,0 +1,37 @@
|
||||||
|
-- Colors from: https://github.com/enkia/tokyo-night-vscode-theme
|
||||||
|
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#1a1b26" }
|
||||||
|
style.background2 = { common.color "#16161e" }
|
||||||
|
style.background3 = { common.color "#24283b" }
|
||||||
|
style.text = { common.color "#a9b1d6" }
|
||||||
|
style.caret = { common.color "#a9b1d6" }
|
||||||
|
style.accent = { common.color "#7aa2f7" } -- Text in autocomplete and command, col(>80) in satusbar
|
||||||
|
style.dim = { common.color "#565f89" } -- Text of nonactive tabs, prefix in log
|
||||||
|
style.divider = { common.color "#101014" }
|
||||||
|
style.selection = { common.color "#282B3C" }
|
||||||
|
style.line_number = { common.color "#363B54" }
|
||||||
|
style.line_number2 = { common.color "#737AA2" } -- Number on line with caret
|
||||||
|
style.line_highlight = { common.color "#1E202E"}
|
||||||
|
style.scrollbar = { common.color "#24283b" }
|
||||||
|
style.scrollbar2 = { common.color "#414868" } -- Hovered
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#9ABDF5" }
|
||||||
|
style.syntax["symbol"] = { common.color "#c0caf5" }
|
||||||
|
style.syntax["comment"] = { common.color "#414868" }
|
||||||
|
style.syntax["keyword"] = { common.color "#bb9af7" } -- local function end, if case
|
||||||
|
style.syntax["keyword2"] = { common.color "#bb9af7" } -- self, int float
|
||||||
|
style.syntax["number"] = { common.color "#ff9e64" }
|
||||||
|
style.syntax["literal"] = { common.color "#c0caf5" }
|
||||||
|
style.syntax["string"] = { common.color "#9ece6a" }
|
||||||
|
style.syntax["operator"] = { common.color "#2ac3de"} -- = + - / < >
|
||||||
|
style.syntax["function"] = { common.color "#7aa2f7" }
|
||||||
|
|
||||||
|
-- PLUGINS
|
||||||
|
style.linter_warning = { common.color "#e0af68" } -- linter
|
||||||
|
style.bracketmatch_color = { common.color "#565f89" } -- bracketmatch
|
||||||
|
style.guide = { common.color "#1E202E" }
|
||||||
|
style.guide_highlight = { common.color "#363B54" } -- indentguide
|
||||||
|
style.guide_width = 1 -- indentguide
|
|
@ -0,0 +1,37 @@
|
||||||
|
-- Most of the colors are taken from:
|
||||||
|
-- https://github.com/microsoft/vscode/tree/master/extensions/theme-defaults/themes
|
||||||
|
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#1E1E1E" }
|
||||||
|
style.background2 = { common.color "#252526" }
|
||||||
|
style.background3 = { common.color "#252526" }
|
||||||
|
style.text = { common.color "#D4D4D4" }
|
||||||
|
style.caret = { common.color "#FFFFFF" }
|
||||||
|
style.accent = { common.color "#76BCFF" } -- Text in autocomplete and command, col(>80) in satusbar
|
||||||
|
style.dim = { common.color "#7A7A7A" } -- Text of nonactive tabs, prefix in log
|
||||||
|
style.divider = { common.color "#1E1E1E" }
|
||||||
|
style.selection = { common.color "#264F78" }
|
||||||
|
style.line_number = { common.color "#707070" }
|
||||||
|
style.line_number2 = { common.color "#A0A0A0" } -- Number on line with caret
|
||||||
|
style.line_highlight = { common.color "#333A40"}
|
||||||
|
style.scrollbar = { common.color "#404040" }
|
||||||
|
style.scrollbar2 = { common.color "#707070" } -- Hovered
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#D4D4D4" }
|
||||||
|
style.syntax["symbol"] = { common.color "#D4D4D4" }
|
||||||
|
style.syntax["comment"] = { common.color "#6A9955" }
|
||||||
|
style.syntax["keyword"] = { common.color "#569CD6" } -- local function end, if case
|
||||||
|
style.syntax["keyword2"] = { common.color "#C586C0" } -- self, int float
|
||||||
|
style.syntax["number"] = { common.color "#B5CEA8" }
|
||||||
|
style.syntax["literal"] = { common.color "#569CD6" }
|
||||||
|
style.syntax["string"] = { common.color "#CE9178" }
|
||||||
|
style.syntax["operator"] = { common.color "#8590A5"} -- = + - / < >
|
||||||
|
style.syntax["function"] = { common.color "#DCDCAA" }
|
||||||
|
|
||||||
|
-- PLUGINS
|
||||||
|
style.linter_warning = { common.color "#B89500" } -- linter
|
||||||
|
style.bracketmatch_color = { common.color "#76BCFF" } -- bracketmatch
|
||||||
|
style.guide = { common.color "#404040" } -- indentguide
|
||||||
|
style.guide_width = 1 -- indentguide
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#282a36" }
|
||||||
|
style.background2 = { common.color "#22242e" }
|
||||||
|
style.background3 = { common.color "#22242e" }
|
||||||
|
style.text = { common.color "#aab3e6" }
|
||||||
|
style.caret = { common.color "#f5faff" }
|
||||||
|
style.accent = { common.color "#ffb86c" }
|
||||||
|
style.dim = { common.color "#4f526b" }
|
||||||
|
style.divider = { common.color "#22242e" }
|
||||||
|
style.selection = { common.color "#4c5163" }
|
||||||
|
style.line_number = { common.color "#44475a" }
|
||||||
|
style.line_number2 = { common.color "#717796" }
|
||||||
|
style.line_highlight = { common.color "#2d303d" }
|
||||||
|
style.scrollbar = { common.color "#44475a" }
|
||||||
|
style.scrollbar2 = { common.color "#4c5163" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#f5faff" }
|
||||||
|
style.syntax["symbol"] = { common.color "#f5faff" }
|
||||||
|
style.syntax["comment"] = { common.color "#6272a4" }
|
||||||
|
style.syntax["keyword"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#8be9fd" }
|
||||||
|
style.syntax["number"] = { common.color "#bd93f9" }
|
||||||
|
style.syntax["literal"] = { common.color "#bd93f9" }
|
||||||
|
style.syntax["string"] = { common.color "#f1fa8c" }
|
||||||
|
style.syntax["operator"] = { common.color "#ff79c6" }
|
||||||
|
style.syntax["function"] = { common.color "#8be9fd" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#404040" }
|
||||||
|
style.background2 = { common.color "#3d3d3d" }
|
||||||
|
style.background3 = { common.color "#2b2b2b" }
|
||||||
|
style.text = { common.color "#dcdccc" }
|
||||||
|
style.caret = { common.color "#f8f8f0" }
|
||||||
|
style.accent = { common.color "#dcdccc" }
|
||||||
|
style.dim = { common.color "#8f8f8f" }
|
||||||
|
style.divider = { common.color "#383838" }
|
||||||
|
style.selection = { common.color "#2f2f2f" }
|
||||||
|
style.line_number = { common.color "#545454" }
|
||||||
|
style.line_number2 = { common.color "#545454" }
|
||||||
|
style.line_highlight = { common.color "#383838" }
|
||||||
|
style.scrollbar = { common.color "#4c4c4c" }
|
||||||
|
style.scrollbar2 = { common.color "#5e5e5e" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#dcdccc" }
|
||||||
|
style.syntax["symbol"] = { common.color "#dcdccc" }
|
||||||
|
style.syntax["comment"] = { common.color "#7f9f7f" }
|
||||||
|
style.syntax["keyword"] = { common.color "#f0dfaf" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#dfdfbf" }
|
||||||
|
style.syntax["number"] = { common.color "#8cd0d3" }
|
||||||
|
style.syntax["literal"] = { common.color "#dfaf8f" }
|
||||||
|
style.syntax["string"] = { common.color "#cc9393" }
|
||||||
|
style.syntax["operator"] = { common.color "#f0efd0" }
|
||||||
|
style.syntax["function"] = { common.color "#efef8f" }
|
Binary file not shown.
|
@ -0,0 +1,31 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
-- GitHubs style varies from language to language so its hard to get perfect
|
||||||
|
-- Originally written by thebirk, 2019
|
||||||
|
|
||||||
|
style.background = { common.color "#fbfbfb" }
|
||||||
|
style.background2 = { common.color "#f2f2f2" }
|
||||||
|
style.background3 = { common.color "#f2f2f2" }
|
||||||
|
style.text = { common.color "#404040" }
|
||||||
|
style.caret = { common.color "#181818" }
|
||||||
|
style.accent = { common.color "#0366d6" }
|
||||||
|
style.dim = { common.color "#b0b0b0" }
|
||||||
|
style.divider = { common.color "#e8e8e8" }
|
||||||
|
style.selection = { common.color "#b7dce8" }
|
||||||
|
style.line_number = { common.color "#d0d0d0" }
|
||||||
|
style.line_number2 = { common.color "#808080" }
|
||||||
|
style.line_highlight = { common.color "#f2f2f2" }
|
||||||
|
style.scrollbar = { common.color "#e0e0e0" }
|
||||||
|
style.scrollbar2 = { common.color "#c0c0c0" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#24292e" }
|
||||||
|
style.syntax["symbol"] = { common.color "#24292e" }
|
||||||
|
style.syntax["comment"] = { common.color "#6a737d" }
|
||||||
|
style.syntax["keyword"] = { common.color "#d73a49" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#d73a49" }
|
||||||
|
style.syntax["number"] = { common.color "#005cc5" }
|
||||||
|
style.syntax["literal"] = { common.color "#005cc5" }
|
||||||
|
style.syntax["string"] = { common.color "#032f62" }
|
||||||
|
style.syntax["operator"] = { common.color "#d73a49" }
|
||||||
|
style.syntax["function"] = { common.color "#005cc5" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#fbf1c7" }
|
||||||
|
style.background2 = { common.color "#f9f5d7" }
|
||||||
|
style.background3 = { common.color "#f9f5d7" }
|
||||||
|
style.text = { common.color "#928374" }
|
||||||
|
style.caret = { common.color "#282828" }
|
||||||
|
style.accent = { common.color "#3c3836" }
|
||||||
|
style.dim = { common.color "#928374" }
|
||||||
|
style.divider = { common.color "#f9f5d7" }
|
||||||
|
style.selection = { common.color "#ebdbb2" }
|
||||||
|
style.line_number = { common.color "#928374" }
|
||||||
|
style.line_number2 = { common.color "#3c3836" }
|
||||||
|
style.line_highlight = { common.color "#f2e5bc" }
|
||||||
|
style.scrollbar = { common.color "#928374" }
|
||||||
|
style.scrollbar2 = { common.color "#282828" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#3c3836" }
|
||||||
|
style.syntax["symbol"] = { common.color "#3c3836" }
|
||||||
|
style.syntax["comment"] = { common.color "#928374" }
|
||||||
|
style.syntax["keyword"] = { common.color "#9d0006" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#076678" }
|
||||||
|
style.syntax["number"] = { common.color "#8f3f71" }
|
||||||
|
style.syntax["literal"] = { common.color "#8f3f71" }
|
||||||
|
style.syntax["string"] = { common.color "#79740e" }
|
||||||
|
style.syntax["operator"] = { common.color "#3c3836" }
|
||||||
|
style.syntax["function"] = { common.color "#427b58" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#f7f9f9" }
|
||||||
|
style.background2 = { common.color "#f7f9f9" }
|
||||||
|
style.background3 = { common.color "#f7f9f9" }
|
||||||
|
style.text = { common.color "#404040" }
|
||||||
|
style.caret = { common.color "#ff5971" }
|
||||||
|
style.accent = { common.color "#ff5971" }
|
||||||
|
style.dim = { common.color "#b0b0b0" }
|
||||||
|
style.divider = { common.color "#e8e8e8" }
|
||||||
|
style.selection = { common.color "#fde6eb" }
|
||||||
|
style.line_number = { common.color "#d0d0d0" }
|
||||||
|
style.line_number2 = { common.color "#808080" }
|
||||||
|
style.line_highlight = { common.color "#f2f2f2" }
|
||||||
|
style.scrollbar = { common.color "#e0e0e0" }
|
||||||
|
style.scrollbar2 = { common.color "#c0c0c0" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#181818" }
|
||||||
|
style.syntax["symbol"] = { common.color "#181818" }
|
||||||
|
style.syntax["comment"] = { common.color "#43cdbd" }
|
||||||
|
style.syntax["keyword"] = { common.color "#5f7dcd" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#9c53c6" }
|
||||||
|
style.syntax["number"] = { common.color "#3daee9" }
|
||||||
|
style.syntax["literal"] = { common.color "#3daee9" }
|
||||||
|
style.syntax["string"] = { common.color "#3daee9" }
|
||||||
|
style.syntax["operator"] = { common.color "#5f7dcd" }
|
||||||
|
style.syntax["function"] = { common.color "#9c53c6" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#fdf6e3" }
|
||||||
|
style.background2 = { common.color "#eee8d5" }
|
||||||
|
style.background3 = { common.color "#eee8d5" }
|
||||||
|
style.text = { common.color "#657b83" }
|
||||||
|
style.caret = { common.color "#657b83" }
|
||||||
|
style.accent = { common.color "#002b36" }
|
||||||
|
style.dim = { common.color "#93a1a1" }
|
||||||
|
style.divider = { common.color "#e0dbc8" }
|
||||||
|
style.selection = { common.color "#073642" }
|
||||||
|
style.line_number = { common.color "#93a1a1" }
|
||||||
|
style.line_number2 = { common.color "#002b36" }
|
||||||
|
style.line_highlight = { common.color "#eee8d5" }
|
||||||
|
style.scrollbar = { common.color "#e0dbc8" }
|
||||||
|
style.scrollbar2 = { common.color "#bfbbaa" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#657b83" }
|
||||||
|
style.syntax["symbol"] = { common.color "#657b83" }
|
||||||
|
style.syntax["comment"] = { common.color "#93a1a1" }
|
||||||
|
style.syntax["keyword"] = { common.color "#859900" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#268bd2" }
|
||||||
|
style.syntax["number"] = { common.color "#d33682" }
|
||||||
|
style.syntax["literal"] = { common.color "#2aa198" }
|
||||||
|
style.syntax["string"] = { common.color "#2aa198" }
|
||||||
|
style.syntax["operator"] = { common.color "#859900" }
|
||||||
|
style.syntax["function"] = { common.color "#268bd2" }
|
|
@ -0,0 +1,28 @@
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
style.background = { common.color "#fdf6e3" }
|
||||||
|
style.background2 = { common.color "#2e2c29" }
|
||||||
|
style.background3 = { common.color "#3e3c37" }
|
||||||
|
style.text = { common.color "#b2ada1" }
|
||||||
|
style.caret = { common.color "#b2ada1" }
|
||||||
|
style.accent = { common.color "#6c71c4" }
|
||||||
|
style.dim = { common.color "#b2ada1" }
|
||||||
|
style.divider = { common.color "#201f1d" }
|
||||||
|
style.selection = { common.color "#eee8d5" }
|
||||||
|
style.line_number = { common.color "#93a1a1" }
|
||||||
|
style.line_number2 = { common.color "#002b36" }
|
||||||
|
style.line_highlight = { common.color "#fcefcd" }
|
||||||
|
style.scrollbar = { common.color "#e0dbc8" }
|
||||||
|
style.scrollbar2 = { common.color "#9d9988" }
|
||||||
|
|
||||||
|
style.syntax["normal"] = { common.color "#3e3c37" }
|
||||||
|
style.syntax["symbol"] = { common.color "#4c4f82" }
|
||||||
|
style.syntax["comment"] = { common.color "#93a1a1" }
|
||||||
|
style.syntax["keyword"] = { common.color "#d33682" }
|
||||||
|
style.syntax["keyword2"] = { common.color "#6c71c4" }
|
||||||
|
style.syntax["number"] = { common.color "#859900" }
|
||||||
|
style.syntax["literal"] = { common.color "#b58900" }
|
||||||
|
style.syntax["string"] = { common.color "#cb4b16" }
|
||||||
|
style.syntax["operator"] = { common.color "#859900" }
|
||||||
|
style.syntax["function"] = { common.color "#268bd2" }
|
Binary file not shown.
|
@ -0,0 +1,129 @@
|
||||||
|
-- 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 = common.merge({ map = {
|
||||||
|
["["] = "]",
|
||||||
|
["{"] = "}",
|
||||||
|
["("] = ")",
|
||||||
|
['"'] = '"',
|
||||||
|
["'"] = "'",
|
||||||
|
["`"] = "`",
|
||||||
|
} }, config.plugins.autoinsert)
|
||||||
|
|
||||||
|
|
||||||
|
-- Workaround for bug in Lite XL 2.1
|
||||||
|
-- Remove this when b029f5993edb7dee5ccd2ba55faac1ec22e24609 is in a release
|
||||||
|
local function get_selection(doc, sort)
|
||||||
|
local line1, col1, line2, col2 = doc:get_selection_idx(doc.last_selection)
|
||||||
|
if line1 then
|
||||||
|
return doc:get_selection_idx(doc.last_selection, sort)
|
||||||
|
else
|
||||||
|
return doc:get_selection_idx(1, sort)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function is_closer(chr)
|
||||||
|
for _, v in pairs(config.plugins.autoinsert.map) do
|
||||||
|
if v == chr then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function count_char(text, chr)
|
||||||
|
local count = 0
|
||||||
|
for _ in text:gmatch(chr) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local on_text_input = DocView.on_text_input
|
||||||
|
|
||||||
|
function DocView:on_text_input(text)
|
||||||
|
local mapping = config.plugins.autoinsert.map[text]
|
||||||
|
|
||||||
|
-- prevents plugin from operating on `CommandView`
|
||||||
|
if getmetatable(self) ~= DocView then
|
||||||
|
return on_text_input(self, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- wrap selection if we have a selection
|
||||||
|
if mapping and self.doc:has_selection() then
|
||||||
|
local l1, c1, l2, c2, swap = get_selection(self.doc, true)
|
||||||
|
self.doc:insert(l2, c2, mapping)
|
||||||
|
self.doc:insert(l1, c1, text)
|
||||||
|
self.doc:set_selection(l1, c1, l2, c2 + 2, swap)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- skip inserting closing text
|
||||||
|
local chr = self.doc:get_char(self.doc:get_selection())
|
||||||
|
if text == chr and is_closer(chr) then
|
||||||
|
self.doc:move_to(1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- don't insert closing quote if we have a non-even number on this line
|
||||||
|
local line = self.doc:get_selection()
|
||||||
|
if text == mapping and count_char(self.doc.lines[line], text) % 2 == 1 then
|
||||||
|
return on_text_input(self, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- auto insert closing bracket
|
||||||
|
if mapping and (chr:find("%s") or is_closer(chr) and chr ~= '"') then
|
||||||
|
on_text_input(self, text)
|
||||||
|
on_text_input(self, mapping)
|
||||||
|
self.doc:move_to(-1)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
on_text_input(self, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
local function predicate()
|
||||||
|
return core.active_view:is(DocView)
|
||||||
|
and not core.active_view.doc:has_selection(), core.active_view.doc
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add(predicate, {
|
||||||
|
["autoinsert:backspace"] = function(doc)
|
||||||
|
local l, c = doc:get_selection()
|
||||||
|
if c > 1 then
|
||||||
|
local chr = doc:get_char(l, c)
|
||||||
|
local mapped = config.plugins.autoinsert.map[doc:get_char(l, c - 1)]
|
||||||
|
if mapped and mapped == chr then
|
||||||
|
doc:delete_to(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
command.perform "doc:backspace"
|
||||||
|
end,
|
||||||
|
|
||||||
|
["autoinsert:delete-to-previous-word-start"] = function(doc)
|
||||||
|
local le, ce = translate.previous_word_start(doc, doc:get_selection())
|
||||||
|
while true do
|
||||||
|
local l, c = doc:get_selection()
|
||||||
|
if l == le and c == ce then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
command.perform "autoinsert:backspace"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["backspace"] = "autoinsert:backspace",
|
||||||
|
["ctrl+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||||
|
["ctrl+shift+backspace"] = "autoinsert:delete-to-previous-word-start",
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
-- 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 = 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
|
||||||
|
|
||||||
|
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
|
||||||
|
for _, ptn in ipairs(config.plugins.autowrap.files) do
|
||||||
|
if filename:match(ptn) then
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not matched then return end
|
||||||
|
|
||||||
|
-- do automatic reflow on line if we're typing at the end of the line and have
|
||||||
|
-- reached the line limit
|
||||||
|
local line, col = self.doc:get_selection()
|
||||||
|
local text = self.doc:get_text(line, 1, line, math.huge)
|
||||||
|
if #text >= config.line_limit and col > #text then
|
||||||
|
command.perform("doc:select-lines")
|
||||||
|
command.perform("reflow:reflow")
|
||||||
|
command.perform("doc:move-to-next-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
|
||||||
|
})
|
|
@ -0,0 +1,108 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local style = require "core.style"
|
||||||
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
local View = require "core.view"
|
||||||
|
|
||||||
|
|
||||||
|
config.plugins.bigclock = common.merge({
|
||||||
|
time_format = "%H:%M:%S",
|
||||||
|
date_format = "%A, %d %B %Y",
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
function ClockView:new()
|
||||||
|
ClockView.super.new(self)
|
||||||
|
self.time_text = ""
|
||||||
|
self.date_text = ""
|
||||||
|
self.last_scale = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function ClockView:get_name()
|
||||||
|
return "Big Clock"
|
||||||
|
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.copy(style["font"], size)
|
||||||
|
self.date_font = renderer.font.copy(style["font"], size * 0.3)
|
||||||
|
self.font_size = size
|
||||||
|
collectgarbage()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function ClockView:update()
|
||||||
|
local time_text = os.date(config.plugins.bigclock.time_format)
|
||||||
|
local date_text = os.date(config.plugins.bigclock.date_format)
|
||||||
|
if self.time_text ~= time_text or self.date_text ~= date_text then
|
||||||
|
core.redraw = true
|
||||||
|
self.time_text = time_text
|
||||||
|
self.date_text = date_text
|
||||||
|
end
|
||||||
|
ClockView.super.update(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function ClockView:draw()
|
||||||
|
self:update_fonts()
|
||||||
|
self:draw_background(style.background)
|
||||||
|
local x, y = self.position.x, self.position.y
|
||||||
|
local w, h = self.size.x, self.size.y
|
||||||
|
local _, y = common.draw_text(self.time_font, style.text, self.time_text, "center", x, y, w, h)
|
||||||
|
local th = self.date_font:get_height()
|
||||||
|
common.draw_text(self.date_font, style.dim, self.date_text, "center", x, y, w, th)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["big-clock:open"] = function()
|
||||||
|
local node = core.root_view:get_active_node()
|
||||||
|
node:add_view(ClockView())
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return ClockView
|
|
@ -0,0 +1,265 @@
|
||||||
|
--- 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`
|
||||||
|
-- bracket color = `style.bracketmatch_char_color`
|
||||||
|
-- background color = `style.bracketmatch_block_color`
|
||||||
|
-- frame color = `style.bracketmatch_frame_color`
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
-- [ ] ( ) { }
|
||||||
|
{ [91] = 93, [40] = 41, [123] = 125, direction = 1 },
|
||||||
|
-- ] [ ) ( } {
|
||||||
|
{ [93] = 91, [41] = 40, [125] = 123, direction = -1 },
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
local function get_token_at(doc, line, col)
|
||||||
|
local column = 0
|
||||||
|
for _,type,text in doc.highlighter:each_token(line) do
|
||||||
|
column = column + #text
|
||||||
|
if column >= col then return type, text end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_matching_bracket(doc, line, col, line_limit, open_byte, close_byte, direction)
|
||||||
|
local end_line = line + line_limit * direction
|
||||||
|
local depth = 0
|
||||||
|
|
||||||
|
while line ~= end_line do
|
||||||
|
local byte = doc.lines[line]:byte(col)
|
||||||
|
if byte == open_byte and get_token_at(doc, line, col) ~= "comment" then
|
||||||
|
depth = depth + 1
|
||||||
|
elseif byte == close_byte and get_token_at(doc, line, col) ~= "comment" then
|
||||||
|
depth = depth - 1
|
||||||
|
if depth == 0 then return line, col end
|
||||||
|
end
|
||||||
|
|
||||||
|
local prev_line, prev_col = line, col
|
||||||
|
line, col = doc:position_offset(line, col, direction)
|
||||||
|
if line == prev_line and col == prev_col then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local state = {}
|
||||||
|
local select_adj = 0
|
||||||
|
|
||||||
|
local function update_state(line_limit)
|
||||||
|
line_limit = line_limit or math.huge
|
||||||
|
|
||||||
|
-- reset if we don't have a document (eg. DocView isn't focused)
|
||||||
|
local doc = core.active_view.doc
|
||||||
|
if not doc then
|
||||||
|
state = {}
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- early exit if nothing has changed since the last call
|
||||||
|
local line, col = doc:get_selection()
|
||||||
|
local change_id = doc:get_change_id()
|
||||||
|
if state.doc == doc and state.line == line and state.col == col
|
||||||
|
and state.change_id == change_id and state.limit == line_limit then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- find matching bracket if we're on a bracket
|
||||||
|
local line2, col2
|
||||||
|
for _, map in ipairs(bracket_maps) do
|
||||||
|
for i = 0, -1, -1 do
|
||||||
|
local line, col = doc:position_offset(line, col, i)
|
||||||
|
local open = doc.lines[line]:byte(col)
|
||||||
|
local close = map[open]
|
||||||
|
if close and get_token_at(doc, line, col) ~= "comment" then
|
||||||
|
-- i == 0 if the cursor is on the left side of a bracket (or -1 when on right)
|
||||||
|
select_adj = i + 1 -- if i == 0 then select_adj = 1 else select_adj = 0 end
|
||||||
|
line2, col2 = get_matching_bracket(doc, line, col, line_limit, open, close, map.direction)
|
||||||
|
goto found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::found::
|
||||||
|
|
||||||
|
-- update
|
||||||
|
state = {
|
||||||
|
change_id = change_id,
|
||||||
|
doc = doc,
|
||||||
|
line = line,
|
||||||
|
col = col,
|
||||||
|
line2 = line2,
|
||||||
|
col2 = col2,
|
||||||
|
limit = line_limit,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local update = DocView.update
|
||||||
|
|
||||||
|
function DocView:update(...)
|
||||||
|
update(self, ...)
|
||||||
|
update_state(100)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function redraw_char(dv, x, y, line, col, bg_color, char_color)
|
||||||
|
local x1 = x + dv:get_col_x_offset(line, col)
|
||||||
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
||||||
|
local lh = dv:get_line_height()
|
||||||
|
local token = get_token_at(dv.doc, line, col)
|
||||||
|
if not char_color then
|
||||||
|
char_color = style.syntax[token]
|
||||||
|
end
|
||||||
|
local font = style.syntax_fonts[token] or dv:get_font()
|
||||||
|
local char = string.sub(dv.doc.lines[line], col, col)
|
||||||
|
|
||||||
|
if not bg_color then
|
||||||
|
-- redraw background
|
||||||
|
core.push_clip_rect(x1, y, x2 - x1, lh)
|
||||||
|
local dlt = DocView.draw_line_text
|
||||||
|
DocView.draw_line_text = function() end
|
||||||
|
dv:draw_line_body(line, x, y)
|
||||||
|
DocView.draw_line_text = dlt
|
||||||
|
core.pop_clip_rect()
|
||||||
|
else
|
||||||
|
renderer.draw_rect(x1, y, x2 - x1, lh, bg_color)
|
||||||
|
end
|
||||||
|
renderer.draw_text(font, char, x1, y + dv:get_line_text_y_offset(), char_color)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function draw_decoration(dv, x, y, line, col)
|
||||||
|
local conf = config.plugins.bracketmatch
|
||||||
|
local color = style.bracketmatch_color or style.syntax["function"]
|
||||||
|
local char_color = style.bracketmatch_char_color
|
||||||
|
or (conf.style == "block" and style.background or style.syntax["keyword"])
|
||||||
|
local block_color = style.bracketmatch_block_color or style.line_number2
|
||||||
|
local frame_color = style.bracketmatch_frame_color or style.line_number2
|
||||||
|
|
||||||
|
local h = conf.line_size
|
||||||
|
|
||||||
|
if conf.color_char or conf.style == "block" then
|
||||||
|
redraw_char(dv, x, y, line, col,
|
||||||
|
conf.style == "block" and block_color, conf.color_char and char_color)
|
||||||
|
end
|
||||||
|
if conf.style == "underline" then
|
||||||
|
local x1 = x + dv:get_col_x_offset(line, col)
|
||||||
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
||||||
|
local lh = dv:get_line_height()
|
||||||
|
|
||||||
|
renderer.draw_rect(x1, y + lh - h, x2 - x1, h, color)
|
||||||
|
elseif conf.style == "frame" then
|
||||||
|
local x1 = x + dv:get_col_x_offset(line, col)
|
||||||
|
local x2 = x + dv:get_col_x_offset(line, col + 1)
|
||||||
|
local lh = dv:get_line_height()
|
||||||
|
|
||||||
|
renderer.draw_rect(x1, y + lh - h, x2 - x1, h, frame_color)
|
||||||
|
renderer.draw_rect(x1, y, x2 - x1, h, frame_color)
|
||||||
|
renderer.draw_rect(x1, y, h, lh, frame_color)
|
||||||
|
renderer.draw_rect(x2, y, h, lh, frame_color)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_text = DocView.draw_line_text
|
||||||
|
|
||||||
|
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 line == state.line2 then
|
||||||
|
draw_decoration(self, x, y, line, state.col2)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
command.add("core.docview", {
|
||||||
|
["bracket-match:move-to-matching"] = function(dv)
|
||||||
|
update_state()
|
||||||
|
if state.line2 then
|
||||||
|
dv.doc:set_selection(state.line2, state.col2)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
["bracket-match:select-to-matching"] = function(dv)
|
||||||
|
update_state()
|
||||||
|
if state.line2 then
|
||||||
|
dv.doc:set_selection(state.line, state.col, state.line2, state.col2 + select_adj)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["ctrl+m"] = "bracket-match:move-to-matching",
|
||||||
|
["ctrl+shift+m"] = "bracket-match:select-to-matching",
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
--mod-version:3 --priority:5
|
||||||
|
|
||||||
|
--[[
|
||||||
|
This code is responsible for the encoding change
|
||||||
|
using codesets library. It requires LiteXL 2.1.1r3
|
||||||
|
and above to work.
|
||||||
|
|
||||||
|
Heavily inspired from the encoding plugin
|
||||||
|
https://github.com/jgmdev/lite-xl-encoding
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
useSystemEncoding
|
||||||
|
By default the system encoding is used to open
|
||||||
|
a file. If you want to disable that you may add
|
||||||
|
the following line in you config file
|
||||||
|
config.plugins.codesets.useSystemEncoding = false
|
||||||
|
]]
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local command = require "core.command"
|
||||||
|
local config = require "core.config"
|
||||||
|
local style = require "core.style"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
local CommandView = require "core.commandview"
|
||||||
|
local StatusView = require "core.statusview"
|
||||||
|
|
||||||
|
---@type encoding
|
||||||
|
local encoding = require "codesetsextra"
|
||||||
|
config.plugins.codesets = common.merge({
|
||||||
|
useSystemEncoding = true
|
||||||
|
}, config.plugins.codesets)
|
||||||
|
|
||||||
|
-- Reference to plugin config
|
||||||
|
local conf = config.plugins.codesets
|
||||||
|
|
||||||
|
local encodings = {}
|
||||||
|
|
||||||
|
---@class encodings.encoding
|
||||||
|
---@field charset string
|
||||||
|
---@field name string
|
||||||
|
|
||||||
|
---List of encoding regions.
|
||||||
|
---@type table<integer,string>
|
||||||
|
encodings.groups = {
|
||||||
|
"West European",
|
||||||
|
"East European",
|
||||||
|
"East Asian",
|
||||||
|
"SE & SW Asian",
|
||||||
|
"Middle Eastern",
|
||||||
|
"Unicode"
|
||||||
|
}
|
||||||
|
|
||||||
|
---Supported iconv encodings grouped by region.
|
||||||
|
---@type table<integer,encodings.encoding[]>
|
||||||
|
encodings.list = {
|
||||||
|
-- West European
|
||||||
|
{
|
||||||
|
{ charset = "ISO-8859-14", name = "Celtic" },
|
||||||
|
{ charset = "ISO-8859-7", name = "Greek" },
|
||||||
|
{ charset = "WINDOWS-1253", name = "Greek" },
|
||||||
|
{ charset = "ISO-8859-10", name = "Nordic" },
|
||||||
|
{ charset = "ISO-8859-3", name = "South European" },
|
||||||
|
{ charset = "IBM850", name = "Western" },
|
||||||
|
{ charset = "ISO-8859-1", name = "Western" },
|
||||||
|
{ charset = "ISO-8859-15", name = "Western" },
|
||||||
|
{ charset = "WINDOWS-1252", name = "Western" }
|
||||||
|
},
|
||||||
|
-- East European
|
||||||
|
{
|
||||||
|
{ charset = "ISO-8859-4", name = "Baltic" },
|
||||||
|
{ charset = "ISO-8859-13", name = "Baltic" },
|
||||||
|
{ charset = "WINDOWS-1257", name = "Baltic" },
|
||||||
|
{ charset = "IBM852", name = "Central European" },
|
||||||
|
{ charset = "ISO-8859-2", name = "Central European" },
|
||||||
|
{ charset = "WINDOWS-1250", name = "Central European" },
|
||||||
|
{ charset = "IBM855", name = "Cyrillic" },
|
||||||
|
{ charset = "ISO-8859-5", name = "Cyrillic" },
|
||||||
|
{ charset = "ISO-IR-111", name = "Cyrillic" },
|
||||||
|
{ charset = "KOI8-R", name = "Cyrillic" },
|
||||||
|
{ charset = "WINDOWS-1251", name = "Cyrillic" },
|
||||||
|
{ charset = "CP866", name = "Cyrillic/Russian" },
|
||||||
|
{ charset = "KOI8-U", name = "Cyrillic/Ukrainian" },
|
||||||
|
{ charset = "ISO-8859-16", name = "Romanian" }
|
||||||
|
},
|
||||||
|
-- East Asian
|
||||||
|
{
|
||||||
|
{ charset = "GB18030", name = "Chinese Simplified" },
|
||||||
|
{ charset = "GB2312", name = "Chinese Simplified" },
|
||||||
|
{ charset = "GBK", name = "Chinese Simplified" },
|
||||||
|
{ charset = "HZ", name = "Chinese Simplified" },
|
||||||
|
{ charset = "BIG5", name = "Chinese Traditional" },
|
||||||
|
{ charset = "BIG5-HKSCS", name = "Chinese Traditional" },
|
||||||
|
{ charset = "EUC-TW", name = "Chinese Traditional" },
|
||||||
|
{ charset = "EUC-JP", name = "Japanese" },
|
||||||
|
{ charset = "ISO-2022-JP", name = "Japanese" },
|
||||||
|
{ charset = "SHIFT_JIS", name = "Japanese" },
|
||||||
|
{ charset = "CP932", name = "Japanese" },
|
||||||
|
{ charset = "EUC-KR", name = "Korean" },
|
||||||
|
{ charset = "ISO-2022-KR", name = "Korean" },
|
||||||
|
{ charset = "JOHAB", name = "Korean" },
|
||||||
|
{ charset = "UHC", name = "Korean" }
|
||||||
|
},
|
||||||
|
-- SE & SW Asian
|
||||||
|
{
|
||||||
|
{ charset = "ARMSCII-8", name = "Armenian" },
|
||||||
|
{ charset = "GEORGIAN-ACADEMY", name = "Georgian" },
|
||||||
|
{ charset = "TIS-620", name = "Thai" },
|
||||||
|
{ charset = "IBM857", name = "Turkish" },
|
||||||
|
{ charset = "WINDOWS-1254", name = "Turkish" },
|
||||||
|
{ charset = "ISO-8859-9", name = "Turkish" },
|
||||||
|
{ charset = "TCVN", name = "Vietnamese" },
|
||||||
|
{ charset = "VISCII", name = "Vietnamese" },
|
||||||
|
{ charset = "WINDOWS-1258", name = "Vietnamese" }
|
||||||
|
},
|
||||||
|
-- Middle Eastern
|
||||||
|
{
|
||||||
|
{ charset = "IBM864", name = "Arabic" },
|
||||||
|
{ charset = "ISO-8859-6", name = "Arabic" },
|
||||||
|
{ charset = "WINDOWS-1256", name = "Arabic" },
|
||||||
|
{ charset = "IBM862", name = "Hebrew" },
|
||||||
|
{ charset = "ISO-8859-8-I", name = "Hebrew" },
|
||||||
|
{ charset = "WINDOWS-1255", name = "Hebrew" },
|
||||||
|
{ charset = "ISO-8859-8", name = "Hebrew Visual" }
|
||||||
|
},
|
||||||
|
-- Unicode
|
||||||
|
{
|
||||||
|
{ charset = "UTF-7", name = "Unicode" },
|
||||||
|
{ charset = "UTF-8", name = "Unicode" },
|
||||||
|
{ charset = "UTF-16LE", name = "Unicode" },
|
||||||
|
{ charset = "UTF-16BE", name = "Unicode" },
|
||||||
|
{ charset = "UCS-2LE", name = "Unicode" },
|
||||||
|
{ charset = "UCS-2BE", name = "Unicode" },
|
||||||
|
{ charset = "UTF-32LE", name = "Unicode" },
|
||||||
|
{ charset = "UTF-32BE", name = "Unicode" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
---Get the list of encodings associated to a region.
|
||||||
|
---@param label string
|
||||||
|
---@return encodings.encoding[] | nil
|
||||||
|
function encodings.get_group(label)
|
||||||
|
for idx, name in ipairs(encodings.groups) do
|
||||||
|
if name == label then
|
||||||
|
return encodings.list[idx]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get the list of encodings associated to a region.
|
||||||
|
---@return encodings.encoding[] | nil
|
||||||
|
function encodings.get_all()
|
||||||
|
local all = {}
|
||||||
|
for idx, _ in ipairs(encodings.groups) do
|
||||||
|
for _, item in ipairs(encodings.list[idx]) do
|
||||||
|
table.insert(all, item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return all
|
||||||
|
end
|
||||||
|
|
||||||
|
---Open a commandview to select a charset and executes the given callback,
|
||||||
|
---@param title_label string Title displayed on the commandview
|
||||||
|
---@param callback fun(charset: string)
|
||||||
|
function encodings.select_encoding(title_label, callback)
|
||||||
|
core.command_view:enter(title_label, {
|
||||||
|
submit = function(_, item)
|
||||||
|
callback(item.charset)
|
||||||
|
end,
|
||||||
|
suggest = function(text)
|
||||||
|
local charsets = encodings.get_all()
|
||||||
|
local list_labels = {}
|
||||||
|
local list_charset = {}
|
||||||
|
for _, element in ipairs(charsets) do
|
||||||
|
local label = element.name .. " (" .. element.charset .. ")"
|
||||||
|
table.insert(list_labels, label)
|
||||||
|
list_charset[label] = element.charset
|
||||||
|
end
|
||||||
|
local res = common.fuzzy_match(list_labels, text)
|
||||||
|
for i, name in ipairs(res) do
|
||||||
|
res[i] = {
|
||||||
|
text = name,
|
||||||
|
charset = list_charset[name]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Overwrite Doc methods to properly add encoding detection and conversion.
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
function Doc:new(filename, abs_filename, new_file)
|
||||||
|
self.new_file = new_file
|
||||||
|
self.encoding = nil
|
||||||
|
self.convert = false
|
||||||
|
self:reset()
|
||||||
|
if filename then
|
||||||
|
self:set_filename(filename, abs_filename)
|
||||||
|
if not new_file then
|
||||||
|
self:load(filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Doc:load(filename)
|
||||||
|
if not self.encoding then
|
||||||
|
local errmsg
|
||||||
|
if conf.useSystemEncoding then
|
||||||
|
self.encoding, errmsg = encoding.systemCodeset();
|
||||||
|
else
|
||||||
|
self.encoding, errmsg = encoding.detect(filename);
|
||||||
|
end
|
||||||
|
if not self.encoding then core.error("%s", errmsg) error(errmsg) end
|
||||||
|
end
|
||||||
|
self.convert = false
|
||||||
|
if self.encoding ~= "UTF-8" and self.encoding ~= "ASCII"
|
||||||
|
and self.encoding ~= "US-ASCII" and self.encoding ~= "ISO-8859-1"
|
||||||
|
then
|
||||||
|
self.convert = true
|
||||||
|
end
|
||||||
|
local fp = assert( io.open(filename, "rb") )
|
||||||
|
self:reset()
|
||||||
|
self.lines = {}
|
||||||
|
local i = 1
|
||||||
|
if self.convert then
|
||||||
|
local content = fp:read("*a");
|
||||||
|
content = assert(encoding.convert("UTF-8", self.encoding, content, {
|
||||||
|
strict = false,
|
||||||
|
handle_from_bom = true
|
||||||
|
}))
|
||||||
|
for line in content:gmatch("([^\n]*)\n?") do
|
||||||
|
if line:byte(-1) == 13 then
|
||||||
|
line = line:sub(1, -2)
|
||||||
|
self.crlf = true
|
||||||
|
end
|
||||||
|
table.insert(self.lines, line .. "\n")
|
||||||
|
self.highlighter.lines[i] = false
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
content = nil
|
||||||
|
else
|
||||||
|
for line in fp:lines() do
|
||||||
|
if (i == 1) then line = encoding.strip_bom(line, "UTF-8") end
|
||||||
|
if line:byte(-1) == 13 then
|
||||||
|
line = line:sub(1, -2)
|
||||||
|
self.crlf = true
|
||||||
|
end
|
||||||
|
table.insert(self.lines, line .. "\n")
|
||||||
|
self.highlighter.lines[i] = false
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #self.lines == 0 then
|
||||||
|
table.insert(self.lines, "\n")
|
||||||
|
end
|
||||||
|
fp:close()
|
||||||
|
self:reset_syntax()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Doc:save(filename, abs_filename)
|
||||||
|
if not filename then
|
||||||
|
assert(self.filename, "no filename set to default to")
|
||||||
|
filename = self.filename
|
||||||
|
abs_filename = self.abs_filename
|
||||||
|
else
|
||||||
|
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
|
||||||
|
end
|
||||||
|
local fp
|
||||||
|
local output = ""
|
||||||
|
if not self.convert then
|
||||||
|
fp = assert( io.open(filename, "wb") )
|
||||||
|
for _, line in ipairs(self.lines) do
|
||||||
|
if self.crlf then line = line:gsub("\n", "\r\n") end
|
||||||
|
fp:write(line)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
output = table.concat(self.lines);
|
||||||
|
if self.crlf then output = output:gsub("\n", "\r\n") end
|
||||||
|
end
|
||||||
|
local conversion_error = false
|
||||||
|
if self.convert then
|
||||||
|
local errmsg
|
||||||
|
output, errmsg = encoding.convert(self.encoding, "UTF-8", output, {
|
||||||
|
strict = true,
|
||||||
|
handle_to_bom = true
|
||||||
|
})
|
||||||
|
if output then
|
||||||
|
fp = assert( io.open(filename, "wb") )
|
||||||
|
fp:write(encoding.get_charset_bom(self.encoding) .. output)
|
||||||
|
fp:close()
|
||||||
|
else
|
||||||
|
conversion_error = true
|
||||||
|
core.error("%s", errmsg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fp:close()
|
||||||
|
end
|
||||||
|
self:set_filename(filename, abs_filename)
|
||||||
|
if not conversion_error then
|
||||||
|
self.new_file = false
|
||||||
|
else
|
||||||
|
self.new_file = true
|
||||||
|
end
|
||||||
|
self:clean()
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Register command to change current document encoding.
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
command.add("core.docview", {
|
||||||
|
["doc:change-encoding"] = function(dv)
|
||||||
|
encodings.select_encoding("Select Output Encoding", function(charset)
|
||||||
|
dv.doc.encoding = charset
|
||||||
|
if charset ~= "UTF-8" and charset ~= "ASCII"
|
||||||
|
and charset ~= "US-ASCII" and charset ~= "ISO-8859-1"
|
||||||
|
then
|
||||||
|
dv.doc.convert = true
|
||||||
|
else
|
||||||
|
dv.doc.convert = false
|
||||||
|
end
|
||||||
|
dv.doc:save()
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
|
||||||
|
["doc:reload-with-encoding"] = function(dv)
|
||||||
|
encodings.select_encoding("Reload With Encoding", function(charset)
|
||||||
|
dv.doc.encoding = charset
|
||||||
|
if charset ~= "UTF-8" and charset ~= "ASCII"
|
||||||
|
and charset ~= "US-ASCII" and charset ~= "ISO-8859-1"
|
||||||
|
then
|
||||||
|
dv.doc.convert = true
|
||||||
|
else
|
||||||
|
dv.doc.convert = false
|
||||||
|
end
|
||||||
|
dv.doc:reload()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Register a statusbar item to view change current doc encoding.
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
core.status_view:add_item({
|
||||||
|
predicate = function()
|
||||||
|
return core.active_view:is(DocView)
|
||||||
|
and not core.active_view:is(CommandView)
|
||||||
|
end,
|
||||||
|
name = "doc:encoding",
|
||||||
|
alignment = StatusView.Item.RIGHT,
|
||||||
|
get_item = function()
|
||||||
|
local dv = core.active_view
|
||||||
|
return {
|
||||||
|
style.text, dv.doc.encoding or "none"
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
command = function(button)
|
||||||
|
if button == "left" then
|
||||||
|
command.perform "doc:change-encoding"
|
||||||
|
elseif button == "right" then
|
||||||
|
command.perform "doc:reload-with-encoding"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
tooltip = "encoding"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return encodings;
|
|
@ -0,0 +1,100 @@
|
||||||
|
-- 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 = {}
|
||||||
|
|
||||||
|
|
||||||
|
-- Workaround for bug in Lite XL 2.1
|
||||||
|
-- Remove this when b029f5993edb7dee5ccd2ba55faac1ec22e24609 is in a release
|
||||||
|
local function get_selection(doc, sort)
|
||||||
|
local line1, col1, line2, col2 = doc:get_selection_idx(doc.last_selection)
|
||||||
|
if line1 then
|
||||||
|
return doc:get_selection_idx(doc.last_selection, sort)
|
||||||
|
else
|
||||||
|
return doc:get_selection_idx(1, sort)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
s, e = text:find(ptn, e + 1)
|
||||||
|
if not s then break end
|
||||||
|
|
||||||
|
local str = text:sub(s, e)
|
||||||
|
local r, g, b, a = str:match(ptn)
|
||||||
|
r, g, b = tonumber(r, base), tonumber(g, base), tonumber(b, base)
|
||||||
|
a = tonumber(a or "", base)
|
||||||
|
if a ~= nil then
|
||||||
|
if base ~= 16 then
|
||||||
|
a = a * 0xff
|
||||||
|
end
|
||||||
|
else
|
||||||
|
a = 0xff
|
||||||
|
end
|
||||||
|
|
||||||
|
-- #123 becomes #112233
|
||||||
|
if nibbles then
|
||||||
|
r = r * 16
|
||||||
|
g = g * 16
|
||||||
|
b = b * 16
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
tmp[1], tmp[2], tmp[3], tmp[4] = r, g, b, a
|
||||||
|
|
||||||
|
local l1, _, l2, _ = get_selection(self.doc, true)
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_text = DocView.draw_line_text
|
||||||
|
|
||||||
|
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
|
|
@ -0,0 +1,156 @@
|
||||||
|
-- mod-version:3
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Author: techie-guy
|
||||||
|
|
||||||
|
Plugin to customize the caret in the editor
|
||||||
|
Thanks to @Guldoman for the initial example on Discord
|
||||||
|
|
||||||
|
Features
|
||||||
|
Change the Color and Opacity of the caret
|
||||||
|
Change the Shape of the caret, available shapes are Line, Block, Underline
|
||||||
|
|
||||||
|
Customizing the Caret: (this can be changed from the .config/lite-xl/init.lua
|
||||||
|
file or from the settings menu plugin)
|
||||||
|
config.plugins.custom_caret.shape - Change the shape of the caret [string]
|
||||||
|
style.caret - Change the rgba color of the caret [table]
|
||||||
|
|
||||||
|
Example Config(in the .config/lite-xl/init.lua)
|
||||||
|
style.caret = {0, 255, 255, 150}
|
||||||
|
config.plugins.custom_caret.shape = "block"
|
||||||
|
]]
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local style = require "core.style"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
config.plugins.custom_caret = common.merge({
|
||||||
|
shape = "line",
|
||||||
|
custom_color = true,
|
||||||
|
color_r = style.caret[1],
|
||||||
|
color_g = style.caret[2],
|
||||||
|
color_b = style.caret[3],
|
||||||
|
opacity = style.caret[4]
|
||||||
|
}, config.plugins.custom_caret)
|
||||||
|
|
||||||
|
-- Reference to plugin config
|
||||||
|
local conf = config.plugins.custom_caret
|
||||||
|
|
||||||
|
-- Get real default caret color after everything is loaded up
|
||||||
|
core.add_thread(function()
|
||||||
|
if
|
||||||
|
conf.color_r == 147 and conf.color_g == 221
|
||||||
|
and
|
||||||
|
conf.color_b == 250 and conf.opacity == 255
|
||||||
|
and
|
||||||
|
(
|
||||||
|
style.caret[1] ~= conf.color_r or style.caret[2] ~= conf.color_g
|
||||||
|
or
|
||||||
|
style.caret[3] ~= conf.color_b or style.caret[4] ~= conf.opacity
|
||||||
|
)
|
||||||
|
then
|
||||||
|
conf.color_r = style.caret[1]
|
||||||
|
conf.color_g = style.caret[2]
|
||||||
|
conf.color_b = style.caret[3]
|
||||||
|
conf.opacity = style.caret[4]
|
||||||
|
end
|
||||||
|
|
||||||
|
local settings_loaded, settings = pcall(require, "plugins.settings")
|
||||||
|
if settings_loaded then
|
||||||
|
conf.config_spec = {
|
||||||
|
name = "Custom Caret",
|
||||||
|
{
|
||||||
|
label = "Shape",
|
||||||
|
description = "The Shape of the cursor.",
|
||||||
|
path = "shape",
|
||||||
|
type = "selection",
|
||||||
|
default = "line",
|
||||||
|
values = {
|
||||||
|
{"Line", "line"},
|
||||||
|
{"Block", "block"},
|
||||||
|
{"Underline", "underline"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Custom Color",
|
||||||
|
description = "Use a custom color for the caret as specified below.",
|
||||||
|
path = "custom_color",
|
||||||
|
type = "toggle",
|
||||||
|
default = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Red Component of Color",
|
||||||
|
description = "The color consists of 3 components RGB, "
|
||||||
|
.. "This modifies the 'R' component of the caret's color",
|
||||||
|
path = "color_r",
|
||||||
|
type = "number",
|
||||||
|
min = 0,
|
||||||
|
max = 255,
|
||||||
|
default = style.caret[1],
|
||||||
|
step = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Green Component of Color",
|
||||||
|
description = "The color consists of 3 components RGB, "
|
||||||
|
.. "This modifies the 'G' component of the caret's color",
|
||||||
|
path = "color_g",
|
||||||
|
type = "number",
|
||||||
|
min = 0,
|
||||||
|
max = 255,
|
||||||
|
default = style.caret[2],
|
||||||
|
step = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Blue Component of Color",
|
||||||
|
description = "The color consists of 3 components RGB, "
|
||||||
|
.. "This modifies the 'B' component of the caret's color",
|
||||||
|
path = "color_b",
|
||||||
|
type = "number",
|
||||||
|
min = 0,
|
||||||
|
max = 255,
|
||||||
|
default = style.caret[3],
|
||||||
|
step = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Opacity of the Cursor",
|
||||||
|
description = "The Opacity of the caret",
|
||||||
|
path = "opacity",
|
||||||
|
type = "number",
|
||||||
|
min = 0,
|
||||||
|
max = 255,
|
||||||
|
default = style.caret[4],
|
||||||
|
step = 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
---@cast settings plugins.settings
|
||||||
|
settings.ui:enable_plugin("custom_caret")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function DocView:draw_caret(x, y)
|
||||||
|
local caret_width = style.caret_width
|
||||||
|
local caret_height = self:get_line_height()
|
||||||
|
local current_caret_shape = conf.shape
|
||||||
|
local caret_color = conf.custom_color and {
|
||||||
|
conf.color_r,
|
||||||
|
conf.color_g,
|
||||||
|
conf.color_b,
|
||||||
|
conf.opacity
|
||||||
|
} or style.caret
|
||||||
|
|
||||||
|
if (current_caret_shape == "block") then
|
||||||
|
caret_width = math.ceil(self:get_font():get_width("a"))
|
||||||
|
elseif (current_caret_shape == "underline") then
|
||||||
|
caret_width = math.ceil(self:get_font():get_width("a"))
|
||||||
|
caret_height = style.caret_width*2
|
||||||
|
y = y+self:get_line_height()
|
||||||
|
else
|
||||||
|
caret_width = style.caret_width
|
||||||
|
caret_height = self:get_line_height()
|
||||||
|
end
|
||||||
|
|
||||||
|
renderer.draw_rect(x, y, caret_width, caret_height, caret_color)
|
||||||
|
end
|
|
@ -0,0 +1,61 @@
|
||||||
|
# EditorConfig
|
||||||
|
|
||||||
|
This plugin implements the [EditorConfig](https://editorconfig.org/) spec
|
||||||
|
purely on lua by leveraging lua patterns and the regex engine on lite-xl.
|
||||||
|
Installing additional dependencies is not required.
|
||||||
|
|
||||||
|
The EditorConfig spec was implemented as best understood,
|
||||||
|
if you find any bugs please report them on this repository
|
||||||
|
[issue tracker](https://github.com/lite-xl/lite-xl-plugins/issues).
|
||||||
|
|
||||||
|
## Implemented Features
|
||||||
|
|
||||||
|
Global options:
|
||||||
|
|
||||||
|
* root - prevents upward searching of .editorconfig files
|
||||||
|
|
||||||
|
Applied to documents indent info:
|
||||||
|
|
||||||
|
* indent_style
|
||||||
|
* indent_size
|
||||||
|
* tab_width
|
||||||
|
|
||||||
|
Applied on document save:
|
||||||
|
|
||||||
|
* end_of_line - if set to `cr` it is ignored
|
||||||
|
* trim_trailing_whitespace
|
||||||
|
* insert_final_newline boolean
|
||||||
|
|
||||||
|
## Not implemented
|
||||||
|
|
||||||
|
* charset - this feature would need the encoding
|
||||||
|
[PR](https://github.com/lite-xl/lite-xl/pull/1161) or
|
||||||
|
[plugin](https://github.com/jgmdev/lite-xl-encoding)
|
||||||
|
|
||||||
|
## Extras
|
||||||
|
|
||||||
|
* Supports multiple project directories
|
||||||
|
* Implements hot reloading, so modifying an .editorconfig file from within
|
||||||
|
the editor will re-apply all rules to currently opened files.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
This plugin includes a test suite to check how well the .editorconfig parser
|
||||||
|
is working.
|
||||||
|
|
||||||
|
The [editorconfig-core-test](https://github.com/editorconfig/editorconfig-core-test)
|
||||||
|
glob, parser and properties cmake tests where ported and we are getting a 100%
|
||||||
|
pass rate.
|
||||||
|
|
||||||
|
If you are interested in running the test suite, from the terminal execute
|
||||||
|
the following:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lite-xl test editorconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
To inspect the generated sections and regex rules:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lite-xl test editorconfig --parsers
|
||||||
|
```
|
|
@ -0,0 +1,441 @@
|
||||||
|
-- mod-version:3
|
||||||
|
--
|
||||||
|
-- EditorConfig plugin for Lite XL
|
||||||
|
-- @copyright Jefferson Gonzalez <jgmdev@gmail.com>
|
||||||
|
-- @license MIT
|
||||||
|
--
|
||||||
|
-- Note: this plugin needs to be loaded after detectindent plugin,
|
||||||
|
-- since the name editorconfig.lua is ordered after detectindent.lua
|
||||||
|
-- there shouldn't be any issues. Just a reminder for the future in
|
||||||
|
-- case of a plugin that could also handle document identation type
|
||||||
|
-- and size, and has a name with more weight than this plugin.
|
||||||
|
--
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local config = require "core.config"
|
||||||
|
local trimwhitespace = require "plugins.trimwhitespace"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
local Parser = require "plugins.editorconfig.parser"
|
||||||
|
|
||||||
|
---@class config.plugins.editorconfig
|
||||||
|
---@field debug boolean
|
||||||
|
config.plugins.editorconfig = common.merge({
|
||||||
|
debug = false,
|
||||||
|
-- The config specification used by the settings gui
|
||||||
|
config_spec = {
|
||||||
|
name = "EditorConfig",
|
||||||
|
{
|
||||||
|
label = "Debug",
|
||||||
|
description = "Display debugging messages on the log.",
|
||||||
|
path = "debug",
|
||||||
|
type = "toggle",
|
||||||
|
default = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, config.plugins.editorconfig)
|
||||||
|
|
||||||
|
---Cache of .editorconfig options to reduce parsing for every opened file.
|
||||||
|
---@type table<string, plugins.editorconfig.parser>
|
||||||
|
local project_configs = {}
|
||||||
|
|
||||||
|
---Keep track of main project directory so when changed we can assign a new
|
||||||
|
---.editorconfig object if neccesary.
|
||||||
|
---@type string
|
||||||
|
local main_project = core.project_dir
|
||||||
|
|
||||||
|
---Functionality that will be exposed by the plugin.
|
||||||
|
---@class plugins.editorconfig
|
||||||
|
local editorconfig = {}
|
||||||
|
|
||||||
|
---Load global .editorconfig options for a project.
|
||||||
|
---@param project_dir string
|
||||||
|
---@return boolean loaded
|
||||||
|
function editorconfig.load(project_dir)
|
||||||
|
local editor_config = project_dir .. "/" .. ".editorconfig"
|
||||||
|
local file = io.open(editor_config)
|
||||||
|
if file then
|
||||||
|
file:close()
|
||||||
|
project_configs[project_dir] = Parser.new(editor_config)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---Helper to add or substract final new line, it also makes final new line
|
||||||
|
---visble which lite-xl does not.
|
||||||
|
---@param doc core.doc
|
||||||
|
---@param raw? boolean If true does not register change on undo stack
|
||||||
|
---@return boolean handled_new_line
|
||||||
|
local function handle_final_new_line(doc, raw)
|
||||||
|
local handled = false
|
||||||
|
---@diagnostic disable-next-line
|
||||||
|
if doc.insert_final_newline then
|
||||||
|
handled = true
|
||||||
|
if doc.lines[#doc.lines] ~= "\n" then
|
||||||
|
if not raw then
|
||||||
|
doc:insert(#doc.lines, math.huge, "\n")
|
||||||
|
else
|
||||||
|
table.insert(doc.lines, "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
---@diagnostic disable-next-line
|
||||||
|
elseif type(doc.insert_final_newline) == "boolean" then
|
||||||
|
handled = true
|
||||||
|
if trimwhitespace.trim_empty_end_lines then
|
||||||
|
trimwhitespace.trim_empty_end_lines(doc, raw)
|
||||||
|
-- TODO: remove this once 2.1.1 is released
|
||||||
|
else
|
||||||
|
for _=#doc.lines, 1, -1 do
|
||||||
|
local l = #doc.lines
|
||||||
|
if l > 1 and doc.lines[l] == "\n" then
|
||||||
|
local current_line = doc:get_selection()
|
||||||
|
if current_line == l then
|
||||||
|
doc:set_selection(l-1, math.huge, l-1, math.huge)
|
||||||
|
end
|
||||||
|
if not raw then
|
||||||
|
doc:remove(l-1, math.huge, l, math.huge)
|
||||||
|
else
|
||||||
|
table.remove(doc.lines, l)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return handled
|
||||||
|
end
|
||||||
|
|
||||||
|
---Split the given relative path by / or \ separators.
|
||||||
|
---@param path string The path to split
|
||||||
|
---@return table
|
||||||
|
local function split_path(path)
|
||||||
|
local result = {};
|
||||||
|
for match in (path.."/"):gmatch("(.-)".."[:\\/]") do
|
||||||
|
table.insert(result, match);
|
||||||
|
end
|
||||||
|
return result;
|
||||||
|
end
|
||||||
|
|
||||||
|
---Check if the given file path exists.
|
||||||
|
---@param file_path string
|
||||||
|
local function file_exists(file_path)
|
||||||
|
local file = io.open(file_path, "r")
|
||||||
|
if not file then return false end
|
||||||
|
file:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---Merge a config options to target if they don't already exists on target.
|
||||||
|
---@param config_target? plugins.editorconfig.parser.section
|
||||||
|
---@param config_from? plugins.editorconfig.parser.section
|
||||||
|
local function merge_config(config_target, config_from)
|
||||||
|
if config_target and config_from then
|
||||||
|
for name, value in pairs(config_from) do
|
||||||
|
if type(config_target[name]) == "nil" then
|
||||||
|
config_target[name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Scan for .editorconfig files from current file path to upper project path
|
||||||
|
---if root attribute is not found first and returns matching config.
|
||||||
|
---@param file_path string
|
||||||
|
---@return plugins.editorconfig.parser.section?
|
||||||
|
local function recursive_get_config(file_path)
|
||||||
|
local project_dir = ""
|
||||||
|
|
||||||
|
local root_config
|
||||||
|
for path, editor_config in pairs(project_configs) do
|
||||||
|
if common.path_belongs_to(file_path, path) then
|
||||||
|
project_dir = path
|
||||||
|
root_config = editor_config:getConfig(
|
||||||
|
common.relative_path(path, file_path)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if project_dir == "" then
|
||||||
|
for _, project in ipairs(core.project_directories) do
|
||||||
|
if common.path_belongs_to(file_path, project.name) then
|
||||||
|
project_dir = project.name
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local relative_file_path = common.relative_path(project_dir, file_path)
|
||||||
|
local dir = common.dirname(relative_file_path)
|
||||||
|
|
||||||
|
local editor_config = {}
|
||||||
|
local config_found = false
|
||||||
|
if not dir and root_config then
|
||||||
|
editor_config = root_config
|
||||||
|
config_found = true
|
||||||
|
elseif dir then
|
||||||
|
local path_list = split_path(dir)
|
||||||
|
local root_found = false
|
||||||
|
for p=#path_list, 1, -1 do
|
||||||
|
local path = project_dir .. "/" .. table.concat(path_list, "/", 1, p)
|
||||||
|
if file_exists(path .. "/" .. ".editorconfig") then
|
||||||
|
---@type plugins.editorconfig.parser
|
||||||
|
local parser = Parser.new(path .. "/" .. ".editorconfig")
|
||||||
|
local pconfig = parser:getConfig(common.relative_path(path, file_path))
|
||||||
|
if pconfig then
|
||||||
|
merge_config(editor_config, pconfig)
|
||||||
|
config_found = true
|
||||||
|
end
|
||||||
|
if parser.root then
|
||||||
|
root_found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not root_found and root_config then
|
||||||
|
merge_config(editor_config, root_config)
|
||||||
|
config_found = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clean unset options
|
||||||
|
if config_found then
|
||||||
|
local all_unset = true
|
||||||
|
for name, value in pairs(editor_config) do
|
||||||
|
if value == "unset" then
|
||||||
|
editor_config[name] = nil
|
||||||
|
else
|
||||||
|
all_unset = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if all_unset then config_found = false end
|
||||||
|
end
|
||||||
|
|
||||||
|
return config_found and editor_config or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---Apply editorconfig rules to given doc if possible.
|
||||||
|
---@param doc core.doc
|
||||||
|
function editorconfig.apply(doc)
|
||||||
|
if not doc.abs_filename and not doc.filename then return end
|
||||||
|
local file_path = doc.abs_filename or (main_project .. "/" .. doc.filename)
|
||||||
|
local options = recursive_get_config(file_path)
|
||||||
|
if options then
|
||||||
|
if config.plugins.editorconfig.debug then
|
||||||
|
core.log_quiet(
|
||||||
|
"[EditorConfig]: %s applied %s",
|
||||||
|
file_path, common.serialize(options, {pretty = true})
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local indent_type, indent_size = doc:get_indent_info()
|
||||||
|
if options.indent_style then
|
||||||
|
if options.indent_style == "tab" then
|
||||||
|
indent_type = "hard"
|
||||||
|
else
|
||||||
|
indent_type = "soft"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.indent_size and options.indent_size == "tab" then
|
||||||
|
if options.tab_width then
|
||||||
|
options.indent_size = options.tab_width
|
||||||
|
else
|
||||||
|
options.indent_size = config.indent_size or 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.indent_size then
|
||||||
|
indent_size = options.indent_size
|
||||||
|
end
|
||||||
|
|
||||||
|
if doc.indent_info then
|
||||||
|
doc.indent_info.type = indent_type
|
||||||
|
doc.indent_info.size = indent_size
|
||||||
|
doc.indent_info.confirmed = true
|
||||||
|
else
|
||||||
|
doc.indent_info = {
|
||||||
|
type = indent_type,
|
||||||
|
size = indent_size,
|
||||||
|
confirmed = true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.end_of_line then
|
||||||
|
if options.end_of_line == "crlf" then
|
||||||
|
doc.crlf = true
|
||||||
|
elseif options.end_of_line == "lf" then
|
||||||
|
doc.crlf = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.trim_trailing_whitespace then
|
||||||
|
doc.trim_trailing_whitespace = true
|
||||||
|
elseif options.trim_trailing_whitespace == false then
|
||||||
|
doc.trim_trailing_whitespace = false
|
||||||
|
else
|
||||||
|
doc.trim_trailing_whitespace = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.insert_final_newline then
|
||||||
|
doc.insert_final_newline = true
|
||||||
|
elseif options.insert_final_newline == false then
|
||||||
|
doc.insert_final_newline = false
|
||||||
|
else
|
||||||
|
doc.insert_final_newline = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if
|
||||||
|
(
|
||||||
|
type(doc.trim_trailing_whitespace) == "boolean"
|
||||||
|
or
|
||||||
|
type(doc.insert_final_newline) == "boolean"
|
||||||
|
)
|
||||||
|
-- TODO: remove this once 2.1.1 is released
|
||||||
|
and
|
||||||
|
trimwhitespace.disable
|
||||||
|
then
|
||||||
|
trimwhitespace.disable(doc)
|
||||||
|
end
|
||||||
|
|
||||||
|
handle_final_new_line(doc, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Applies .editorconfig options to all open documents if possible.
|
||||||
|
function editorconfig.apply_all()
|
||||||
|
for _, doc in ipairs(core.docs) do
|
||||||
|
editorconfig.apply(doc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Load .editorconfig on all projects loaded at startup and apply it
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
core.add_thread(function()
|
||||||
|
local loaded = false
|
||||||
|
|
||||||
|
-- scan all opened project directories
|
||||||
|
if core.project_directories then
|
||||||
|
for i=1, #core.project_directories do
|
||||||
|
local found = editorconfig.load(core.project_directories[i].name)
|
||||||
|
if found then loaded = true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if an editorconfig was found then try to apply it to opened docs
|
||||||
|
if loaded then
|
||||||
|
editorconfig.apply_all()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Override various core project loading functions for .editorconfig scanning
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local core_open_folder_project = core.open_folder_project
|
||||||
|
function core.open_folder_project(directory)
|
||||||
|
core_open_folder_project(directory)
|
||||||
|
if project_configs[main_project] then project_configs[main_project] = nil end
|
||||||
|
main_project = core.project_dir
|
||||||
|
editorconfig.load(main_project)
|
||||||
|
end
|
||||||
|
|
||||||
|
local core_remove_project_directory = core.remove_project_directory
|
||||||
|
function core.remove_project_directory(path)
|
||||||
|
local out = core_remove_project_directory(path)
|
||||||
|
if project_configs[path] then project_configs[path] = nil end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local core_add_project_directory = core.add_project_directory
|
||||||
|
function core.add_project_directory(directory)
|
||||||
|
local out = core_add_project_directory(directory)
|
||||||
|
editorconfig.load(directory)
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Hook into the core.doc to apply editor config options
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
local doc_new = Doc.new
|
||||||
|
function Doc:new(...)
|
||||||
|
doc_new(self, ...)
|
||||||
|
editorconfig.apply(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Cloned trimwitespace plugin until it is exposed for other plugins.
|
||||||
|
---@param doc core.doc
|
||||||
|
local function trim_trailing_whitespace(doc)
|
||||||
|
if trimwhitespace.trim then
|
||||||
|
trimwhitespace.trim(doc)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO: remove this once 2.1.1 is released
|
||||||
|
local cline, ccol = doc:get_selection()
|
||||||
|
for i = 1, #doc.lines do
|
||||||
|
local old_text = doc:get_text(i, 1, i, math.huge)
|
||||||
|
local new_text = old_text:gsub("%s*$", "")
|
||||||
|
|
||||||
|
-- don't remove whitespace which would cause the caret to reposition
|
||||||
|
if cline == i and ccol > #new_text then
|
||||||
|
new_text = old_text:sub(1, ccol - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
if old_text ~= new_text then
|
||||||
|
doc:insert(i, 1, new_text)
|
||||||
|
doc:remove(i, #new_text + 1, i, math.huge)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local doc_save = Doc.save
|
||||||
|
function Doc:save(...)
|
||||||
|
local new_file = self.new_file
|
||||||
|
|
||||||
|
---@diagnostic disable-next-line
|
||||||
|
if self.trim_trailing_whitespace then
|
||||||
|
trim_trailing_whitespace(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
local lc = #self.lines
|
||||||
|
local handle_new_line = handle_final_new_line(self)
|
||||||
|
|
||||||
|
-- remove the unnecesary visible \n\n or the disabled \n
|
||||||
|
if handle_new_line then
|
||||||
|
self.lines[lc] = self.lines[lc]:gsub("\n$", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
doc_save(self, ...)
|
||||||
|
|
||||||
|
-- restore the visible \n\n or disabled \n
|
||||||
|
if handle_new_line then
|
||||||
|
self.lines[lc] = self.lines[lc] .. "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
if common.basename(self.abs_filename) == ".editorconfig" then
|
||||||
|
-- blindlessly reload related project .editorconfig options
|
||||||
|
for _, project in ipairs(core.project_directories) do
|
||||||
|
if common.path_belongs_to(self.abs_filename, project.name) then
|
||||||
|
editorconfig.load(project.name)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- re-apply editorconfig options to all open files
|
||||||
|
editorconfig.apply_all()
|
||||||
|
elseif new_file then
|
||||||
|
-- apply editorconfig options for file that was previously unsaved
|
||||||
|
editorconfig.apply(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
-- Run the test suite if requested on CLI with: lite-xl test editorconfig
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
for i, argument in ipairs(ARGS) do
|
||||||
|
if argument == "test" and ARGS[i+1] == "editorconfig" then
|
||||||
|
require "plugins.editorconfig.runtest"
|
||||||
|
os.exit()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return editorconfig
|
|
@ -0,0 +1,553 @@
|
||||||
|
-- Lua parser implementation of the .editorconfig spec as best understood.
|
||||||
|
-- @copyright Jefferson Gonzalez <jgmdev@gmail.com>
|
||||||
|
-- @license MIT
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local config = require "core.config"
|
||||||
|
|
||||||
|
local STANDALONE = false
|
||||||
|
for i, argument in ipairs(ARGS) do
|
||||||
|
if argument == "test" and ARGS[i+1] == "editorconfig" then
|
||||||
|
STANDALONE = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Logger that will output using lite-xl logging functions or print to
|
||||||
|
---terminal if the parser is running in standalone mode.
|
||||||
|
---@param type "log" | "error"
|
||||||
|
---@param format string
|
||||||
|
---@param ... any
|
||||||
|
local function log(type, format, ...)
|
||||||
|
if not STANDALONE then
|
||||||
|
core[type]("[EditorConfig]: " .. format, ...)
|
||||||
|
else
|
||||||
|
print("[" .. type:upper() .. "]: " .. string.format(format, ...))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Represents an .editorconfig path rule/expression.
|
||||||
|
---@class plugins.editorconfig.parser.rule
|
||||||
|
---Path expression as found between square brackets.
|
||||||
|
---@field expression string | table<integer,string>
|
||||||
|
---The expression converted to a regex.
|
||||||
|
---@field regex string | table<integer,string>
|
||||||
|
---@field regex_compiled any? | table<integer,string>
|
||||||
|
---@field negation boolean Indicates that the expression is a negation.
|
||||||
|
---@field ranges table<integer,number> List of ranges found on the expression.
|
||||||
|
|
||||||
|
---Represents a section of the .editorconfig with all its config options.
|
||||||
|
---@class plugins.editorconfig.parser.section
|
||||||
|
---@field rule plugins.editorconfig.parser.rule
|
||||||
|
---@field equivalent_rules plugins.editorconfig.parser.rule[]
|
||||||
|
---@field indent_style "tab" | "space"
|
||||||
|
---@field indent_size integer
|
||||||
|
---@field tab_width integer
|
||||||
|
---@field end_of_line "lf" | "cr" | "crlf"
|
||||||
|
---@field charset "latin1" | "utf-8" | "utf-8-bom" | "utf-16be" | "utf-16le"
|
||||||
|
---@field trim_trailing_whitespace boolean
|
||||||
|
---@field insert_final_newline boolean
|
||||||
|
|
||||||
|
---EditorConfig parser class and filename config matching.
|
||||||
|
---@class plugins.editorconfig.parser
|
||||||
|
---@field config_path string
|
||||||
|
---@field sections plugins.editorconfig.parser.section[]
|
||||||
|
---@field root boolean
|
||||||
|
local Parser = {}
|
||||||
|
Parser.__index = Parser
|
||||||
|
|
||||||
|
---Constructor
|
||||||
|
---@param config_path string
|
||||||
|
---@return plugins.editorconfig.parser
|
||||||
|
function Parser.new(config_path)
|
||||||
|
local self = {}
|
||||||
|
setmetatable(self, Parser)
|
||||||
|
self.config_path = config_path
|
||||||
|
self.sections = {}
|
||||||
|
self.root = false
|
||||||
|
self:read()
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- char to hex cache and automatic converter
|
||||||
|
---@type table<string,string>
|
||||||
|
local hex_value = {}
|
||||||
|
setmetatable(hex_value, {
|
||||||
|
__index = function(t, k)
|
||||||
|
local v = rawget(t, k)
|
||||||
|
if v == nil then
|
||||||
|
v = string.format("%x", string.byte(k))
|
||||||
|
rawset(t, k, v)
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
---Simplifies managing rules with other inner rules like {...} which can
|
||||||
|
---contain escaped \\{ \\} and expressions that are easier handled after
|
||||||
|
---converting the escaped special characters to \xXX counterparts.
|
||||||
|
---@param value string
|
||||||
|
---@return string escaped_values
|
||||||
|
local function escapes_to_regex_hex(value)
|
||||||
|
local escaped_chars = {}
|
||||||
|
for char in value:ugmatch("\\(.)") do
|
||||||
|
table.insert(escaped_chars, char)
|
||||||
|
end
|
||||||
|
for _, char in ipairs(escaped_chars) do
|
||||||
|
value = value:ugsub("\\" .. char, "\\x" .. hex_value[char])
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
---An .editorconfig path expression to regex conversion rule.
|
||||||
|
---@class rule
|
||||||
|
---@field rule string Lua pattern.
|
||||||
|
---Callback conversion function.
|
||||||
|
---@field conversion fun(match:string, section:plugins.editorconfig.parser.section):string
|
||||||
|
|
||||||
|
---List of conversion rules applied to brace expressions.
|
||||||
|
---@type rule[]
|
||||||
|
local RULES_BRACES = {
|
||||||
|
{ rule = "^%(", conversion = function() return "\\(" end },
|
||||||
|
{ rule = "^%)", conversion = function() return "\\)" end },
|
||||||
|
{ rule = "^%.", conversion = function() return "\\." end },
|
||||||
|
{ rule = "^\\%[", conversion = function() return "\\[" end },
|
||||||
|
{ rule = "^\\%]", conversion = function() return "\\]" end },
|
||||||
|
{ rule = "^\\!", conversion = function() return "!" end },
|
||||||
|
{ rule = "^\\;", conversion = function() return ";" end },
|
||||||
|
{ rule = "^\\#", conversion = function() return "#" end },
|
||||||
|
{ rule = "^\\,", conversion = function() return "," end },
|
||||||
|
{ rule = "^\\{", conversion = function() return "{" end },
|
||||||
|
{ rule = "^\\}", conversion = function() return "}" end },
|
||||||
|
{ rule = "^,", conversion = function() return "|" end },
|
||||||
|
{ rule = "^\\%*", conversion = function() return "\\*" end },
|
||||||
|
{ rule = "^%*", conversion = function() return "[^\\/]*" end },
|
||||||
|
{ rule = "^%*%*", conversion = function() return ".*" end },
|
||||||
|
{ rule = "^%?", conversion = function() return "." end },
|
||||||
|
{ rule = "^{}", conversion = function() return "{}" end },
|
||||||
|
{ rule = "^{[^,]+}", conversion = function(match) return match end },
|
||||||
|
{ rule = "^%b{}",
|
||||||
|
conversion = function(match)
|
||||||
|
local out = match:ugsub("%(", "\\(")
|
||||||
|
:ugsub("%)", "\\)")
|
||||||
|
:ugsub("%.", "\\.")
|
||||||
|
:ugsub("\\%[", "[\\[]")
|
||||||
|
:ugsub("\\%]", "[\\]]")
|
||||||
|
:ugsub("^\\!", "!")
|
||||||
|
:ugsub("^\\;", ";")
|
||||||
|
:ugsub("^\\#", "#")
|
||||||
|
-- negation chars list
|
||||||
|
:ugsub("%[!(%a+)%]", "[^%1]")
|
||||||
|
:ugsub("\\\\", "[\\]")
|
||||||
|
-- escaped braces
|
||||||
|
:ugsub("\\{", "[{]")
|
||||||
|
:ugsub("\\}", "[}]")
|
||||||
|
-- non escaped braces
|
||||||
|
:ugsub("{([^%]])", "(%1")
|
||||||
|
:ugsub("}([^%]])", ")%1")
|
||||||
|
:ugsub("^{", "(")
|
||||||
|
:ugsub("}$", ")")
|
||||||
|
-- escaped globs
|
||||||
|
:ugsub("\\%*", "[\\*]")
|
||||||
|
:ugsub("\\%?", "[\\?]")
|
||||||
|
-- non escaped globs
|
||||||
|
:ugsub("%*%*", "[*][*]") -- prevent this glob from expanding to next sub
|
||||||
|
:ugsub("%*([^%]])", "[^\\/]*%1")
|
||||||
|
:ugsub("%[%*%]%[%*%]", ".*")
|
||||||
|
:ugsub("%?([^%]])", ".%1")
|
||||||
|
-- escaped comma
|
||||||
|
:ugsub("\\,", "[,]")
|
||||||
|
-- non escaped comma
|
||||||
|
:ugsub(",([^%]])", "|%1")
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{ rule = "^%[[^/%]]*%]",
|
||||||
|
conversion = function(match)
|
||||||
|
local negation = match:umatch("^%[!")
|
||||||
|
local chars = match:umatch("^%[!?(.-)%]")
|
||||||
|
chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-")
|
||||||
|
local out = ""
|
||||||
|
if negation then
|
||||||
|
out = "[^"..chars.."]"
|
||||||
|
else
|
||||||
|
out = "["..chars.."]"
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
---List of conversion rules applied to .editorconfig path expressions.
|
||||||
|
---@type rule[]
|
||||||
|
local RULES = {
|
||||||
|
-- normalize escaped .editorconfig special chars or keep them escaped
|
||||||
|
{ rule = "^\\x[a-fA-F][a-fA-F]", conversion = function(match) return match end },
|
||||||
|
{ rule = "^\\%*", conversion = function() return "\\*" end },
|
||||||
|
{ rule = "^\\%?", conversion = function() return "\\?" end },
|
||||||
|
{ rule = "^\\{", conversion = function() return "{" end },
|
||||||
|
{ rule = "^\\}", conversion = function() return "}" end },
|
||||||
|
{ rule = "^\\%[", conversion = function() return "\\[" end },
|
||||||
|
{ rule = "^\\%]", conversion = function() return "\\]" end },
|
||||||
|
{ rule = "^\\!", conversion = function() return "!" end },
|
||||||
|
{ rule = "^\\;", conversion = function() return ";" end },
|
||||||
|
{ rule = "^\\#", conversion = function() return "#" end },
|
||||||
|
-- escape special chars
|
||||||
|
{ rule = "^%.", conversion = function() return "\\." end },
|
||||||
|
{ rule = "^%(", conversion = function() return "\\(" end },
|
||||||
|
{ rule = "^%)", conversion = function() return "\\)" end },
|
||||||
|
{ rule = "^%[[^/%]]*%]",
|
||||||
|
conversion = function(match)
|
||||||
|
local negation = match:umatch("^%[!")
|
||||||
|
local chars = match:umatch("^%[!?(.-)%]")
|
||||||
|
chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-")
|
||||||
|
local out = ""
|
||||||
|
if negation then
|
||||||
|
out = "[^"..chars.."]"
|
||||||
|
else
|
||||||
|
out = "["..chars.."]"
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-- Is this negation rule valid?
|
||||||
|
{ rule = "^!%w+",
|
||||||
|
conversion = function(match)
|
||||||
|
local chars = match:umatch("%w+")
|
||||||
|
return "[^"..chars.."]"
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-- escape square brackets
|
||||||
|
{ rule = "^%[", conversion = function() return "\\[" end },
|
||||||
|
{ rule = "^%]", conversion = function() return "\\]" end },
|
||||||
|
-- match any characters
|
||||||
|
{ rule = "^%*%*", conversion = function() return ".*" end },
|
||||||
|
-- match any characters excluding path separators, \ not needed but just in case
|
||||||
|
{ rule = "^%*", conversion = function() return "[^\\/]*" end },
|
||||||
|
-- match optional character, doesn't matters what or should only be a \w?
|
||||||
|
{ rule = "^%?", conversion = function() return "[^/]" end },
|
||||||
|
-- threat empty braces literally
|
||||||
|
{ rule = "^{}", conversion = function() return "{}" end },
|
||||||
|
-- match a number range
|
||||||
|
{ rule = "^{%-?%d+%.%.%-?%d+}",
|
||||||
|
conversion = function(match, section)
|
||||||
|
local min, max = match:umatch("(-?%d+)%.%.(-?%d+)")
|
||||||
|
min = tonumber(min)
|
||||||
|
max = tonumber(max)
|
||||||
|
if min and max then
|
||||||
|
if not section.rule.ranges then section.rule.ranges = {} end
|
||||||
|
table.insert(section.rule.ranges, {
|
||||||
|
math.min(min, max),
|
||||||
|
math.max(min, max)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
local minus = ""
|
||||||
|
if min < 0 or max < 0 then minus = "\\-?" end
|
||||||
|
return "(?<!0)("..minus.."[1-9]\\d*)"
|
||||||
|
end
|
||||||
|
},
|
||||||
|
-- threat single option braces literally
|
||||||
|
{ rule = "^{[^,]+}", conversion = function(match) return match end },
|
||||||
|
-- match invalid range
|
||||||
|
{ rule = "^{[^%.]+%.%.[^%.]+}", conversion = function(match) return match end },
|
||||||
|
-- match any of the strings separated by commas inside the curly braces
|
||||||
|
{ rule = "^%b{}",
|
||||||
|
conversion = function(rule, section)
|
||||||
|
rule = rule:gsub("^{", ""):gsub("}$", "")
|
||||||
|
local pos, len, exp = 1, rule:ulen(), ""
|
||||||
|
|
||||||
|
while pos <= len do
|
||||||
|
local found = false
|
||||||
|
for _, r in ipairs(RULES_BRACES) do
|
||||||
|
local match = rule:umatch(r.rule, pos)
|
||||||
|
if match then
|
||||||
|
exp = exp .. r.conversion(match, section)
|
||||||
|
pos = pos + match:ulen()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
exp = exp .. rule:usub(pos, pos)
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return "(" .. exp .. ")"
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---Adds the regex equivalent of a section path expression.
|
||||||
|
---@param section plugins.editorconfig.parser.section | string
|
||||||
|
---@return plugins.editorconfig.parser.section
|
||||||
|
function Parser:rule_to_regex(section)
|
||||||
|
if type(section) == "string" then
|
||||||
|
section = {rule = {expression = section}}
|
||||||
|
end
|
||||||
|
|
||||||
|
local rule = section.rule.expression
|
||||||
|
|
||||||
|
-- match everything rule which is different from regular *
|
||||||
|
-- that doesn't matches path separators
|
||||||
|
if rule == "*" then
|
||||||
|
section.rule.regex = ".+"
|
||||||
|
section.rule.regex_compiled = regex.compile(".+")
|
||||||
|
return section
|
||||||
|
end
|
||||||
|
|
||||||
|
rule = escapes_to_regex_hex(section.rule.expression)
|
||||||
|
|
||||||
|
local pos, len, exp = 1, rule:ulen(), ""
|
||||||
|
|
||||||
|
-- if expression starts with ! it is treated entirely as a negation
|
||||||
|
local negation = rule:umatch("^%s*!")
|
||||||
|
if negation then
|
||||||
|
pos = pos + negation:ulen() + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- apply all conversion rules by looping the path expression/rule
|
||||||
|
while pos <= len do
|
||||||
|
local found = false
|
||||||
|
for _, r in ipairs(RULES) do
|
||||||
|
local match = rule:umatch(r.rule, pos)
|
||||||
|
if match then
|
||||||
|
exp = exp .. r.conversion(match, section)
|
||||||
|
pos = pos + match:ulen()
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not found then
|
||||||
|
exp = exp .. rule:usub(pos, pos)
|
||||||
|
pos = pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- force match up to the end
|
||||||
|
exp = exp .. "$"
|
||||||
|
|
||||||
|
-- allow expressions that start with * to match anything on start
|
||||||
|
if exp:match("^%[^\\/%]%*") then
|
||||||
|
exp = exp:gsub("^%[^\\/%]%*", ".*")
|
||||||
|
-- fixes two failing tests
|
||||||
|
elseif exp:match("^%[") then
|
||||||
|
exp = "^" .. exp
|
||||||
|
-- match only on root dir
|
||||||
|
elseif exp:match("^/") then
|
||||||
|
exp = exp:gsub("^/", "^")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- store changes to the section rule
|
||||||
|
section.rule.regex, section.rule.negation = exp, negation
|
||||||
|
section.rule.regex_compiled = regex.compile(section.rule.regex)
|
||||||
|
if not section.rule.regex_compiled then
|
||||||
|
log(
|
||||||
|
"error",
|
||||||
|
"could not compile '[%s]' to regex '%s'",
|
||||||
|
rule, section.rule.regex
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return section
|
||||||
|
end
|
||||||
|
|
||||||
|
---Parses the associated .editorconfig file and stores each section.
|
||||||
|
function Parser:read()
|
||||||
|
local file = io.open(self.config_path, "r")
|
||||||
|
|
||||||
|
self.sections = {}
|
||||||
|
|
||||||
|
if not file then
|
||||||
|
log("log", "could not read %s", self.config_path)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type plugins.editorconfig.parser.section
|
||||||
|
local section = {}
|
||||||
|
|
||||||
|
for line in file:lines() do
|
||||||
|
---@cast line string
|
||||||
|
|
||||||
|
-- first we try to see if the line is a rule section
|
||||||
|
local rule = ""
|
||||||
|
rule = line:umatch("^%s*%[(.+)%]%s*$")
|
||||||
|
if rule then
|
||||||
|
if section.rule then
|
||||||
|
-- save previous section and crerate new one
|
||||||
|
table.insert(self.sections, section)
|
||||||
|
section = {}
|
||||||
|
end
|
||||||
|
section.rule = {
|
||||||
|
expression = rule
|
||||||
|
}
|
||||||
|
-- convert the expression to a regex directly on the section table
|
||||||
|
self:rule_to_regex(section)
|
||||||
|
|
||||||
|
local clone = rule
|
||||||
|
if clone:match("//+") or clone:match("/%*%*/") then
|
||||||
|
section.equivalent_rules = {}
|
||||||
|
end
|
||||||
|
while clone:match("//+") or clone:match("/%*%*/") do
|
||||||
|
---@type plugins.editorconfig.parser.section[]
|
||||||
|
if clone:match("//+") then
|
||||||
|
clone = clone:ugsub("//+", "/", 1)
|
||||||
|
table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule)
|
||||||
|
end
|
||||||
|
if clone:match("/%*%*/") then
|
||||||
|
clone = clone:ugsub("/%*%*/", "/", 1)
|
||||||
|
table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not rule then
|
||||||
|
local name, value = line:umatch("^%s*(%w%S+)%s*=%s*([^\n\r]+)")
|
||||||
|
if name and value then
|
||||||
|
name = name:ulower()
|
||||||
|
-- do not lowercase property values that start with test_
|
||||||
|
if not name:match("^test_") then
|
||||||
|
value = value:ulower()
|
||||||
|
end
|
||||||
|
if value == "true" then
|
||||||
|
value = true
|
||||||
|
elseif value == "false" then
|
||||||
|
value = false
|
||||||
|
elseif math.tointeger and math.tointeger(value) then
|
||||||
|
value = math.tointeger(value)
|
||||||
|
elseif tonumber(value) then
|
||||||
|
value = tonumber(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
if section.rule then
|
||||||
|
section[name] = value
|
||||||
|
elseif name == "root" and type(value) == "boolean" then
|
||||||
|
self.root = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if section.rule then
|
||||||
|
table.insert(self.sections, section)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Helper function that converts a regex offset results into a list
|
||||||
|
---of strings, omitting the first result which is the complete match.
|
||||||
|
---@param offsets table<integer,integer>
|
||||||
|
---@param value string
|
||||||
|
---@return table<integer, string>
|
||||||
|
local function regex_result_to_table(offsets, value)
|
||||||
|
local result = {}
|
||||||
|
local offset_fix = 0
|
||||||
|
if not regex.find_offsets then
|
||||||
|
offset_fix = 1
|
||||||
|
end
|
||||||
|
for i=3, #offsets, 2 do
|
||||||
|
table.insert(result, value:sub(offsets[i], offsets[i+1]-offset_fix))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get a matching config for the given filename or nil if nothing found.
|
||||||
|
---@param file_name string
|
||||||
|
---@param defaults? boolean Set indent size to defaults when needed,
|
||||||
|
---@return plugins.editorconfig.parser.section?
|
||||||
|
function Parser:getConfig(file_name, defaults)
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
file_name = file_name:gsub("\\", "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
local regex_match = regex.match
|
||||||
|
if regex.find_offsets then
|
||||||
|
regex_match = regex.find_offsets
|
||||||
|
end
|
||||||
|
|
||||||
|
local properties = {}
|
||||||
|
|
||||||
|
local found = false
|
||||||
|
for _, section in ipairs(self.sections) do
|
||||||
|
if section.rule.regex_compiled then
|
||||||
|
local negation = section.rule.negation
|
||||||
|
-- default rule
|
||||||
|
local matched = {regex_match(section.rule.regex_compiled, file_name)}
|
||||||
|
-- try equivalent rules if available
|
||||||
|
if not matched[1] and section.equivalent_rules then
|
||||||
|
for _, esection in ipairs(section.equivalent_rules) do
|
||||||
|
matched = {regex_match(esection.regex_compiled, file_name)}
|
||||||
|
if matched[1] then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (matched[1] and not negation) or (not matched[1] and negation) then
|
||||||
|
local ranges_match = true
|
||||||
|
if section.rule.ranges then
|
||||||
|
local results = regex_result_to_table(matched, file_name)
|
||||||
|
if #results < #section.rule.ranges then
|
||||||
|
ranges_match = false
|
||||||
|
else
|
||||||
|
for i, range in ipairs(section.rule.ranges) do
|
||||||
|
local number = tonumber(results[i])
|
||||||
|
if not number then
|
||||||
|
ranges_match = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if number < range[1] or number > range[2] then
|
||||||
|
ranges_match = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if ranges_match then
|
||||||
|
found = true
|
||||||
|
for name, value in pairs(section) do
|
||||||
|
if name ~= "rule" and name ~= "equivalent_rules" then
|
||||||
|
properties[name] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if found and defaults then
|
||||||
|
if properties.indent_style and properties.indent_style == "space" then
|
||||||
|
if properties.indent_size and not properties.tab_width then
|
||||||
|
properties.tab_width = 4
|
||||||
|
end
|
||||||
|
elseif properties.indent_style and properties.indent_style == "tab" then
|
||||||
|
if not properties.tab_width and not properties.indent_size then
|
||||||
|
properties.indent_size = "tab"
|
||||||
|
elseif properties.tab_width then
|
||||||
|
properties.indent_size = properties.tab_width
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return found and properties or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get a matching config for the given filename or nil if nothing found.
|
||||||
|
---@param file_name string
|
||||||
|
---@return string
|
||||||
|
function Parser:getConfigString(file_name)
|
||||||
|
local out = ""
|
||||||
|
local properties = self:getConfig(file_name, true)
|
||||||
|
if properties then
|
||||||
|
local config_sorted = {}
|
||||||
|
for name, value in pairs(properties) do
|
||||||
|
table.insert(config_sorted, {name = name, value = value})
|
||||||
|
end
|
||||||
|
table.sort(config_sorted, function(a, b)
|
||||||
|
return a.name < b.name
|
||||||
|
end)
|
||||||
|
for _, value in ipairs(config_sorted) do
|
||||||
|
out = out .. value.name .. "=" .. tostring(value.value) .. "\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
return Parser
|
|
@ -0,0 +1,63 @@
|
||||||
|
local core = require "core"
|
||||||
|
local tests = require "plugins.editorconfig.tests"
|
||||||
|
|
||||||
|
-- disable print buffer for immediate output
|
||||||
|
io.stdout:setvbuf "no"
|
||||||
|
|
||||||
|
-- overwrite to print into stdout
|
||||||
|
function core.error(format, ...)
|
||||||
|
print(string.format(format, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function core.log(format, ...)
|
||||||
|
print(string.format(format, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
function core.log_quiet(format, ...)
|
||||||
|
print(string.format(format, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if --parsers flag was given to only output the path expressions and
|
||||||
|
-- their conversion into regular expressions.
|
||||||
|
local PARSERS = false
|
||||||
|
for _, argument in ipairs(ARGS) do
|
||||||
|
if argument == "--parsers" then
|
||||||
|
PARSERS = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not PARSERS then
|
||||||
|
require "plugins.editorconfig.tests.glob"
|
||||||
|
require "plugins.editorconfig.tests.parser"
|
||||||
|
require "plugins.editorconfig.tests.properties"
|
||||||
|
|
||||||
|
tests.run()
|
||||||
|
else
|
||||||
|
-- Globs
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/braces.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/brackets.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/question.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star_star.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/utf8char.in")
|
||||||
|
|
||||||
|
-- Parser
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/basic.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/bom.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_and_newlines.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_only.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/crlf.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/empty.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/limits.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/newlines_only.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/whitespace.in")
|
||||||
|
|
||||||
|
-- Properties
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/indent_size_default.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_names.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_values.in")
|
||||||
|
tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/tab_width_default.in")
|
||||||
|
|
||||||
|
tests.run_parsers()
|
||||||
|
end
|
|
@ -0,0 +1,71 @@
|
||||||
|
; test { and }
|
||||||
|
|
||||||
|
root=true
|
||||||
|
|
||||||
|
; word choice
|
||||||
|
[*.{py,js,html}]
|
||||||
|
choice=true
|
||||||
|
|
||||||
|
; single choice
|
||||||
|
[{single}.b]
|
||||||
|
choice=single
|
||||||
|
|
||||||
|
; empty choice
|
||||||
|
[{}.c]
|
||||||
|
empty=all
|
||||||
|
|
||||||
|
; choice with empty word
|
||||||
|
[a{b,c,}.d]
|
||||||
|
empty=word
|
||||||
|
|
||||||
|
; choice with empty words
|
||||||
|
[a{,b,,c,}.e]
|
||||||
|
empty=words
|
||||||
|
|
||||||
|
; no closing brace
|
||||||
|
[{.f]
|
||||||
|
closing=false
|
||||||
|
|
||||||
|
; nested braces
|
||||||
|
[{word,{also},this}.g]
|
||||||
|
nested=true
|
||||||
|
|
||||||
|
; nested braces, adjacent at start
|
||||||
|
[{{a,b},c}.k]
|
||||||
|
nested_start=true
|
||||||
|
|
||||||
|
; nested braces, adjacent at end
|
||||||
|
[{a,{b,c}}.l]
|
||||||
|
nested_end=true
|
||||||
|
|
||||||
|
; closing inside beginning
|
||||||
|
[{},b}.h]
|
||||||
|
closing=inside
|
||||||
|
|
||||||
|
; opening inside beginning
|
||||||
|
[{{,b,c{d}.i]
|
||||||
|
unmatched=true
|
||||||
|
|
||||||
|
; escaped comma
|
||||||
|
[{a\,b,cd}.txt]
|
||||||
|
comma=yes
|
||||||
|
|
||||||
|
; escaped closing brace
|
||||||
|
[{e,\},f}.txt]
|
||||||
|
closing=yes
|
||||||
|
|
||||||
|
; escaped backslash
|
||||||
|
[{g,\\,i}.txt]
|
||||||
|
backslash=yes
|
||||||
|
|
||||||
|
; patterns nested in braces
|
||||||
|
[{some,a{*c,b}[ef]}.j]
|
||||||
|
patterns=nested
|
||||||
|
|
||||||
|
; numeric braces
|
||||||
|
[{3..120}]
|
||||||
|
number=true
|
||||||
|
|
||||||
|
; alphabetical
|
||||||
|
[{aardvark..antelope}]
|
||||||
|
words=a
|
|
@ -0,0 +1,51 @@
|
||||||
|
; test [ and ]
|
||||||
|
|
||||||
|
root=true
|
||||||
|
|
||||||
|
; Character choice
|
||||||
|
[[ab].a]
|
||||||
|
choice=true
|
||||||
|
|
||||||
|
; Negative character choice
|
||||||
|
[[!ab].b]
|
||||||
|
choice=false
|
||||||
|
|
||||||
|
; Character range
|
||||||
|
[[d-g].c]
|
||||||
|
range=true
|
||||||
|
|
||||||
|
; Negative character range
|
||||||
|
[[!d-g].d]
|
||||||
|
range=false
|
||||||
|
|
||||||
|
; Range and choice
|
||||||
|
[[abd-g].e]
|
||||||
|
range_and_choice=true
|
||||||
|
|
||||||
|
; Choice with dash
|
||||||
|
[[-ab].f]
|
||||||
|
choice_with_dash=true
|
||||||
|
|
||||||
|
; Close bracket inside
|
||||||
|
[[\]ab].g]
|
||||||
|
close_inside=true
|
||||||
|
|
||||||
|
; Close bracket outside
|
||||||
|
[[ab]].g]
|
||||||
|
close_outside=true
|
||||||
|
|
||||||
|
; Negative close bracket inside
|
||||||
|
[[!\]ab].g]
|
||||||
|
close_inside=false
|
||||||
|
|
||||||
|
; Negative¬close bracket outside
|
||||||
|
[[!ab]].g]
|
||||||
|
close_outside=false
|
||||||
|
|
||||||
|
; Slash inside brackets
|
||||||
|
[ab[e/]cd.i]
|
||||||
|
slash_inside=true
|
||||||
|
|
||||||
|
; Slash after an half-open bracket
|
||||||
|
[ab[/c]
|
||||||
|
slash_half_open=true
|
|
@ -0,0 +1,241 @@
|
||||||
|
local tests = require "plugins.editorconfig.tests"
|
||||||
|
|
||||||
|
-- Tests for *
|
||||||
|
|
||||||
|
-- matches a single characters
|
||||||
|
tests.add("star_single_ML", "glob/star.in", "ace.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- matches zero characters
|
||||||
|
tests.add("star_zero_ML", "glob/star.in", "ae.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- matches multiple characters
|
||||||
|
tests.add("star_multiple_ML", "glob/star.in", "abcde.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- does not match path separator
|
||||||
|
tests.add("star_over_slash", "glob/star.in", "a/e.c", "^[ \t\n\r]*keyc=valuec[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- star after a slash
|
||||||
|
tests.add("star_after_slash_ML", "glob/star.in", "Bar/foo.txt", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- star matches a dot file after slash
|
||||||
|
tests.add("star_matches_dot_file_after_slash_ML", "glob/star.in", "Bar/.editorconfig", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- star matches a dot file
|
||||||
|
tests.add("star_matches_dot_file", "glob/star.in", ".editorconfig", "^keyc=valuec[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Tests for ?
|
||||||
|
|
||||||
|
-- matches a single character
|
||||||
|
tests.add("question_single", "glob/question.in", "some.c", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- does not match zero characters
|
||||||
|
tests.add("question_zero", "glob/question.in", "som.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- does not match multiple characters
|
||||||
|
tests.add("question_multiple", "glob/question.in", "something.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- does not match slash
|
||||||
|
tests.add("question_slash", "glob/question.in", "som/.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Tests for [ and ]
|
||||||
|
|
||||||
|
-- close bracket inside
|
||||||
|
tests.add("brackets_close_inside", "glob/brackets.in", "].g", "^close_inside=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- close bracket outside
|
||||||
|
tests.add("brackets_close_outside", "glob/brackets.in", "b].g", "^close_outside=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative close bracket inside
|
||||||
|
tests.add("brackets_nclose_inside", "glob/brackets.in", "c.g", "^close_inside=false[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative close bracket outside
|
||||||
|
tests.add("brackets_nclose_outside", "glob/brackets.in", "c].g", "^close_outside=false[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- character choice
|
||||||
|
tests.add("brackets_choice", "glob/brackets.in", "a.a", "^choice=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- character choice 2
|
||||||
|
tests.add("brackets_choice2", "glob/brackets.in", "c.a", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative character choice
|
||||||
|
tests.add("brackets_nchoice", "glob/brackets.in", "c.b", "^choice=false[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative character choice 2
|
||||||
|
tests.add("brackets_nchoice2", "glob/brackets.in", "a.b", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- character range
|
||||||
|
tests.add("brackets_range", "glob/brackets.in", "f.c", "^range=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- character range 2
|
||||||
|
tests.add("brackets_range2", "glob/brackets.in", "h.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative character range
|
||||||
|
tests.add("brackets_nrange", "glob/brackets.in", "h.d", "^range=false[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- negative character range 2
|
||||||
|
tests.add("brackets_nrange2", "glob/brackets.in", "f.d", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- range and choice
|
||||||
|
tests.add("brackets_range_and_choice", "glob/brackets.in", "e.e",
|
||||||
|
"^range_and_choice=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- character choice with a dash
|
||||||
|
tests.add("brackets_choice_with_dash", "glob/brackets.in", "-.f",
|
||||||
|
"^choice_with_dash=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- slash inside brackets
|
||||||
|
tests.add("brackets_slash_inside1", "glob/brackets.in", "ab/cd.i",
|
||||||
|
"^[ \t\n\r]*$")
|
||||||
|
tests.add("brackets_slash_inside2", "glob/brackets.in", "abecd.i",
|
||||||
|
"^[ \t\n\r]*$")
|
||||||
|
tests.add("brackets_slash_inside3", "glob/brackets.in", "ab[e/]cd.i",
|
||||||
|
"^slash_inside=true[ \t\n\r]*$")
|
||||||
|
tests.add("brackets_slash_inside4", "glob/brackets.in", "ab[/c",
|
||||||
|
"^slash_half_open=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Tests for { and }
|
||||||
|
|
||||||
|
-- word choice
|
||||||
|
tests.add("braces_word_choice1", "glob/braces.in", "test.py", "^choice=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_word_choice2", "glob/braces.in", "test.js", "^choice=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_word_choice3", "glob/braces.in", "test.html", "^choice=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_word_choice4", "glob/braces.in", "test.pyc", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- single choice
|
||||||
|
tests.add("braces_single_choice", "glob/braces.in", "{single}.b", "^choice=single[ \t\n\r]*$")
|
||||||
|
tests.add("braces_single_choice_negative", "glob/braces.in", ".b", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- empty choice
|
||||||
|
tests.add("braces_empty_choice", "glob/braces.in", "{}.c", "^empty=all[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_choice_negative", "glob/braces.in", ".c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- choice with empty word
|
||||||
|
tests.add("braces_empty_word1", "glob/braces.in", "a.d", "^empty=word[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_word2", "glob/braces.in", "ab.d", "^empty=word[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_word3", "glob/braces.in", "ac.d", "^empty=word[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_word4", "glob/braces.in", "a,.d", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- choice with empty words
|
||||||
|
tests.add("braces_empty_words1", "glob/braces.in", "a.e", "^empty=words[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_words2", "glob/braces.in", "ab.e", "^empty=words[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_words3", "glob/braces.in", "ac.e", "^empty=words[ \t\n\r]*$")
|
||||||
|
tests.add("braces_empty_words4", "glob/braces.in", "a,.e", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- no closing brace
|
||||||
|
tests.add("braces_no_closing", "glob/braces.in", "{.f", "^closing=false[ \t\n\r]*$")
|
||||||
|
tests.add("braces_no_closing_negative", "glob/braces.in", ".f", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- nested braces
|
||||||
|
tests.add("braces_nested1", "glob/braces.in", "word,this}.g", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested2", "glob/braces.in", "{also,this}.g", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested3", "glob/braces.in", "word.g", "^nested=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested4", "glob/braces.in", "{also}.g", "^nested=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested5", "glob/braces.in", "this.g", "^nested=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- nested braces, adjacent at start
|
||||||
|
tests.add("braces_nested_start1", "glob/braces.in", "{{a,b},c}.k", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_start2", "glob/braces.in", "{a,b}.k", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_start3", "glob/braces.in", "a.k", "^nested_start=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_start4", "glob/braces.in", "b.k", "^nested_start=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_start5", "glob/braces.in", "c.k", "^nested_start=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- nested braces, adjacent at end
|
||||||
|
tests.add("braces_nested_end1", "glob/braces.in", "{a,{b,c}}.l", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_end2", "glob/braces.in", "{b,c}.l", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_end3", "glob/braces.in", "a.l", "^nested_end=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_end4", "glob/braces.in", "b.l", "^nested_end=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_nested_end5", "glob/braces.in", "c.l", "^nested_end=true[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- closing inside beginning
|
||||||
|
tests.add("braces_closing_in_beginning", "glob/braces.in", "{},b}.h", "^closing=inside[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- missing closing braces
|
||||||
|
tests.add("braces_unmatched1", "glob/braces.in", "{{,b,c{d}.i", "^unmatched=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_unmatched2", "glob/braces.in", "{.i", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_unmatched3", "glob/braces.in", "b.i", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_unmatched4", "glob/braces.in", "c{d.i", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_unmatched5", "glob/braces.in", ".i", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- escaped comma
|
||||||
|
tests.add("braces_escaped_comma1", "glob/braces.in", "a,b.txt", "^comma=yes[ \t\n\r]*$")
|
||||||
|
tests.add("braces_escaped_comma2", "glob/braces.in", "a.txt", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_escaped_comma3", "glob/braces.in", "cd.txt", "^comma=yes[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- escaped closing brace
|
||||||
|
tests.add("braces_escaped_brace1", "glob/braces.in", "e.txt", "^closing=yes[ \t\n\r]*$")
|
||||||
|
tests.add("braces_escaped_brace2", "glob/braces.in", "}.txt", "^closing=yes[ \t\n\r]*$")
|
||||||
|
tests.add("braces_escaped_brace3", "glob/braces.in", "f.txt", "^closing=yes[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- escaped backslash
|
||||||
|
tests.add("braces_escaped_backslash1", "glob/braces.in", "g.txt", "^backslash=yes[ \t\n\r]*$")
|
||||||
|
if PLATFORM ~= "Windows" then
|
||||||
|
tests.add("braces_escaped_backslash2", "glob/braces.in", "\\.txt", "^backslash=yes[ \t\n\r]*$")
|
||||||
|
end
|
||||||
|
tests.add("braces_escaped_backslash3", "glob/braces.in", "i.txt", "^backslash=yes[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- patterns nested in braces
|
||||||
|
tests.add("braces_patterns_nested1", "glob/braces.in", "some.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested2", "glob/braces.in", "abe.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested3", "glob/braces.in", "abf.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested4", "glob/braces.in", "abg.j", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested5", "glob/braces.in", "ace.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested6", "glob/braces.in", "acf.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested7", "glob/braces.in", "acg.j", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested8", "glob/braces.in", "abce.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested9", "glob/braces.in", "abcf.j", "^patterns=nested[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested10", "glob/braces.in", "abcg.j", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested11", "glob/braces.in", "ae.j", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_patterns_nested12", "glob/braces.in", ".j", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- numeric brace range
|
||||||
|
tests.add("braces_numeric_range1", "glob/braces.in", "1", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range2", "glob/braces.in", "3", "^number=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range3", "glob/braces.in", "15", "^number=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range4", "glob/braces.in", "60", "^number=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range5", "glob/braces.in", "5a", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range6", "glob/braces.in", "120", "^number=true[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range7", "glob/braces.in", "121", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_numeric_range8", "glob/braces.in", "060", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- alphabetical brace range: letters should not be considered for ranges
|
||||||
|
tests.add("braces_alpha_range1", "glob/braces.in", "{aardvark..antelope}", "^words=a[ \t\n\r]*$")
|
||||||
|
tests.add("braces_alpha_range2", "glob/braces.in", "a", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_alpha_range3", "glob/braces.in", "aardvark", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_alpha_range4", "glob/braces.in", "agreement", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_alpha_range5", "glob/braces.in", "antelope", "^[ \t\n\r]*$")
|
||||||
|
tests.add("braces_alpha_range6", "glob/braces.in", "antimatter", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
|
||||||
|
-- Tests for **
|
||||||
|
|
||||||
|
-- test EditorConfig files with UTF-8 characters larger than 127
|
||||||
|
tests.add("utf_8_char", "glob/utf8char.in", "中文.txt", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- matches over path separator
|
||||||
|
tests.add("star_star_over_separator1", "glob/star_star.in", "a/z.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator2", "glob/star_star.in", "amnz.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator3", "glob/star_star.in", "am/nz.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator4", "glob/star_star.in", "a/mnz.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator5", "glob/star_star.in", "amn/z.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator6", "glob/star_star.in", "a/mn/z.c", "^key1=value1[ \t\n\r]*$")
|
||||||
|
|
||||||
|
tests.add("star_star_over_separator7", "glob/star_star.in", "b/z.c", "^key2=value2[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator8", "glob/star_star.in", "b/mnz.c", "^key2=value2[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator9", "glob/star_star.in", "b/mn/z.c", "^key2=value2[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator10", "glob/star_star.in", "bmnz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator11", "glob/star_star.in", "bm/nz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator12", "glob/star_star.in", "bmn/z.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
tests.add("star_star_over_separator13", "glob/star_star.in", "c/z.c", "^key3=value3[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator14", "glob/star_star.in", "cmn/z.c", "^key3=value3[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator15", "glob/star_star.in", "c/mn/z.c", "^key3=value3[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator16", "glob/star_star.in", "cmnz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator17", "glob/star_star.in", "cm/nz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator18", "glob/star_star.in", "c/mnz.c", "^[ \t\n\r]*$")
|
||||||
|
|
||||||
|
tests.add("star_star_over_separator19", "glob/star_star.in", "d/z.c", "^key4=value4[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator20", "glob/star_star.in", "d/mn/z.c", "^key4=value4[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator21", "glob/star_star.in", "dmnz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator22", "glob/star_star.in", "dm/nz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator23", "glob/star_star.in", "d/mnz.c", "^[ \t\n\r]*$")
|
||||||
|
tests.add("star_star_over_separator24", "glob/star_star.in", "dmn/z.c", "^[ \t\n\r]*$")
|
|
@ -0,0 +1,7 @@
|
||||||
|
; test ?
|
||||||
|
|
||||||
|
root=true
|
||||||
|
|
||||||
|
[som?.c]
|
||||||
|
key=value
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
; test *
|
||||||
|
|
||||||
|
root=true
|
||||||
|
|
||||||
|
[a*e.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
[Bar/*]
|
||||||
|
keyb=valueb
|
||||||
|
|
||||||
|
[*]
|
||||||
|
keyc=valuec
|
|
@ -0,0 +1,15 @@
|
||||||
|
; test **
|
||||||
|
|
||||||
|
root=true
|
||||||
|
|
||||||
|
[a**z.c]
|
||||||
|
key1=value1
|
||||||
|
|
||||||
|
[b/**z.c]
|
||||||
|
key2=value2
|
||||||
|
|
||||||
|
[c**/z.c]
|
||||||
|
key3=value3
|
||||||
|
|
||||||
|
[d/**/z.c]
|
||||||
|
key4=value4
|
|
@ -0,0 +1,6 @@
|
||||||
|
; test EditorConfig files with UTF-8 characters larger than 127
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[中文.txt]
|
||||||
|
key = value
|
|
@ -0,0 +1,143 @@
|
||||||
|
local Parser = require "plugins.editorconfig.parser"
|
||||||
|
|
||||||
|
local tests = {}
|
||||||
|
|
||||||
|
---@class tests.test
|
||||||
|
---@field name string Name of test
|
||||||
|
---@field config string Path to config file
|
||||||
|
---@field in_match string A path to test against the config
|
||||||
|
---@field out_match string A regex to match against the result
|
||||||
|
|
||||||
|
---Registered tests
|
||||||
|
---@type tests.test[]
|
||||||
|
tests.list = {}
|
||||||
|
|
||||||
|
--- parsers cache
|
||||||
|
---@type table<string,plugins.editorconfig.parser>
|
||||||
|
local parsers = {}
|
||||||
|
setmetatable(parsers, {
|
||||||
|
__index = function(t, k)
|
||||||
|
local v = rawget(t, k)
|
||||||
|
if v == nil then
|
||||||
|
v = Parser.new(k)
|
||||||
|
rawset(t, k, v)
|
||||||
|
end
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
---Adds color to given text on non windows systems.
|
||||||
|
---@param text string
|
||||||
|
---@param color "red" | "green" | "yellow"
|
||||||
|
---@return string colorized_text
|
||||||
|
local function colorize(text, color)
|
||||||
|
if PLATFORM ~= "Windows" then
|
||||||
|
if color == "green" then
|
||||||
|
return "\27[92m"..text.."\27[0m"
|
||||||
|
elseif color == "red" then
|
||||||
|
return "\27[91m"..text.."\27[0m"
|
||||||
|
elseif color == "yellow" then
|
||||||
|
return "\27[93m"..text.."\27[0m"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return text
|
||||||
|
end
|
||||||
|
|
||||||
|
local PASSED = colorize("PASSED", "green")
|
||||||
|
local FAILED = colorize("FAILED", "red")
|
||||||
|
|
||||||
|
---Runs an individual test (executed by tests.run())
|
||||||
|
---@param name string Test name
|
||||||
|
---@param config_path string Relative path to tests diretory for a [config].in
|
||||||
|
---@param in_match string Filename to match
|
||||||
|
---@param out_match string | table Result to match regex
|
||||||
|
function tests.check_config(name, config_path, in_match, out_match, pos, total)
|
||||||
|
if type(out_match) == "string" then
|
||||||
|
out_match = { out_match }
|
||||||
|
end
|
||||||
|
local parser = parsers[USERDIR .. "/plugins/editorconfig/tests/" .. config_path]
|
||||||
|
local config = parser:getConfigString(in_match)
|
||||||
|
local passed = true
|
||||||
|
for _, match in ipairs(out_match) do
|
||||||
|
if not regex.match(match, config) then
|
||||||
|
passed = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if pos then
|
||||||
|
pos = "[" .. pos .. "/" .. total .. "] "
|
||||||
|
else
|
||||||
|
pos = ""
|
||||||
|
end
|
||||||
|
if passed then
|
||||||
|
print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, PASSED))
|
||||||
|
else
|
||||||
|
print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, FAILED))
|
||||||
|
print(config)
|
||||||
|
end
|
||||||
|
return passed
|
||||||
|
end
|
||||||
|
|
||||||
|
---Register a new test to be run later.
|
||||||
|
---@param name string Test name
|
||||||
|
---@param config_path string Relative path to tests diretory for a [config].in
|
||||||
|
---@param in_match string Filename to match
|
||||||
|
---@param out_match string | table Result to match regex
|
||||||
|
function tests.add(name, config_path, in_match, out_match)
|
||||||
|
table.insert(tests.list, {
|
||||||
|
name = name,
|
||||||
|
config = config_path,
|
||||||
|
in_match = in_match,
|
||||||
|
out_match = out_match
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
---Runs all registered tests and outputs the results to terminal.
|
||||||
|
function tests.run()
|
||||||
|
print "========================================================="
|
||||||
|
print "Running Tests"
|
||||||
|
print "========================================================="
|
||||||
|
local failed = 0
|
||||||
|
local passed = 0
|
||||||
|
local total = #tests.list
|
||||||
|
for i, test in ipairs(tests.list) do
|
||||||
|
local res = tests.check_config(
|
||||||
|
test.name, test.config, test.in_match, test.out_match, i, total
|
||||||
|
)
|
||||||
|
if res then passed = passed + 1 else failed = failed + 1 end
|
||||||
|
end
|
||||||
|
print "========================================================="
|
||||||
|
print (
|
||||||
|
string.format(
|
||||||
|
"%s %s %s",
|
||||||
|
colorize("Total tests: " .. #tests.list, "yellow"),
|
||||||
|
colorize("Passed: " .. passed, "green"),
|
||||||
|
colorize("Failed: " .. failed, "red")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print "========================================================="
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.add_parser(config_path)
|
||||||
|
return parsers[config_path]
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.run_parsers()
|
||||||
|
print "========================================================="
|
||||||
|
print "Running Parsers"
|
||||||
|
print "========================================================="
|
||||||
|
|
||||||
|
for config, parser in pairs(parsers) do
|
||||||
|
print "---------------------------------------------------------"
|
||||||
|
print(string.format("%s results:", config))
|
||||||
|
for _, section in ipairs(parser.sections) do
|
||||||
|
print(string.format("\nPath expression: %s", section.rule.expression))
|
||||||
|
print(string.format("Regex: %s", section.rule.regex))
|
||||||
|
print(string.format("Negation: %s", section.rule.negation and "true" or "false"))
|
||||||
|
print(string.format("Ranges: %s\n", section.rule.ranges and #section.rule.ranges or "0"))
|
||||||
|
end
|
||||||
|
print "---------------------------------------------------------"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tests
|
|
@ -0,0 +1,16 @@
|
||||||
|
[*.a]
|
||||||
|
option1=value1
|
||||||
|
|
||||||
|
; repeat section
|
||||||
|
[*.a]
|
||||||
|
option2=value2
|
||||||
|
|
||||||
|
[*.b]
|
||||||
|
option1 = a
|
||||||
|
option2 = a
|
||||||
|
|
||||||
|
[b.b]
|
||||||
|
option2 = b
|
||||||
|
|
||||||
|
[*.b]
|
||||||
|
option1 = c
|
|
@ -0,0 +1,6 @@
|
||||||
|
; test EditorConfig files with BOM
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
key = value
|
|
@ -0,0 +1,47 @@
|
||||||
|
; test comments
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[test3.c]
|
||||||
|
; Comment before properties ignored
|
||||||
|
key=value
|
||||||
|
|
||||||
|
[test4.c]
|
||||||
|
key1=value1
|
||||||
|
; Comment between properties ignored
|
||||||
|
key2=value2
|
||||||
|
|
||||||
|
; Semicolon or hash at end of value read as part of value
|
||||||
|
[test5.c]
|
||||||
|
key1=value; not comment
|
||||||
|
key2=value # not comment
|
||||||
|
|
||||||
|
; Backslash before a semicolon or hash is part of the value
|
||||||
|
[test6.c]
|
||||||
|
key1=value \; not comment
|
||||||
|
key2=value \# not comment
|
||||||
|
|
||||||
|
; Escaped semicolon in section name
|
||||||
|
[test\;.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
[test9.c]
|
||||||
|
# Comment before properties ignored
|
||||||
|
key=value
|
||||||
|
|
||||||
|
[test10.c]
|
||||||
|
key1=value1
|
||||||
|
# Comment between properties ignored
|
||||||
|
key2=value2
|
||||||
|
|
||||||
|
# Octothorpe at end of value read as part of value
|
||||||
|
[test11.c]
|
||||||
|
key=value# not comment
|
||||||
|
|
||||||
|
# Escaped octothorpe in value
|
||||||
|
[test12.c]
|
||||||
|
key=value \# not comment
|
||||||
|
|
||||||
|
# Escaped octothorpe in section name
|
||||||
|
[test\#.c]
|
||||||
|
key=value
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
# Just comments
|
||||||
|
|
||||||
|
# ... and newlines
|
|
@ -0,0 +1 @@
|
||||||
|
# Just a comment, nothing else
|
|
@ -0,0 +1,6 @@
|
||||||
|
; test EditorConfig files with CRLF line separators
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
key = value
|
|
@ -0,0 +1,107 @@
|
||||||
|
local tests = require "plugins.editorconfig.tests"
|
||||||
|
|
||||||
|
-- Basic parser tests
|
||||||
|
|
||||||
|
-- test repeat sections
|
||||||
|
tests.add("repeat_sections_ML", "parser/basic.in", "a.a", "option1=value1[ \t]*[\n\r]+option2=value2[ \t\n\r]*")
|
||||||
|
tests.add("basic_cascade_ML", "parser/basic.in", "b.b", "option1=c[ \t]*[\n\r]+option2=b[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- Tests for whitespace parsing
|
||||||
|
|
||||||
|
-- test no whitespaces in property assignment
|
||||||
|
tests.add("no_whitespace", "parser/whitespace.in", "test1.c", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test single spaces around equals sign
|
||||||
|
tests.add("single_spaces_around_equals", "parser/whitespace.in", "test2.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test multiple spaces around equals sign
|
||||||
|
tests.add("multiple_spaces_around_equals", "parser/whitespace.in", "test3.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test spaces before property name
|
||||||
|
tests.add("spaces_before_property_name", "parser/whitespace.in", "test4.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test spaces before after property value
|
||||||
|
tests.add("spaces_after_property_value", "parser/whitespace.in", "test5.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test blank lines between properties
|
||||||
|
tests.add("blank_lines_between_properties_ML", "parser/whitespace.in", "test6.c",
|
||||||
|
"key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- test spaces in section name
|
||||||
|
tests.add("spaces_in_section_name", "parser/whitespace.in", " test 7 ",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test spaces before section name are ignored
|
||||||
|
tests.add("spaces_before_section_name", "parser/whitespace.in", "test8.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test spaces after section name
|
||||||
|
tests.add("spaces_after_section_name", "parser/whitespace.in", "test9.c", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test spaces at beginning of line between properties
|
||||||
|
tests.add("spaces_before_middle_property_ML", "parser/whitespace.in", "test10.c",
|
||||||
|
"key1=value1[ \t]*[\n\r]+key2=value2[ \t]*[\n\r]+key3=value3[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- Tests for comment parsing
|
||||||
|
|
||||||
|
-- test comments ignored before properties
|
||||||
|
tests.add("comment_before_props", "parser/comments.in", "test3.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test comments ignored between properties
|
||||||
|
tests.add("comment_between_props_ML", "parser/comments.in", "test4.c",
|
||||||
|
"key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- test semicolons and hashes at end of property value are included in value
|
||||||
|
tests.add("semicolon_or_hash_in_property", "parser/comments.in", "test5.c",
|
||||||
|
"^key1=value; not comment[\n\r]+key2=value # not comment[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test that backslashes before semicolons and hashes in property values
|
||||||
|
-- are included in value.
|
||||||
|
-- NOTE: [\\] matches a single literal backslash.
|
||||||
|
tests.add("backslashed_semicolon_or_hash_in_property", "parser/comments.in", "test6.c",
|
||||||
|
"^key1=value [\\\\]; not comment[\n\r]+key2=value [\\\\]# not comment[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test escaped semicolons are included in section names
|
||||||
|
tests.add("escaped_semicolon_in_section", "parser/comments.in", "test;.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test octothorpe comments ignored before properties
|
||||||
|
tests.add("octothorpe_comment_before_props", "parser/comments.in", "test9.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test octothorpe comments ignored between properties
|
||||||
|
tests.add("octothorpe_comment_between_props_ML", "parser/comments.in", "test10.c",
|
||||||
|
"key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- test octothorpe at end of property value are included in value
|
||||||
|
tests.add("octothorpe_in_value", "parser/comments.in", "test11.c",
|
||||||
|
"^key=value# not comment[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test escaped octothorpes are included in section names
|
||||||
|
tests.add("escaped_octothorpe_in_section", "parser/comments.in", "test#.c",
|
||||||
|
"^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test EditorConfig files with BOM at the head
|
||||||
|
tests.add("bom_at_head", "parser/bom.in", "a.c", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test EditorConfig files with CRLF line separators
|
||||||
|
tests.add("crlf_linesep", "parser/crlf.in", "a.c", "^key=value[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Test minimum supported lengths of section name, key and value
|
||||||
|
tests.add("min_supported_key_length", "parser/limits.in", "test1",
|
||||||
|
"^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024[ \t\n\r]*$")
|
||||||
|
tests.add("min_supported_value_length", "parser/limits.in", "test2",
|
||||||
|
"^k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ \t\n\r]*$")
|
||||||
|
tests.add("min_supported_section_name_length", "parser/limits.in", "test3",
|
||||||
|
"^k1024=v1024[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Empty .editorconfig files
|
||||||
|
tests.add("empty_editorconfig_file", "parser/empty.in", "test4", "^[ \t\n\r]*$")
|
||||||
|
tests.add("newlines_only_editorconfig_file", "parser/newlines_only.in", "test4", "^[ \t\n\r]*$")
|
||||||
|
tests.add("comments_only_editorconfig_file", "parser/comments_only.in", "test4", "^[ \t\n\r]*$")
|
||||||
|
tests.add("comments_and_newlines_editorconfig_file", "parser/comments_and_newlines.in", "test4", "^[ \t\n\r]*$")
|
|
@ -0,0 +1,13 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
; minimum supported key length of 1024 characters
|
||||||
|
[test1]
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024
|
||||||
|
|
||||||
|
; minimum supported value length of 4096 characters
|
||||||
|
[test2]
|
||||||
|
k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||||
|
|
||||||
|
; minimum supported section name length of 1024 characters (excluding [] brackets)
|
||||||
|
[{test3,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}]
|
||||||
|
k1024=v1024
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
; test whitespace usage
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
; no whitespace
|
||||||
|
[test1.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; spaces around equals
|
||||||
|
[test2.c]
|
||||||
|
key = value
|
||||||
|
|
||||||
|
; lots of space after equals
|
||||||
|
[test3.c]
|
||||||
|
key = value
|
||||||
|
|
||||||
|
; spaces before property name
|
||||||
|
[test4.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; spaces after property value
|
||||||
|
[test5.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; blank lines between properties
|
||||||
|
[test6.c]
|
||||||
|
|
||||||
|
key1=value1
|
||||||
|
|
||||||
|
key2=value2
|
||||||
|
|
||||||
|
; spaces in section name
|
||||||
|
[ test 7 ]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; spaces before section name
|
||||||
|
[test8.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; spaces after section name
|
||||||
|
[test9.c]
|
||||||
|
key=value
|
||||||
|
|
||||||
|
; spacing before middle property
|
||||||
|
[test10.c]
|
||||||
|
key1=value1
|
||||||
|
key2=value2
|
||||||
|
key3=value3
|
|
@ -0,0 +1,11 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[test.c]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[test2.c]
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[test3.c]
|
||||||
|
indent_style = tab
|
||||||
|
tab_width = 2
|
|
@ -0,0 +1,42 @@
|
||||||
|
local tests = require "plugins.editorconfig.tests"
|
||||||
|
|
||||||
|
-- test tab_width default
|
||||||
|
tests.add("tab_width_default_ML", "properties/tab_width_default.in", "test.c",
|
||||||
|
"indent_size=4[ \t]*[\n\r]+indent_style=space[ \t]*[\n\r]+tab_width=4[\t\n\r]*")
|
||||||
|
|
||||||
|
-- Tab_width should not be set to any value if indent_size is "tab" and
|
||||||
|
-- tab_width is not set
|
||||||
|
tests.add("tab_width_default_indent_size_tab_ML", "properties/tab_width_default.in",
|
||||||
|
"test2.c", "indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- Test indent_size default. When indent_style is "tab", indent_size defaults to
|
||||||
|
-- "tab".
|
||||||
|
tests.add("indent_size_default_ML", "properties/indent_size_default.in", "test.c",
|
||||||
|
"indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- Test indent_size default. When indent_style is "space", indent_size has no
|
||||||
|
-- default value.
|
||||||
|
tests.add("indent_size_default_space", "properties/indent_size_default.in", "test2.c",
|
||||||
|
"^indent_style=space[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- Test indent_size default. When indent_style is "tab" and tab_width is set,
|
||||||
|
-- indent_size should default to tab_width
|
||||||
|
tests.add("indent_size_default_with_tab_width_ML",
|
||||||
|
"properties/indent_size_default.in", "test3.c",
|
||||||
|
"indent_size=2[ \t]*[\n\r]+indent_style=tab[ \t]*[\n\r]+tab_width=2[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- test that same property values are lowercased (v0.9.0 properties)
|
||||||
|
tests.add("lowercase_values1_ML", "properties/lowercase_values.in", "test1.c",
|
||||||
|
"end_of_line=crlf[ \t]*[\n\r]+indent_style=space[ \t\n\r]*")
|
||||||
|
|
||||||
|
-- test that same property values are lowercased (v0.9.0 properties)
|
||||||
|
tests.add("lowercase_values2_ML", "properties/lowercase_values.in", "test2.c",
|
||||||
|
"charset=utf-8[ \t]*[\n\r]+insert_final_newline=true[ \t]*[\n\r]+trim_trailing_whitespace=false[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test that same property values are not lowercased
|
||||||
|
tests.add("lowercase_values3", "properties/lowercase_values.in", "test3.c",
|
||||||
|
"^test_property=TestValue[ \t\n\r]*$")
|
||||||
|
|
||||||
|
-- test that all property names are lowercased
|
||||||
|
tests.add("lowercase_names", "properties/lowercase_names.in", "test.c",
|
||||||
|
"^testproperty=testvalue[ \t\n\r]*$")
|
|
@ -0,0 +1,6 @@
|
||||||
|
; test that property names are lowercased
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[test.c]
|
||||||
|
TestProperty = testvalue
|
|
@ -0,0 +1,15 @@
|
||||||
|
; test property name lowercasing
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[test1.c]
|
||||||
|
indent_style = Space
|
||||||
|
end_of_line = CRLF
|
||||||
|
|
||||||
|
[test2.c]
|
||||||
|
insert_final_newline = TRUE
|
||||||
|
trim_trailing_whitespace = False
|
||||||
|
charset = UTF-8
|
||||||
|
|
||||||
|
[test3.c]
|
||||||
|
test_property = TestValue
|
|
@ -0,0 +1,9 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[test.c]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[test2.c]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = tab
|
|
@ -0,0 +1,77 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
local RootView = require "core.rootview"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
local TreeView = require "plugins.treeview"
|
||||||
|
|
||||||
|
local RootView_open_doc = RootView.open_doc
|
||||||
|
function RootView:open_doc(doc)
|
||||||
|
local docview = RootView_open_doc(self, doc)
|
||||||
|
-- The absence of the ephemeral flag means that before this moment in this
|
||||||
|
-- node this document was not exists
|
||||||
|
if docview.ephemeral == nil then
|
||||||
|
local node = self:get_active_node_default()
|
||||||
|
-- We assume that ephemeral tab is always the last one
|
||||||
|
-- But user can drag and drop tabs so full check is needed
|
||||||
|
for i, v in ipairs(node.views) do
|
||||||
|
if v.ephemeral then
|
||||||
|
node:close_view(self.root_node, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
docview.ephemeral = true
|
||||||
|
end
|
||||||
|
return docview
|
||||||
|
end
|
||||||
|
|
||||||
|
local Doc_get_name = DocView.get_name
|
||||||
|
function DocView:get_name()
|
||||||
|
return self.doc and self.ephemeral and ("~ " .. Doc_get_name(self) .. " ~")
|
||||||
|
or Doc_get_name(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Any change to the document makes the tab normal
|
||||||
|
local Doc_on_text_change = Doc.on_text_change
|
||||||
|
function Doc:on_text_change(type)
|
||||||
|
core.active_view.ephemeral = false
|
||||||
|
Doc_on_text_change(self, type)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Double clicking in the TreeView makes the tab normal
|
||||||
|
local TreeView_on_mouse_pressed = TreeView.on_mouse_pressed
|
||||||
|
function TreeView:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local result = TreeView_on_mouse_pressed(self, button, x, y, clicks)
|
||||||
|
if (clicks > 1) and (core.active_view.doc ~= nil) then
|
||||||
|
core.active_view.ephemeral = false
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Double clicking on a tab makes it normal
|
||||||
|
local RootView_on_mouse_pressed = RootView.on_mouse_pressed
|
||||||
|
function RootView:on_mouse_pressed(button, x, y, clicks)
|
||||||
|
local result = RootView_on_mouse_pressed(self, button, x, y, clicks)
|
||||||
|
if clicks > 1 then
|
||||||
|
local node = self.root_node:get_child_overlapping_point(x, y)
|
||||||
|
local idx = node:get_tab_overlapping_point(x, y)
|
||||||
|
if idx then
|
||||||
|
node.views[idx].ephemeral = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Dragging a tab makes it normal
|
||||||
|
local RootView_on_mouse_released = RootView.on_mouse_released
|
||||||
|
function RootView:on_mouse_released(button, x, y, ...)
|
||||||
|
if self.dragged_node then
|
||||||
|
if button == "left" then
|
||||||
|
if self.dragged_node.dragging then
|
||||||
|
local view = self.dragged_node.node.views[self.dragged_node.idx]
|
||||||
|
view.ephemeral = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return RootView_on_mouse_released(self, button, x, y, ...)
|
||||||
|
end
|
|
@ -0,0 +1,68 @@
|
||||||
|
-- mod-version:3 -- lite-xl 2.1
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
-- NAME : External Terminal
|
||||||
|
-- DESCRIPTION: A plugin to open an external terminal
|
||||||
|
-- AUTHOR : Shady Goat
|
||||||
|
-- GOALS : Open an external terminal in the project directory
|
||||||
|
-- SHORT NAME : exterm
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local config = require "core.config"
|
||||||
|
local plugins = config.plugins
|
||||||
|
local os = require "os"
|
||||||
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
|
||||||
|
plugins.exterm = common.merge({
|
||||||
|
executable = "cmd",
|
||||||
|
keymap = "ctrl+shift+space"
|
||||||
|
}, plugins.exterm)
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["exterm:open-terminal"] = function()
|
||||||
|
-- adds the & for background process, idk if it works for windows
|
||||||
|
-- also, it opens in the project dir, since the working dir = project dir, and terminals open in the working dir :)
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
os.execute('start "" ' .. plugins.exterm.executable)
|
||||||
|
elseif PLATFORM == "Linux" or PLATFORM == "Mac OS X" then
|
||||||
|
os.execute(plugins.exterm.executable .. " &")
|
||||||
|
elseif PLATFORM == "AmigaOS 4" then
|
||||||
|
plugins.exterm.executable = "newshell WINDOW \"CON:500/700/900/300/LiteXL Console/CLOSE\""
|
||||||
|
os.execute(plugins.exterm.executable .. " &")
|
||||||
|
else
|
||||||
|
core.error("Exterm: Platform not supported")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add({[plugins.exterm.keymap] = "exterm:open-terminal"})
|
||||||
|
|
||||||
|
local settings = nil
|
||||||
|
if pcall(require, "plugins.settings") then
|
||||||
|
settings = require "plugins.settings"
|
||||||
|
end
|
||||||
|
|
||||||
|
if settings then
|
||||||
|
settings.add("External Terminal",
|
||||||
|
{
|
||||||
|
{
|
||||||
|
label = "Shortcut",
|
||||||
|
description = "The shortcut to run the terminal",
|
||||||
|
path = "keymap",
|
||||||
|
default = "ctrl+shift+space",
|
||||||
|
type = settings.type.STRING
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label = "Executable",
|
||||||
|
description = "The command to run to open the terminal",
|
||||||
|
path = "executable",
|
||||||
|
type = settings.type.STRING,
|
||||||
|
default = "cmd"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"exterm"
|
||||||
|
)
|
||||||
|
end
|
|
@ -0,0 +1,76 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
|
||||||
|
|
||||||
|
local html = [[
|
||||||
|
<html>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin:80 auto 100 auto;
|
||||||
|
max-width: 750px;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: Open Sans, Arial;
|
||||||
|
color: #444;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
h1, h2, h3 { line-height: 1.2; padding-top: 14px; }
|
||||||
|
hr { border: 0px; border-top: 1px solid #ddd; }
|
||||||
|
code, pre { background: #f3f3f3; padding: 8px; }
|
||||||
|
code { padding: 4px; }
|
||||||
|
a { text-decoration: none; color: #0366d6; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
table { border-collapse: collapse; }
|
||||||
|
table, th, td { border: 1px solid #ddd; padding: 6px; }
|
||||||
|
</style>
|
||||||
|
<head>
|
||||||
|
<title>${title}</title>
|
||||||
|
<head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
var xhr = new XMLHttpRequest;
|
||||||
|
xhr.open("POST", "https://api.github.com/markdown/raw");
|
||||||
|
xhr.setRequestHeader("Content-Type", "text/plain");
|
||||||
|
xhr.onload = function() { document.body.innerHTML = xhr.responseText; };
|
||||||
|
xhr.send("${content}");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
command.add("core.docview!", {
|
||||||
|
["ghmarkdown:show-preview"] = function(dv)
|
||||||
|
local content = dv.doc:get_text(1, 1, math.huge, math.huge)
|
||||||
|
local esc = { ['"'] = '\\"', ["\n"] = '\\n' }
|
||||||
|
local text = html:gsub("${(.-)}", {
|
||||||
|
title = dv:get_name(),
|
||||||
|
content = content:gsub(".", esc)
|
||||||
|
})
|
||||||
|
|
||||||
|
local htmlfile = core.temp_filename(".html")
|
||||||
|
local fp = io.open(htmlfile, "w")
|
||||||
|
fp:write(text)
|
||||||
|
fp:close()
|
||||||
|
|
||||||
|
core.log("Opening markdown preview for \"%s\"", dv:get_name())
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
system.exec("start " .. htmlfile)
|
||||||
|
elseif PLATFORM == "AmigaOS 4" then
|
||||||
|
system.exec('urlopen "file=' .. htmlfile .. '"')
|
||||||
|
elseif PLATFORM == "MorphOS" then
|
||||||
|
system.exec('openurl "' .. htmlfile .. '" FILE;')
|
||||||
|
else
|
||||||
|
system.exec(string.format("xdg-open %q", htmlfile))
|
||||||
|
end
|
||||||
|
|
||||||
|
core.add_thread(function()
|
||||||
|
coroutine.yield(5)
|
||||||
|
os.remove(htmlfile)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
keymap.add { ["ctrl+alt+m"] = "ghmarkdown:show-preview" }
|
|
@ -0,0 +1,141 @@
|
||||||
|
-- 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 = 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"
|
||||||
|
|
||||||
|
-- TODO: only set document indent type if there are no selections
|
||||||
|
-- TODO: correctly restore selections accounting for the offset caused by the conversion
|
||||||
|
|
||||||
|
-- To replace N spaces with tabs, we match the last N spaces before the start of
|
||||||
|
-- the actual code and replace them with a tab.
|
||||||
|
-- We repeat this until we can't find any more spaces before the code.
|
||||||
|
-- The problem we encounter with this method is that if we have less than N
|
||||||
|
-- remaining spaces, those will end up at the start of the line.
|
||||||
|
-- Eg:
|
||||||
|
-- int main() {
|
||||||
|
-- __printf("Hello world\n");
|
||||||
|
-- ___return 0;
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- Becomes
|
||||||
|
-- int main() {
|
||||||
|
-- #printf("Hello world\n");
|
||||||
|
-- _#return 0;
|
||||||
|
-- }
|
||||||
|
--
|
||||||
|
-- Instead of
|
||||||
|
-- int main() {
|
||||||
|
-- #printf("Hello world\n");
|
||||||
|
-- #_return 0;
|
||||||
|
-- }
|
||||||
|
-- With regex we could do something like
|
||||||
|
-- `regex.gsub("(^(?: {2})*)(?: {2})", "\\1\t")`
|
||||||
|
-- but the implementation of `regex.gsub` is very slow.
|
||||||
|
--
|
||||||
|
-- The workaround is to find the longest possible repetition of N*X spaces and
|
||||||
|
-- use that information to replace the longest repetition of spaces starting
|
||||||
|
-- from the beginning of the line, then the second longest...
|
||||||
|
local function spaces_replacer(text, indent_size)
|
||||||
|
local spaces = string.rep(" ", indent_size)
|
||||||
|
local total = 0
|
||||||
|
local n
|
||||||
|
local reps = 0
|
||||||
|
-- find the longest repetition of indent_size*spaces
|
||||||
|
repeat
|
||||||
|
reps = reps + 1
|
||||||
|
local s, _ = string.find(text, "%f[^"..zero_pattern.."\n]"..string.rep(spaces, reps))
|
||||||
|
until not s
|
||||||
|
reps = reps - 1
|
||||||
|
while reps > 0 do
|
||||||
|
text, n = string.gsub(text,
|
||||||
|
"(%f[^"..zero_pattern.."\n])("..string.rep(spaces, reps)..")",
|
||||||
|
"%1"..string.rep("\t", reps))
|
||||||
|
total = total + n
|
||||||
|
reps = reps - 1
|
||||||
|
end
|
||||||
|
return text, total
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tabs_replacer(text, indent_size)
|
||||||
|
local spaces = string.rep(" ", indent_size)
|
||||||
|
local total = 0
|
||||||
|
local n
|
||||||
|
-- replace the last tab before the text until there aren't anymore
|
||||||
|
repeat
|
||||||
|
text, n = string.gsub(text, "(%f[^"..zero_pattern.."\n]\t*)(\t)", "%1"..spaces)
|
||||||
|
total = total + n
|
||||||
|
until n == 0
|
||||||
|
return text, total
|
||||||
|
end
|
||||||
|
|
||||||
|
local function replacer(doc, fn, indent_size)
|
||||||
|
return function(text)
|
||||||
|
return fn(text, indent_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_indent_size(doc)
|
||||||
|
local indent_size = config.indent_size
|
||||||
|
if type(doc.get_indent_info) == "function" then
|
||||||
|
-- use the new `Doc:get_indent_info` function
|
||||||
|
indent_size = select(2, doc:get_indent_info())
|
||||||
|
end
|
||||||
|
return indent_size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tabs_to_spaces(dv)
|
||||||
|
local doc = dv.doc
|
||||||
|
local indent_size = get_indent_size(doc)
|
||||||
|
local selections = doc.selections
|
||||||
|
doc:replace(replacer(doc, tabs_replacer, indent_size))
|
||||||
|
doc.selections = selections
|
||||||
|
doc:sanitize_selection()
|
||||||
|
if config.plugins.indent_convert.update_indent_type then
|
||||||
|
doc.indent_info = {
|
||||||
|
type = "soft",
|
||||||
|
size = indent_size,
|
||||||
|
confirmed = true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function spaces_to_tabs(dv)
|
||||||
|
local doc = dv.doc
|
||||||
|
local indent_size = get_indent_size(doc)
|
||||||
|
local selections = doc.selections
|
||||||
|
doc:replace(replacer(doc, spaces_replacer, indent_size))
|
||||||
|
doc.selections = selections
|
||||||
|
doc:sanitize_selection()
|
||||||
|
if config.plugins.indent_convert.update_indent_type then
|
||||||
|
doc.indent_info = {
|
||||||
|
type = "hard",
|
||||||
|
size = indent_size,
|
||||||
|
confirmed = true
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add("core.docview!", {
|
||||||
|
["indent-convert:tabs-to-spaces"] = tabs_to_spaces,
|
||||||
|
["indent-convert:spaces-to-tabs"] = spaces_to_tabs
|
||||||
|
}
|
||||||
|
)
|
|
@ -0,0 +1,144 @@
|
||||||
|
-- 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)
|
||||||
|
if doc.get_indent_info then
|
||||||
|
return doc:get_indent_info()
|
||||||
|
end
|
||||||
|
return config.tab_type, config.indent_size
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_line_spaces(doc, line, dir)
|
||||||
|
local _, indent_size = get_indent_info(doc)
|
||||||
|
local text = doc.lines[line]
|
||||||
|
if not text or #text == 1 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
local s, e = text:find("^%s*")
|
||||||
|
if e == #text then
|
||||||
|
return get_line_spaces(doc, line + dir, dir)
|
||||||
|
end
|
||||||
|
local n = 0
|
||||||
|
for _,b in pairs({text:byte(s, e)}) do
|
||||||
|
n = n + (b == 9 and indent_size or 1)
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_line_indent_guide_spaces(doc, line)
|
||||||
|
if doc.lines[line]:find("^%s*\n") then
|
||||||
|
return math.max(
|
||||||
|
get_line_spaces(doc, line - 1, -1),
|
||||||
|
get_line_spaces(doc, line + 1, 1))
|
||||||
|
end
|
||||||
|
return get_line_spaces(doc, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
local docview_update = DocView.update
|
||||||
|
function DocView:update()
|
||||||
|
docview_update(self)
|
||||||
|
|
||||||
|
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[line]
|
||||||
|
end
|
||||||
|
|
||||||
|
self.indentguide_indents = {}
|
||||||
|
self.indentguide_indent_active = {}
|
||||||
|
|
||||||
|
local minline, maxline = self:get_visible_line_range()
|
||||||
|
for i = minline, maxline do
|
||||||
|
self.indentguide_indents[i] = get_line_indent_guide_spaces(self.doc, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, indent_size = get_indent_info(self.doc)
|
||||||
|
for _,line in self.doc:get_selections() do
|
||||||
|
local lvl = get_indent(line)
|
||||||
|
local top, bottom
|
||||||
|
|
||||||
|
if not self.indentguide_indent_active[line]
|
||||||
|
or self.indentguide_indent_active[line] > lvl then
|
||||||
|
|
||||||
|
-- check if we're the header or the footer of a block
|
||||||
|
if get_indent(line + 1) > lvl and get_indent(line + 1) <= lvl + indent_size then
|
||||||
|
top = true
|
||||||
|
lvl = get_indent(line + 1)
|
||||||
|
elseif get_indent(line - 1) > lvl and get_indent(line - 1) <= lvl + indent_size then
|
||||||
|
bottom = true
|
||||||
|
lvl = get_indent(line - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
self.indentguide_indent_active[line] = lvl
|
||||||
|
|
||||||
|
-- check if the lines before the current are part of the block
|
||||||
|
local i = line - 1
|
||||||
|
if i > 0 and not top then
|
||||||
|
repeat
|
||||||
|
if get_indent(i) <= lvl - indent_size then break end
|
||||||
|
self.indentguide_indent_active[i] = lvl
|
||||||
|
i = i - 1
|
||||||
|
until i < minline
|
||||||
|
end
|
||||||
|
-- check if the lines after the current are part of the block
|
||||||
|
i = line + 1
|
||||||
|
if i <= #self.doc.lines and not bottom then
|
||||||
|
repeat
|
||||||
|
if get_indent(i) <= lvl - indent_size then break end
|
||||||
|
self.indentguide_indent_active[i] = lvl
|
||||||
|
i = i + 1
|
||||||
|
until i > maxline
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_text = DocView.draw_line_text
|
||||||
|
function DocView:draw_line_text(line, x, y)
|
||||||
|
if config.plugins.indentguide.enabled and self:is(DocView) then
|
||||||
|
local spaces = self.indentguide_indents[line] or -1
|
||||||
|
local _, indent_size = get_indent_info(self.doc)
|
||||||
|
local w = math.max(1, SCALE)
|
||||||
|
local 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
|
||||||
|
end
|
||||||
|
return draw_line_text(self, line, x, y)
|
||||||
|
end
|
|
@ -0,0 +1,74 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
syntax.add {
|
||||||
|
name = "Amigaguide",
|
||||||
|
files = { "%.guide$" },
|
||||||
|
patterns = {
|
||||||
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
|
{ pattern = '@{[a-zA-Z0-9& "\'.]+}', type = "keyword" },
|
||||||
|
{ pattern = "@[$:A-Za-z]*", type = "keyword2" },
|
||||||
|
},
|
||||||
|
symbols = {
|
||||||
|
["@$VER:"] = "keyword2",
|
||||||
|
["@(C)"] = "keyword2",
|
||||||
|
["@AUTHOR"] = "keyword2",
|
||||||
|
["@DATABASE"] = "keyword2",
|
||||||
|
["@DNODE"] = "keyword2",
|
||||||
|
["@FONT"] = "keyword2",
|
||||||
|
["@HEIGHT"] = "keyword2",
|
||||||
|
["@HELP"] = "keyword2",
|
||||||
|
["@INDEX"] = "keyword2",
|
||||||
|
["@MACRO"] = "keyword2",
|
||||||
|
["@MASTER"] = "keyword2",
|
||||||
|
["@NODE"] = "keyword2",
|
||||||
|
["@ONCLOSE"] = "keyword2",
|
||||||
|
["@ONOPEN"] = "keyword2",
|
||||||
|
["@REM"] = "keyword2",
|
||||||
|
["@REMARK"] = "keyword2",
|
||||||
|
["@SMARTWRAP"] = "keyword2",
|
||||||
|
["@TAB"] = "keyword2",
|
||||||
|
["@WIDTH"] = "keyword2",
|
||||||
|
["@WORDWRAP"] = "keyword2",
|
||||||
|
["@ENDNODE"] = "keyword2",
|
||||||
|
["@FONT"] = "keyword2",
|
||||||
|
["@HELP"] = "keyword2",
|
||||||
|
["@INDEX"] = "keyword2",
|
||||||
|
["@KEYWORDS"] = "keyword2",
|
||||||
|
["@NEXT"] = "keyword2",
|
||||||
|
["@ONCLOSE"] = "keyword2",
|
||||||
|
["@ONOPEN"] = "keyword2",
|
||||||
|
["@PREV"] = "keyword2",
|
||||||
|
["@SMARTWRAP"] = "keyword2",
|
||||||
|
["@TAB"] = "keyword2",
|
||||||
|
["@TITLE"] = "keyword2",
|
||||||
|
["@TOC"] = "keyword2",
|
||||||
|
["@WORDWRAP"] = "keyword2",
|
||||||
|
["@{AMIGAGUIDE}"] = "keyword",
|
||||||
|
["@{APEN}"] = "keyword",
|
||||||
|
["@{B}"] = "keyword",
|
||||||
|
["@{BG}"] = "keyword",
|
||||||
|
["@{BODY}"] = "keyword",
|
||||||
|
["@{BPEN}"] = "keyword",
|
||||||
|
["@{CLEARTABS}"] = "keyword",
|
||||||
|
["@{CODE}"] = "keyword",
|
||||||
|
["@{FG}"] = "keyword",
|
||||||
|
["@{I}"] = "keyword",
|
||||||
|
["@{JCENTER}"] = "keyword",
|
||||||
|
["@{JLEFT}"] = "keyword",
|
||||||
|
["@{JRIGHT}"] = "keyword",
|
||||||
|
["@{LINDENT}"] = "keyword",
|
||||||
|
["@{LINE}"] = "keyword",
|
||||||
|
["@{PAR}"] = "keyword",
|
||||||
|
["@{PARD}"] = "keyword",
|
||||||
|
["@{PARI}"] = "keyword",
|
||||||
|
["@{PLAIN}"] = "keyword",
|
||||||
|
["@{SETTABS}"] = "keyword",
|
||||||
|
["@{TAB}"] = "keyword",
|
||||||
|
["@{U}"] = "keyword",
|
||||||
|
["@{UB}"] = "keyword",
|
||||||
|
["@{UI}"] = "keyword",
|
||||||
|
["@{UU}"] = "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
syntax.add {
|
||||||
|
name = "Hollywood",
|
||||||
|
files = { "%.hws$" },
|
||||||
|
comment = ";",
|
||||||
|
block_comment = {"/*", "*/"},
|
||||||
|
patterns = {
|
||||||
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
|
{ pattern = "#[A-Z_]*", type = "keyword2" },
|
||||||
|
{ pattern = "@[A-Z_]*", type = "keyword" },
|
||||||
|
{ pattern = ";.-\n", type = "comment" },
|
||||||
|
{ pattern = { "/%*", "%*/" }, type = "comment" },
|
||||||
|
-- Respect control structures, treat as keyword not function
|
||||||
|
{ pattern = "Block[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Break[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Case[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Const[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Continue[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Default[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Dim[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "DimStr[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Do[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Else[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "EndBlock[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "EndFunction[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "EndIf[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "EndSwitch[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "FallThrough[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "False", type = "literal"},
|
||||||
|
{ pattern = "For[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Forever[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Function[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Global[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Gosub[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Goto[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "If[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "In[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Label[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Local[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Next[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Nil", type = "literal"},
|
||||||
|
{ pattern = "Repeat[ \t\n]", type = "keyword"},
|
||||||
|
{ pattern = "Return[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Step[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Switch[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Then[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "To[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "True", type = "literal"},
|
||||||
|
{ pattern = "Until[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "Wend", type = "keyword"},
|
||||||
|
{ pattern = "While[ \t]", type = "keyword"},
|
||||||
|
{ pattern = "[%a_][%w_]*[%s]*%f[(]", type = "function" },
|
||||||
|
{ pattern = "[%+%-=/\\%*%^%%<>!~|&]", type = "operator" },
|
||||||
|
{ pattern = "0[bB][%d]+", type = "number" },
|
||||||
|
{ pattern = "0[xX][%da-fA-F]+", type = "number" },
|
||||||
|
{ pattern = "$[%d]+", type = "number" },
|
||||||
|
{ pattern = "-?%d[%d_%.eE]*", type = "number" },
|
||||||
|
{ pattern = "-?%.?%d+", type = "number" }
|
||||||
|
},
|
||||||
|
symbols = {
|
||||||
|
["Block"] = "keyword",
|
||||||
|
["Break"] = "keyword",
|
||||||
|
["Case"] = "keyword",
|
||||||
|
["Const"] = "keyword",
|
||||||
|
["Continue"] = "keyword",
|
||||||
|
["Default"] = "keyword",
|
||||||
|
["Dim"] = "keyword",
|
||||||
|
["DimStr"] = "keyword",
|
||||||
|
["Do"] = "keyword",
|
||||||
|
["Else"] = "keyword",
|
||||||
|
["EndBlock"] = "keyword",
|
||||||
|
["EndFunction"] = "keyword",
|
||||||
|
["EndIf"] = "keyword",
|
||||||
|
["EndSwitch"] = "keyword",
|
||||||
|
["FallThrough"] = "keyword",
|
||||||
|
["For"] = "keyword",
|
||||||
|
["Forever"] = "keyword",
|
||||||
|
["Function"] = "keyword",
|
||||||
|
["Global"] = "keyword",
|
||||||
|
["Gosub"] = "keyword",
|
||||||
|
["Goto"] = "keyword",
|
||||||
|
["If"] = "keyword",
|
||||||
|
["In"] = "keyword",
|
||||||
|
["Label"] = "keyword",
|
||||||
|
["Local"] = "keyword",
|
||||||
|
["Next"] = "keyword",
|
||||||
|
["Repeat"] = "keyword",
|
||||||
|
["Return"] = "keyword",
|
||||||
|
["Step"] = "keyword",
|
||||||
|
["Switch"] = "keyword",
|
||||||
|
["Then"] = "keyword",
|
||||||
|
["To"] = "keyword",
|
||||||
|
["Until"] = "keyword",
|
||||||
|
["Wend"] = "keyword",
|
||||||
|
["While"] = "keyword",
|
||||||
|
["False"] = "literal",
|
||||||
|
["Nil"] = "literal",
|
||||||
|
["True"] = "literal",
|
||||||
|
["%"] = "operator",
|
||||||
|
["&"] = "operator",
|
||||||
|
["*"] = "operator",
|
||||||
|
["+"] = "operator",
|
||||||
|
["-"] = "operator",
|
||||||
|
["."] = "operator",
|
||||||
|
["// /"] = "operator",
|
||||||
|
[":"] = "operator",
|
||||||
|
["<"] = "operator",
|
||||||
|
["="] = "operator",
|
||||||
|
[">"] = "operator",
|
||||||
|
["<>"] = "operator",
|
||||||
|
["<="] = "operator",
|
||||||
|
["=>"] = "operator",
|
||||||
|
["^"] = "operator",
|
||||||
|
["|"] = "operator",
|
||||||
|
["~"] = "operator",
|
||||||
|
["\\"] = "operator",
|
||||||
|
["And"] = "operator",
|
||||||
|
["Not"] = "operator",
|
||||||
|
["Or"] = "operator"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
syntax.add {
|
||||||
|
name = "Makefile",
|
||||||
|
files = { "Makefile", "makefile", "%.mk$" },
|
||||||
|
comment = "#",
|
||||||
|
patterns = {
|
||||||
|
{ pattern = "#.*\n", type = "comment" },
|
||||||
|
{ pattern = [[\.]], type = "normal" },
|
||||||
|
{ pattern = "$[@^<%%?+|*]", type = "keyword2" },
|
||||||
|
{ pattern = "$%(.-%)", type = "symbol" },
|
||||||
|
{ pattern = "%f[%w_][%d%.]+%f[^%w_]", type = "number" },
|
||||||
|
{ pattern = "%..*:", type = "keyword2" },
|
||||||
|
{ pattern = ".*:", type = "function" },
|
||||||
|
},
|
||||||
|
symbols = {
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local syntax = require "core.syntax"
|
||||||
|
|
||||||
|
syntax.add {
|
||||||
|
name = "Shell script",
|
||||||
|
files = { "%.sh$", "%.bash$", "^%.bashrc$", "^%.bash_profile$", "^%.profile$" },
|
||||||
|
headers = "^#!.*bin.*sh\n",
|
||||||
|
comment = "#",
|
||||||
|
patterns = {
|
||||||
|
-- $# is a bash special variable and the '#' shouldn't be interpreted
|
||||||
|
-- as a comment.
|
||||||
|
{ pattern = "$[%a_@*#][%w_]*", type = "keyword2" },
|
||||||
|
-- Comments
|
||||||
|
{ pattern = "#.*\n", type = "comment" },
|
||||||
|
-- Strings
|
||||||
|
{ pattern = { '"', '"', '\\' }, type = "string" },
|
||||||
|
{ pattern = { "'", "'", '\\' }, type = "string" },
|
||||||
|
{ pattern = { '`', '`', '\\' }, type = "string" },
|
||||||
|
-- Ignore numbers that start with dots or slashes
|
||||||
|
{ pattern = "%f[%w_%.%/]%d[%d%.]*%f[^%w_%.]", type = "number" },
|
||||||
|
-- Operators
|
||||||
|
{ pattern = "[!<>|&%[%]:=*]", type = "operator" },
|
||||||
|
-- Match parameters
|
||||||
|
{ pattern = "%f[%S][%+%-][%w%-_:]+", type = "function" },
|
||||||
|
{ pattern = "%f[%S][%+%-][%w%-_]+%f[=]", type = "function" },
|
||||||
|
-- Prevent parameters with assignments from been matched as variables
|
||||||
|
{
|
||||||
|
pattern = "%s%-%a[%w_%-]*%s+()%d[%d%.]+",
|
||||||
|
type = { "function", "number" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern = "%s%-%a[%w_%-]*%s+()%a[%a%-_:=]+",
|
||||||
|
type = { "function", "symbol" }
|
||||||
|
},
|
||||||
|
-- Match variable assignments
|
||||||
|
{ pattern = "[_%a][%w_]+%f[%+=]", type = "keyword2" },
|
||||||
|
-- Match variable expansions
|
||||||
|
{ pattern = "${.-}", type = "keyword2" },
|
||||||
|
{ pattern = "$[%d$%a_@*][%w_]*", type = "keyword2" },
|
||||||
|
-- Functions
|
||||||
|
{ pattern = "[%a_%-][%w_%-]*[%s]*%f[(]", type = "function" },
|
||||||
|
-- Everything else
|
||||||
|
{ pattern = "[%a_][%w_]*", type = "symbol" },
|
||||||
|
},
|
||||||
|
symbols = {
|
||||||
|
["case"] = "keyword",
|
||||||
|
["in"] = "keyword",
|
||||||
|
["esac"] = "keyword",
|
||||||
|
["if"] = "keyword",
|
||||||
|
["then"] = "keyword",
|
||||||
|
["elif"] = "keyword",
|
||||||
|
["else"] = "keyword",
|
||||||
|
["fi"] = "keyword",
|
||||||
|
["while"] = "keyword",
|
||||||
|
["do"] = "keyword",
|
||||||
|
["done"] = "keyword",
|
||||||
|
["for"] = "keyword",
|
||||||
|
["break"] = "keyword",
|
||||||
|
["continue"] = "keyword",
|
||||||
|
["function"] = "keyword",
|
||||||
|
["local"] = "keyword",
|
||||||
|
["echo"] = "keyword",
|
||||||
|
["return"] = "keyword",
|
||||||
|
["exit"] = "keyword",
|
||||||
|
["alias"] = "keyword",
|
||||||
|
["test"] = "keyword",
|
||||||
|
["cd"] = "keyword",
|
||||||
|
["declare"] = "keyword",
|
||||||
|
["enable"] = "keyword",
|
||||||
|
["eval"] = "keyword",
|
||||||
|
["exec"] = "keyword",
|
||||||
|
["export"] = "keyword",
|
||||||
|
["getopts"] = "keyword",
|
||||||
|
["hash"] = "keyword",
|
||||||
|
["history"] = "keyword",
|
||||||
|
["help"] = "keyword",
|
||||||
|
["jobs"] = "keyword",
|
||||||
|
["kill"] = "keyword",
|
||||||
|
["let"] = "keyword",
|
||||||
|
["mapfile"] = "keyword",
|
||||||
|
["printf"] = "keyword",
|
||||||
|
["read"] = "keyword",
|
||||||
|
["readarray"] = "keyword",
|
||||||
|
["pwd"] = "keyword",
|
||||||
|
["select"] = "keyword",
|
||||||
|
["set"] = "keyword",
|
||||||
|
["shift"] = "keyword",
|
||||||
|
["source"] = "keyword",
|
||||||
|
["time"] = "keyword",
|
||||||
|
["type"] = "keyword",
|
||||||
|
["until"] = "keyword",
|
||||||
|
["unalias"] = "keyword",
|
||||||
|
["unset"] = "keyword",
|
||||||
|
["true"] = "literal",
|
||||||
|
["false"] = "literal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
-- 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 = common.merge({ map = {
|
||||||
|
["{%s*\n"] = "}",
|
||||||
|
["%(%s*\n"] = ")",
|
||||||
|
["%f[[]%[%s*\n"] = "]",
|
||||||
|
["=%s*\n"] = false,
|
||||||
|
[":%s*\n"] = false,
|
||||||
|
["->%s*\n"] = false,
|
||||||
|
["^%s*<([^/!][^%s>]*)[^>]*>%s*\n"] = "</$TEXT>",
|
||||||
|
["^%s*{{#([^/][^%s}]*)[^}]*}}%s*\n"] = "{{/$TEXT}}",
|
||||||
|
["/%*%s*\n"] = "*/",
|
||||||
|
["c/c++"] = {
|
||||||
|
file_patterns = {
|
||||||
|
"%.c$", "%.h$", "%.inl$", "%.cpp$", "%.hpp$",
|
||||||
|
"%.cc$", "%.C$", "%.cxx$", "%.c++$", "%.hh$",
|
||||||
|
"%.H$", "%.hxx$", "%.h++$"
|
||||||
|
},
|
||||||
|
map = {
|
||||||
|
["^#if.*\n"] = "#endif",
|
||||||
|
["^#else.*\n"] = "#endif",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["lua"] = {
|
||||||
|
file_patterns = { "%.lua$", "%.nelua$" },
|
||||||
|
map = {
|
||||||
|
["%f[%w]do%s*\n"] = "end",
|
||||||
|
["%f[%w]then%s*\n"] = "end",
|
||||||
|
["%f[%w]else%s*\n"] = "end",
|
||||||
|
["%f[%w]repeat%s*\n"] = "until",
|
||||||
|
["%f[%w]function.*%)%s*\n"] = "end",
|
||||||
|
["%[%[%s*\n"] = "]]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} }, config.plugins.lfautoinsert)
|
||||||
|
|
||||||
|
local function get_autoinsert_map(filename)
|
||||||
|
local map = {}
|
||||||
|
if not filename then return map end
|
||||||
|
for pattern, closing in pairs(config.plugins.lfautoinsert.map) do
|
||||||
|
if type(closing) == "table" then
|
||||||
|
if common.match_pattern(filename, closing.file_patterns) then
|
||||||
|
for p, e in pairs(closing.map) do
|
||||||
|
map[p] = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
map[pattern] = closing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return map
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function indent_size(doc, line)
|
||||||
|
local text = doc.lines[line] or ""
|
||||||
|
local s, e = text:find("^[\t ]*")
|
||||||
|
return e - s
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add("core.docview!", {
|
||||||
|
["autoinsert:newline"] = function(dv)
|
||||||
|
command.perform("doc:newline")
|
||||||
|
|
||||||
|
local doc = dv.doc
|
||||||
|
local line, col = doc:get_selection()
|
||||||
|
local text = doc.lines[line - 1]
|
||||||
|
|
||||||
|
for ptn, close in pairs(get_autoinsert_map(doc.filename)) do
|
||||||
|
local s, _, str = text:find(ptn)
|
||||||
|
if s then
|
||||||
|
if close
|
||||||
|
and col == #doc.lines[line]
|
||||||
|
and indent_size(doc, line + 1) <= indent_size(doc, line - 1)
|
||||||
|
then
|
||||||
|
close = str and close:gsub("$TEXT", str) or close
|
||||||
|
command.perform("doc:newline")
|
||||||
|
core.active_view:on_text_input(close)
|
||||||
|
command.perform("doc:move-to-previous-line")
|
||||||
|
if doc.lines[line+1] == doc.lines[line+2] then
|
||||||
|
doc:remove(line+1, 1, line+2, 1)
|
||||||
|
end
|
||||||
|
elseif col < #doc.lines[line] then
|
||||||
|
command.perform("doc:newline")
|
||||||
|
command.perform("doc:move-to-previous-line")
|
||||||
|
end
|
||||||
|
command.perform("doc:indent")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["return"] = { "autoinsert:newline" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
add = function(file_patterns, map)
|
||||||
|
table.insert(
|
||||||
|
config.plugins.lfautoinsert.map,
|
||||||
|
{ file_patterns = file_patterns, map=map }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
-- mod-version:3
|
||||||
|
-- Markers plugin for lite text editor
|
||||||
|
-- original implementation by Petri Häkkinen
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local style = require "core.style"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
|
||||||
|
local cache = {} -- this table contains subtables for each document, each subtable is a set of line numbers
|
||||||
|
setmetatable(cache, {
|
||||||
|
__mode = "k",
|
||||||
|
__index = function(t, k)
|
||||||
|
t[k] = {}
|
||||||
|
return t[k]
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
local function shift_lines(doc, at, diff)
|
||||||
|
if diff == 0 then return end
|
||||||
|
local t = {}
|
||||||
|
for line in pairs(cache[doc]) do
|
||||||
|
line = line >= at and line + diff or line
|
||||||
|
t[line] = true
|
||||||
|
end
|
||||||
|
cache[doc] = t
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local raw_insert = Doc.raw_insert
|
||||||
|
|
||||||
|
function Doc:raw_insert(line, col, text, ...)
|
||||||
|
raw_insert(self, line, col, text, ...)
|
||||||
|
local line_count = 0
|
||||||
|
for _ in text:gmatch("\n") do
|
||||||
|
line_count = line_count + 1
|
||||||
|
end
|
||||||
|
shift_lines(self, line, line_count)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local raw_remove = Doc.raw_remove
|
||||||
|
|
||||||
|
function Doc:raw_remove(line1, col1, line2, col2, ...)
|
||||||
|
raw_remove(self, line1, col1, line2, col2, ...)
|
||||||
|
shift_lines(self, line2, line1 - line2)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_gutter = DocView.draw_line_gutter
|
||||||
|
|
||||||
|
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
|
||||||
|
return draw_line_gutter(self, line, x, y, width)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add("core.docview!", {
|
||||||
|
["markers:toggle-marker"] = function(dv)
|
||||||
|
local doc = dv.doc
|
||||||
|
local line = doc:get_selection()
|
||||||
|
local markers = cache[doc]
|
||||||
|
|
||||||
|
if markers[line] then
|
||||||
|
markers[line] = nil
|
||||||
|
else
|
||||||
|
markers[line] = true
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
["markers:go-to-next-marker"] = function(dv)
|
||||||
|
local doc = dv.doc
|
||||||
|
local line = doc:get_selection()
|
||||||
|
local markers = cache[doc]
|
||||||
|
|
||||||
|
local first_marker = math.huge
|
||||||
|
local next_marker = math.huge
|
||||||
|
for l, _ in pairs(markers) do
|
||||||
|
if l > line and l < next_marker then
|
||||||
|
next_marker = l
|
||||||
|
end
|
||||||
|
first_marker = math.min(first_marker, l)
|
||||||
|
end
|
||||||
|
if next_marker == math.huge then
|
||||||
|
next_marker = first_marker
|
||||||
|
end
|
||||||
|
if next_marker ~= math.huge then
|
||||||
|
doc:set_selection(next_marker, 1)
|
||||||
|
core.active_view:scroll_to_line(next_marker, true)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["ctrl+f2"] = "markers:toggle-marker",
|
||||||
|
["f2"] = "markers:go-to-next-marker",
|
||||||
|
}
|
|
@ -0,0 +1,666 @@
|
||||||
|
-- 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 = 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_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()
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
function MiniMap:on_mouse_moved(x, y, dx, dy)
|
||||||
|
local percent = MiniMap.super.on_mouse_moved(self, x, y, dx, dy)
|
||||||
|
if not self:is_minimap_enabled() or type(percent) ~= "number" then return percent end
|
||||||
|
local _, visible_lines_count, minimap_lines_start, minimap_lines_count, is_file_too_large = self:get_minimap_dimensions()
|
||||||
|
local lh = self.dv:get_line_height()
|
||||||
|
local _, _, w, h = self:get_track_rect()
|
||||||
|
local tx, ty, tw, th = self:get_thumb_rect()
|
||||||
|
if x >= tx and x < tx + tw and y >= ty and y < ty + th then self.hovering.thumb = true end
|
||||||
|
if not self.hovering.thumb then return self.percent end
|
||||||
|
y = y - self.drag_start_offset
|
||||||
|
percent = math.max(0.0, math.min((y - self.dv.position.y) / h, 1.0))
|
||||||
|
return (((percent * minimap_lines_count) + minimap_lines_start) * lh / self.dv:get_scrollable_size()) - (visible_lines_count / #self.dv.doc.lines / 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MiniMap:draw_thumb()
|
||||||
|
local color = self.hovering.thumb and style.scrollbar2 or style.scrollbar
|
||||||
|
local x, y, w, h = self:get_thumb_rect()
|
||||||
|
renderer.draw_rect(x, y, w, h, color)
|
||||||
|
end
|
||||||
|
|
||||||
|
function MiniMap:draw()
|
||||||
|
if not self:is_minimap_enabled() then return MiniMap.super.draw(self) end
|
||||||
|
local dv = self.dv
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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 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
|
||||||
|
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(dv)
|
||||||
|
local sb = dv.v_scrollbar
|
||||||
|
if sb.enabled ~= nil then
|
||||||
|
sb.enabled = not sb.enabled
|
||||||
|
else
|
||||||
|
sb.enabled = not config.plugins.minimap.enabled
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
return MiniMap
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
-- mod-version:3
|
||||||
|
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
local navigate = {
|
||||||
|
list = {},
|
||||||
|
current = nil,
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Private functions
|
||||||
|
--
|
||||||
|
local function get_active_view()
|
||||||
|
if getmetatable(core.active_view) == DocView then
|
||||||
|
return core.active_view
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Solution to safely remove elements from array table:
|
||||||
|
-- found at https://stackoverflow.com/a/53038524
|
||||||
|
local function array_remove(t, fnKeep)
|
||||||
|
local j, n = 1, #t;
|
||||||
|
|
||||||
|
for i=1, n do
|
||||||
|
if (fnKeep(t, i, j)) then
|
||||||
|
if (i ~= j) then
|
||||||
|
t[j] = t[i];
|
||||||
|
t[i] = nil;
|
||||||
|
end
|
||||||
|
j = j + 1;
|
||||||
|
else
|
||||||
|
t[i] = nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add(doc)
|
||||||
|
-- Make new navigation point last in list
|
||||||
|
if navigate.index > 0 and navigate.index < #navigate.list then
|
||||||
|
local remove_start = navigate.index + 1
|
||||||
|
local remove_end = #navigate.list
|
||||||
|
array_remove(navigate.list, function(_, i)
|
||||||
|
if i >= remove_start and i <= remove_end then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local line, col = doc:get_selection()
|
||||||
|
table.insert(navigate.list, {
|
||||||
|
filename = doc.filename,
|
||||||
|
line = line,
|
||||||
|
col = col
|
||||||
|
})
|
||||||
|
|
||||||
|
navigate.current = navigate.list[#navigate.list]
|
||||||
|
navigate.index = #navigate.list
|
||||||
|
end
|
||||||
|
|
||||||
|
local function open_doc(doc)
|
||||||
|
core.root_view:open_doc(
|
||||||
|
core.open_doc(
|
||||||
|
common.home_expand(
|
||||||
|
doc.filename
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
local av_doc = get_active_view().doc
|
||||||
|
local line, col = av_doc:get_selection()
|
||||||
|
if doc.line ~= line or doc.col ~= col then
|
||||||
|
av_doc:set_selection(doc.line, doc.col, doc.line, doc.col)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Public functions
|
||||||
|
--
|
||||||
|
function navigate.next()
|
||||||
|
if navigate.index < #navigate.list then
|
||||||
|
navigate.index = navigate.index + 1
|
||||||
|
navigate.current = navigate.list[navigate.index]
|
||||||
|
open_doc(navigate.current)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function navigate.prev()
|
||||||
|
if navigate.index > 1 then
|
||||||
|
navigate.index = navigate.index - 1
|
||||||
|
navigate.current = navigate.list[navigate.index]
|
||||||
|
open_doc(navigate.current)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Thread
|
||||||
|
--
|
||||||
|
core.add_thread(function()
|
||||||
|
while true do
|
||||||
|
local av = get_active_view()
|
||||||
|
if av and av.doc and av.doc.filename then
|
||||||
|
local doc = av.doc
|
||||||
|
local line, col = doc:get_selection()
|
||||||
|
local current = navigate.current
|
||||||
|
if
|
||||||
|
not current
|
||||||
|
or
|
||||||
|
current.filename ~= doc.filename
|
||||||
|
or
|
||||||
|
current.line ~= line
|
||||||
|
then
|
||||||
|
add(doc)
|
||||||
|
else
|
||||||
|
current.col = col
|
||||||
|
end
|
||||||
|
end
|
||||||
|
coroutine.yield(0.5)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Patching
|
||||||
|
--
|
||||||
|
local doc_on_close = Doc.on_close
|
||||||
|
|
||||||
|
function Doc:on_close()
|
||||||
|
local filename = self.filename
|
||||||
|
-- remove all positions referencing closed file
|
||||||
|
array_remove(navigate.list, function(t, i)
|
||||||
|
if t[i].filename == filename then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
doc_on_close(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Commands
|
||||||
|
--
|
||||||
|
command.add("core.docview", {
|
||||||
|
["navigate:previous"] = function()
|
||||||
|
navigate.prev()
|
||||||
|
end,
|
||||||
|
|
||||||
|
["navigate:next"] = function()
|
||||||
|
navigate.next()
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Default Keybindings
|
||||||
|
--
|
||||||
|
keymap.add {
|
||||||
|
["alt+left"] = "navigate:previous",
|
||||||
|
["alt+right"] = "navigate:next",
|
||||||
|
}
|
||||||
|
|
||||||
|
return navigate
|
|
@ -0,0 +1,181 @@
|
||||||
|
-- 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
|
||||||
|
[".cpp"] = { "#519aba", "" },
|
||||||
|
[".c"] = { "#599eff", "" },
|
||||||
|
[".h"] = { "#599eff", "" },
|
||||||
|
[".py"] = { "#3572A5", "" }, -- Python
|
||||||
|
[".pyc"] = { "#519aba", "" }, [".pyd"] = { "#519aba", "" },
|
||||||
|
[".php"] = { "#a074c4", "" },
|
||||||
|
[".cs"] = { "#596706", "" }, -- C#
|
||||||
|
[".conf"] = { "#6d8086", "" }, [".cfg"] = { "#6d8086", "" },
|
||||||
|
[".toml"] = { "#6d8086", "" },
|
||||||
|
[".yaml"] = { "#6d8086", "" }, [".yml"] = { "#6d8086", "" },
|
||||||
|
[".json"] = { "#854CC7", "" },
|
||||||
|
[".css"] = { "#563d7c", "" },
|
||||||
|
[".html"] = { "#e34c26", "" },
|
||||||
|
[".js"] = { "#cbcb41", "" }, -- JavaScript
|
||||||
|
[".go"] = { "#519aba", "" },
|
||||||
|
[".jpg"] = { "#a074c4", "" }, [".png"] = { "#a074c4", "" },
|
||||||
|
[".sh"] = { "#4d5a5e", "" }, -- Shell
|
||||||
|
[".java"] = { "#cc3e44", "" },
|
||||||
|
[".scala"] = { "#cc3e44", "" },
|
||||||
|
[".kt"] = { "#F88A02", "" }, -- Kotlin
|
||||||
|
[".pl"] = { "#519aba", "" }, -- Perl
|
||||||
|
[".r"] = { "#358a5b", "" },
|
||||||
|
[".rake"] = { "#701516", "" },
|
||||||
|
[".rb"] = { "#701516", "" }, -- Ruby
|
||||||
|
[".rs"] = { "#dea584", "" }, -- Rust
|
||||||
|
[".rss"] = { "#cc3e44", "" },
|
||||||
|
[".sql"] = { "#dad8d8", "" },
|
||||||
|
[".swift"] = { "#e37933", "" },
|
||||||
|
[".ts"] = { "#519aba", "" }, -- TypeScript
|
||||||
|
[".elm"] = { "#519aba", "" },
|
||||||
|
[".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", "" },
|
||||||
|
["license"] = { "#d0bf41", "" },
|
||||||
|
["cmakelists.txt"] = { "#6d8086", "" },
|
||||||
|
["readme.md"] = { "#72b886", "" }, ["readme"] = { "#72b886", "" },
|
||||||
|
["init.lua"] = { "#2d6496", "" },
|
||||||
|
["setup.py"] = { "#559dd9", "" },
|
||||||
|
["build.zig"] = { "#6d8086", "" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Preparing colors
|
||||||
|
for k, v in pairs(extension_icons) do
|
||||||
|
v[1] = { common.color(v[1]) }
|
||||||
|
end
|
||||||
|
for k, v in pairs(known_names_icons) do
|
||||||
|
v[1] = { common.color(v[1]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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 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
|
||||||
|
return chevron_width + style.padding.x/4
|
||||||
|
end
|
||||||
|
return TreeView_draw_item_chevron(self, item, active, hovered, x, y, w, h)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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
|
|
@ -0,0 +1,72 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
local RootView = require "core.rootview"
|
||||||
|
|
||||||
|
local opacity_on = true
|
||||||
|
local use_mousewheel = true
|
||||||
|
local opacity_steps = 0.05
|
||||||
|
local default_opacity = 1
|
||||||
|
local current_opacity = default_opacity
|
||||||
|
|
||||||
|
local function set_opacity(opacity)
|
||||||
|
if not opacity_on then return end
|
||||||
|
current_opacity = common.clamp(opacity, 0.2, 1)
|
||||||
|
system.set_window_opacity(current_opacity)
|
||||||
|
end
|
||||||
|
|
||||||
|
local on_mouse_wheel = RootView.on_mouse_wheel
|
||||||
|
|
||||||
|
function RootView:on_mouse_wheel(d, ...)
|
||||||
|
if keymap.modkeys["cmd"] and use_mousewheel then
|
||||||
|
if d < 0 then command.perform "opacity:decrease" end
|
||||||
|
if d > 0 then command.perform "opacity:increase" end
|
||||||
|
else
|
||||||
|
return on_mouse_wheel(self, d, ...)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
|
||||||
|
local function res_opacity()
|
||||||
|
set_opacity(default_opacity)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function inc_opacity()
|
||||||
|
set_opacity(current_opacity + opacity_steps)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dec_opacity()
|
||||||
|
set_opacity(current_opacity - opacity_steps)
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["opacity:toggle" ] = function() tog_opacity() end,
|
||||||
|
["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
|
||||||
|
if use_mousewheel then
|
||||||
|
core.log("Opacity (shift + mouse wheel): on")
|
||||||
|
else
|
||||||
|
core.log("Opacity (shift + mouse wheel): off")
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["shift+f11"] = "opacity:toggle",
|
||||||
|
["ctrl+f11"] = "opacity:toggle mouse wheel use",
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local common = require "core.common"
|
||||||
|
local command = require "core.command"
|
||||||
|
local config = require "core.config"
|
||||||
|
|
||||||
|
local platform_filemanager
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
platform_filemanager = "explorer"
|
||||||
|
elseif PLATFORM == "Mac OS X" then
|
||||||
|
platform_filemanager = "open"
|
||||||
|
else
|
||||||
|
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(dv)
|
||||||
|
local doc = dv.doc
|
||||||
|
if not doc.filename then
|
||||||
|
core.error "Cannot open location of unsaved doc"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local folder = doc.filename:match("^(.*)[/\\].*$") or "."
|
||||||
|
core.log("Opening \"%s\"", folder)
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
system.exec(string.format("%s %s", config.plugins.openfilelocation.filemanager, folder))
|
||||||
|
else
|
||||||
|
system.exec(string.format("%s %q", config.plugins.openfilelocation.filemanager, folder))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
|
@ -0,0 +1,110 @@
|
||||||
|
-- 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 extract_subsyntaxes = tokenizer.extract_subsyntaxes
|
||||||
|
local closers = {
|
||||||
|
["("] = ")",
|
||||||
|
["["] = "]",
|
||||||
|
["{"] = "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
local function parenstyle(parenstack)
|
||||||
|
return "paren" .. ((#parenstack % config.plugins.rainbowparen.parens) + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function tokenizer.extract_subsyntaxes(base_syntax, state)
|
||||||
|
if not config.plugins.rainbowparen.enabled then
|
||||||
|
return extract_subsyntaxes(base_syntax, state)
|
||||||
|
end
|
||||||
|
return extract_subsyntaxes(base_syntax, state.istate)
|
||||||
|
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 ""
|
||||||
|
local newres = {}
|
||||||
|
-- split parens out
|
||||||
|
-- the stock tokenizer can't do this because it merges identical adjacent tokens
|
||||||
|
for i, type, text in tokenizer.each_token(res) do
|
||||||
|
if type == "normal" or type == "symbol" then
|
||||||
|
for normtext1, paren, normtext2 in text:gmatch("([^%(%[{}%]%)]*)([%(%[{}%]%)]?)([^%(%[{}%]%)]*)") do
|
||||||
|
if #normtext1 > 0 then
|
||||||
|
table.insert(newres, type)
|
||||||
|
table.insert(newres, normtext1)
|
||||||
|
end
|
||||||
|
if #paren > 0 then
|
||||||
|
if paren == parenstack:sub(-1) then -- expected closer
|
||||||
|
parenstack = parenstack:sub(1, -2)
|
||||||
|
table.insert(newres, parenstyle(parenstack))
|
||||||
|
elseif closers[paren] then -- opener
|
||||||
|
table.insert(newres, parenstyle(parenstack))
|
||||||
|
parenstack = parenstack .. closers[paren]
|
||||||
|
else -- unexpected closer
|
||||||
|
table.insert(newres, "paren_unbalanced")
|
||||||
|
end
|
||||||
|
table.insert(newres, paren)
|
||||||
|
end
|
||||||
|
if #normtext2 > 0 then
|
||||||
|
table.insert(newres, type)
|
||||||
|
table.insert(newres, normtext2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(newres, type)
|
||||||
|
table.insert(newres, text)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return newres, { parenstack = parenstack, istate = istate }
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
|
@ -0,0 +1,55 @@
|
||||||
|
-- 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"
|
||||||
|
local RootView = require "core.rootview"
|
||||||
|
local command = require "core.command"
|
||||||
|
local keymap = require "core.keymap"
|
||||||
|
|
||||||
|
local update = RootView.update
|
||||||
|
local initialized_tab_system = false
|
||||||
|
|
||||||
|
local tab_history = { }
|
||||||
|
local history_size = 10
|
||||||
|
|
||||||
|
RootView.update = function(self)
|
||||||
|
update(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
|
||||||
|
for i,filename in ipairs(tab_history) do
|
||||||
|
if filename == closing_filename then
|
||||||
|
table.remove(tab_history, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(tab_history, closing_filename)
|
||||||
|
if #tab_history > history_size then
|
||||||
|
table.remove(tab_history, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
old_close(self, root, view)
|
||||||
|
end
|
||||||
|
|
||||||
|
initialized_tab_system = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["restore-tabs:restore-tab"] = function()
|
||||||
|
if #tab_history > 0 then
|
||||||
|
local file = tab_history[#tab_history]
|
||||||
|
core.root_view:open_doc(core.open_doc(file))
|
||||||
|
table.remove(tab_history)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
keymap.add {
|
||||||
|
["ctrl+shift+t"] = "restore-tabs:restore-tab"
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
local style = require "core.style"
|
||||||
|
local CommandView = require "core.commandview"
|
||||||
|
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
local PATH_CONFIG = USERDIR .. "/color_settings.lua"
|
||||||
|
|
||||||
|
local Settings = {}
|
||||||
|
Settings.color_scheme = ""
|
||||||
|
Settings.color_list = {}
|
||||||
|
local color_default = {name = "Default", module = "core.style"}
|
||||||
|
local plugin_enable = false
|
||||||
|
|
||||||
|
-- =========================Proxy method==========================
|
||||||
|
local move_suggestion_idx = CommandView.move_suggestion_idx
|
||||||
|
|
||||||
|
function CommandView:move_suggestion_idx(dir)
|
||||||
|
move_suggestion_idx(self, dir)
|
||||||
|
if plugin_enable then
|
||||||
|
local color_name = self.suggestions[self.suggestion_idx].text
|
||||||
|
Settings:change_color(color_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local on_quit_project = core.on_quit_project
|
||||||
|
|
||||||
|
function core.on_quit_project()
|
||||||
|
Settings:save_settings()
|
||||||
|
on_quit_project()
|
||||||
|
end
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
|
||||||
|
function Settings:get_color_list()
|
||||||
|
return self.color_list
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:init()
|
||||||
|
self:load_settings()
|
||||||
|
self:make_color_list()
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:make_color_list()
|
||||||
|
for _, root_dir in ipairs {DATADIR, USERDIR} do
|
||||||
|
local plugin_dir = root_dir .. "/colors"
|
||||||
|
for _, filename in ipairs(system.list_dir(plugin_dir) or {}) do
|
||||||
|
table.insert(self.color_list, filename:match("(.-)%.lua$"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(self.color_list, color_default.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:is_change_color(color_name)
|
||||||
|
return not (self.color_scheme == color_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:get_color_scheme()
|
||||||
|
return (self.color_scheme == "") and color_default.name or self.color_scheme
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_color_module_name(name)
|
||||||
|
return (name == color_default.name) and color_default.module or "colors."..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
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:save_settings()
|
||||||
|
local fp = io.open(PATH_CONFIG, "w")
|
||||||
|
if fp then
|
||||||
|
fp:write(self.color_scheme)
|
||||||
|
fp:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Settings:load_settings()
|
||||||
|
local fp = io.open(PATH_CONFIG, "r")
|
||||||
|
if fp then
|
||||||
|
local name = fp:read("*a")
|
||||||
|
if name and name ~= "" then
|
||||||
|
core.reload_module(make_color_module_name(name))
|
||||||
|
Settings.color_scheme = name
|
||||||
|
end
|
||||||
|
fp:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- -------------------------------Utility--------------------------
|
||||||
|
local function table_remove_value(list, value)
|
||||||
|
for i=1, #list do
|
||||||
|
if list[i] == value then
|
||||||
|
table.remove(list, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
local function normalize_color_list(list)
|
||||||
|
table_remove_value(list, Settings:get_color_scheme())
|
||||||
|
table.sort(list, function(a, b) return string.lower(a) > string.lower(b) end)
|
||||||
|
return {Settings:get_color_scheme(), table.unpack(list)}
|
||||||
|
end
|
||||||
|
-- =========================Add Commands==========================
|
||||||
|
local color_scheme_submit = function(text, item)
|
||||||
|
if item then
|
||||||
|
Settings:change_color(item.text)
|
||||||
|
plugin_enable = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local color_scheme_suggest = function(text)
|
||||||
|
plugin_enable = true
|
||||||
|
local res_list = common.fuzzy_match(Settings:get_color_list(), text)
|
||||||
|
return normalize_color_list(res_list)
|
||||||
|
end
|
||||||
|
|
||||||
|
command.add(nil, {
|
||||||
|
["ui:color scheme"] = function()
|
||||||
|
core.command_view:enter("Select color scheme", {
|
||||||
|
submit = color_scheme_submit, suggest = color_scheme_suggest
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
-- ----------------------------------------------------------------
|
||||||
|
|
||||||
|
Settings:init()
|
|
@ -0,0 +1,58 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local style = require "core.style"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
|
||||||
|
-- originally written by luveti
|
||||||
|
|
||||||
|
-- Workaround for bug in Lite XL 2.1
|
||||||
|
-- Remove this when b029f5993edb7dee5ccd2ba55faac1ec22e24609 is in a release
|
||||||
|
local function get_selection(doc, sort)
|
||||||
|
local line1, col1, line2, col2 = doc:get_selection_idx(doc.last_selection)
|
||||||
|
if line1 then
|
||||||
|
return doc:get_selection_idx(doc.last_selection, sort)
|
||||||
|
else
|
||||||
|
return doc:get_selection_idx(1, sort)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function draw_box(x, y, w, h, color)
|
||||||
|
local r = renderer.draw_rect
|
||||||
|
local s = math.ceil(SCALE)
|
||||||
|
r(x, y, w, s, color)
|
||||||
|
r(x, y + h - s, w, s, color)
|
||||||
|
r(x, y + s, s, h - s * 2, color)
|
||||||
|
r(x + w - s, y + s, s, h - s * 2, color)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_body = DocView.draw_line_body
|
||||||
|
|
||||||
|
function DocView:draw_line_body(line, x, y)
|
||||||
|
local line_height = draw_line_body(self, line, x, y)
|
||||||
|
local line1, col1, line2, col2 = get_selection(self.doc, 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[line]
|
||||||
|
local last_col = 1
|
||||||
|
while true do
|
||||||
|
local start_col, end_col = current_line_text:find(
|
||||||
|
selected_text, last_col, true
|
||||||
|
)
|
||||||
|
if start_col == nil then break end
|
||||||
|
-- don't draw box around the selection
|
||||||
|
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
|
||||||
|
last_col = end_col + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return line_height
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local config = require "core.config"
|
||||||
|
local common = require "core.common"
|
||||||
|
local style = require "core.style"
|
||||||
|
local StatusView = require "core.statusview"
|
||||||
|
|
||||||
|
config.plugins.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)
|
||||||
|
|
||||||
|
local time = ""
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
})
|
|
@ -0,0 +1,331 @@
|
||||||
|
-- mod-version:3
|
||||||
|
local core = require "core"
|
||||||
|
local style = require "core.style"
|
||||||
|
local config = require "core.config"
|
||||||
|
local command = require "core.command"
|
||||||
|
local common = require "core.common"
|
||||||
|
local DocView = require "core.docview"
|
||||||
|
local Highlighter = require "core.doc.highlighter"
|
||||||
|
local Doc = require "core.doc"
|
||||||
|
|
||||||
|
local platform_dictionary_file
|
||||||
|
if PLATFORM == "Windows" then
|
||||||
|
platform_dictionary_file = EXEDIR .. "/words.txt"
|
||||||
|
elseif PLATFORM == "AmigaOS 4" then
|
||||||
|
platform_dictionary_file = "PROGDIR:words.txt"
|
||||||
|
else
|
||||||
|
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
|
||||||
|
|
||||||
|
local spell_cache = setmetatable({}, { __mode = "k" })
|
||||||
|
local font_canary
|
||||||
|
local font_size_canary
|
||||||
|
|
||||||
|
|
||||||
|
-- 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 spell_cache[self] then
|
||||||
|
spell_cache[self] = {}
|
||||||
|
end
|
||||||
|
for i = 1, n do
|
||||||
|
blanks[i] = false
|
||||||
|
end
|
||||||
|
common.splice(spell_cache[self], line, 0, blanks)
|
||||||
|
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 spell_cache[self] then
|
||||||
|
spell_cache[self] = {}
|
||||||
|
end
|
||||||
|
common.splice(spell_cache[self], line, n)
|
||||||
|
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 spell_cache[self] then
|
||||||
|
spell_cache[self] = {}
|
||||||
|
end
|
||||||
|
spell_cache[self][idx] = false
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reset_cache()
|
||||||
|
for i=1,#spell_cache do
|
||||||
|
local cache = spell_cache[i]
|
||||||
|
for j=1,#cache do
|
||||||
|
cache[j] = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
for _, ptn in ipairs(ptns) do
|
||||||
|
if filename:find(ptn) then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function active_word(doc, line, tail)
|
||||||
|
local l, c = doc:get_selection()
|
||||||
|
return l == line and c == tail
|
||||||
|
and doc == core.active_view.doc
|
||||||
|
and system.get_time() - last_input_time < 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local text_input = Doc.text_input
|
||||||
|
function Doc:text_input(...)
|
||||||
|
text_input(self, ...)
|
||||||
|
last_input_time = system.get_time()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function compare_arrays(a, b)
|
||||||
|
if b == a then return true end
|
||||||
|
if not a or not b then return false end
|
||||||
|
if #b ~= #a then return false end
|
||||||
|
for i=1,#a do
|
||||||
|
if b[i] ~= a[i] then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local draw_line_text = DocView.draw_line_text
|
||||||
|
function DocView:draw_line_text(idx, x, y)
|
||||||
|
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
|
||||||
|
|
||||||
|
if font_canary ~= style.code_font
|
||||||
|
or font_size_canary ~= style.code_font:get_size()
|
||||||
|
or not compare_arrays(self.wrapped_lines, self.old_wrapped_lines)
|
||||||
|
then
|
||||||
|
spell_cache[self.doc.highlighter] = {}
|
||||||
|
font_canary = style.code_font
|
||||||
|
font_size_canary = style.code_font:get_size()
|
||||||
|
self.old_wrapped_lines = self.wrapped_lines
|
||||||
|
reset_cache()
|
||||||
|
end
|
||||||
|
if not spell_cache[self.doc.highlighter][idx] then
|
||||||
|
local calculated = {}
|
||||||
|
local s, e = 0, 0
|
||||||
|
local text = self.doc.lines[idx]
|
||||||
|
|
||||||
|
while true do
|
||||||
|
s, e = text:find(word_pattern, e + 1)
|
||||||
|
if not s then break end
|
||||||
|
local word = text:sub(s, e):lower()
|
||||||
|
if not words[word] and not active_word(self.doc, idx, e + 1) then
|
||||||
|
local x,y = self:get_line_screen_position(idx, s)
|
||||||
|
table.insert(calculated, x + self.scroll.x)
|
||||||
|
table.insert(calculated, y + self.scroll.y)
|
||||||
|
x,y = self:get_line_screen_position(idx, e + 1)
|
||||||
|
table.insert(calculated, x + self.scroll.x)
|
||||||
|
table.insert(calculated, y + self.scroll.y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
spell_cache[self.doc.highlighter][idx] = calculated
|
||||||
|
end
|
||||||
|
|
||||||
|
local color = style.spellcheck_error or style.syntax.keyword2
|
||||||
|
local h = math.ceil(1 * SCALE)
|
||||||
|
local slh = self:get_line_height()
|
||||||
|
local calculated = spell_cache[self.doc.highlighter][idx]
|
||||||
|
for i=1,#calculated,4 do
|
||||||
|
local x1, y1, x2, y2 = calculated[i], calculated[i+1], calculated[i+2], calculated[i+3]
|
||||||
|
renderer.draw_rect(x1 - self.scroll.x, y1 + slh - self.scroll.y, x2 - x1, h, color)
|
||||||
|
end
|
||||||
|
return lh
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_word_at_caret()
|
||||||
|
local doc = core.active_view.doc
|
||||||
|
local l, c = doc:get_selection()
|
||||||
|
local s, e = 0, 0
|
||||||
|
local text = doc.lines[l]
|
||||||
|
while true do
|
||||||
|
s, e = text:find(word_pattern, e + 1)
|
||||||
|
if c >= s and c <= e + 1 then
|
||||||
|
return text:sub(s, e):lower(), s, e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function compare_words(word1, word2)
|
||||||
|
local res = 0
|
||||||
|
for i = 1, math.max(#word1, #word2) do
|
||||||
|
if word1:byte(i) ~= word2:byte(i) then
|
||||||
|
res = res + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res
|
||||||
|
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
|
||||||
|
core.error("\"%s\" already exists in the dictionary", word)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if word then
|
||||||
|
local fp = assert(io.open(config.plugins.spellcheck.dictionary_file, "a"))
|
||||||
|
fp:write("\n" .. word .. "\n")
|
||||||
|
fp:close()
|
||||||
|
words[word] = true
|
||||||
|
core.log("Added \"%s\" to dictionary", word)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
["spell-check:replace"] = function(dv)
|
||||||
|
local word, s, e = get_word_at_caret()
|
||||||
|
|
||||||
|
-- find suggestions
|
||||||
|
local suggestions = {}
|
||||||
|
local word_len = #word
|
||||||
|
for w in pairs(words) do
|
||||||
|
if math.abs(#w - word_len) <= 2 then
|
||||||
|
local diff = compare_words(word, w)
|
||||||
|
if diff < word_len * 0.5 then
|
||||||
|
table.insert(suggestions, { diff = diff, text = w })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #suggestions == 0 then
|
||||||
|
core.error("Could not find any suggestions for \"%s\"", word)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- sort suggestions table and convert to properly-capitalized text
|
||||||
|
table.sort(suggestions, function(a, b) return a.diff < b.diff end)
|
||||||
|
local doc = dv.doc
|
||||||
|
local line = doc:get_selection()
|
||||||
|
local has_upper = doc.lines[line]:sub(s, s):match("[A-Z]")
|
||||||
|
for k, v in pairs(suggestions) do
|
||||||
|
if has_upper then
|
||||||
|
v.text = v.text:gsub("^.", string.upper)
|
||||||
|
end
|
||||||
|
suggestions[k] = v.text
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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, {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
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" }
|
||||||
|
})
|
Loading…
Reference in New Issue