Compare commits
22 Commits
Author | SHA1 | Date |
---|---|---|
Francesco Abbate | 80fb59c73f | |
Francesco Abbate | 6113c7b38e | |
Francesco Abbate | 3986233b5c | |
Francesco Abbate | 87613a7619 | |
Francesco Abbate | 4584f98a23 | |
Francesco Abbate | ee040b5785 | |
Francesco Abbate | db8938413c | |
Francesco Abbate | e44a408088 | |
Francesco Abbate | ff3362f9a9 | |
Francesco Abbate | 686ff9b2ad | |
Francesco Abbate | 7f37451398 | |
Francesco Abbate | fbefcc2b72 | |
Francesco Abbate | 879018502f | |
Francesco Abbate | 4b68ce431c | |
Francesco Abbate | 296ea8b03d | |
Robert Štojs | 40d69470fb | |
Francesco Abbate | 9ead6f6427 | |
Francesco Abbate | 948a4e046d | |
Francesco Abbate | 3f4856bccd | |
Francesco Abbate | b0438b60dc | |
Francesco Abbate | 50bd5e8b2b | |
Francesco Abbate | 97a00f946d |
|
@ -58,6 +58,18 @@ function command.perform(...)
|
|||
end
|
||||
|
||||
|
||||
function command.perform_many(n, name)
|
||||
local cmd = command.map[name]
|
||||
if not cmd or not cmd.predicate() then return false end
|
||||
pcall(function()
|
||||
for i = 1, n do
|
||||
cmd.perform()
|
||||
end
|
||||
end)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function command.add_defaults()
|
||||
local reg = { "core", "root", "command", "doc", "findreplace", "files" }
|
||||
for _, name in ipairs(reg) do
|
||||
|
|
|
@ -171,4 +171,20 @@ command.add(nil, {
|
|||
return common.home_encode_list(common.dir_list_suggest(text, dir_list))
|
||||
end)
|
||||
end,
|
||||
|
||||
["core:toggle-vim-mode"] = function()
|
||||
core.vim_mode = not core.vim_mode
|
||||
end,
|
||||
|
||||
["core:set-command-mode"] = function()
|
||||
core.set_editing_mode(core.active_view, 'command')
|
||||
end,
|
||||
|
||||
["core:set-insert-mode"] = function()
|
||||
core.set_editing_mode(core.active_view, 'insert')
|
||||
end,
|
||||
|
||||
["core:set-visual-mode"] = function()
|
||||
core.set_editing_mode(core.active_view, 'visual')
|
||||
end,
|
||||
})
|
||||
|
|
|
@ -337,11 +337,13 @@ local translations = {
|
|||
["next-char"] = translate.next_char,
|
||||
["previous-word-start"] = translate.previous_word_start,
|
||||
["next-word-end"] = translate.next_word_end,
|
||||
["next-word-begin"] = translate.next_word_begin,
|
||||
["previous-block-start"] = translate.previous_block_start,
|
||||
["next-block-end"] = translate.next_block_end,
|
||||
["start-of-doc"] = translate.start_of_doc,
|
||||
["end-of-doc"] = translate.end_of_doc,
|
||||
["start-of-line"] = translate.start_of_line,
|
||||
["start-of-line-content"] = translate.start_of_line_content,
|
||||
["end-of-line"] = translate.end_of_line,
|
||||
["start-of-word"] = translate.start_of_word,
|
||||
["end-of-word"] = translate.end_of_word,
|
||||
|
@ -375,4 +377,14 @@ commands["doc:move-to-next-char"] = function()
|
|||
end
|
||||
end
|
||||
|
||||
commands["doc:move-to-end-of-selection"] = function()
|
||||
local _, _, line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
end
|
||||
|
||||
commands["doc:move-to-start-of-selection"] = function()
|
||||
local line, col = doc():get_selection(true)
|
||||
doc():set_selection(line, col)
|
||||
end
|
||||
|
||||
command.add("core.docview", commands)
|
||||
|
|
|
@ -3,6 +3,7 @@ local Highlighter = require "core.doc.highlighter"
|
|||
local syntax = require "core.syntax"
|
||||
local config = require "core.config"
|
||||
local common = require "core.common"
|
||||
local translate = require "core.doc.translate"
|
||||
|
||||
|
||||
local Doc = Object:extend()
|
||||
|
@ -399,4 +400,10 @@ function Doc:select_to(...)
|
|||
end
|
||||
|
||||
|
||||
function Doc:select_with_delimiters(delims, outer)
|
||||
local line, col = self:get_selection()
|
||||
self:set_selection(self:position_offset(line, col, translate.inside_delimiters, delims, outer))
|
||||
end
|
||||
|
||||
|
||||
return Doc
|
||||
|
|
|
@ -42,7 +42,7 @@ function translate.previous_word_start(doc, line, col)
|
|||
end
|
||||
|
||||
|
||||
function translate.next_word_end(doc, line, col)
|
||||
function translate.up_to_word(doc, line, col)
|
||||
local prev
|
||||
local end_line, end_col = translate.end_of_doc(doc, line, col)
|
||||
while line < end_line or col < end_col do
|
||||
|
@ -53,10 +53,22 @@ function translate.next_word_end(doc, line, col)
|
|||
line, col = doc:position_offset(line, col, 1)
|
||||
prev = char
|
||||
end
|
||||
return line, col
|
||||
end
|
||||
|
||||
|
||||
function translate.next_word_end(doc, line, col)
|
||||
line, col = translate.up_to_word(doc, line, col)
|
||||
return translate.end_of_word(doc, line, col)
|
||||
end
|
||||
|
||||
|
||||
function translate.next_word_begin(doc, line, col)
|
||||
line, col = translate.end_of_word(doc, line, col)
|
||||
return translate.up_to_word(doc, line, col)
|
||||
end
|
||||
|
||||
|
||||
function translate.start_of_word(doc, line, col)
|
||||
while true do
|
||||
local line2, col2 = doc:position_offset(line, col, -1)
|
||||
|
@ -118,6 +130,11 @@ function translate.start_of_line(doc, line, col)
|
|||
end
|
||||
|
||||
|
||||
function translate.start_of_line_content(doc, line, col)
|
||||
return translate.up_to_word(doc, line, 1)
|
||||
end
|
||||
|
||||
|
||||
function translate.end_of_line(doc, line, col)
|
||||
return line, math.huge
|
||||
end
|
||||
|
@ -133,4 +150,45 @@ function translate.end_of_doc(doc, line, col)
|
|||
end
|
||||
|
||||
|
||||
function translate.inside_delimiters(doc, line, col, delims, outer)
|
||||
print('translate.inside_delimiters', delims[2], outer)
|
||||
local line1, col1 = line, col
|
||||
while true do
|
||||
local lineb, colb = doc:position_offset(line1, col1, -1)
|
||||
local char = doc:get_char(lineb, colb)
|
||||
if char == delims[1]
|
||||
or line1 == lineb and col1 == colb then
|
||||
break
|
||||
end
|
||||
line1, col1 = lineb, colb
|
||||
end
|
||||
|
||||
if outer then
|
||||
line1, col1 = doc:position_offset(line1, col1, -1)
|
||||
end
|
||||
|
||||
local line2, col2 = line, col
|
||||
while true do
|
||||
local linef, colf = doc:position_offset(line2, col2, 1)
|
||||
local char = doc:get_char(line2, col2)
|
||||
if char == delims[2]
|
||||
or linef == line2 and colf == col2 then
|
||||
break
|
||||
end
|
||||
line2, col2 = linef, colf
|
||||
end
|
||||
|
||||
if outer then
|
||||
line2, col2 = doc:position_offset(line2, col2, 1)
|
||||
while doc:get_char(line2, col2) == ' ' do
|
||||
local nline2, ncol2 = doc:position_offset(line2, col2, 1)
|
||||
if nline2 == line2 and ncol2 == col2 then break end
|
||||
line2, col2 = nline2, ncol2
|
||||
end
|
||||
end
|
||||
|
||||
return line1, col1, line2, col2
|
||||
end
|
||||
|
||||
|
||||
return translate
|
||||
|
|
|
@ -56,6 +56,7 @@ function DocView:new(doc)
|
|||
self.font = "code_font"
|
||||
self.last_x_offset = {}
|
||||
self.blink_timer = 0
|
||||
self.editing_mode = 'command'
|
||||
end
|
||||
|
||||
|
||||
|
@ -168,6 +169,16 @@ function DocView:get_x_offset_col(line, x)
|
|||
end
|
||||
|
||||
|
||||
function DocView:get_editing_mode()
|
||||
return self.editing_mode
|
||||
end
|
||||
|
||||
|
||||
function DocView:set_editing_mode(mode)
|
||||
self.editing_mode = mode
|
||||
end
|
||||
|
||||
|
||||
function DocView:resolve_screen_position(x, y)
|
||||
local ox, oy = self:get_line_screen_position(1)
|
||||
local line = math.floor((y - oy) / self:get_line_height()) + 1
|
||||
|
@ -273,7 +284,9 @@ end
|
|||
|
||||
|
||||
function DocView:on_text_input(text)
|
||||
self.doc:text_input(text)
|
||||
if not core.using_vim_mode(self) or self.editing_mode ~= 'command' then
|
||||
self.doc:text_input(text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -349,7 +362,13 @@ function DocView:draw_line_body(idx, x, y)
|
|||
and system.window_has_focus() then
|
||||
local lh = self:get_line_height()
|
||||
local x1 = x + self:get_col_x_offset(line, col)
|
||||
renderer.draw_rect(x1, y, style.caret_width, lh, style.caret)
|
||||
local caret_width
|
||||
if core.get_editing_mode(self) == 'command' then
|
||||
caret_width = self:get_font():get_width(self.doc.lines[line1]:sub(col1, col1))
|
||||
else
|
||||
caret_width = style.caret_width
|
||||
end
|
||||
renderer.draw_rect(x1, y, caret_width, lh, style.caret)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -395,6 +395,7 @@ function core.init()
|
|||
core.log_items = {}
|
||||
core.docs = {}
|
||||
core.threads = setmetatable({}, { __mode = "k" })
|
||||
core.vim_mode = false
|
||||
|
||||
local project_dir_abs = system.absolute_path(project_dir)
|
||||
local set_project_ok = project_dir_abs and core.set_project_dir(project_dir_abs)
|
||||
|
@ -713,10 +714,18 @@ function core.try(fn, ...)
|
|||
return false, err
|
||||
end
|
||||
|
||||
-- vim module but loaded only when required.
|
||||
local vim
|
||||
|
||||
function core.on_event(type, ...)
|
||||
local did_keymap = false
|
||||
if type == "textinput" then
|
||||
local vim_mode = core.get_editing_mode(core.active_view)
|
||||
if vim_mode then
|
||||
local vim = require "core.vim"
|
||||
local accepted = vim.on_text_input(vim_mode, ...)
|
||||
if accepted then return false end
|
||||
end
|
||||
core.root_view:on_text_input(...)
|
||||
elseif type == "keypressed" then
|
||||
did_keymap = keymap.on_key_pressed(...)
|
||||
|
@ -907,5 +916,23 @@ core.add_save_hook(function(filename)
|
|||
end)
|
||||
|
||||
|
||||
function core.using_vim_mode(view)
|
||||
return core.vim_mode and getmetatable(view) == DocView
|
||||
end
|
||||
|
||||
|
||||
function core.get_editing_mode(view)
|
||||
if core.using_vim_mode(view) then
|
||||
return view:get_editing_mode()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function core.set_editing_mode(view, mode)
|
||||
if core.using_vim_mode(view) then
|
||||
view:set_editing_mode(mode)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return core
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
local core = require "core"
|
||||
local command = require "core.command"
|
||||
local keymap = {}
|
||||
|
||||
|
@ -62,6 +63,14 @@ function keymap.on_key_pressed(k)
|
|||
end
|
||||
else
|
||||
local stroke = key_to_stroke(k)
|
||||
local vim_mode = core.get_editing_mode(core.active_view)
|
||||
if vim_mode then
|
||||
local vim = require "core.vim"
|
||||
local accepted = vim.on_text_input(vim_mode, nil, stroke)
|
||||
if accepted then return true end
|
||||
-- if the command is not recognized by vim fall throught to process
|
||||
-- it as an ordinary command
|
||||
end
|
||||
local commands = keymap.map[stroke]
|
||||
if commands then
|
||||
for _, cmd in ipairs(commands) do
|
||||
|
|
|
@ -110,6 +110,7 @@ function StatusView:get_items()
|
|||
local indent = dv.doc.indent_info
|
||||
local indent_label = (indent and indent.type == "hard") and "tabs: " or "spaces: "
|
||||
local indent_size = indent and tostring(indent.size) .. (indent.confirmed and "" or "*") or "unknown"
|
||||
local editing_mode = core.get_editing_mode(dv)
|
||||
|
||||
return {
|
||||
dirty and style.accent or style.text, style.icon_font, "f",
|
||||
|
@ -124,7 +125,8 @@ function StatusView:get_items()
|
|||
self.separator,
|
||||
string.format("%d%%", line / #dv.doc.lines * 100),
|
||||
}, {
|
||||
style.text, indent_label, indent_size,
|
||||
style.caret, core.vim_mode and string.upper(editing_mode) or '', style.text, self.separator2,
|
||||
indent_label, indent_size,
|
||||
style.dim, self.separator2, style.text,
|
||||
style.icon_font, "g",
|
||||
style.font, style.dim, self.separator2, style.text,
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
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
|
Loading…
Reference in New Issue