570 lines
16 KiB
Plaintext
570 lines
16 KiB
Plaintext
{
|
||
Simple text editor
|
||
2009
|
||
March
|
||
5 started; basic screen drawing; cursor movement
|
||
6 InsertChar, InsertCR, DeleteLeft/Right
|
||
6 filename edit field
|
||
6 file load, save
|
||
June
|
||
9 Sphinxified
|
||
30 Page up/down, minor cursor movement improvement
|
||
2010
|
||
January
|
||
29 Bug: Was dropping last character when reading a file that didn't end with CR/LF. Fixed.
|
||
|
||
ENTER(CR) Carriage return
|
||
BKSP Delete to the left
|
||
DEL Delete to the right
|
||
?, ?, ?, ? Cursor movement
|
||
HOME Start of line
|
||
CTRL-HOME Start of file
|
||
END End of line
|
||
CTRL-END End of file
|
||
CTRL-O Open a file
|
||
CTRL-S Save to a file
|
||
CTRL-Q Quit to Sphinx
|
||
PGUP Page up
|
||
PGDN Page down
|
||
|
||
to do:
|
||
block select, cut/copy/paste, find/replace, undo/redo
|
||
|
||
}
|
||
obj
|
||
kb: "isxkb"
|
||
sxtv: "isxtv"
|
||
term: "tvtexted"
|
||
sd: "sxfile"
|
||
|
||
var
|
||
byte x[10]
|
||
|
||
pub Main | e
|
||
if e := \Try
|
||
if e > 0
|
||
sxtv.str( e )
|
||
else
|
||
sxtv.dec( e )
|
||
sxtv.out( 13 )
|
||
term.stop
|
||
sxtv.Enable
|
||
|
||
pub Try | i, key, refresh, blink, t, done
|
||
sxtv.Disable
|
||
term.start( sxtv.GetBasepin )
|
||
|
||
Init
|
||
|
||
Args
|
||
|
||
done~
|
||
blink~
|
||
t := cnt
|
||
repeat
|
||
refresh~
|
||
repeat while key := kb.key
|
||
refresh~~
|
||
blink~
|
||
case key
|
||
$20..$7f: InsertChar( key )
|
||
CR: InsertCR
|
||
BKSP: DeleteLeft
|
||
DEL: DeleteRight
|
||
LARROW: MoveCursorLeft
|
||
RARROW: MoveCursorRight
|
||
UARROW: MoveCursorUp( false )
|
||
DARROW: MoveCursorDown( false )
|
||
HOME: MoveToStartOfLine
|
||
CTRL+HOME: MoveToTop
|
||
END: MoveToEndOfLine
|
||
CTRL+END: MoveToBottom
|
||
CTRL+"o": FileOp( OPEN, FALSE )
|
||
CTRL+"s": FileOp( SAVE, FALSE )
|
||
CTRL+"q": FileOp( SAVE, FALSE )
|
||
done~~
|
||
PGUP: PageCursorUp
|
||
PGDN: PageCursorDown
|
||
other: refresh~
|
||
if cnt - t > 0
|
||
refresh~~
|
||
if refresh
|
||
RefreshScreen( blink ^= 1 )
|
||
t := cnt + clkfreq/10
|
||
until done
|
||
|
||
term.stop
|
||
sxtv.Enable
|
||
|
||
sd.Close
|
||
sd.Open( string("sphinx.bin"), "R" )
|
||
sd.Execute( 0 )
|
||
|
||
pri Args
|
||
if sd.Open( string("args.d8a"), "R" ) == 0
|
||
if sd.ReadByte
|
||
sd.ReadString( @filename, FILENAMEBUFFERSIZE-1 )
|
||
sd.Close
|
||
FileOp( OPEN, true )
|
||
sd.Close
|
||
|
||
con
|
||
VISIBLELINES = 13
|
||
VISIBLECOLUMNS = 40
|
||
con
|
||
CR = $0d
|
||
LARROW = $c0
|
||
RARROW = $c1
|
||
UARROW = $c2
|
||
DARROW = $c3
|
||
HOME = $c4
|
||
END = $c5
|
||
PGUP = $c6
|
||
PGDN = $c7
|
||
BKSP = $c8
|
||
DEL = $c9
|
||
ESC = $cb
|
||
CTRL = $0200
|
||
CTRL_ALT_DEL = $06c9
|
||
|
||
con
|
||
BUFFERSIZE = 20000
|
||
var
|
||
long nLines
|
||
long firstVisibleLine
|
||
long firstVisibleColumn
|
||
long bytesUsed
|
||
long cursorLine
|
||
long cursorColumn
|
||
long desiredColumn
|
||
long cp
|
||
long currentLineLength
|
||
long topPtr ' points to start of first visible line
|
||
|
||
pri InsertChar( ch ) | n
|
||
if cursorLine < nLines
|
||
if bytesUsed + 1 => BUFFERSIZE
|
||
return
|
||
else ' starting new line at end of file
|
||
if bytesUsed + 2 => BUFFERSIZE
|
||
return
|
||
buffer[bytesUsed++]~ ' terminating null for the new line
|
||
++nLines
|
||
n := bytesUsed - (cp - @buffer)
|
||
bytemove( cp+1, cp, n )
|
||
byte[cp] := ch
|
||
++bytesUsed
|
||
++currentLineLength
|
||
MoveCursorRight
|
||
|
||
pri InsertCR | n
|
||
if cursorLine < nLines
|
||
if bytesUsed + 1 => BUFFERSIZE
|
||
return
|
||
else ' starting new line at end of file
|
||
if bytesUsed + 2 => BUFFERSIZE
|
||
return
|
||
buffer[bytesUsed++]~ ' terminating null for the new line
|
||
++nLines
|
||
n := bytesUsed - (cp - @buffer)
|
||
bytemove( cp+1, cp, n )
|
||
byte[cp]~
|
||
++bytesUsed
|
||
++nLines
|
||
currentLineLength := cursorColumn
|
||
MoveCursorRight
|
||
|
||
pri DeleteLeft
|
||
if cursorLine or cursorColumn
|
||
MoveCursorLeft
|
||
DeleteRight
|
||
|
||
pri DeleteRight | n
|
||
if cursorLine == nLines ' can't delete if we're on the phantom line
|
||
return
|
||
if byte[cp] == 0 and cursorLine == nLines - 1 and cursorColumn
|
||
return ' can't delete if we're at the end of the last real line
|
||
' unless we're at the start (start = end => empty line
|
||
' which we can delete, making this line the new phantom line)
|
||
n := bytesUsed - (cp - @buffer) - 1
|
||
ifnot byte[cp]
|
||
--nLines
|
||
bytemove( cp, cp+1, n )
|
||
--bytesUsed
|
||
repeat while byte[--cp]
|
||
currentLineLength := strsize( ++cp )
|
||
cp += cursorColumn
|
||
AdjustVisibleColumn
|
||
|
||
pri MoveCursorLeft
|
||
if cursorColumn
|
||
desiredColumn := --cursorColumn
|
||
--cp
|
||
AdjustVisibleColumn
|
||
else
|
||
ifnot cursorLine
|
||
return
|
||
MoveCursorUp( true )
|
||
|
||
pri MoveCursorRight
|
||
if cursorColumn < currentLineLength
|
||
desiredColumn := ++cursorColumn
|
||
++cp
|
||
AdjustVisibleColumn
|
||
else
|
||
if cursorLine == nLines
|
||
return
|
||
MoveCursorDown( true )
|
||
|
||
pri MoveCursorDown( f )
|
||
' if f, also move cursor to start of line
|
||
if cursorLine == nLines
|
||
return
|
||
if ++cursorLine - firstVisibleLine => VISIBLELINES
|
||
++firstVisibleLine
|
||
repeat while byte[topPtr++]
|
||
MoveCpToNextLine
|
||
if f
|
||
cursorColumn~
|
||
desiredColumn~
|
||
elseif cursorLine < nLines
|
||
cursorColumn := currentLineLength <# desiredColumn
|
||
else
|
||
cursorColumn~
|
||
currentLineLength~
|
||
cp += cursorColumn
|
||
AdjustVisibleColumn
|
||
|
||
pri PageCursorDown
|
||
if firstVisibleLine + VISIBLELINES - 1 => nLines
|
||
return
|
||
repeat VISIBLELINES - 1
|
||
repeat while byte[topPtr++]
|
||
++firstVisibleLine
|
||
MoveCursorDown( false )
|
||
|
||
pri MoveCursorUp( f )
|
||
' if f, also move cursor to end of line
|
||
ifnot cursorLine
|
||
return
|
||
if --cursorLine < firstVisibleLine
|
||
--firstVisibleLine
|
||
repeat while byte[--topPtr]
|
||
repeat while byte[--topPtr]
|
||
++topPtr
|
||
MoveCpToPreviousLine
|
||
if f
|
||
cursorColumn := desiredColumn := currentLineLength
|
||
else
|
||
cursorColumn := currentLineLength <# desiredColumn
|
||
cp += cursorColumn
|
||
AdjustVisibleColumn
|
||
|
||
pri PageCursorUp | n
|
||
n := firstVisibleLine <# VISIBLELINES - 1
|
||
repeat n
|
||
repeat 2
|
||
repeat while byte[--topPtr]
|
||
++topPtr
|
||
--firstVisibleLine
|
||
MoveCursorUp( false )
|
||
|
||
pri MoveCpToNextLine
|
||
repeat while byte[cp++]
|
||
currentLineLength := strsize( cp )
|
||
|
||
pri MoveCpToPreviousLine
|
||
repeat while byte[--cp]
|
||
repeat while byte[--cp]
|
||
++cp
|
||
currentLineLength := strsize( cp )
|
||
|
||
pri MoveToStartOfLine
|
||
cp -= cursorColumn
|
||
cursorColumn~
|
||
desiredColumn~
|
||
AdjustVisibleColumn
|
||
|
||
pri MoveToEndOfLine
|
||
cp -= cursorColumn
|
||
cursorColumn := desiredColumn := currentLineLength
|
||
cp += currentLineLength
|
||
AdjustVisibleColumn
|
||
|
||
pri MoveToTop
|
||
cp := topPtr := @buffer[1]
|
||
currentLineLength := strsize( cp )
|
||
cursorLine~
|
||
cursorColumn~
|
||
desiredColumn~
|
||
firstVisibleLine~
|
||
firstVisibleColumn~
|
||
|
||
pri MoveToBottom ' cheesy implementation
|
||
MoveToTop
|
||
if nLines
|
||
repeat nLines-1
|
||
MoveCursorDown( false )
|
||
MoveToEndOfLine
|
||
|
||
pri AdjustVisibleColumn
|
||
firstVisibleColumn~
|
||
firstVisibleColumn #>= cursorColumn - VISIBLECOLUMNS + 1
|
||
|
||
var
|
||
byte buffer[BUFFERSIZE]
|
||
{ buffer contains nLines null-terminated strings, with sentinel nulls at the beginning and end:
|
||
+-+---------+-+--------+-+- ---------+-+-+
|
||
<20>0<EFBFBD> line0 0<> line1 0<> ... lineN-1 0<>0<EFBFBD>
|
||
+-+---------+-+--------+-+- ---------+-+-+
|
||
}
|
||
pri Init
|
||
nLines~
|
||
firstVisibleLine~
|
||
firstVisibleColumn~
|
||
bytesUsed := 2
|
||
buffer[0]~
|
||
buffer[1]~
|
||
cursorLine~
|
||
cursorColumn~
|
||
desiredColumn~
|
||
|
||
currentLineLength~
|
||
cp := topPtr := @buffer[1]
|
||
|
||
eolLength := 2
|
||
eol[0] := $0d
|
||
eol[1] := $0a
|
||
|
||
pri RefreshScreen( drawCursor ) | p, len, m, n, l, beyondEnd
|
||
p := topPtr
|
||
beyondEnd~
|
||
repeat l from 0 to VISIBLELINES-1
|
||
term.setXY( 0, l )
|
||
if firstVisibleLine + l => nLines
|
||
beyondEnd~~
|
||
len~
|
||
else
|
||
len := strsize( p )
|
||
if len > firstVisibleColumn
|
||
m := (len - firstVisibleColumn) <# VISIBLECOLUMNS
|
||
p += firstVisibleColumn
|
||
repeat m-1
|
||
term.out( byte[p++] )
|
||
term.printNoAdvance( byte[p++] )
|
||
term.setX( m )
|
||
n := VISIBLECOLUMNS - m
|
||
if n
|
||
repeat n-1
|
||
term.out( " " )
|
||
term.printNoAdvance( " " )
|
||
else
|
||
repeat VISIBLECOLUMNS-1
|
||
term.out( " " )
|
||
term.printNoAdvance( " " )
|
||
ifnot beyondEnd
|
||
repeat while byte[p++]
|
||
if drawCursor
|
||
term.setXY( cursorColumn-firstVisibleColumn, cursorLine-firstVisibleLine )
|
||
term.printNoAdvance( "_" )
|
||
|
||
con
|
||
#0, OPEN, SAVE ' File ops
|
||
' File errors
|
||
#1, FILE_TOO_BIG, CARD_FULL_MAYBE
|
||
|
||
FILENAMEBUFFERSIZE = 13 ' 8.3 + null
|
||
|
||
pri FileOp( op, t ) | e, i
|
||
if e := \_FileOp( op, t )
|
||
RefreshScreen( false ) ' cursor off
|
||
term.setXY( 0, VISIBLELINES-1 )
|
||
term.out( $0c ) ' set color
|
||
term.out( 1 )
|
||
repeat VISIBLECOLUMNS-1
|
||
term.out( " " )
|
||
term.printNoAdvance( " " )
|
||
term.setX( 0 )
|
||
case e
|
||
FILE_TOO_BIG: term.str( string("File too large") )
|
||
CARD_FULL_MAYBE: term.str( string("SD card full?") )
|
||
-13: term.str( string("Unable to mount SD card") )
|
||
other:
|
||
term.str( string("Error code ") )
|
||
term.dec( e )
|
||
kb.getkey
|
||
term.out( $0c ) ' set color
|
||
term.out( 0 )
|
||
|
||
pri _FileOp( op, t ) | r
|
||
case op
|
||
OPEN:
|
||
if t
|
||
ReadFile
|
||
elseif EditFilename
|
||
ReadFile
|
||
SAVE:
|
||
WriteFile
|
||
|
||
pri ReadFile | r, p, pEnd
|
||
sd.Close
|
||
if r := sd.Open( @filename, "R" ) <> 0
|
||
abort r
|
||
|
||
Init
|
||
r := sd.Read( @buffer[1], BUFFERSIZE-2 )
|
||
|
||
if r < 0
|
||
abort r
|
||
p := @buffer[1]
|
||
pEnd := @buffer[r+1]
|
||
repeat while p <> pEnd
|
||
if byte[p] == $0d or byte[p] == $0a ' change CR, LF, or CRLF
|
||
if byte[p] == $0d and byte[p+1] == $0a '
|
||
eolLength := 2 '
|
||
bytemove( p, p + 1, pEnd - p - 1 ) '
|
||
--pEnd '
|
||
else '
|
||
eolLength := 1 '
|
||
byte[p]~ ' to null
|
||
++nLines
|
||
++p
|
||
|
||
if byte[pEnd-1] ' if byte[pEnd-1] isn't null, that means that the file didn't
|
||
byte[pEnd++]~ ' end with CR, LF, or CRLF, so we have to fake it. (fixed 2010/01/29 mp)
|
||
++nLines
|
||
|
||
byte[pEnd]~ ' add final null
|
||
bytesUsed := pEnd + 1 - @buffer
|
||
|
||
currentLineLength := strsize( topPtr )
|
||
|
||
if r == BUFFERSIZE-2
|
||
abort FILE_TOO_BIG ' technically we don't know that it's too big at this point, but it's a good guess.
|
||
if r := sd.Close
|
||
abort r
|
||
|
||
pri WriteFile | p, len, r
|
||
ifnot EditFilename
|
||
return
|
||
sd.Close
|
||
if r := sd.Open( @filename, "W" )
|
||
abort r
|
||
|
||
p := @buffer[1]
|
||
repeat nLines
|
||
len := strsize( p )
|
||
r := sd.Write( p, len )
|
||
if r < 0
|
||
abort r
|
||
if r <> len ' Does this ever happen?
|
||
abort CARD_FULL_MAYBE
|
||
|
||
r := sd.Write( @eol, eolLength )
|
||
if r < 0
|
||
abort r
|
||
if r <> eolLength ' Does this ever happen?
|
||
abort CARD_FULL_MAYBE
|
||
p += len + 1
|
||
|
||
if (r := sd.Close) < 0
|
||
abort r
|
||
|
||
var
|
||
byte filename[FILENAMEBUFFERSIZE]
|
||
long filenameCursorColumn
|
||
long filenameLength
|
||
byte eolLength
|
||
byte eol[2]
|
||
con
|
||
EDITOFFSET = 10 ' filename edit field starts at column 10
|
||
|
||
pri EditFilename | t, key, blink, refresh
|
||
RefreshScreen( false ) ' cursor off
|
||
term.setXY( 0, VISIBLELINES-1 )
|
||
term.out( $0c ) ' set color
|
||
term.out( 2 )
|
||
repeat VISIBLECOLUMNS-1
|
||
term.out( " " )
|
||
term.printNoAdvance( " " )
|
||
term.setX( 0 )
|
||
term.str( string("Filename: ") )
|
||
|
||
filenameCursorColumn := filenameLength := strsize( @filename )
|
||
blink~
|
||
t := cnt
|
||
repeat
|
||
refresh~
|
||
repeat while key := kb.key
|
||
refresh~~
|
||
blink~
|
||
case key
|
||
$20..$7f: FilenameInsertChar( key )
|
||
CR:
|
||
term.out( $0c ) ' set color
|
||
term.out( 0 ) ' back to normal
|
||
return true
|
||
BKSP:
|
||
if filenameCursorColumn
|
||
filenameCursorColumn := (filenameCursorColumn - 1) #> 0
|
||
FilenameDeleteRight
|
||
DEL: FilenameDeleteRight
|
||
LARROW: filenameCursorColumn := (filenameCursorColumn - 1) #> 0
|
||
RARROW: filenameCursorColumn := (filenameCursorColumn + 1) <# filenameLength
|
||
ESC:
|
||
term.out( $0c ) ' set color
|
||
term.out( 0 ) ' back to normal
|
||
return false
|
||
other: refresh~
|
||
if cnt - t > 0
|
||
refresh~~
|
||
if refresh
|
||
RefreshFilename( blink ^= 1 )
|
||
t := cnt + clkfreq/10
|
||
|
||
pri RefreshFilename( drawCursor ) | i
|
||
term.setXY( EDITOFFSET, VISIBLELINES-1 )
|
||
repeat i from 0 to FILENAMEBUFFERSIZE-1
|
||
if drawCursor and i == filenameCursorColumn
|
||
term.out( "_" )
|
||
else
|
||
if i < filenameLength
|
||
term.out( filename[i] )
|
||
else
|
||
term.out( " " )
|
||
|
||
pri FilenameInsertChar( ch ) | n
|
||
if filenameLength => FILENAMEBUFFERSIZE-1
|
||
return
|
||
n := filenameLength - filenameCursorColumn + 1
|
||
bytemove( @filename[filenameCursorColumn+1], @filename[filenameCursorColumn], n )
|
||
++filenameLength
|
||
filename[filenameCursorColumn++] := ch
|
||
|
||
pri FilenameDeleteRight | n
|
||
ifnot filenameLength
|
||
return
|
||
ifnot n := filenameLength - filenameCursorColumn
|
||
return
|
||
bytemove( @filename[filenameCursorColumn], @filename[filenameCursorColumn+1], n )
|
||
--filenameLength
|
||
|
||
{{
|
||
Copyright (c) 2009 Michael Park
|
||
+------------------------------------------------------------------------------------------------------------------------------+
|
||
| TERMS OF USE: MIT License |
|
||
+------------------------------------------------------------------------------------------------------------------------------+
|
||
|Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation |
|
||
|files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, |
|
||
|modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software|
|
||
|is furnished to do so, subject to the following conditions: |
|
||
| |
|
||
|The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.|
|
||
| |
|
||
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|
||
|WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
||
|COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
|
||
|ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
||
+------------------------------------------------------------------------------------------------------------------------------+
|
||
}}
|