local wv = require 'nylon.debug' { name = 'pls-ed' }
local ok,e = pcall(function()
require 'NylonOs'
end)
if not ok then
wv.log('abnorm','NylonOs not found')
end
local Nylon = require 'nylon.core'()
local edopts = {
replaceMark = true
}
local function wait_first_event( cord, handlers )
local rc
local done = false
for nEvent, fHandler in pairs(handlers) do
cord.event[nEvent] = function(...)
rc = fHandler(...)
done = true
end
end
while not done do
cord:yield_to_sleep()
end
for k, v in pairs(handlers) do
cord.event[k] = nil
end
return rc
end
local killring = {}
local killring_yankndx = 0
local function killring_add( t )
table.insert( killring, t )
killring_yankndx = #killring
if NylonOs then
NylonOs.Static.setclipboard(killring[#killring])
end
end
local function killring_append(t)
killring[#killring] = killring[#killring] .. t
if NylonOs then
NylonOs.Static.setclipboard(killring[#killring])
end
end
local function killring_getyanktext()
if NylonOs then
return NylonOs.Static.getclipboard()
else
return killring[killring_yankndx]
end
end
local function watch_cursor_pos( ptTopLeft, thePoint, wdim )
local charsInto = thePoint - ptTopLeft
local ptRow, ptCol
local drow = 0
local dcol = 0
local charsDrawn = 0
return function( l, col, eol )
local toDraw = eol and (eol - col + 1) or (#l-col+1) if (charsDrawn + toDraw) > charsInto then
return (dcol + charsInto-charsDrawn), drow
else
charsDrawn = charsDrawn + toDraw
end
if eol then
dcol = 0
drow = drow + 1
else
dcol = dcol + toDraw
end
end
end
local Theme = require 'pls-theme'
local function entryfn_edit( cord, env, buffer, opt )
local wdim = opt.wdim
local w = env.w
env.on = env.on or {} env.report = env.report or function() end
opt.plug = opt.plug or {}
local wordWrap = false
local walkFun = wordWrap and buffer.walkFragmentsEOL_orWidth or buffer.walkFragmentsEOL
if true then
local refresh_cord = Nylon.cord('refreshedit', function(refcord)
while true do
refcord:sleep(10)
cord.event.refresh()
end
end )
end
local function cursorpos( ptTopLeft, thePoint, wdim )
local f = watch_cursor_pos(ptTopLeft,thePoint,wdim)
for l, col, eol in walkFun(buffer, ptTopLeft, wdim.w) do
local cx, cy = f( l, col, eol )
if cx then
return cx, cy
end
end
end
local ptTopLeft = 1
local thePoint = 1
local theMark
local isearch_fstring = nil
local wasding
local function get_marked()
local markbeg = theMark and (thePoint < theMark) and thePoint or theMark
local markend = theMark and (theMark < thePoint) and thePoint or theMark
return markbeg, markend
end
local function get_line_marked( drawPoint, drawPointAfterLine )
local markbeg, markend = get_marked()
local lineMarkBeg, lineMarkEnd
if markbeg and drawPointAfterLine > markbeg and drawPoint < markend then
lineMarkBeg = drawPoint >= markbeg and 1 or (markbeg-drawPoint+1)
lineMarkEnd = drawPointAfterLine <= markend and (drawPointAfterLine-drawPoint) or (markend - drawPoint)
end
return lineMarkBeg, lineMarkEnd
end
local function pdcur_draw_window( wdim )
local charsInto = thePoint - ptTopLeft
local c = opt and opt.theme and opt.theme.text and Theme[opt.theme.text] or Theme.normal
w:attron( c )
local function draw_line( drow, l, markBeg, markEnd )
w:move(drow+wdim.y,wdim.x)
local s, e
if isearch_fstring and #isearch_fstring > 0 then
if isearch_fstring:find '[A-Z]' then
s,e = l:find(isearch_fstring)
else
s,e = l:lower():find(isearch_fstring)
end
end
local ndrawn = #l
if s then
local Theme = require 'pls-theme'
w:addstr( l:sub(1,s-1) )
w:attroff( c )
Theme.with.classicmenu( w, function()
w:addstr( l:sub(s,e) )
end )
w:attron( c )
w:addstr( l:sub(e+1,wdim.w) )
else
if opt.plug.drawline then
ndrawn = opt.plug.drawline( w, wdim.x, drow+wdim.y, l, markBeg, markEnd ) or ndrawn
else
if not markBeg then
w:addstr((#l > wdim.w) and l:sub(1,wdim.w) or l)
else
local endPoint = markEnd > wdim.w and wdim.w or markEnd
w:addstr( l:sub(1,markBeg-1) )
local function drawMarked() w:addstr( l:sub(markBeg,endPoint) ) end
if opt and opt.theme and opt.theme.text == 'inverse' then
w:attroff(c)
Theme.with.classicmenu( w, drawMarked )
w:attron(c)
else
Theme.with.inverse( w, drawMarked )
end
w:addstr( l:sub(endPoint+1,wdim.w) )
end
end
end
if ndrawn < wdim.w then
w:mvaddstr( drow+wdim.y,wdim.x+ndrawn,string.rep(' ', (wdim.w-ndrawn)) )
end
end
local drow = 0
local linefrags = {}
local drawPoint = ptTopLeft
for l, col, eol in walkFun( buffer, ptTopLeft, wdim.w ) do
if eol then
table.insert( linefrags, l:sub(col,eol-1) )
local wholeLine = table.concat(linefrags)
local drawPointAfterLine = drawPoint + #wholeLine + 1
local lineMarkBeg, lineMarkEnd = get_line_marked( drawPoint, drawPointAfterLine )
draw_line( drow, wholeLine, lineMarkBeg, lineMarkEnd )
drawPoint = drawPointAfterLine
drow = drow + 1
linefrags = {}
elseif col > 1 then
table.insert( linefrags, l:sub(col) )
else
table.insert( linefrags, l )
end
if drow >= wdim.h then
break
end
end
if #linefrags > 0 then
draw_line( drow, table.concat(linefrags) )
drow = drow + 1
end
while drow < wdim.h do
w:mvaddstr(drow+wdim.x,wdim.y,string.rep(' ',wdim.w))
drow = drow + 1
end
local cx,cy=cursorpos(ptTopLeft,thePoint,wdim)
w:attroff(c)
if isearch_fstring then
env.report('isearch','i-search: ' .. isearch_fstring)
else
if not wasding then
env.report( 'navstatus', string.format('sz=%d pt=%d top=%d x,y=%02d,%02d',
buffer:end_point(), thePoint, ptTopLeft, cx or -1, cy or -1))
end
end
end
local keybindings = (require 'pls-keys').edit
local function ding(msg,p,...)
Pdcurses.Static.beep()
if msg then
wasding = true
env.report('oops',p and string.format(msg,p,...) or msg)
end
end
local function report(...)
env.report(...)
wasding = true
end
local function next_isearch_forward( startPoint )
local nextpoint = buffer:search_forward_to( startPoint, isearch_fstring )
wv.log('debug','isearch got nextpoint=%s pt=%d', nextpoint, thePoint)
if nextpoint then
thePoint = nextpoint
local cx, cy = cursorpos(ptTopLeft,thePoint,wdim)
if cy >= wdim.h then local adjust = math.floor(cy - (wdim.h / 3)) wv.log('debug','isearch moved beyond screen! wdim.h=%d cy=%d adjust=%d', wdim.h, cy, adjust)
local st = buffer:forward_lines(ptTopLeft, adjust)
if st then
ptTopLeft = st
end
end
else
ding 'i-search failed'
end
cord.event.refresh()
end
cord.event.key = function(k)
wv.log('debug','edit got key=%s => %s', k, keybindings[k] )
if keybindings[k] then
cord.event[ keybindings[k] ]()
elseif type(k) == 'number' then
if k < 256 and k >= 32 then if isearch_fstring then
isearch_fstring = isearch_fstring .. string.char(k)
next_isearch_forward( thePoint )
cord.event.refresh()
else
cord.event.text(k)
end
elseif k == 27 then if isearch_fstring then
isearch_fstring = nil
cord.event.refresh()
else
return k
end
else
return k
end
end
end
local baseline = 1
local ccol = 1
local crow = 1
local function cursor_line()
return buffer[crow+baseline-1] or ''
end
local function char_at_point()
return buffer:char_at_point(thePoint)
end
local function insert_at_point(t)
buffer:insert( thePoint, t )
thePoint = thePoint + #t
if env.on.modified then
env.on.modified()
end
end
function cord.event.getPoint(ret)
ret(thePoint)
end
function cord.event.replace_marked(cbfun)
if cbfun then
local mb, me = get_marked()
if mb and me > mb then
local rc = cbfun( buffer:sub(mb,me-1) )
if rc ~= false then buffer:remove(mb,me-1)
buffer:insert(mb,rc or '')
theMark = nil
cord.event.refresh()
end
else
cbfun() end
end
end
function cord.event.kill_buffer()
if env.on.kill_buffer then
env.on.kill_buffer()
else
ding 'kill-buffer not implemented'
end
end
function cord.event.undo()
local undopoint = buffer:undo()
if undopoint then
thePoint = undopoint
if env.on.modified then
env.on.modified()
end
cord.event.refresh()
else
ding 'No further undo information'
end
end
function cord.event.isearch_forward()
if isearch_fstring then next_isearch_forward( thePoint + 1 )
else isearch_fstring = ''
end
cord.event.refresh()
end
function cord.event.ctrlg()
isearch_fstring = nil
theMark = nil
cord.event.refresh()
end
function cord.event.isearch_backward()
ding 'isearch_backward not implemented'
end
function cord.event.jumptotag()
local w = buffer:word_at_point(thePoint > 1 and (thePoint-1) or thePoint)
local w2 = buffer:word_at_point( buffer:end_of_line(thePoint)-1 )
local _ = env.on.tagActivated and env.on.tagActivated( w, w2 )
end
function cord.event.query_citations()
local _ = env.on.queryCitations and env.on.queryCitations()
end
function cord.event.save_buffer()
if env.on.save then
report('major','Saving buffer')
local one = buffer:asOneString()
if true then local f = io.open(string.format('/tmp/pls-r-%f.txt',Nylon.uptime()),"w")
f:write(one)
f:close()
end
env.on.save( one )
report('major','Buffer saved')
else
report('warn','No buffer save handler')
end
end
function cord.event.insert_at_point( t )
insert_at_point( t )
cord.event.refresh()
end
local function do_yank( special )
if env.on.yank and env.on.yank( special ) then
return
end
local yanktext = killring_getyanktext()
insert_at_point( yanktext )
cord.event.refresh()
end
function cord.event.yank()
do_yank()
end
function cord.event.special_yank() do_yank(true)
end
function cord.event.text(t)
if t < 256 then
local theString = string.char(t)
if theMark and edopts.replaceMark then
local markbeg = get_marked()
cord.event.replace_marked( function()
thePoint = markbeg + 1
return theString
end)
else
insert_at_point(theString)
end
save_col = true
else
ding("unrecognized key=%d",t)
end
cord.event.refresh()
end
function cord.event.indent_for_tab_command()
insert_at_point(' ')
cord.event.refresh()
end
function cord.event.delete_char()
buffer:remove(thePoint)
cord.event.refresh()
end
local lastkill
local function killring_addorappend(t)
wv.log('debug','killring_addorappend lastkill=%s',lastkill)
if lastkill then
killring_append(t)
else
killring_add(t)
end
end
function cord.event.kill_line()
if buffer:char_at_point(thePoint) == '\n' then
buffer:remove(thePoint)
killring_addorappend '\n'
else
local eolPoint = buffer:end_of_line(thePoint)
if buffer:char_at_point(eolPoint) == '\n' then
eolPoint = eolPoint - 1
end
if eolPoint ~= thePoint then
local deleted = buffer:sub( thePoint, eolPoint )
buffer:remove( thePoint, eolPoint )
killring_addorappend( deleted )
end
end
cord.event.refresh_kill()
end
function cord.event.escape()
if isearch_fstring then
isearch_fstring = nil
cord.event.refresh()
end
if opt.oneLine then
wv.log 'oneLine edit got escape (cancel)'
local _ = env.on.cancelled and env.on.cancelled()
end
end
function cord.event.newline()
if opt.oneLine then
if env.on.save then
env.on.save( buffer:asOneString() )
end
else
buffer:insert(thePoint,'\n')
thePoint = thePoint+1
local cx, cy = cursorpos( ptTopLeft, thePoint, wdim )
if cy and (cy + 1 > wdim.h) then
local nextline = buffer:search_forward_to( ptTopLeft, '\n' )
if nextline then
ptTopLeft = nextline + 1
end
end
cord.event.refresh()
end
end
function cord.event.delete_backward_char()
if isearch_fstring then
isearch_fstring = isearch_fstring:sub(1,#isearch_fstring-1)
else
if thePoint > 1 then
thePoint = thePoint - 1
buffer:remove(thePoint)
else
ding '(delete back) beginning of buffer'
end
end
cord.event.refresh()
end
local last_col
local save_col
local function _go_if(newPoint)
if newPoint and newPoint ~= thePoint then
thePoint = newPoint
save_col = true
return newPoint
end
end
local function lfn_backward_word()
_go_if( buffer:backward_word(thePoint) )
cord.event.refresh()
end
cord.event.backward_word = lfn_backward_word
local function lfn_delete_word()
local endword = buffer:forward_word(thePoint) or (buffer:end_point()+1)
if endword and endword > thePoint then
local removed = buffer:sub(thePoint,endword-1)
killring_addorappend(removed)
buffer:remove(thePoint,endword-1)
cord.event.refresh_kill()
else
ding 'No more words (and no more promises)'
end
end
cord.event.delete_word = lfn_delete_word
function cord.event.context_menu()
local w = buffer:word_at_point(thePoint > 1 and (thePoint-1) or thePoint)
wv.log('debug','context_menu word_at_point=%s',w)
local function consume_word()
lfn_backward_word()
lfn_delete_word()
end
local _ = env.on.contextmenu and env.on.contextmenu(w,{ consume_word = consume_word })
cord.event.refresh()
end
local function restore_col()
if last_col then
local eolpoint = buffer:end_of_line(thePoint)
wv.log('debug','eolpoint=%d thePoint=%d last_col=%d',eolpoint,thePoint,last_col)
if (eolpoint - thePoint) > last_col then
thePoint = thePoint + last_col
else
thePoint = eolpoint
end
end
end
function cord.event.set_mark_command()
theMark = thePoint
cord.event.refresh()
end
local function _copy_marked_and(fn)
local mb, me = get_marked()
if me and me > mb then
local removed = buffer:sub(mb,me-1)
killring_addorappend(removed)
if fn then
fn(mb,me)
end
theMark = nil
cord.event.refresh()
else
ding 'No selection - use [C-x Spc] to mark'
end
end
function cord.event.kill_ring_save()
_copy_marked_and()
end
function cord.event.kill_region()
_copy_marked_and( function(mb,me)
buffer:remove(mb,me-1)
thePoint = mb
end)
end
function cord.event.next_line()
local point = ptTopLeft
local nextLineFromTop
local cx, cy
local thePointStart = thePoint
local curfun = watch_cursor_pos(ptTopLeft, thePoint, wdim)
for l, col, nl in walkFun(buffer,ptTopLeft,wdim.w) do
if not cx then
cx, cy = curfun( l, col, nl )
end
local toDraw = nl and (nl - col + 1) or (#l-col+1)
point = point + toDraw
if nl then
if not nextLineFromTop then
nextLineFromTop = point + 1
end
if point > thePoint then
if wordWrap and point >= (thePoint + wdim.w) then
thePoint = thePoint + wdim.w
break else
thePoint = point
end
wv.log('debug','point=%d cy=%d nextLineFromTop=%d',point,cy,nextLineFromTop)
assert(cy) if cy + 1 >= wdim.h then
ptTopLeft = nextLineFromTop
end
restore_col()
break
end
end
end
if thePoint == thePointStart then
thePoint = buffer:end_point() + 2
end
cord.event.refresh()
end
function cord.event.previous_line()
if thePoint == 1 then
ding 'no previous line'
return
end
nextpoint = buffer:beginning_of_line(thePoint)
if (nextpoint <= 2) and buffer:char_at_point_dec(1) == 10 then
thePoint = 1
restore_col()
else
if nextpoint > 1 then
local bol = buffer:beginning_of_line(nextpoint-1)
if bol < ptTopLeft then
ptTopLeft = bol
end
thePoint = bol
restore_col()
else
ding 'no previous line'
end
end
cord.event.refresh()
end
local function _backward_char()
if thePoint > 1 then
thePoint = thePoint - 1
save_col = true
end
end
function cord.event.backward_char()
if thePoint > 1 then
_backward_char()
cord.event.refresh()
else
ding 'At beginning of buffer'
end
end
local function _forward_char()
local ep = buffer:end_point()
if thePoint <= ep then
thePoint = thePoint + 1
save_col = true
elseif thePoint == (ep + 1) then
if buffer:char_at_point(ep) ~= '\n' then
thePoint = thePoint + 1
save_col = true
end
else
ding 'End of buffer'
end
end
function cord.event.forward_char()
_forward_char()
cord.event.refresh()
end
function cord.event.capitalize_word()
local w, bow, eow = buffer:word_at_point(thePoint)
if bow < thePoint then
w = w:sub( thePoint-bow+1 )
bow = thePoint
end
if w then
buffer:replace( bow, bow, w:sub(1,1):upper() )
buffer:replace( bow+1, eow, w:sub(2):lower() )
end
cord.event.forward_word()
end
function cord.event.upcase_word()
local w, bow, eow = buffer:word_at_point(thePoint)
if w then
buffer:replace( bow, eow, w:upper() )
end
cord.event.forward_word()
end
function cord.event.downcase_word()
local w, bow, eow = buffer:word_at_point(thePoint)
if w then
buffer:replace( bow, eow, w:lower() )
end
cord.event.forward_word()
end
local function _forward_to(pat)
return _go_if( buffer:search_forward_to(thePoint,pat) )
end
local function _forward_past(pat)
return _go_if( buffer:search_forward_past(thePoint,pat) )
end
local function _backward_to(pat)
return _go_if(buffer:search_backward_to(thePoint,pat))
end
local function _backward_past(pat)
return _go_if(buffer:search_backward_past(thePoint,pat))
end
function cord.event.move_beginning_of_line()
_go_if( buffer:beginning_of_line(thePoint) )
cord.event.refresh()
end
function cord.event.move_end_of_line()
local eol = buffer:end_of_line(thePoint)
wv.log('debug','move_end_of_line eol=%d',eol)
if eol == buffer:end_point() then
eol = eol + 1
end
_go_if( eol )
cord.event.refresh()
end
function cord.event.forward_word()
local fw = buffer:forward_word(thePoint) or (buffer:end_point() + 1)
_go_if( fw )
cord.event.refresh()
end
function cord.event.scroll_down_command()
local start = (thePoint < buffer:end_point()) and thePoint or (buffer:end_point()-1)
local prior = buffer:backward_lines(start,wdim.h)
if prior and prior ~= thePoint then
thePoint = prior
restore_col()
local st = buffer:backward_lines(ptTopLeft,wdim.h)
if st then ptTopLeft = st end
else
ding()
end
cord.event.refresh()
end
function cord.event.scroll_up_command()
local s = NylonSysCore.uptime()
local nextpoint = buffer:forward_lines(thePoint,wdim.h)
if nextpoint and nextpoint ~= startPoint then
thePoint = nextpoint
restore_col()
local st = buffer:forward_lines(ptTopLeft,wdim.h)
if st then
ptTopLeft = st
end
end
local e = NylonSysCore.uptime()
wv.log('debug','time to page down=%f',(e-s))
cord.event.refresh()
end
function cord.event.beginning_of_buffer()
ptTopLeft = 1
thePoint = 1
save_col = true
cord.event.refresh()
end
function cord.event.end_of_buffer()
local b = NylonSysCore.uptime()
local ep = buffer:end_point()
local top = buffer:backward_lines(ep,wdim.h-2)
if top then
local prevline = buffer:backward_lines(top-1,1)
wv.log('debug','eob top=%s prevl=%s',top,prevline)
if prevline then
ptTopLeft = prevline
else
ptTopLeft = 1
end
else
ptTopLeft = 1
end
thePoint = ep + (buffer:char_at_point_dec(buffer:end_point())==10 and 1 or 2)
save_col = true
local e = NylonSysCore.uptime()
wv.log('debug','time for end_of_buffer=%f ptl=%d',(e-b),ptTopLeft)
cord.event.refresh()
end
local function do_redraw()
pdcur_draw_window( wdim )
local curx,cury = cursorpos(ptTopLeft,thePoint,wdim)
if curx then
w:move(wdim.y+cury, wdim.x+curx)
else
local ep = buffer:end_point()
if thePoint > ep then
local curx, cury = cursorpos( ptTopLeft, ep, wdim)
cury = cury or 0
curx = curx or 0
if (thePoint > ep+1) or buffer:char_at_point(ep)=='\n' then
w:move( wdim.y+cury+1, wdim.x )
else
w:move( wdim.y+cury, wdim.x+curx+1 )
end
end
env.report( 'warn', string.format('sz=%d pt=%d tl=%d (end of buffer)', ep, thePoint, ptTopLeft ))
end
if save_col then
last_col = curx
save_col = false
end
end
cord.event.redraw = function(ondone)
wv.log('debug','window redraw, %dx%d@%d,%d', wdim.w, wdim.h, wdim.x, wdim.y)
do_redraw()
w:refresh()
w:redraw()
if ondone then
ondone()
end
end
while true do
do_redraw()
w:refresh()
local waskill = false
wasding = false
wait_first_event( cord, {
refresh=function( force )
if force then
w:redraw()
end
end,
refresh_kill=function()
waskill = true
end,
})
lastkill = waskill
end
end
return {
entryfn_edit = entryfn_edit
}