lite-xl/data/core/vim.lua

220 lines
6.9 KiB
Lua

local core = require "core"
local command = require "core.command"
local vim = {}
local command_buffer = {verb = '', mult_accu = '', inside = ''}
function command_buffer:reset()
self.verb = ''
self.mult_accu = ''
self.inside = ''
end
function command_buffer:mult()
return self.mult_accu == '' and 1 or tostring(self.mult_accu)
end
function command_buffer:add_mult_char(k)
self.mult_accu = self.mult_accu .. k
end
local function table_find(t, e)
for i = 1, #t do
if t[i] == e then return i end
end
end
local verbs_obj = {'c', 'd', 'r', 'y'}
local verbs_imm = {'a', 'h', 'i', 'j', 'k', 'l', 'o', 'p', 'u', 'v', 'x', 'O',
'left', 'right', 'up', 'down', 'escape'}
local vim_objects = {'a', 'b', 'd', 'e', 'i', 'w', 'y', '^', '0', '$'}
local vim_object_map = {
['b'] = 'start-of-word',
['e'] = 'next-word-end',
['w'] = 'next-word-begin',
['$'] = 'end-of-line',
['^'] = 'start-of-line-content',
['0'] = 'start-of-line',
}
local inside_delims = {
[')'] = {'(', ')'},
[']'] = {'[', ']'},
['}'] = {'{', '}'},
['>'] = {'<', '>'},
['"'] = {'"', '"'},
["'"] = {"'", "'"},
}
local vim_previous_command
local function doc_command(action, command)
return 'doc:' .. action .. '-' .. command
end
local function vim_execute(mode, verb, mult, object)
vim_previous_command = {verb, mult, object}
local action = (mode == 'command' and 'move-to' or 'select-to')
if verb == '' then
if object == '$' then
command.perform_many(mult - 1, doc_command(action, 'next-line'))
command.perform(doc_command(action, 'end-of-line'))
else
if object == 'e' then
command.perform(doc_command(action, 'next-char'))
end
command.perform_many(mult, doc_command(action, vim_object_map[object]))
if object == 'e' then
command.perform(doc.command(action, 'previous-char'))
end
end
elseif verb == 'd' or verb == 'y' then
if mode == 'command' then -- d and y act as immediate mode commands in visual mode
if object == '$' then
command.perform_many(mult - 1, 'doc:select-to-next-line')
command.perform('doc:select-to-end-of-line')
elseif object == verb then
command.perform('doc:move-to-start-of-line')
command.perform_many(mult, 'doc:select-to-next-line')
else
command.perform_many(mult, 'doc:select-to-' .. vim_object_map[object])
end
end
command.perform('doc:copy')
if verb == 'd' then
command.perform('doc:cut')
else
command.perform('doc:move-to-start-of-selection')
end
command.perform('core:set-command-mode')
elseif verb == 'c' then
command.perform_many(mult, 'doc:select-to-' .. vim_object_map[object])
command.perform('doc:copy')
command.perform('doc:cut')
command.perform('core:set-insert-mode')
elseif verb == 'h' or verb == 'left' then
command.perform_many(mult, doc_command(action, 'previous-char'))
elseif verb == 'j' or verb == 'down' then
command.perform_many(mult, doc_command(action, 'next-line'))
elseif verb == 'k' or verb == 'up' then
command.perform_many(mult, doc_command(action, 'previous-line'))
elseif verb == 'l' or verb == 'right' then
command.perform_many(mult, doc_command(action, 'next-char'))
elseif verb == 'x' then
command.perform_many(mult, 'doc:delete')
elseif verb == 'a' then
command.perform('core:set-insert-mode')
command.perform('doc:move-to-next-char')
elseif verb == 'i' then
command.perform('core:set-insert-mode')
elseif verb == 'o' then
command.perform('doc:move-to-end-of-line')
command.perform('doc:newline')
command.perform('core:set-insert-mode')
elseif verb == 'O' then
command.perform('doc:move-to-start-of-line')
command.perform('doc:newline')
command.perform('doc:move-to-previous-line')
command.perform('core:set-insert-mode')
elseif verb == 'p' then
command.perform('doc:paste')
elseif verb == 'u' then
command.perform('doc:undo')
elseif verb == 'v' then
command.perform('core:set-visual-mode')
elseif verb == 'y' then
command.perform('doc:copy')
command.perform('doc:move-to-start-of-selection')
command.perform('core:set-command-mode')
elseif verb == 'escape' then
command.perform('doc:move-to-end-of-selection')
command.perform('core:set-command-mode')
else
return false
end
return true
end
function vim.on_text_input(mode, text_raw, stroke)
local text = text_raw or stroke
local byte = text_raw and string.byte(text_raw)
local byte0, byte9 = string.byte('0'), string.byte('9')
local view = core.active_view
local doc = view.doc
if mode == 'command' or mode == 'visual' then
if command_buffer.inside ~= '' and inside_delims[text] then
-- got character for inside delimiter edits
local outer = command_buffer.inside == 'a'
doc:select_with_delimiters(inside_delims[text], outer)
command.perform('doc:delete')
if command_buffer.verb == 'c' then
view:set_editing_mode('insert')
end
command_buffer:reset()
return true
elseif command_buffer.verb == 'r' then
if text_raw then
command.perform('doc:delete')
local line, col = doc:get_selection()
doc:insert(line, col, text_raw)
command_buffer:reset()
return true
end
elseif text == '.' then
if vim_previous_command then
vim_execute(mode, unpack(vim_previous_command))
return true
end
elseif command_buffer.verb == '' and table_find(verbs_imm, text) then
-- execute immediate vim command
vim_execute(mode, text, command_buffer:mult())
command_buffer:reset()
return true
elseif command_buffer.verb == '' and table_find(verbs_obj, text) then
-- vim command that takes an object
if mode == 'command' then
-- store the command without executing
command_buffer.verb = text
else
-- visual mode: execute the command
vim_execute(mode, text, command_buffer:mult())
command_buffer:reset()
end
return true
elseif text_raw and byte
and (byte > byte0 or command_buffer.mult_accu ~= '' and byte == byte0)
and byte <= byte9 then
-- numeric command multiplier
command_buffer:add_mult_char(text_raw)
return true
elseif table_find(vim_objects, text) then
-- object of a verb
if text == 'i' or text == 'a' then
command_buffer.inside = text
else
vim_execute(mode, command_buffer.verb, command_buffer:mult(), text)
command_buffer:reset()
end
return true
elseif stroke == 'escape' then
core.active_view:set_editing_mode('command')
command_buffer:reset()
return true
end
elseif mode == 'insert' then
if stroke == 'escape' or stroke == 'ctrl+c' then
core.active_view:set_editing_mode('command')
return true
end
if stroke == 'backspace' then
command.perform('doc:backspace')
end
end
return false
end
return vim