2021-01-12 23:11:00 +11:00
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
--
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-- THIS SOFTWARE.
2018-03-19 10:10:54 +11:00
-- 'neolithic': Text Editor
2018-03-22 13:33:52 +11:00
-- This was textedit (femto) from KittenOS 'ported' to NEO.
2018-03-19 10:10:54 +11:00
-- It also has fixes for bugs involving wide text, and runs faster due to the chars -> lines change.
local lines = {
" Neolithic: Text Editor " ,
2018-03-24 02:35:43 +11:00
" F3, F4, F1: Load, Save, New " ,
" F5, F6, ^←: Copy, Paste, Delete Line " ,
2018-03-19 10:10:54 +11:00
-- These two are meant to replace similar functionality in GNU Nano
-- (which I consider the best console text editor out there - Neolithic is an *imitation* and a poor one at that),
2018-06-12 10:29:44 +10:00
-- except fixing a UI flaw by instead adding a visible way to reset the append flag,
2018-03-19 10:10:54 +11:00
-- so the user can more or less arbitrarily mash together lines
2018-03-24 02:35:43 +11:00
" F7: Reset 'append' flag for Cut Lines " ,
" F8: Cut Line(s) " ,
2018-03-19 10:10:54 +11:00
" ^<arrows>: Resize Win " ,
" '^' is Control. " ,
2018-03-22 13:33:52 +11:00
" Wide text & clipboard supported. " ,
2018-03-19 10:10:54 +11:00
" F o r e x a m p l e , t h i s . " ,
}
-- If replicating Nano's clipboard :
-- Nano starts off in a "replace" mode,
-- and then after an action occurs switches to "append" until *any cursor action is performed*.
-- The way I have things setup is that you perform J then K(repeat) *instead*, which means you have to explicitly say "destroy current clipboard".
2018-03-24 02:35:43 +11:00
local clipsrc = neo.requireAccess ( " x.neo.pub.globals " , " clipboard " )
2018-04-26 08:58:31 +10:00
local windows = neo.requireAccess ( " x.neo.pub.window " , " windows " )
local files = neo.requireAccess ( " x.neo.pub.base " , " files " ) . showFileDialogAsync
2018-03-19 10:10:54 +11:00
2020-03-30 22:08:12 +11:00
local lineEdit = require ( " lineedit " )
2018-03-19 10:10:54 +11:00
local cursorX = 1
local cursorY = math.ceil ( # lines / 2 )
2020-03-30 22:08:12 +11:00
local ctrlFlag , appendFlag
2018-03-19 10:10:54 +11:00
local dialogLock = false
local sW , sH = 37 , # lines + 2
2018-03-28 00:40:05 +11:00
local window = windows ( sW , sH )
2018-04-26 08:58:31 +10:00
local filedialog = nil
2018-03-19 10:10:54 +11:00
local flush
2018-03-28 00:40:05 +11:00
local cbs = { }
2018-03-19 10:10:54 +11:00
local function fileDialog ( writing , callback )
2018-04-26 08:58:31 +10:00
filedialog = function ( res )
local ok , e = pcall ( callback , res )
if not ok then
e = unicode.safeTextFormat ( tostring ( e ) )
local wnd = windows ( unicode.len ( e ) , 1 , " ERROR " )
cbs [ wnd.id ] = {
wnd.close ,
wnd.span ,
e
}
2018-03-19 10:10:54 +11:00
end
end
2018-04-26 08:58:31 +10:00
files ( writing )
2018-03-19 10:10:54 +11:00
end
-- Save/Load
local function startSave ( )
dialogLock = true
fileDialog ( true , function ( res )
dialogLock = false
2018-04-26 08:58:31 +10:00
local x = " "
2018-03-19 10:10:54 +11:00
if res then
for k , v in ipairs ( lines ) do
if k ~= 1 then
2018-04-26 08:58:31 +10:00
x = x .. " \n "
end
x = x .. v
while # x >= neo.readBufSize do
res.write ( x : sub ( 1 , neo.readBufSize ) )
x = x : sub ( neo.readBufSize + 1 )
2018-03-19 10:10:54 +11:00
end
end
2018-04-26 08:58:31 +10:00
res.write ( x )
2018-03-19 10:10:54 +11:00
res.close ( )
end
end )
end
local function startLoad ( )
dialogLock = true
fileDialog ( false , function ( res )
dialogLock = false
if res then
lines = { }
local lb = " "
while true do
2018-04-26 08:58:31 +10:00
local l = res.read ( neo.readBufSize )
2018-03-19 10:10:54 +11:00
if not l then
table.insert ( lines , lb )
cursorX = 1
cursorY = 1
res.close ( )
flush ( )
return
end
local lp = l : find ( " \n " )
while lp do
lb = lb .. l : sub ( 1 , lp - 1 )
table.insert ( lines , lb )
lb = " "
l = l : sub ( lp + 1 )
lp = l : find ( " \n " )
end
lb = lb .. l
end
end
end )
end
local function getline ( y )
-- do rY first since unw
-- only requires that
-- horizontal stuff be
-- messed with...
-- ...thankfully
local rY = ( y + cursorY ) - math.ceil ( sH / 2 )
-- rX is difficult!
local rX = 1
2018-03-23 10:37:19 +11:00
local Xthold = math.max ( 1 , math.floor ( sW / 2 ) - 1 )
2020-03-30 22:08:12 +11:00
local cLine , cursorXP = unicode.safeTextFormat ( lines [ cursorY ] , cursorX )
2018-03-23 10:37:19 +11:00
rX = ( math.max ( 0 , math.floor ( cursorXP / Xthold ) - 1 ) * Xthold ) + 1
2018-03-19 10:10:54 +11:00
local line = lines [ rY ]
if not line then
return ( " ¬ " ) : rep ( sW )
end
line = unicode.safeTextFormat ( line )
2020-03-31 00:36:33 +11:00
return lineEdit.draw ( sW , line , rY == cursorY and cursorXP , rX )
2018-03-19 10:10:54 +11:00
end
local function delLine ( )
local contents = lines [ cursorY ]
if cursorY == # lines then
if cursorY == 1 then
lines [ 1 ] = " "
else
cursorY = cursorY - 1
lines [ # lines ] = nil
end
else
table.remove ( lines , cursorY )
end
return contents
end
2020-03-30 22:08:12 +11:00
local function key ( ks , kc , down )
2018-03-19 10:10:54 +11:00
if dialogLock then
return false
end
if kc == 29 then
ctrlFlag = down
return false
end
2018-03-24 02:35:43 +11:00
-- Action keys
if not down then return false end
2018-03-19 10:10:54 +11:00
if ctrlFlag then
2018-03-24 02:35:43 +11:00
-- Control Action Keys
2018-03-19 10:10:54 +11:00
if kc == 200 then -- Up
sH = sH - 1
if sH == 0 then
sH = 1
end
sW , sH = window.setSize ( sW , sH )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 208 then -- Down
2018-03-19 10:10:54 +11:00
sH = sH + 1
sW , sH = window.setSize ( sW , sH )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 203 then -- Left
2018-03-19 10:10:54 +11:00
sW = sW - 1
if sW == 0 then
sW = 1
end
sW , sH = window.setSize ( sW , sH )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 205 then -- Right
2018-03-19 10:10:54 +11:00
sW = sW + 1
sW , sH = window.setSize ( sW , sH )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 14 then -- ^Backspace
2018-03-24 02:35:43 +11:00
delLine ( )
return true
2018-03-19 10:10:54 +11:00
end
2018-03-24 02:35:43 +11:00
else
-- Non-Control Action Keys
-- Basic Action Keys
if kc == 200 or kc == 201 then -- Go up one - go up page
local moveAmount = 1
if kc == 201 then
moveAmount = math.floor ( sH / 2 )
end
cursorY = cursorY - moveAmount
if cursorY < 1 then
cursorY = 1
end
2020-03-30 22:08:12 +11:00
cursorX = lineEdit.clamp ( lines [ cursorY ] , cursorX )
2018-03-24 02:35:43 +11:00
return true
2018-06-12 10:29:44 +10:00
elseif kc == 208 or kc == 209 then -- Go down one - go down page
2018-03-24 02:35:43 +11:00
local moveAmount = 1
if kc == 209 then
moveAmount = math.floor ( sH / 2 )
end
cursorY = cursorY + moveAmount
if cursorY > # lines then
cursorY = # lines
end
2020-03-30 22:08:12 +11:00
cursorX = lineEdit.clamp ( lines [ cursorY ] , cursorX )
2018-03-24 02:35:43 +11:00
return true
end
-- Major Actions
if kc == 59 then -- F1
lines = { " " }
cursorX = 1
cursorY = 1
2018-03-19 10:10:54 +11:00
return true
2018-06-12 10:29:44 +10:00
elseif kc == 61 then -- F3
2018-03-24 02:35:43 +11:00
startLoad ( )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 62 then -- F4
2018-03-24 02:35:43 +11:00
startSave ( )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 63 then -- F5
2018-03-24 02:35:43 +11:00
clipsrc.setSetting ( " clipboard " , lines [ cursorY ] )
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 64 then -- F6
2018-03-24 02:35:43 +11:00
local tx = clipsrc.getSetting ( " clipboard " ) or " "
2018-03-19 10:10:54 +11:00
local txi = tx : find ( " \n " )
local nt = { }
while txi do
table.insert ( nt , 1 , tx : sub ( 1 , txi - 1 ) )
tx = tx : sub ( txi + 1 )
txi = tx : find ( " \n " )
end
table.insert ( lines , cursorY , tx )
for _ , v in ipairs ( nt ) do
table.insert ( lines , cursorY , v )
end
return true
2018-06-12 10:29:44 +10:00
elseif kc == 65 then -- F7
2018-03-24 02:35:43 +11:00
appendFlag = false
2020-03-30 22:08:12 +11:00
return false
2018-06-12 10:29:44 +10:00
elseif kc == 66 then -- F8
2018-03-24 02:35:43 +11:00
if appendFlag then
local base = clipsrc.getSetting ( " clipboard " )
clipsrc.setSetting ( " clipboard " , base .. " \n " .. delLine ( ) )
else
clipsrc.setSetting ( " clipboard " , delLine ( ) )
end
appendFlag = true
return true
2018-03-19 10:10:54 +11:00
end
end
2020-03-30 22:08:12 +11:00
-- LEL Keys
local lT , lC , lX = lineEdit.key ( ks , kc , lines [ cursorY ] , cursorX )
if lT then
lines [ cursorY ] = lT
2018-04-24 05:16:30 +10:00
end
2020-03-30 22:08:12 +11:00
if lC then
cursorX = lC
2018-03-19 10:10:54 +11:00
end
2020-03-30 22:08:12 +11:00
if lX == " l< " and cursorY > 1 then
cursorY = cursorY - 1
cursorX = unicode.len ( lines [ cursorY ] ) + 1
elseif lX == " l> " and cursorY < # lines then
cursorY = cursorY + 1
cursorX = 1
2020-03-31 00:36:33 +11:00
elseif lX == " w< " and cursorY ~= 1 then
2020-03-30 22:08:12 +11:00
local l = table.remove ( lines , cursorY )
cursorY = cursorY - 1
cursorX = unicode.len ( lines [ cursorY ] ) + 1
lines [ cursorY ] = lines [ cursorY ] .. l
2020-03-31 05:11:50 +11:00
elseif lX == " w> " and cursorY ~= # lines then
local l = table.remove ( lines , cursorY )
cursorX = unicode.len ( l ) + 1
lines [ cursorY ] = l .. lines [ cursorY ]
2020-03-30 22:08:12 +11:00
elseif lX == " nl " then
local line = lines [ cursorY ]
lines [ cursorY ] = unicode.sub ( line , 1 , cursorX - 1 )
table.insert ( lines , cursorY + 1 , unicode.sub ( line , cursorX ) )
cursorX = 1
cursorY = cursorY + 1
end
return true
2018-03-19 10:10:54 +11:00
end
flush = function ( )
for i = 1 , sH do
2018-03-23 10:40:49 +11:00
window.span ( 1 , i , getline ( i ) , 0xFFFFFF , 0 )
2018-03-19 10:10:54 +11:00
end
end
2018-04-26 08:58:31 +10:00
2018-03-19 10:10:54 +11:00
while true do
2018-04-26 08:58:31 +10:00
local e = { coroutine.yield ( ) }
2020-03-30 22:08:12 +11:00
if e [ 1 ] == " x.neo.pub.window " then
2018-03-19 10:10:54 +11:00
if e [ 2 ] == window.id then
2018-04-26 08:58:31 +10:00
if e [ 3 ] == " line " then
window.span ( 1 , e [ 4 ] , getline ( e [ 4 ] ) , 0xFFFFFF , 0 )
elseif filedialog then
elseif e [ 3 ] == " touch " then
2018-03-19 10:10:54 +11:00
-- reverse:
--local rY = (y + cursorY) - math.ceil(sH / 2)
local csY = math.ceil ( sH / 2 )
local nY = math.max ( 1 , math.min ( # lines , ( math.floor ( e [ 5 ] ) - csY ) + cursorY ) )
cursorY = nY
2020-03-30 22:08:12 +11:00
cursorX = lineEdit.clamp ( lines [ cursorY ] , cursorX )
2018-03-19 10:10:54 +11:00
flush ( )
2018-04-26 08:58:31 +10:00
elseif e [ 3 ] == " key " then
2020-03-30 22:08:12 +11:00
if key ( e [ 4 ] ~= 0 and unicode.char ( e [ 4 ] ) , e [ 5 ] , e [ 6 ] ) then
2018-03-19 10:10:54 +11:00
flush ( )
end
2018-04-26 08:58:31 +10:00
elseif e [ 3 ] == " focus " then
2018-03-19 10:10:54 +11:00
ctrlFlag = false
2018-04-26 08:58:31 +10:00
elseif e [ 3 ] == " close " then
2018-03-19 10:10:54 +11:00
return
2018-04-26 08:58:31 +10:00
elseif e [ 3 ] == " clipboard " then
local t = e [ 4 ]
for i = 1 , unicode.len ( t ) do
local c = unicode.sub ( t , i , i )
if c ~= " \r " then
if c == " \n " then
c = " \r "
end
2020-03-30 22:08:12 +11:00
key ( c , 0 , true )
2018-04-26 08:58:31 +10:00
end
end
2018-03-19 10:10:54 +11:00
flush ( )
end
2018-03-28 00:40:05 +11:00
elseif cbs [ e [ 2 ] ] then
if e [ 3 ] == " line " then
cbs [ e [ 2 ] ] [ 2 ] ( 1 , 1 , cbs [ e [ 2 ] ] [ 3 ] , 0 , 0xFFFFFF )
2018-04-26 08:58:31 +10:00
elseif e [ 3 ] == " close " then
2018-03-28 00:40:05 +11:00
cbs [ e [ 2 ] ] [ 1 ] ( )
cbs [ e [ 2 ] ] = nil
end
2018-03-19 10:10:54 +11:00
end
2018-04-26 08:58:31 +10:00
elseif e [ 1 ] == " x.neo.pub.base " and e [ 2 ] == " filedialog " and filedialog then
filedialog ( e [ 4 ] )
filedialog = nil
2018-03-19 10:10:54 +11:00
end
end