Merge branch 'master' into master-2.1

This commit is contained in:
jgmdev 2022-05-22 22:26:47 -04:00
commit b9bb64a2f0
15 changed files with 223 additions and 140 deletions

View File

@ -6,15 +6,44 @@ command.map = {}
local always_true = function() return true end
function command.add(predicate, map)
---Used iternally by command.add, statusview, and contextmenu to generate a
---function with a condition to evaluate returning the boolean result of this
---evaluation.
---
---If a string predicate is given it is treated as a require import that should
---return a valid object which is checked against the current active view,
---eg: "core.docview" will match any view that inherits from DocView. Appending
---a `!` at the end of the string means we want to match the given object
---from the import strcitly eg: "core.docview!" only DocView is matched.
---A function that returns a boolean can be used instead to perform a custom
---evaluation, setting to nil means always evaluates to true.
---
---@param predicate string | table | function
---@return function
function command.generate_predicate(predicate)
predicate = predicate or always_true
local strict = false
if type(predicate) == "string" then
if predicate:match("!$") then
strict = true
predicate = predicate:gsub("!$", "")
end
predicate = require(predicate)
end
if type(predicate) == "table" then
local class = predicate
predicate = function() return core.active_view:is(class) end
if not strict then
predicate = function() return core.active_view:extends(class) end
else
predicate = function() return core.active_view:is(class) end
end
end
return predicate
end
function command.add(predicate, map)
predicate = command.generate_predicate(predicate)
for name, fn in pairs(map) do
assert(not command.map[name], "command already exists: " .. name)
command.map[name] = { predicate = predicate, perform = fn }

View File

@ -180,7 +180,7 @@ local commands = {
local line, col = doc():get_selection()
doc():set_selection(line, col)
end,
["doc:cut"] = function()
cut_or_copy(true)
end,
@ -402,11 +402,11 @@ local commands = {
end,
["doc:upper-case"] = function()
doc():replace(string.upper)
doc():replace(string.uupper)
end,
["doc:lower-case"] = function()
doc():replace(string.lower)
doc():replace(string.ulower)
end,
["doc:go-to-line"] = function()

View File

@ -39,14 +39,7 @@ local function get_item_size(item)
end
function ContextMenu:register(predicate, items)
if type(predicate) == "string" then
predicate = require(predicate)
end
if type(predicate) == "table" then
local class = predicate
predicate = function() return core.active_view:is(class) end
end
predicate = command.generate_predicate(predicate)
local width, height = 0, 0 --precalculate the size of context menu
for i, item in ipairs(items) do
if item ~= DIVIDER then

View File

@ -458,7 +458,7 @@ end
function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
local old_text = self:get_text(line1, col1, line2, col2)
local new_text, n = fn(old_text)
local new_text, res = fn(old_text)
if old_text ~= new_text then
self:insert(line2, col2, new_text)
self:remove(line1, col1, line2, col2)
@ -467,22 +467,22 @@ function Doc:replace_cursor(idx, line1, col1, line2, col2, fn)
self:set_selections(idx, line1, col1, line2, col2)
end
end
return n
return res
end
function Doc:replace(fn)
local has_selection, n = false, 0
local has_selection, results = false, { }
for idx, line1, col1, line2, col2 in self:get_selections(true) do
if line1 ~= line2 or col1 ~= col2 then
n = n + self:replace_cursor(idx, line1, col1, line2, col2, fn)
results[idx] = self:replace_cursor(idx, line1, col1, line2, col2, fn)
has_selection = true
end
end
if not has_selection then
self:set_selection(table.unpack(self.selections))
n = n + self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
results[1] = self:replace_cursor(1, 1, 1, #self.lines, #self.lines[#self.lines], fn)
end
return n
return results
end

View File

@ -3,33 +3,111 @@ local command = require "core.command"
local config = require "core.config"
local keymap = {}
---@alias keymap.shortcut string
---@alias keymap.command string
---@alias keymap.modkey string
---@alias keymap.pressed boolean
---@alias keymap.map table<keymap.shortcut,keymap.command|keymap.command[]>
---@alias keymap.rmap table<keymap.command, keymap.shortcut|keymap.shortcut[]>
---Pressed status of mod keys.
---@type table<keymap.modkey, keymap.pressed>
keymap.modkeys = {}
---List of commands assigned to a shortcut been the key of the map the shortcut.
---@type keymap.map
keymap.map = {}
---List of shortcuts assigned to a command been the key of the map the command.
---@type keymap.rmap
keymap.reverse_map = {}
local macos = PLATFORM == "Mac OS X"
-- Thanks to mathewmariani, taken from his lite-macos github repository.
local modkeys_os = require("core.modkeys-" .. (macos and "macos" or "generic"))
---@type table<keymap.modkey, keymap.modkey>
local modkey_map = modkeys_os.map
---@type keymap.modkey[]
local modkeys = modkeys_os.keys
local function key_to_stroke(k)
---Generates a stroke sequence including currently pressed mod keys.
---@param key string
---@return string
local function key_to_stroke(key)
local stroke = ""
for _, mk in ipairs(modkeys) do
if keymap.modkeys[mk] then
stroke = stroke .. mk .. "+"
end
end
return stroke .. k
return stroke .. key
end
---Remove the given value from an array associated to a key in a table.
---@param tbl table<string, string> The table containing the key
---@param k string The key containing the array
---@param v? string The value to remove from the array
local function remove_only(tbl, k, v)
if tbl[k] then
if v then
local j = 0
for i=1, #tbl[k] do
while tbl[k][i + j] == v do
j = j + 1
end
tbl[k][i] = tbl[k][i + j]
end
else
tbl[k] = nil
end
end
end
---Removes from a keymap.map the bindings that are already registered.
---@param map keymap.map
local function remove_duplicates(map)
for stroke, commands in pairs(map) do
if type(commands) == "string" or type(commands) == "function" then
commands = { commands }
end
if keymap.map[stroke] then
for _, registered_cmd in ipairs(keymap.map[stroke]) do
local j = 0
for i=1, #commands do
while commands[i + j] == registered_cmd do
j = j + 1
end
commands[i] = commands[i + j]
end
end
end
if #commands < 1 then
map[stroke] = nil
else
map[stroke] = commands
end
end
end
---Add bindings by replacing commands that were previously assigned to a shortcut.
---@param map keymap.map
function keymap.add_direct(map)
for stroke, commands in pairs(map) do
if type(commands) == "string" then
if type(commands) == "string" or type(commands) == "function" then
commands = { commands }
end
if keymap.map[stroke] then
for _, cmd in ipairs(keymap.map[stroke]) do
remove_only(keymap.reverse_map, cmd, stroke)
end
end
keymap.map[stroke] = commands
for _, cmd in ipairs(commands) do
keymap.reverse_map[cmd] = keymap.reverse_map[cmd] or {}
@ -38,15 +116,23 @@ function keymap.add_direct(map)
end
end
---Adds bindings by appending commands to already registered shortcut or by
---replacing currently assigned commands if overwrite is specified.
---@param map keymap.map
---@param overwrite? boolean
function keymap.add(map, overwrite)
remove_duplicates(map)
for stroke, commands in pairs(map) do
if macos then
stroke = stroke:gsub("%f[%a]ctrl%f[%A]", "cmd")
end
if type(commands) == "string" or type(commands) == "function" then
commands = { commands }
end
if overwrite then
if keymap.map[stroke] then
for _, cmd in ipairs(keymap.map[stroke]) do
remove_only(keymap.reverse_map, cmd, stroke)
end
end
keymap.map[stroke] = commands
else
keymap.map[stroke] = keymap.map[stroke] or {}
@ -62,35 +148,34 @@ function keymap.add(map, overwrite)
end
local function remove_only(tbl, k, v)
for key, values in pairs(tbl) do
if key == k then
if v then
for i, value in ipairs(values) do
if value == v then
table.remove(values, i)
end
end
else
tbl[key] = nil
end
break
end
end
end
function keymap.unbind(key, cmd)
remove_only(keymap.map, key, cmd)
remove_only(keymap.reverse_map, cmd, key)
---Unregisters the given shortcut and associated command.
---@param shortcut string
---@param cmd string
function keymap.unbind(shortcut, cmd)
remove_only(keymap.map, shortcut, cmd)
remove_only(keymap.reverse_map, cmd, shortcut)
end
---Returns all the shortcuts associated to a command unpacked for easy assignment.
---@param cmd string
---@return ...
function keymap.get_binding(cmd)
return table.unpack(keymap.reverse_map[cmd] or {})
end
---Returns all the shortcuts associated to a command packed in a table.
---@param cmd string
---@return table<integer, string> | nil shortcuts
function keymap.get_bindings(cmd)
return keymap.reverse_map[cmd]
end
--------------------------------------------------------------------------------
-- Events listening
--------------------------------------------------------------------------------
function keymap.on_key_pressed(k, ...)
local mk = modkey_map[k]
if mk then
@ -101,7 +186,7 @@ function keymap.on_key_pressed(k, ...)
end
else
local stroke = key_to_stroke(k)
local commands, performed = keymap.map[stroke]
local commands, performed = keymap.map[stroke], false
if commands then
for _, cmd in ipairs(commands) do
if type(cmd) == "function" then
@ -143,6 +228,9 @@ function keymap.on_key_released(k)
end
--------------------------------------------------------------------------------
-- Register default bindings
--------------------------------------------------------------------------------
if macos then
local keymap_macos = require("core.keymap-macos")
keymap_macos(keymap)

View File

@ -21,6 +21,11 @@ end
function Object:is(T)
return getmetatable(self) == T
end
function Object:extends(T)
local mt = getmetatable(self)
while mt do
if mt == T then

View File

@ -113,9 +113,6 @@ function StatusView.Item:hide() self.visible = false end
---Show the item on the status bar.
function StatusView.Item:show() self.visible = true end
---Function assiged by default when user provides a nil predicate.
local function predicate_always_true() return true end
---A condition to evaluate if the item should be displayed. If a string
---is given it is treated as a require import that should return a valid object
---which is checked against the current active view, the sames applies if a
@ -123,15 +120,7 @@ local function predicate_always_true() return true end
---perform a custom evaluation, setting to nil means always evaluates to true.
---@param predicate string | table | StatusView.Item.predicate
function StatusView.Item:set_predicate(predicate)
predicate = predicate or predicate_always_true
if type(predicate) == "string" then
predicate = require(predicate)
end
if type(predicate) == "table" then
local class = predicate
predicate = function() return core.active_view:is(class) end
end
self.predicate = predicate
self.predicate = command.generate_predicate(predicate)
end
@ -655,14 +644,14 @@ local function merge_deprecated_items(destination, items, alignment)
local position = alignment == StatusView.Item.LEFT and "left" or "right"
local item_start = StatusView.Item(
predicate_always_true,
nil,
"deprecated:"..position.."-start",
alignment
)
item_start.get_item = items_start
local item_end = StatusView.Item(
predicate_always_true,
nil,
"deprecated:"..position.."-end",
alignment
)

View File

@ -169,12 +169,12 @@ function tokenizer.tokenize(incoming_syntax, text, state)
if p.whole_line[p_idx] and next > 1 then
return
end
-- go to the start of the next utf-8 character
while text:byte(next) and common.is_utf8_cont(text, next) do
next = next + 1
end
res = p.pattern and { text:ufind((at_start or p.whole_line[p_idx]) and "^" .. code or code, next) }
or { regex.match(code, text, next, (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
or { regex.match(code, text, text:ucharpos(next), (at_start or p.whole_line[p_idx]) and regex.ANCHORED or 0) }
if p.regex and #res > 0 then -- set correct utf8 len for regex result
res[2] = res[1] + string.ulen(text:sub(res[1], res[2])) - 1
res[1] = next
end
if res[1] and close and target[3] then
local count = 0
for i = res[1] - 1, 1, -1 do
@ -189,7 +189,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
return table.unpack(res)
end
while i <= #text do
local text_len = text:ulen()
while i <= text_len do
-- continue trying to match the end pattern of a pair if we have a state set
if current_pattern_idx > 0 then
local p = current_syntax.patterns[current_pattern_idx]
@ -262,13 +263,8 @@ function tokenizer.tokenize(incoming_syntax, text, state)
-- consume character if we didn't match
if not matched then
local n = 0
-- reach the next character
while text:byte(i + n + 1) and common.is_utf8_cont(text, i + n + 1) do
n = n + 1
end
push_token(res, "normal", text:usub(i, i + n))
i = i + n + 1
push_token(res, "normal", text:usub(i, i))
i = i + 1
end
end

View File

@ -2,6 +2,8 @@
-- inject utf8 functions to strings
--------------------------------------------------------------------------------
local utf8 = require "utf8extra"
string.ubyte = utf8.byte
string.uchar = utf8.char
string.ufind = utf8.find

View File

@ -3,7 +3,6 @@ local core = require "core"
local command = require "core.command"
local keymap = require "core.keymap"
local ContextMenu = require "core.contextmenu"
local DocView = require "core.docview"
local RootView = require "core.rootview"
local menu = ContextMenu()
@ -33,7 +32,7 @@ function RootView:draw(...)
menu:draw()
end
command.add(function() return getmetatable(core.active_view) == DocView end, {
command.add("core.docview!", {
["context:show"] = function()
menu:show(core.active_view.position.x, core.active_view.position.y)
end

View File

@ -6,7 +6,9 @@
renderer = {}
---
---Represents a color used by the rendering functions.
---Array of bytes that represents a color used by the rendering functions.
---Note: indexes for rgba are numerical 1 = r, 2 = g, 3 = b, 4 = a but for
---documentation purposes the letters r, g, b, a were used.
---@class renderer.color
---@field public r number Red
---@field public g number Green
@ -17,7 +19,7 @@ renderer.color = {}
---
---Represent options that affect a font's rendering.
---@class renderer.fontoptions
---@field public antialiasing "'grayscale'" | "'subpixel'"
---@field public antialiasing "'none'" | "'grayscale'" | "'subpixel'"
---@field public hinting "'slight'" | "'none'" | '"full"'
-- @field public bold boolean
-- @field public italic boolean
@ -38,6 +40,16 @@ renderer.font = {}
---@return renderer.font
function renderer.font.load(path, size, options) end
---
---Combines an array of fonts into a single one for broader charset support,
---the order of the list determines the fonts precedence when retrieving
---a symbol from it.
---
---@param fonts renderer.font[]
---
---@return renderer.font
function renderer.font.group(fonts) end
---
---Clones a font object into a new one.
---
@ -80,25 +92,6 @@ function renderer.font:get_size() end
---@param size number
function renderer.font:set_size(size) end
---
---Assistive functionality to replace characters in a
---rendered text with other characters.
---@class renderer.replacements
renderer.replacements = {}
---
---Create a new character replacements object.
---
---@return renderer.replacements
function renderer.replacements.new() end
---
---Add to internal map a character to character replacement.
---
---@param original_char string Should be a single character like '\t'
---@param replacement_char string Should be a single character like '»'
function renderer.replacements:add(original_char, replacement_char) end
---
---Toggles drawing debugging rectangles on the currently rendered sections
---of the window to help troubleshoot the renderer.
@ -141,29 +134,13 @@ function renderer.set_clip_rect(x, y, width, height) end
function renderer.draw_rect(x, y, width, height, color) end
---
---Draw text.
---Draw text and return the x coordinate where the text finished drawing.
---
---@param font renderer.font
---@param text string
---@param x number
---@param y number
---@param color renderer.color
---@param replace renderer.replacements
---@param color_replace renderer.color
---
---@return number x_subpixel
function renderer.draw_text(font, text, x, y, color, replace, color_replace) end
---
---Draw text at subpixel level.
---
---@param font renderer.font
---@param text string
---@param x number
---@param y number
---@param color renderer.color
---@param replace renderer.replacements
---@param color_replace renderer.color
---
---@return number x_subpixel
function renderer.draw_text_subpixel(font, text, x, y, color, replace, color_replace) end
---@return number x
function renderer.draw_text(font, text, x, y, color) end

View File

@ -1,19 +1,23 @@
---@meta
---Additional utf8 support not provided by lua.
---@class utf8extra
utf8extra = {}
---UTF-8 equivalent of string.byte
---@param s string
---@param i? integer
---@param j? integer
---@return integer
---@return ...
function utf8.byte(s, i, j) end
function utf8extra.byte(s, i, j) end
---UTF-8 equivalent of string.char
---@param byte integer
---@param ... integer
---@return string
---@return ...
function utf8.char(byte, ...) end
function utf8extra.char(byte, ...) end
---UTF-8 equivalent of string.find
---@param s string
@ -23,14 +27,14 @@ function utf8.char(byte, ...) end
---@return integer start
---@return integer end
---@return ... captured
function utf8.find(s, pattern, init, plain) end
function utf8extra.find(s, pattern, init, plain) end
---UTF-8 equivalent of string.gmatch
---@param s string
---@param pattern string
---@param init? integer
---@return fun():string, ...
function utf8.gmatch(s, pattern, init) end
function utf8extra.gmatch(s, pattern, init) end
---UTF-8 equivalent of string.gsub
---@param s string
@ -39,41 +43,41 @@ function utf8.gmatch(s, pattern, init) end
---@param n integer
---@return string
---@return integer count
function utf8.gsub(s, pattern, repl, n) end
function utf8extra.gsub(s, pattern, repl, n) end
---UTF-8 equivalent of string.len
---@param s string
---@return integer
function utf8.len(s) end
function utf8extra.len(s) end
---UTF-8 equivalent of string.lower
---@param s string
---@return string
function utf8.lower(s) end
function utf8extra.lower(s) end
---UTF-8 equivalent of string.match
---@param s string
---@param pattern string
---@param init? integer
---@return string | number captured
function utf8.match(s, pattern, init) end
function utf8extra.match(s, pattern, init) end
---UTF-8 equivalent of string.reverse
---@param s string
---@return string
function utf8.reverse(s) end
function utf8extra.reverse(s) end
---UTF-8 equivalent of string.sub
---@param s string
---@param i integer
---@param j? integer
---@return string
function utf8.sub(s, i, j) end
function utf8extra.sub(s, i, j) end
---UTF-8 equivalent of string.upper
---@param s string
---@return string
function utf8.upper(s) end
function utf8extra.upper(s) end
---Escape a str to UTF-8 format string. It support several escape format:
---* %ddd - which ddd is a decimal number at any length: change Unicode code point to UTF-8 format.
@ -91,7 +95,7 @@ function utf8.upper(s) end
---```
---@param s string
---@return string utf8_string
function utf8.escape(s) end
function utf8extra.escape(s) end
---Convert UTF-8 position to byte offset. if only index is given, return byte
---offset of this UTF-8 char index. if both charpos and index is given, a new
@ -103,7 +107,7 @@ function utf8.escape(s) end
---@param index? integer
---@return integer charpos
---@return integer codepoint
function utf8.charpos(s, charpos, index) end
function utf8extra.charpos(s, charpos, index) end
---Iterate though the UTF-8 string s. If only s is given, it can used as a iterator:
---```lua
@ -120,7 +124,7 @@ function utf8.charpos(s, charpos, index) end
---@param index? integer
---@return integer charpos
---@return integer codepoint
function utf8.next(s, charpos, index) end
function utf8extra.next(s, charpos, index) end
---Insert a substring to s. If idx is given, insert substring before char at
---this index, otherwise substring will concat to s. idx can be negative.
@ -128,7 +132,7 @@ function utf8.next(s, charpos, index) end
---@param idx? integer
---@param substring string
---return string new_string
function utf8.insert(s, idx, substring) end
function utf8extra.insert(s, idx, substring) end
---Delete a substring in s. If neither start nor stop is given, delete the last
---UTF-8 char in s, otherwise delete char from start to end of s. if stop is
@ -138,7 +142,7 @@ function utf8.insert(s, idx, substring) end
---@param start? integer
---@param stop? integer
---return string new_string
function utf8.remove(s, start, stop) end
function utf8extra.remove(s, start, stop) end
---Calculate the width of UTF-8 string s. if ambi_is_double is given, the
---ambiguous width character's width is 2, otherwise it's 1. fullwidth/doublewidth
@ -150,7 +154,7 @@ function utf8.remove(s, start, stop) end
---@param ambi_is_double? boolean
---@param default_width? integer
---@return integer width
function utf8.width(s, ambi_is_double, default_width) end
function utf8extra.width(s, ambi_is_double, default_width) end
---Return the character index at given location in string s. this is a reverse
---operation of utf8.width(). this function returns a index of location, and a
@ -164,24 +168,24 @@ function utf8.width(s, ambi_is_double, default_width) end
---@return integer idx
---@return integer offset
---@return integer width
function utf8.widthindex(s, location, ambi_is_double, default_width) end
function utf8extra.widthindex(s, location, ambi_is_double, default_width) end
---Convert UTF-8 string s to title-case, used to compare by ignore case. if s
---is a number, it's treat as a code point and return a convert code point
---(number). utf8.lower/utf8.pper has the same extension.
---@param s string
---return string new_string
function utf8.title(s) end
function utf8extra.title(s) end
---Convert UTF-8 string s to folded case, used to compare by ignore case. if s
---is a number, it's treat as a code point and return a convert code point
---(number). utf8.lower/utf8.pper has the same extension.
---@param s string
---return string new_string
function utf8.fold(s) end
function utf8extra.fold(s) end
---Compare a and b without case, -1 means a < b, 0 means a == b and 1 means a > b.
---@param a string
---@param b string
---@return integer result
function utf8.ncasecmp(a, b) end
function utf8extra.ncasecmp(a, b) end

View File

@ -174,6 +174,7 @@ endif
install_data('licenses/licenses.md', install_dir : lite_docdir)
install_subdir('docs/api' , install_dir : lite_datadir, strip_directory: true)
install_subdir('data/core' , install_dir : lite_datadir, exclude_files : 'start.lua')
foreach data_module : ['fonts', 'plugins', 'colors']
install_subdir(join_paths('data', data_module), install_dir : lite_datadir)

View File

@ -5,7 +5,7 @@ int luaopen_renderer(lua_State *L);
int luaopen_regex(lua_State *L);
int luaopen_process(lua_State *L);
int luaopen_dirmonitor(lua_State* L);
int luaopen_utf8(lua_State* L);
int luaopen_utf8extra(lua_State* L);
static const luaL_Reg libs[] = {
{ "system", luaopen_system },
@ -13,7 +13,7 @@ static const luaL_Reg libs[] = {
{ "regex", luaopen_regex },
{ "process", luaopen_process },
{ "dirmonitor", luaopen_dirmonitor },
{ "utf8", luaopen_utf8 },
{ "utf8extra", luaopen_utf8extra },
{ NULL, NULL }
};

View File

@ -1264,7 +1264,7 @@ static const char UTF8PATT[] = "[\0-\x7F\xC2-\xF4][\x80-\xBF]*";
static const char UTF8PATT[] = "[%z\1-\x7F\xC2-\xF4][\x80-\xBF]*";
#endif
int luaopen_utf8 (lua_State *L) {
int luaopen_utf8extra (lua_State *L) {
luaL_Reg libs[] = {
#define ENTRY(name) { #name, Lutf8_##name }
ENTRY(offset),