opus/sys/modules/opus/entry.lua

422 lines
8.9 KiB
Lua

local class = require('opus.class')
local os = _G.os
-- convert value to a string (supporting nils or numbers in value)
local function _val(a)
return a and tostring(a) or ''
end
local Entry = class()
function Entry:init(args)
self.pos = 0
self.scroll = 0
self.value = args.value
self.width = args.width or 256
self.limit = args.limit or 1024
self.mark = { }
self.offset = args.offset or 1
self.transform = args.transform or function(a) return a end
end
function Entry:reset()
self.pos = 0
self.scroll = 0
self.value = nil
self.mark = { }
end
function Entry:nextWord()
local value = _val(self.value)
return select(2, value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #value
end
function Entry:prevWord()
local value = _val(self.value)
local x = #value - (self.pos - 1)
local _, n = value:reverse():find("[%s%p]?%w[%s%p]", x)
return n and #value - n + 1 or 0
end
function Entry:updateScroll()
local ps = self.scroll
local len = #_val(self.value)
if self.pos > len then
self.pos = len
self.scroll = 0 -- ??
end
if self.pos - self.scroll > self.width then
self.scroll = self.pos - self.width
elseif self.pos < self.scroll then
self.scroll = self.pos
end
if self.scroll > 0 then
if self.scroll + self.width > len then
self.scroll = len - self.width
end
end
if ps ~= self.scroll then
self.textChanged = true
end
end
function Entry:copyText(cx, ex)
-- this should be transformed (ie. if number)
return _val(self.value):sub(cx + 1, ex)
end
function Entry:insertText(x, text)
text = tostring(self.transform(text) or '')
if #text > 0 then
local value = _val(self.value)
if #value + #text > self.limit then
text = text:sub(1, self.limit-#value)
end
self.value = self.transform(value:sub(1, x) .. text .. value:sub(x + 1))
self.pos = self.pos + #text
end
end
function Entry:deleteText(sx, ex)
local value = _val(self.value)
local front = value:sub(1, sx)
local back = value:sub(ex + 1, #value)
self.value = self.transform(front .. back)
self.pos = sx
end
function Entry:moveLeft()
if self.pos > 0 then
self.pos = self.pos - 1
return true
end
end
function Entry:moveRight()
if self.pos < #_val(self.value) then
self.pos = self.pos + 1
return true
end
end
function Entry:moveHome()
if self.pos ~= 0 then
self.pos = 0
return true
end
end
function Entry:moveEnd()
if self.pos ~= #_val(self.value) then
self.pos = #_val(self.value)
return true
end
end
function Entry:moveTo(ie)
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #_val(self.value)))
end
function Entry:backspace()
if self.mark.active then
self:delete()
elseif self:moveLeft() then
self:delete()
end
end
function Entry:moveWordRight()
if self.pos < #_val(self.value) then
self.pos = self:nextWord(self.value, self.pos + 1)
return true
end
end
function Entry:moveWordLeft()
if self.pos > 0 then
self.pos = self:prevWord(self.value, self.pos - 1) or 0
return true
end
end
function Entry:delete()
if self.mark.active then
self:deleteText(self.mark.x, self.mark.ex)
elseif self.pos < #_val(self.value) then
self:deleteText(self.pos, self.pos + 1)
end
end
function Entry:cutFromStart()
if self.pos > 0 then
local text = self:copyText(1, self.pos)
self:deleteText(1, self.pos)
os.queueEvent('clipboard_copy', text)
end
end
function Entry:cutToEnd()
local value = _val(self.value)
if self.pos < #value then
local text = self:copyText(self.pos, #value)
self:deleteText(self.pos, #value)
os.queueEvent('clipboard_copy', text)
end
end
function Entry:cutNextWord()
if self.pos < #_val(self.value) then
local ex = self:nextWord(self.value, self.pos)
local text = self:copyText(self.pos, ex)
self:deleteText(self.pos, ex)
os.queueEvent('clipboard_copy', text)
end
end
function Entry:cutPrevWord()
if self.pos > 0 then
local sx = self:prevWord(self.value, self.pos)
local text = self:copyText(sx, self.pos)
self:deleteText(sx, self.pos)
os.queueEvent('clipboard_copy', text)
end
end
function Entry:insertChar(ie)
if self.mark.active then
self:delete()
end
self:insertText(self.pos, ie.ch)
end
function Entry:copy()
if #_val(self.value) > 0 then
self.mark.continue = true
if self.mark.active then
self:copyMarked()
else
os.queueEvent('clipboard_copy', self.value)
end
end
end
function Entry:cut()
if self.mark.active then
self:copyMarked()
self:delete()
end
end
function Entry:copyMarked()
local text = self:copyText(self.mark.x, self.mark.ex)
os.queueEvent('clipboard_copy', text)
end
function Entry:paste(ie)
if #ie.text > 0 then
if self.mark.active then
self:delete()
end
self:insertText(self.pos, ie.text)
end
end
function Entry:forcePaste()
os.queueEvent('clipboard_paste')
end
function Entry:clearLine()
if #_val(self.value) > 0 then
self:reset()
end
end
function Entry:markBegin()
if not self.mark.active then
self.mark.active = true
self.mark.anchor = { x = self.pos }
end
end
function Entry:markFinish()
if self.pos == self.mark.anchor.x then
self.mark.active = false
else
self.mark.x = math.min(self.mark.anchor.x, self.pos)
self.mark.ex = math.max(self.mark.anchor.x, self.pos)
end
self.textChanged = true
self.mark.continue = self.mark.active
end
function Entry:unmark()
if self.mark.active then
self.textChanged = true
self.mark.active = false
end
end
function Entry:markAnchor(ie)
local wasMarking = self.mark.active
self:unmark()
self:moveTo(ie)
self:markBegin()
self:markFinish()
self.textChanged = wasMarking
end
function Entry:markLeft()
self:markBegin()
if self:moveLeft() then
self:markFinish()
end
end
function Entry:markRight()
self:markBegin()
if self:moveRight() then
self:markFinish()
end
end
function Entry:markWord(ie)
local index = 1
self:moveTo(ie)
while true do
local s, e = _val(self.value):find('%w+', index)
if not s or s - 1 > self.pos then
break
end
if self.pos >= s - 1 and self.pos < e then
self.pos = s - 1
self:markBegin()
self.pos = e
self:markFinish()
self:moveTo(ie)
break
end
index = e + 1
end
end
function Entry:markNextWord()
self:markBegin()
if self:moveWordRight() then
self:markFinish()
end
end
function Entry:markPrevWord()
self:markBegin()
if self:moveWordLeft() then
self:markFinish()
end
end
function Entry:markAll()
if #_val(self.value) > 0 then
self.mark.anchor = { x = 1 }
self.mark.active = true
self.mark.continue = true
self.mark.x = 0
self.mark.ex = #_val(self.value)
self.textChanged = true
end
end
function Entry:markHome()
self:markBegin()
if self:moveHome() then
self:markFinish()
end
end
function Entry:markEnd()
self:markBegin()
if self:moveEnd() then
self:markFinish()
end
end
function Entry:markTo(ie)
self:markBegin()
self:moveTo(ie)
self:markFinish()
end
local mappings = {
[ 'left' ] = Entry.moveLeft,
[ 'control-b' ] = Entry.moveLeft,
[ 'right' ] = Entry.moveRight,
[ 'control-f' ] = Entry.moveRight,
[ 'home' ] = Entry.moveHome,
[ 'end' ] = Entry.moveEnd,
[ 'control-e' ] = Entry.moveEnd,
[ 'mouse_click' ] = Entry.moveTo,
[ 'control-right' ] = Entry.moveWordRight,
[ 'alt-f' ] = Entry.moveWordRight,
[ 'control-left' ] = Entry.moveWordLeft,
[ 'alt-b' ] = Entry.moveWordLeft,
[ 'backspace' ] = Entry.backspace,
[ 'delete' ] = Entry.delete,
[ 'char' ] = Entry.insertChar,
[ 'mouse_rightclick' ] = Entry.clearLine,
[ 'control-c' ] = Entry.copy,
[ 'control-u' ] = Entry.cutFromStart,
[ 'control-k' ] = Entry.cutToEnd,
[ 'control-w' ] = Entry.cutPrevWord,
--[ 'control-d' ] = Entry.cutNextWord,
[ 'control-x' ] = Entry.cut,
[ 'paste' ] = Entry.paste,
[ 'control-y' ] = Entry.forcePaste, -- well this won't work...
[ 'mouse_doubleclick' ] = Entry.markWord,
[ 'mouse_tripleclick' ] = Entry.markAll,
[ 'shift-left' ] = Entry.markLeft,
[ 'shift-right' ] = Entry.markRight,
[ 'mouse_down' ] = Entry.markAnchor,
[ 'mouse_drag' ] = Entry.markTo,
[ 'shift-mouse_click' ] = Entry.markTo,
[ 'control-a' ] = Entry.markAll,
[ 'control-shift-right' ] = Entry.markNextWord,
[ 'control-shift-left' ] = Entry.markPrevWord,
[ 'shift-end' ] = Entry.markEnd,
[ 'shift-home' ] = Entry.markHome,
}
function Entry:process(ie)
local action = mappings[ie.code]
self.textChanged = false
if action then
local pos = self.pos
local line = self.value
local wasMarking = self.mark.continue
self.mark.continue = false
action(self, ie)
if not self.value or #_val(self.value) == 0 then
self.value = nil
end
self.textChanged = self.textChanged or self.value ~= line
self.posChanged = pos ~= self.pos
self:updateScroll()
if not self.mark.continue and wasMarking then
self:unmark()
end
return true
end
end
return Entry