Merge branch 'master' into master-2.1
This commit is contained in:
+31
-2
@@ -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 }
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
+116
-28
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+9
-13
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user