local ARGS = { ... }
if not string.find(arg[-1],'lua523r') then
table.move(package.searchers, 1, #package.searchers, 2)
package.searchers[1] = function(...)
print('searching... ', ...)
return
end
end
require 'clibs'
local Nylon = require 'nylon.core'()
wv = require 'nylon.debug' { name = 'pls-main' }
wv.log('debug', 'pls-main created; syscore.addCallback=%s', type(NylonSysCore.addCallback))
local Sqlite = require 'sqlite'
package.path = '../nylabus/?.lua;' .. package.path
glOpts = {
space = 'ncr'
}
if ARGS[1] then
glOpts.space = ARGS[1]
end
local Theme = require 'pls-theme'
local plugin = {
fixLine = function() end,
onEditRecord = function() end,
}
pcall( function()
plugin = loadfile( 'space/' .. glOpts.space .. '/plugin.lua' ){
Theme = Theme
}
end)
local VLINE = tonumber(Pdcurses.Lines.vline)
local HLINE = string.byte('-',1) local HLINE = tonumber(Pdcurses.Lines.hline)
local WINDOWS = (NylonOs and NylonOs.IsWindows())
wv.log('debug', 'color_pairs=%d', Pdcurses.Static.color_pairs() )
local json = require 'JSON' local Winman = require 'pls-winman'
local Numword = require 'pls-numword'
local Service
local ok, err = pcall( function() Service = require 'nylaservice' end )
if not ok then
wv.log('debug','could not load nylaservice, e=%s', err )
end
local gIdUser = 1
local cord_app
if arg[1] then
recid = tonumber(arg[1])
end
local dbname = ( 'space/' .. (glOpts.space) .. '/notes.db' )
local db = Sqlite:new( dbname )
if not db then
wv.log('error', 'db.a01 could not open name=%s', dbname)
error 'no database'
else
wv.log('debug', 'db open db=%s db.db = %s', tostring(db), type(db.db))
end
local lastnote = db:selectOne('select ROWID from note where dt_modified=(select MAX(dt_modified) from note)')
if not lastnote then
local rc = db:exec('insert into note (detail,title,dt_created,dt_modified,id_user) values (?,?,DATETIME("NOW"),DATETIME("NOW"),?)',
'Welcome to PLS Notes', 'Welcome - First Record', gIdUser)
lastnote = db:selectOne('select ROWID from note where dt_modified=(select MAX(dt_modified) from note)')
end
local recid = lastnote.ROWID
local Buffer = require 'pls-buffer'
local keyhandler
local curses_started
local function start_curses()
if curses_started then
Pdcurses.Static.refresh()
else
Pdcurses.Static.noecho()
Pdcurses.Static.start_color()
Pdcurses.Static.raw()
curses_started = true
end
end
local function end_curses()
if curses_started then wv.log('debug', 'stopping curses end_curses()')
Pdcurses.Static.endwin()
end
end
local function curses_init()
local screen = Pdcurses.Static.initscr()
start_curses()
local ncols, nrows = screen:getmaxx(), screen:getmaxy()
return { screen = screen, ncols = ncols, nrows = nrows }
end
local env = { curses = curses_init() }
local function shell_cmd( fmt, ... )
Pdcurses.Static.endwin()
os.execute( string.format('powershell -noninteractive /c "%s"'
,string.format(fmt,...) ) )
Pdcurses.Static.noecho()
env.curses.screen:redraw()
env.curses.screen:refresh()
Winman.Refresh()
end
local function simple_shell_cmd( fmt, ... )
Pdcurses.Static.endwin()
os.execute( string.format(fmt,...) )
Pdcurses.Static.noecho()
env.curses.screen:redraw()
env.curses.screen:refresh()
Winman.Refresh()
end
local function WindowDim( x, y, w, h )
return { x = x, y = y, w = w, h = h }
end
function centerwindow(env,h,w)
local x = (env.curses.ncols - w)/2
local y = (env.curses.nrows - h)/2
return h, w, y, x
end
local function withFocusAttrib( mw, fun )
Theme.with.focuswinborder( Winman.isFocused(mw) and mw.win, fun )
end
function ui_yesno(cord,env,prompt,default)
local wid = (#prompt)+2
wid = wid < 14 and 14 or wid
local w = Pdcurses.Window(centerwindow(env,4,wid))
local function showopt( hiliteyes )
Theme.with.inverse( hiliteyes and w, function()
w:mvaddstr(2,2,'[Y]es')
end)
Theme.with.inverse( (not hiliteyes) and w, function()
w:mvaddstr(2,8,'[N]o')
end )
w:refresh()
end
default = default ~= nil and default or true
local mw
local function drawme()
withFocusAttrib( mw, function() w:stdbox_() end )
w:mvaddstr(1,1,prompt)
showopt(default)
end
local removeme
local yes = Nylon.self:sleep_manual(
function(wakefun)
mw = Winman.win:new{ win = w, on = { key = function(k)
local s = (type(k) == 'number' and k < 256 and string.char(k))
wv.log('debug','yesno got key=%s/%s',k,s)
if s == 'n' or s == 'N' then
wakefun(false)
elseif s == 'y' or s == 'Y' then
wakefun(true)
elseif k == 27 then wakefun(false)
elseif default and k == 10 then wakefun(default)
end
end,
resized = function()
drawme()
end} }
drawme()
removeme = Winman.push_modal(mw)
end)
showopt( yes )
w:refresh()
Nylon.self:sleep(0.15)
removeme()
return yes
end
function ui_oneline(env,prompt,opt)
opt = opt or {}
local cord = Nylon.self
local wid = type(opt)=='table' and opt.w or math.floor(env.curses.ncols * 0.8)
local w = Pdcurses.Window(centerwindow(env,3,wid))
local function draw()
Theme.with.inverse( w, function()
w:box( VLINE, HLINE )
w:mvaddstr(0, 3, '[ ' .. prompt .. ': ]')
w:mvaddstr(1, 1, string.rep(' ', wid-2))
end)
w:refresh()
end
local ed = require 'pls-ed'
local bbuffer = Buffer.withText( (type(opt)=='string') and opt or opt.text or '')
local e = setmetatable( { w = w,
on = {}
}, { __index = env } )
local editcord = Nylon.cord( 'editoneline', ed.entryfn_edit, e, bbuffer,
{ wdim = WindowDim(1,1,wid-2,1),
theme = { text = 'inverse' },
oneLine = true } )
if opt and opt.text and opt.replace then
if true then
editcord.event.move_end_of_line()
Nylon.self:sleep(0.03)
editcord.event.set_mark_command()
Nylon.self:sleep(0.03)
editcord.event.move_beginning_of_line()
end
end
local mw = Winman.win:new{ win = w, on = { key = function(k)
editcord.event.key(k)
end,
resized = function()
draw()
editcord.event.redraw()
end} }
local removeme = Winman.push_modal( mw )
draw()
local edited = Nylon.self:sleep_manual(function(wakefun)
function e.on.save( text )
wv.log('debug','got save oneline text=%s',text)
wakefun( text )
end
function e.on.cancelled()
wv.log('debug','cancelled oneline text edit')
wakefun()
end
end)
removeme()
editcord.event._shutdown()
wv.log('debug','got edited text=%s',edited)
return edited
end
local function picklist( title, query_sql, ... )
local on_callbacks = {}
if type(title) == 'table' then
if title.on then
on_callbacks = title.on
end
title = title.title
end
local records = {}
local function run_db_query(...)
local ok, err = pcall( function(...)
records = db:selectMany( query_sql, ... )
end, ...)
if not ok then
wv.log('error','error running SQL query=%s', tostring(err))
end
end
run_db_query(...)
local width = math.floor(env.curses.ncols/3)
local height = math.floor(env.curses.nrows*2/3)
if height > #records + 2 then
height = #records + 2
end
local dim = WindowDim(env.curses.ncols-width,0,width,height)
local w = Pdcurses.Window(dim.h, dim.w, dim.y, dim.x)
local active = #records > 0 and 1 or 0
local theTop = 0
local mw
local sstring
local function draw()
if (dim.w < 2) or (dim.h <2) then
return
end
local maxtextlen = dim.w - 2
withFocusAttrib( mw, function()
w:box(VLINE, HLINE)
w:mvaddstr(0,3,title)
end )
for row = 1, dim.h-2 do
local drawingRecord = row + theTop
local r = records[drawingRecord]
if not r then break end
local text = string.format("%.10s %5s %s", r.dt_modified, Numword.to_s(r.ROWID),
r.title):sub(1,maxtextlen)
Theme.with.inverse( drawingRecord == active and w, function()
local s,e
if sstring then
s,e = text:lower():find(sstring)
end
if s then
w:mvaddstr(row,1,text:sub(1,s-1))
Theme.with.classicmenu( w, function()
w:addstr(text:sub(s,e))
end)
Theme.with.inverse( drawingRecord == active and w, function()
w:addstr(text:sub(e+1))
end)
else
w:mvaddstr(row,1,text)
end
end )
if #text < maxtextlen then
w:addstr( string.rep(' ',maxtextlen-#text) )
end
end
if sstring then
Theme.with.classicmenu( w, function()
w:mvaddstr(dim.h-1,4,'[i-search: ' .. sstring .. ' ]')
end)
end
w:refresh()
end
local removewin
local cord = Nylon.cord('picklist',
function(cord,args)
function cord.event.beginning_of_buffer()
theTop = 0
active = 1
draw()
end
function cord.event.end_of_buffer()
active = #records
theTop = active - (dim.h-2)
draw()
end
function cord.event.scroll_up_command()
active = active + (dim.h-2)
if active > #records then
active = #records
end
if active > theTop + (dim.h-2) then
theTop = active - (dim.h-2)
end
draw()
end
function cord.event.next_line()
if active < #records then
active = active + 1
if active > theTop + (dim.h-2) then
theTop = active - (dim.h-2)
end
draw()
end
end
function cord.event.scroll_down_command()
active = active - (dim.h-2)
if active < 1 then
active = 1
end
if (active-1) < theTop then
theTop = (active-1)
end
draw()
end
function cord.event.previous_line()
if active > 1 then
active = active - 1
if (active-1) < theTop then
theTop = (active-1)
end
draw()
end
end
local done
function cord.event.kill_buffer()
done = true
removewin()
wv.log('debug','close picklist, on_callbacks.closed=%s',on_callbacks.closed)
local _ = on_callbacks.closed and on_callbacks.closed()
end
while true do
cord:sleep(20)
if done then break end
run_db_query(table.unpack(args))
draw()
end
end, { ... } )
local function isearch_search_from(sstring,from,dir)
dir = dir or 1
local nrecords = #records
if nrecords < 1 then wv.log('error','no search records?'); return end
for j = from, from+(nrecords*dir), dir do
local i = ((j-1) % nrecords)+1
if string.find(records[i].title:lower(),sstring) or
string.find(Numword.to_s(records[i].ROWID):lower(),sstring) then
active = i
return
end
end
end
local function isearch_find_next(str,dir)
dir = dir or 1
isearch_search_from(sstring,active+dir,dir)
draw()
end
local function isearch_on_string_update(sstring)
isearch_search_from(sstring,active)
draw()
end
mw = Winman.win:new{ win = w, on = {
resized = function(newdim)
dim = (newdim or dim)
wv.log('debug','picklist resized, %dx%d@%d,%d', dim.w, dim.h, dim.x, dim.y)
draw()
end,
key = function(k)
wv.log('debug','picklist got key=%s',k)
if type(k) == 'string' and cord:has_event(k) then
cord.event[k]()
elseif k == 10 then local _ = records and records[active] and cord_app.event.OpenRecord( records[active].ROWID )
elseif k == 46 then if not sstring then
cord.event.kill_buffer()
local _ = records and records[active] and cord_app.event.OpenRecord( records[active].ROWID )
else
sstring = sstring .. string.char(k)
isearch_on_string_update( sstring )
end
elseif k == 8 and sstring then
sstring = sstring:sub(1,#sstring-1)
isearch_on_string_update(sstring)
elseif k == 19 or k == 18 then if sstring then
isearch_find_next(sstring, (k == 19) and 1 or -1) else
sstring = '' draw()
end
else
if sstring then
if type(k) == 'number' and k >= 32 and k <= 128 then
sstring = sstring .. string.char(k)
isearch_on_string_update(sstring)
elseif k == 27 or k == 7 then sstring = nil
draw()
end
else
return k
end
end
end } }
removewin = Winman.push( mw )
end
local function args_concat( ... )
local t = { ... }
return table.concat(t)
end
local function menuOptions( keys )
menutext = {}
for _,v in pairs(keys) do
local key = type(v)=='string' and v:sub(1,1) or v[1]
local str = type(v)=='string' and v:sub(2) or v[2]
table.insert( menutext, args_concat( '[', key, ']', str ) )
end
return menutext
end
local function MakeMenu( keys )
local menuText = menuOptions(keys)
local text = args_concat( ' ', table.concat(menutext, ' '), ' ' )
local menuwin = Pdcurses.Window(1,#text,0,0)
local function draw()
Theme.with.classicmenu( menuwin, function()
menuwin:mvaddstr(0,0,text)
end)
menuwin:refresh()
end
return menuwin, draw
end
local function MakeMenuVert( keys )
local menuText = menuOptions(keys)
local maxlen = 0
for i, v in ipairs(menuText) do
v = args_concat(' ', v, ' ')
menuText[i] = v
maxlen = (#v > maxlen) and #v or maxlen
end
wv.log('debug','MakeMenuVert: maxlen=%d #menuText=%d', maxlen, #menuText)
local text = args_concat( ' ', table.concat(menutext, ' '), ' ' )
local menuwin = Pdcurses.Window(#menuText,maxlen,1,0)
local function draw()
Theme.with.classicmenu( menuwin, function()
for i, v in ipairs(menuText) do
menuwin:mvaddstr(i-1,0,v)
menuwin:mvaddstr(i-1,#v, (' '):sub(1,maxlen-#v))
end
end)
menuwin:refresh()
end
return menuwin, draw
end
local function menuedFunctions( invokingCord, ftab, opt )
local tOptions = {}
for k, _ in pairs(ftab) do
table.insert( tOptions, k )
end
local menuwin
local drawmenu
if opt and opt.vert then
menuwin, drawmenu = MakeMenuVert(tOptions)
else
menuwin, drawmenu = MakeMenu(tOptions)
end
local grabbed = {}
for k, v in pairs(ftab) do
local optkey
if type(k) == 'table' then
optkey = string.byte(k[1],1)
else
optkey = string.byte(string.lower(string.sub(k,1,1)),1)
end
grabbed[ optkey ] = v
end
local managed = Winman.win:new{ win = menuwin,
on = { key = function(k) invokingCord.event.exitContextMenu(k) end,
resized = function() drawmenu() end
} }
local winman_remove = Winman.push_modal( managed )
local key = invokingCord:sleep_manual( function(wakefun)
invokingCord.event.exitContextMenu = function(k)
wv.log('debug','app key grab, k=%s',k)
wakefun(k)
end
end )
winman_remove()
if grabbed[key] then
wv.log('debug','app grab key 2=%s',key)
grabbed[key]()
end
end
local function getrecid(recid)
recid = recid and (type(recid)=='number' or recid:find('^%d')) and recid or Numword.to_i(recid)
return recid
end
local function FindRecordById(recid1)
local altrecid
if type(recid1) == 'table' then
altrecid = recid1[2]
recid1 = recid1[1]
wv.log('debug','got rec/alt=%s/%s',recid1, altrecid)
end
local recid = getrecid(recid1)
wv.log('debug','getrecid=%s recid1=%s',recid, recid1)
if not recid and altrecid then
recid = getrecid(altrecid)
end
if not recid then
if not recid1 then
recid = ui_oneline(env,'Enter record id', {w=30})
if not recid then return
end
recid1 = recid
local r2 = getrecid(recid)
wv.log('debug','getrecid oneline=%s r2=%s',recid, r2)
recid = r2
end
end
local function selectBestMatch(set,id)
id = id:lower()
local strmatch = ':' .. id .. '%W'
local best
for _, r in pairs(set) do
local downtitle = r.title:lower()
local hasExact = string.find( downtitle, ':' .. id .. '$')
wv.log('debug','selectBestMatch match=/%s/ hasExact=%s title="%s" r=%s best=%s', strmatch, hasExact, r.title, r, best)
if hasExact then best = r
else
best = best or (string.find( downtitle, strmatch ) and r) end
end
return best or set[1]
end
local record
if recid then record = db:selectOne('select rowid, title, detail, dt_created, dt_modified from note where rowid=?', tonumber(recid) )
end
if record then
return record, false, recid1
else
wv.log('debug','looking for title like=%s',recid1)
records = db:selectMany('select rowid, title, detail, dt_created, dt_modified from note where title like ?', '%:' .. recid1 .. '%' )
record = selectBestMatch( records, recid1 )
record = record or db:selectOne('select rowid, title, detail, dt_created, dt_modified from note where title like ?', '%' .. recid1 .. '%' )
return record, true, recid1
end
end
local Keys = require 'pls-keys'
local on_save_scan_citations = require 'on-save-scan-citations'
local gOpenRecords = {}
local function make_editor( env, record )
local ed = require 'pls-ed'
if record.ROWID and gOpenRecords[record.ROWID] then
wv.log('abnorm','record[%s] already open', Numword.to_s(record.ROWID))
local win = gOpenRecords[record.ROWID]
Winman.this_window_to_primary(win)
return
end
local nrows, ncols = env.curses.nrows, env.curses.ncols
local dim = WindowDim( 0, 0, math.floor(ncols/2), env.curses.nrows )
local currentRev = 0
local function setCurrentRev()
if record.ROWID then
local found = db:selectOne('SELECT COUNT(*) as rev from patch where id_note=?',record.ROWID)
currentRev = found and tonumber(found.rev) or currentRev
end
end
setCurrentRev()
local win = Pdcurses.Window( dim.h, dim.w, dim.y, dim.x )
local mw = { win = win }
local function drawbox()
withFocusAttrib( mw, function()
win:box( tonumber(VLINE), HLINE ) end )
end
local bbuffer
local function settitle()
local recword
if record.ROWID then
recword = Numword.to_s(record.ROWID)
else
recword = "*NEW*"
end
local ttext = ' ' .. record.title:sub(1,dim.w-16) .. ' [ ' ..
recword .. '.' .. currentRev .. (bbuffer and bbuffer:isModified() and '* ] ' or ' ] ')
win:move(0,1)
withFocusAttrib( mw, function()
win:hline(HLINE,dim.w-2)
win:mvaddstr(0,(dim.w-2-#ttext),ttext)
end )
end
settitle()
local cord = Nylon.self
local function editTitle()
wv.log('debug','got call to editTitle')
local starttext = record.title == 'new record' and '' or record.title
local title = ui_oneline(env,'Edit Title', { text=starttext })
if title then
record.title = title
settitle()
win:refresh()
if record.ROWID then
db:retryexec('update note set title=?,dt_modified=DATETIME("NOW") where rowid=?', title, record.ROWID)
end
end
return true
end
local laststatus
local function winstatus( sttype, data )
local prevx, prevy = win:getx(), win:gety()
local statusrow = dim.y + dim.h - 1
laststatus = data
local crdate = '???'
local update = '???'
if record and record.dt_created then
local d = record.dt_created
crdate = d:sub(3,4) .. d:sub(6,7) .. d:sub(9,10)
end
if record and record.dt_modified then
local d = record.dt_modified
update = d:sub(3,4) .. d:sub(6,7) .. d:sub(9,10)
end
data = string.format('date=%s/%s %s', crdate, update, data)
win:move(statusrow,1)
withFocusAttrib( mw, function()
win:hline(HLINE,dim.w-2)
end )
win:mvaddstr(statusrow,3,'[')
win:addstr(data)
win:addstr(']')
win:move(prevy, prevx)
win:refresh()
end
local titlecache = {}
local editcordrefresh = nil
local function plsed_drawline( win, x, y, text, markBeg, markEnd )
if #text > (dim.w - 2) then
win:mvaddstr( y, x, text:sub(1,dim.w-2) )
return dim.w-2
end
if not markBeg then
local b,e,hdr,htext = text:find("^(h%d%.)(.*)") if not b then
win:mvaddstr( y, x, text )
else
win:mvaddstr( y, x, hdr )
Theme.with.heading( win, function()
win:addstr(htext)
end)
end
else
local endPoint = markEnd > (dim.w-2) and (dim.w-2) or markEnd
win:move(y,x)
win:addstr( text:sub(1,markBeg-1) )
Theme.with.inverse( win, function()
win:addstr( text:sub(markBeg,endPoint) )
end )
win:addstr( text:sub(endPoint+1,dim.w-2) )
end
local drawn = #text
local remain = (dim.w -2) - drawn
local s,e,tag = string.find(text,':(%w+)$')
if s then
if Numword.isvalid( tag ) then
if not titlecache[tag] then
local rec = db:selectOne( 'select title from note where rowid=?',
Numword.to_i(tag) )
titlecache[tag] = rec and rec.title
end
local append = titlecache[tag] or '???'
local todraw = string.sub( (' ' .. append), 1, remain)
Theme.with.autotext( win, function()
win:addstr( todraw ) end)
return drawn + #todraw
end
end
plugin.fixLine(text, win)
end
local e = setmetatable( { w = win,
report = winstatus,
on = {}
}, { __index = env } )
bbuffer = Buffer.withText( record.detail )
local editordim = WindowDim(1,1,dim.w-2,dim.h-2)
local cord_edit = Nylon.cord( 'edit', ed.entryfn_edit, e, bbuffer,
{ wdim = editordim,
plug = {
drawline = plsed_drawline
}
} )
editcordrefresh = function() cord_edit.event.refresh() end
local removewin
function e.on.kill_buffer()
wv.log('debug','got kill buffer removewin=%s',removewin)
if record.ROWID then
gOpenRecords[record.ROWID] = nil
end
removewin()
end
function e.on.contextmenu(wordAtPoint, opt)
wv.log 'got on.contextmenu callback'
local function show_insert_menu()
local function insert_link()
local record, makenew, recid1 = FindRecordById(wordAtPoint)
wv.log('debug', 'frbid record=%s makenew=%s recid1=%s', record, makenew, recid1 )
if record then
local id = record.ROWID
local nw = Numword.to_s(id)
wv.log( 'debug', 'got record, id=%s / nw=%s', id, nw )
local text = string.format(':%s\n', nw)
local _ = opt and opt.consume_word and opt.consume_word()
cord_edit.event.insert_at_point( text )
end
end
menuedFunctions( cord_edit, {
XML = function() end,
Nonsense = function() end,
Link = insert_link
}, { vert = true })
end
local function insert_yank_with_tag( tag )
cord_edit.event.insert_at_point( '\n<' .. tag .. '>\n' )
cord_edit.event.yank()
Nylon.self:sleep(0.01) cord_edit.event.insert_at_point( '</' .. tag .. '>\n' )
end
menuedFunctions( cord_edit,
{
Insert = show_insert_menu,
Paste = function()
menuedFunctions( cord_edit, {
Pre = function() insert_yank_with_tag 'pre' end,
Quote = function() insert_yank_with_tag 'blockquote' end
}, { vert = true })
end
})
end
local function possibly_transform_clipboard_text( text )
wv.log('debug', 'transform clibpboard=[[%s]]', text)
if string.find(text,'^(http://.*)') or string.find(text,'^(https://.*)') then
return string.gsub(text, ' ', '%%20')
else
return text
end
end
function e.on.yank(special)
if not NylonOs then
return
end
local puthtm
local cbrc = {}
NylonOs.Static.getclipboard_ext(
function(tyyp, content)
cbrc = { tyyp = tyyp, content = content }
end )
if cbrc.tyyp then
local tyyp = cbrc.tyyp
local content = cbrc.content
if special and tyyp == 'html' then
local _, _, m = content:find '<!%-%-StartFragment%-%->(.*)<!%-%-EndFragment%-%->'
if m then
cord_edit.event.insert_at_point( m ) puthtm = true
end
end
if tyyp == 'text' and (not puthtm) then
cord_edit.event.insert_at_point( possibly_transform_clipboard_text(content) )
end
if tyyp == 'CF_HDROP' then
(function()
local File = require 'filelib'
local Table = require 'extable'
local fileURLsRaw = Service.archiver.archiveFileList( content )
local fileURLs = Table.map( function(url)
local base = File.leaf(url)
return string.format('"%s":%s', base, url)
end, fileURLsRaw )
local itext
if #content > 1 then
itext = '* ' .. table.concat( fileURLs, '\n* ' )
else
itext = fileURLs[1]
end
cord_edit.event.insert_at_point( itext )
end)()
end
if tyyp == 'image/png' then
dbimg = dbimg or (Sqlite:new 'images.db')
dbimg:exec('insert into image (raw) values (?)', content)
local rowid = dbimg:lastRowId()
cord_edit.event.insert_at_point(
string.format("!:img:%s!\n", Numword.to_s(rowid) ) )
local copy = dbimg:selectOne('select raw from image where ROWID=?', rowid);
if copy then
wv.log('debug','Got image out, sz=%d', #copy.raw)
else
wv.log('error','cant read saved image??')
end
end
end
return true;
end
function e.on.save( text )
wv.log('debug','save new record=%d text=%s',record.ROWID,text:sub(1,40))
local prevText = (currentRev > 0) and record.detail or ''
local Diff = require 'diff_match_patch'
local patches = Diff.patch_make(text,prevText)
local patchtext = Diff.patch_toText(patches)
local rpatches = Diff.patch_make(prevText,text)
local rpatchtext = Diff.patch_toText(rpatches)
if #patchtext > 0 then
wv.log('debug','edit, patch is this (retryexec=%s):\n%s', tostring(db.retryexec), patchtext)
db:retryexec('insert into patch (id_note,dt_created,content,rcontent,id_user,revision) values (?,DATETIME("NOW"),?,?,?,(SELECT COUNT(*) from patch where id_note=?))',
record.ROWID, patchtext, rpatchtext,gIdUser, record.ROWID)
end
on_save_scan_citations( db, record.ROWID, text )
local isNew = false
if record.ROWID then
db:retryexec('update note set detail=?,dt_modified=DATETIME("NOW") where rowid=?', text, record.ROWID )
else
local rc = db:retryexec('insert into note (detail,title,dt_created,dt_modified,id_user) values (?,?,DATETIME("NOW"),DATETIME("NOW"),?)',
text, record.title, gIdUser)
record.ROWID = db:lastRowId()
gOpenRecords[record.ROWID] = mw
isNew = true
end
plugin.onEditRecord( isNew, record )
bbuffer:setUnmodified()
record.detail = text
setCurrentRev()
settitle()
end
function e.on.modified()
settitle() end
function e.on.tagActivated( tag, alttag )
wv.log('debug','tagActivated, tag=%s (alt=%s)',tag, alttag)
cord_app.event.OpenRecord{ tag, alttag }
end
function e.on.queryCitations( tag, alttag )
local nw = Numword.to_s(record.ROWID)
wv.log('debug','queryCitations, id=%s/%s', record.ROWID,nw)
picklist( ('Citations of :' .. nw .. ' ' .. record.title),
'select ROWID,dt_modified,title from note where detail like ? order by dt_modified desc limit 50', ("%:" .. nw .. "%") )
end
local function winman_keyhandler(k)
if k == Keys.control.editTitle then
cord_app:add_pending(editTitle)
else
if type(k) == 'string' then
if cord_edit:has_event(k) then
cord_edit.event[k]()
else
if k == 'mail_record' then
local recname = Numword.to_s(record.ROWID)
wv.log('debug','mailing record=%s',recname)
shell_cmd( 'mpnote-sendmail %s', recname )
elseif k == 'extract_to_new_record' then
Nylon.cord('extract_to_new_record',
function(cord)
cord:sleep_manual(
function(wake)
cord_edit.event.replace_marked(
function(marked_text)
if not marked_text then
wake()
return
end
local proposedTitle = 'New Record from: ' .. record.title
local newtitle = ui_oneline( env, 'Extracted Record Title', { text = proposedTitle })
if not newtitle then
return false
end
local r = { title = newtitle, detail = marked_text }
local rc = db:retryexec('insert into note (detail,title,dt_created,dt_modified) values (?,?,DATETIME("NOW"),DATETIME("NOW"))', r.detail, r.title)
r.ROWID = db:lastRowId()
make_editor(env,r)
wake()
return ':' .. Numword.to_s(r.ROWID) .. '\n'
end)
end )
end )
elseif k == 'insert_ref_to_new_record' then
Nylon.cord(k, function(cord)
local function doit(wakefun)
local proposedTitle = 'New Record from: ' .. record.title
local newtitle = ui_oneline( env, 'New Record Title', { text = proposedTitle })
if not newtitle then
wakefun()
return
end
local r = { title = newtitle, detail = '\n\n' }
local rc = db:retryexec('insert into note (detail,title,dt_created,dt_modified) values (?,?,DATETIME("NOW"),DATETIME("NOW"))', r.detail, r.title)
r.ROWID = db:lastRowId()
make_editor(env,r)
cord_edit.event.insert_at_point( ':' .. Numword.to_s(r.ROWID) )
end
cord:sleep_manual( doit )
end )
elseif k == 'insert_file' then
Nylon.cord('insert_file_cord',function(cord)
cord:sleep_manual( function(wake)
local fname = ui_oneline( env, 'Insert file' )
if fname then
wv.log('debug','insert_file=%s',fname)
cord_edit.event.getPoint(function(thePoint)
wv.log('debug','insert_file point=%d',thePoint)
bbuffer:insertFileAtPoint( thePoint, fname )
cord_edit.event.refresh()
wake()
end)
end
end )
end )
else
return k
end
end
else
cord_edit.event.key(k)
end
end
end
function winman_resized( newdim )
if newdim then
dim.x, dim.y, dim.w, dim.h = newdim.x, newdim.y, newdim.w, newdim.h
end
editordim.w, editordim.h = dim.w-2, dim.h-2
drawbox()
settitle()
winstatus('resized', laststatus or '-resized-')
wv.log('debug','winman_resized win@%d,%d , ed, %dx%d@%d,%d', dim.x,dim.y,editordim.w, editordim.h, editordim.x, editordim.y)
if newdim then Nylon.self:sleep_manual(function(wakefun)
cord_edit.event.redraw( wakefun )
end)
end
wv.log('debug','winman_resize done')
end
mw = Winman.win:new{
win = win,
on = { resized = winman_resized,
key = winman_keyhandler,
}
}
removewin = Winman.push( mw )
if (record.ROWID) then
gOpenRecords[record.ROWID] = mw
end
end
local function entryfn_app( cord, env )
cord.event.quit = function()
local yes = ui_yesno(cord,env,'Exit now?')
wv.log('debug','exit=%d',yes and 1 or 0)
if yes then
collectgarbage();
Pdcurses.Static.clear()
Pdcurses.Static.refresh()
Pdcurses.Static.endwin()
os.exit(1)
end
collectgarbage()
Pdcurses.Static.refresh()
end
function cord.event.save_buffers_kill_terminal()
cord.event.quit()
end
function cord.event.other_window()
Winman.other_window()
end
function cord.event.focused_window_to_primary()
Winman.focused_window_to_primary()
end
cord.event.SwapBuffers = function()
wv.log 'app cord.event.SwapBuffers'
Winman.swap_tiled()
end
cord.event.NewRecord = function( opt )
local record = { title = opt and opt.title or 'new record', detail = '\n\n' }
make_editor( env, record )
end
cord.event.OpenRecord = function(recid1)
local record, makenew, recid1 = FindRecordById(recid1)
wv.log('debug', 'frbid record=%s makenew=%s recid1=%s', record, makenew, recid1 )
if (not record) and makenew and recid1 then
local newt = tostring(recid1)
if newt:sub(1,1) == ':' then
newt = newt:sub(1,1) .. newt:sub(2,2):upper() .. newt:sub(3)
else
newt = ':' .. newt:sub(1,1):upper() .. newt:sub(2)
end
cord.event.NewRecord{ title = (newt .. ' new record') }
return
end
if record then
wv.log('debug','got record=%s',json:encode(record))
make_editor( env, record )
end
end
local lastsearch = ''
cord.event.Search = function()
local sstr = ui_oneline( env, 'Search For', { text = lastsearch, replace = true } )
if not sstr then return end
local S = require 'pls-db'
local fancysearch = S.megasearch( sstr )
wv.log('debug','fancysearch=%s',fancysearch)
lastsearch = sstr
picklist( ('Search: ' .. sstr),
'select ROWID,dt_modified,title from note where ' .. fancysearch .. string.format(' order by dt_modified desc limit %d',((env.curses.nrows-2)*6)) )
end
while true do
cord.event.grab.wait()
wv.log('debug','main window got grab key')
menuedFunctions( cord, {
New = function() cord.event.NewRecord() end,
Goto = function() cord.event.OpenRecord() end,
PushFS = function() Winman.toggle_fullscreen() end,
Quit = function() cord.event.quit() end,
[{ '/', 'Search' }] = function() cord.event.Search() end,
})
end
end
local function ding(x,p,...)
end
local command_prefix = false
local personal_prefix = false
local esc_prefix = false
local cord_eschandler=Nylon.cord('escsender',
function( cord )
local count = 1
local esccount
cord.event.gotesc = function(k)
esccount = count
cord.event.sendesc()
end
cord.event.gotnonesc = function(k)
count = count + 1
end
while true do
cord.event.sendesc.wait()
cord:sleep(0.1) if esccount == count then wv.log('debug','should send escape here')
Winman.inject_key( 9027 ) end
end
end)
local keymap_everybody = Keys.everybody
local keymap_command_prefix = Keys.command_prefix
local keymap_personal_prefix = Keys.personal_prefix
local keymap_esc_prefix = Keys.esc_prefix
local function app_keymapper( k )
local function handle_if_app_event_or_return( k )
if cord_app:has_event(k) then
cord_app.event[k]()
else
return k
end
end
local function unknown(c)
if k >= 32 and k < 128 then
ding( 'Unknown prefix command Ctrl-%s + "%c"', c, k )
else
if k < 27 then
ding( 'Unknown prefix command Ctrl-%s + Ctrl-%c', c, (k+64) )
else
ding( 'Unknown prefix command Ctrl-%s + %d', c, k )
end
end
end
if k == 27 then esc_prefix = true
cord_eschandler.event.gotesc()
return
else
cord_eschandler.event.gotnonesc()
end
if k == 9027 then k = 27
end
if esc_prefix then
esc_prefix = false
if keymap_esc_prefix[k] then
return app_keymapper( keymap_esc_prefix[k] )
end
end
if command_prefix then
command_prefix = false
if keymap_command_prefix[k] then
wv.log('debug','got cmd prefix mapped [%d=>%s]',k,keymap_command_prefix[k])
return handle_if_app_event_or_return( keymap_command_prefix[k] )
else
unknown 'X'
end
elseif personal_prefix then
personal_prefix = false
return handle_if_app_event_or_return( keymap_personal_prefix[k] )
elseif keymap_everybody[k] then
return handle_if_app_event_or_return( keymap_everybody[k] )
elseif k == 3 then personal_prefix = true
wv.log 'start personal prefix'
elseif k == 435 then wv.log 'got swap / M-s key'
cord_app.event.SwapBuffers()
elseif k == Keys.control.menu then cord_app.event.grab()
elseif k == 20 or k == 24 then command_prefix = true
else
return k end
end
local function app_unhandledkeys(k)
wv.log('debug','app unhandled key handler key=%s',k)
if k == 'jumptotag' then
cord_app.event.OpenRecord()
elseif k == 'toggle_landscape' then
Winman.toggle_landscape()
else
wv.log('debug','unhandled key/input event=%s',k)
end
end
local function cordfn_followup( cord )
while true do
local today = os.date("%y%m%d",os.time())
local fupdate = string.format('%%followup%s%%', today)
local followupCount = db:selectOne(
'select count(*) as thecount from note where title like ? or detail like ?', fupdate, fupdate)
wv.log('debug','followup search [%s] rc=%s', fupdate, json:encode(followupCount))
if followupCount and tonumber(followupCount.thecount) > 0 then
local manualClose = false
local done = false
picklist(
{ title = string.format('Follow Up %s', today),
on = {
closed = function()
wv.log('debug','followup picklist manually closed')
manualClose = true
if done then
done()
end
done = function() end
end
} },
'select ROWID, dt_modified, title from note where title like ? or detail like ?', fupdate, fupdate)
while (not done) and (today == os.date("%y%m%d", os.time())) do
wv.log('debug', 'followup picklist open; wait until closed or new day')
cord:sleep_manual( function(wakefn)
done = function()
done = false
wakefn()
end
NylonSysCore.addOneShot( 120*1000, function() if done then done() end end) end)
end
if manualClose then
wv.log('debug', 'followup picklist closed manually, wait 15min')
cord:sleep(15*60*1000) end
else
cord:sleep(20) end
end
end
Nylon.cord('followup', cordfn_followup)
Winman.create( env )
picklist(' Recent Edits ',
string.format('select ROWID,dt_modified,title from note order by dt_modified desc limit %d', (env.curses.nrows*6)))
Winman.set_input( app_keymapper, app_unhandledkeys )
cord_app = Nylon.cord('app', entryfn_app, env)
cord_app.event.OpenRecord( recid )
Nylon.run()