-- Authors: Unknown
-- License: Unknown
-- Last Changed: Unknown
--
-- vim-bindings.lua
--
-- This only affects queries.
--
-- Features:
--	Vi-keys for normal and insert modes.
--	Vim-style highlighting.
--	Numbered prefixes work for most maneuvers.
--
-- Limitations:
--	All WEdln objects share the same mode and undo history.
--	No r replace
--	No . repeat last action
--	No character search.
--	3cw will work; c3w won't.
--	Not all unused symbols are silenced in normal mode
--	    (so don't type them)
--	Use Shift+Insert to paste from other apps, p to paste yanked text.
--
vim={clip="",old="",oldpoint=0}
function vim.normal_mode()
    defbindings("WEdln", {
	kpress("a", "{WEdln.forward(_), vim.insert_mode(), vim.savehist(_)}"),
	kpress("Shift+a", "{WEdln.eol(_), vim.insert_mode(), vim.savehist(_)}"),
	kpress("b", "vim.multiply(WEdln.bskip_word, _)"),
	kpress("Shift+b", "vim.multiply(WEdln.bskip_word, _)"),
	submap("c", {
	    kpress("b", "vim.yank(_, 'b', true, true)"),
	    kpress("c", "vim.yank(_, 'd', true, true)"),
	    kpress("h", "vim.yank(_, 'h', true, true)"),
	    kpress("l", "vim.yank(_, 'l', true, true)"),
	    kpress("w", "vim.yank(_, 'w', true, true)"),
	    kpress("0", "vim.yank(_, '0', true, true)"),
	    kpress("Shift+6", "vim.yank(_, '0', true, true)"),
	    kpress("Shift+4", "vim.yank(_, 'D', true, true)"),
	}),
	kpress("Shift+c", "vim.yank(_, 'D', true, true)"),
	submap("d", {
	    kpress("b", "vim.yank(_, 'b', true)"),
	    kpress("d", "vim.yank(_, 'd', true)"),
	    kpress("h", "vim.yank(_, 'h', true)"),
	    kpress("l", "vim.yank(_, 'l', true)"),
	    kpress("w", "vim.yank(_, 'w', true)"),
	    kpress("0", "vim.yank(_, '0', true)"),
	    kpress("Shift+6", "vim.yank(_, '0', true)"),
	    kpress("Shift+4", "vim.yank(_, 'D', true)"),
	}),
	kpress("Shift+d", "vim.yank(_, 'D', true)"),
	kpress("e", "{_:forward(), vim.multiply(WEdln.skip_word,_), _:back()}"),
	kpress("Shift+e", "{_:forward(), vim.multiply(WEdln.skip_word,_), _:back()}"),
	kpress("f", "vim.cleardigit()"),
	kpress("g", "vim.cleardigit()"),
	kpress("h", "vim.multiply(WEdln.back,_)"),
	kpress("i", "{vim.insert_mode(), vim.savehist(_)}"),
	kpress("Shift+i", "{WEdln.bol(_), vim.insert_mode(), vim.savehist(_)}"),
	kpress("j", "vim.multiply(WEdln.history_next, _)"),
	kpress("k", "vim.multiply(WEdln.history_prev, _)"),
	kpress("Control+n", "vim.multiply(WEdln.history_next, _)"),
	kpress("Control+p", "vim.multiply(WEdln.history_prev, _)"),
	kpress("l", "vim.multiply(WEdln.forward, _)"),
	kpress("m", "vim.cleardigit()"),
	kpress("n", "vim.cleardigit()"),
	kpress("o", "vim.cleardigit()"),
	kpress("p", "{WEdln.forward(_), vim.multiply(vim.paste,_,true),"
	    .."WEdln.back(_)}"),
	kpress("Shift+p", "vim.multiply(vim.paste,_,true)"),
	kpress("q", "vim.cleardigit()"),
	kpress("r", "vim.cleardigit()"),
	kpress("s", "vim.yank(_, 'l', true, true)"),
	kpress("Shift+s", "vim.yank(_, 'd', true, true)"),
	kpress("t", "vim.cleardigit()"),
	kpress("u", "vim.undo(_)"),
	kpress("v", "vim.highlight(_)"),
	kpress("w", "vim.multiply(WEdln.skip_word, _)"),
	kpress("Shift+w", "vim.multiply(WEdln.skip_word, _)"),
	kpress("x", "vim.x(_)"),
	kpress("Shift+x", "vim.yank(_, 'h', true)"),
	kpress("y", "vim.yank(_)"),
	kpress("Shift+y", "vim.yank(_, 'd')"),
	kpress("z", "vim.cleardigit()"),
	kpress("0", "vim.zero(_)"),
	kpress("1", "vim.dodigit(1)"),
	kpress("2", "vim.dodigit(2)"),
	kpress("3", "vim.dodigit(3)"),
	kpress("4", "vim.dodigit(4)"),
	kpress("5", "vim.dodigit(5)"),
	kpress("6", "vim.dodigit(6)"),
	kpress("7", "vim.dodigit(7)"),
	kpress("8", "vim.dodigit(8)"),
	kpress("9", "vim.dodigit(9)"),
	kpress("Shift+6", "{vim.cleardigit(), WEdln.bol(_)}"),
	kpress("Shift+4", "{vim.cleardigit(), WEdln.eol(_)}"),
	kpress("BackSpace", "vim.multiply(WEdln.back, _)"),
	kpress("space", "vim.multiply(WEdln.forward, _)"),
	kpress("Shift+grave", "vim.multiply(vim.switchcase,_,true)"),
	kpress("Control+A", "vim.multiply(vim.increment, _)"),
	kpress("Control+X", "vim.multiply(vim.decrement, _)"),

	kpress("Shift+f", "vim.cleardigit()"),
	kpress("Shift+g", "vim.cleardigit()"), kpress("Shift+h", "vim.cleardigit()"),
	kpress("Shift+j", "vim.cleardigit()"), kpress("Shift+k", "vim.cleardigit()"),
	kpress("Shift+l", "vim.cleardigit()"), kpress("Shift+m", "vim.cleardigit()"),
	kpress("Shift+n", "vim.cleardigit()"), kpress("Shift+o", "vim.cleardigit()"),
	kpress("Shift+q", "vim.cleardigit()"), kpress("Shift+r", "vim.cleardigit()"),
	kpress("Shift+t", "vim.cleardigit()"), kpress("Shift+u", "vim.cleardigit()"),
	kpress("Shift+v", "vim.cleardigit()"),
	kpress("Shift+z", "vim.cleardigit()"), kpress("Shift+0", "vim.cleardigit()"),
	kpress("Shift+1", "vim.cleardigit()"), kpress("Shift+2", "vim.cleardigit()"),
	kpress("Shift+3", "vim.cleardigit()"), kpress("Shift+5", "vim.cleardigit()"),
	kpress("Shift+7", "vim.cleardigit()"), kpress("Shift+8", "vim.cleardigit()"),
	kpress("Shift+9", "vim.cleardigit()"),
    })
end

function vim.insert_mode()
    for _,edln in pairs(ioncore.region_list("WEdln")) do
	edln:clear_mark()
    end
    defbindings("WEdln", {
	kpress("Control+w", "WEdln.bkill_word(_)"),
	kpress("Control+n", "WEdln.next_completion(_, true)"),
	kpress("Control+p", "WEdln.prev_completion(_, true)"),
	kpress("Escape", "vim.normal_mode()"),
	kpress("Control+bracketleft", "vim.normal_mode()"),

	kpress("BackSpace", "WEdln.backspace(_)"),
	kpress("Mod1+BackSpace", "WEdln.bkill_word(_)"),
	kpress("a", nil), kpress("b", nil), kpress("c", nil),
	kpress("d", nil), kpress("e", nil), kpress("f", nil),
	kpress("g", nil), kpress("h", nil), kpress("i", nil),
	kpress("j", nil), kpress("k", nil), kpress("l", nil),
	kpress("m", nil), kpress("n", nil), kpress("o", nil),
	kpress("p", nil), kpress("q", nil), kpress("r", nil),
	kpress("s", nil), kpress("t", nil), kpress("u", nil),
	kpress("v", nil), kpress("w", nil), kpress("x", nil),
	kpress("y", nil), kpress("z", nil), kpress("0", nil),
	kpress("1", nil), kpress("2", nil), kpress("3", nil),
	kpress("4", nil), kpress("5", nil), kpress("6", nil),
	kpress("7", nil), kpress("8", nil), kpress("9", nil),
	kpress("Shift+a", nil), kpress("Shift+b", nil), kpress("Shift+c", nil),
	kpress("Shift+d", nil), kpress("Shift+e", nil), kpress("Shift+f", nil),
	kpress("Shift+g", nil), kpress("Shift+h", nil), kpress("Shift+i", nil),
	kpress("Shift+j", nil), kpress("Shift+k", nil), kpress("Shift+l", nil),
	kpress("Shift+m", nil), kpress("Shift+n", nil), kpress("Shift+o", nil),
	kpress("Shift+p", nil), kpress("Shift+q", nil), kpress("Shift+r", nil),
	kpress("Shift+s", nil), kpress("Shift+t", nil), kpress("Shift+u", nil),
	kpress("Shift+v", nil), kpress("Shift+w", nil), kpress("Shift+x", nil),
	kpress("Shift+y", nil), kpress("Shift+z", nil), kpress("Shift+0", nil),
	kpress("Shift+1", nil), kpress("Shift+2", nil), kpress("Shift+3", nil),
	kpress("Shift+4", nil), kpress("Shift+5", nil), kpress("Shift+6", nil),
	kpress("Shift+7", nil), kpress("Shift+8", nil), kpress("Shift+9", nil),
	kpress("space", nil),
	kpress("Shift+grave", nil),
	kpress("Control+A", nil),
	kpress("Control+X", nil),
    })
end

defbindings("WInput", {
    kpress("Control+C", "{vim.cleardigit(), WInput.cancel(_)}"),
    kpress("Control+F", "WInput.scrollup(_)"),
    kpress("Control+B", "WInput.scrolldown(_)"),
    kpress("Page_Up", "WInput.scrollup(_)"),
    kpress("Page_Down", "WInput.scrolldown(_)"),
})
defbindings("WEdln", {
    kpress("Shift+Insert", "WEdln.paste(_)"),
    kpress("Return", "{vim.cleardigit(), WEdln.finish(_)}"),
})

function vim.yank(edln, how, kill, insert)
    vim.old=edln:contents()
    vim.oldpoint=edln:point()
    local first,last
    local n=1
    if vim.digit1 then n=vim.digit1 end
    if vim.digit10 then n=n+10*vim.digit10 end

    if edln:mark() < 0 then
	edln:set_mark()
	for i=1,n do
	    if how=="b" then
		edln:bskip_word()
	    elseif how=="D" then
		edln:eol()
	    elseif how=="d" then
		edln:bol()
		edln:set_mark()
		edln:eol()
	    elseif how=="h" then
		edln:back()
	    elseif how=="l" then
		edln:forward()
	    elseif how=="w" then
		edln:skip_word()
	    elseif how=="0" then
		edln:bol()
	    end
	end
    end
    first=edln:mark()+1
    last=edln:point()
    if first>last then
	first=last+1
	last=edln:mark()
    end
    vim.clip=string.sub(edln:contents(), first, last)
    if kill then
	edln:cut()
    else
	edln:copy()
    end
    edln:clear_mark() -- just in case?
    vim.cleardigit()
    if insert then
	vim.insert_mode()
    end
end

function vim.undo(edln)
    local str=edln:contents()
    edln:kill_line()
    edln:insstr(vim.old)
    vim.old=str
    edln:bol()
    for i=1,vim.oldpoint do
	edln:forward()
    end
end

function vim.savehist(edln)
    vim.old=edln:contents()
    vim.oldpoint=edln:point()
end

function vim.zero(edln)
    if vim.digit1 then
	vim.dodigit(0)
    else
	edln:bol()
	vim.cleardigit()
    end
end

function vim.x(edln)
    -- x is only a backspace when there is nothing ahead.
    if string.len(edln:contents()) == edln:point() then
	vim.yank(edln, 'h', true)
    else
	vim.yank(edln, 'l', true)
    end
end

function vim.highlight(edln)
    if edln:mark() > -1 then
	edln:clear_mark()
    else
	edln:set_mark()
    end
    vim.cleardigit()
end

function vim.paste(edln)
    -- if something is highlighted, replace it.
    local clip2=vim.clip
    if edln:mark() > -1 then
	first=edln:mark()+1
	last=edln:point()
	if first>last then
	    first=last+1
	    last=edln:mark()
	end
	vim.clip=string.sub(edln:contents(), first, last)
	edln:cut()
    end
    edln:insstr(clip2)
    vim.cleardigit()
end

function vim.multiply(f, edln, addtohistory)
    if edln and addtohistory then
	vim.old=edln:contents()
	vim.oldpoint=edln:point()
    end
    local n=1
    if vim.digit1 then n=vim.digit1 end
    if vim.digit10 then n=n+10*vim.digit10 end
    for i=1,n do
	f(edln)
    end
    vim.cleardigit()
end

function vim.dodigit(digit)
    vim.digit10=vim.digit1
    vim.digit1=digit
end

function vim.cleardigit()
    -- This function needs to go almost everywhere. Doing the wrong thing
    -- 10 times because a number was pressed ages ago, would be bad.
    vim.digit1=nil
    vim.digit10=nil
end

function vim.switchcase(edln)
    local c=string.sub(edln:contents(), edln:point()+1, edln:point()+1)
    if string.find(c, "%u") then
	edln:delete()
	edln:insstr(string.lower(c))
    elseif string.find(c, "%l") then
	edln:delete()
	edln:insstr(string.upper(c))
    else
	edln:forward()
    end
end

function vim.decrement(edln)
    local c=string.sub(edln:contents(), edln:point()+1, edln:point()+1)
    if string.find(c, "%d") then
	edln:delete()
	c=c-1
	if c==-1 then c=9 end
	edln:insstr(tostring(c))
	edln:back()
    end
end

function vim.increment(edln)
    local c=string.sub(edln:contents(), edln:point()+1, edln:point()+1)
    if string.find(c, "%d") then
	edln:delete()
	c=c+1
	if c==10 then c=0 end
	edln:insstr(tostring(c))
	edln:back()
    end
end

vim.normal_mode()
vim.insert_mode()
