REM ReadLine.bbc  Version 2.2  29-Mar-2006
     REM (C) 2006 Jon Ripley

     
ERROR 0,"This is a library."

     REM Read a line of text
     
DEF FN_ReadLine
     LOCAL temp%, buffer%
     length% = 65536
     DIM buffer% LOCAL length%
     temp% = FN_ReadLineF(buffer%, length%, "~", 1)
     = $buffer%

     REM Read a number
     
DEF FN_ReadNumber
     LOCAL temp%, buffer%
     length% = 65536
     DIM buffer% LOCAL length%
     temp% = FN_ReadLineF(buffer%, length%, "0-9E.+\-", 1)
     = VAL $buffer%

     REM Read a password
     
DEF FN_ReadPassword
     LOCAL temp%, buffer%
     length% = 65536
     DIM buffer% LOCAL length%
     temp% = FN_ReadLineF(buffer%, length%, "~", 512+2)
     = $buffer%

     REM Equivalent to FN_ReadLineF without requiring a buffer
     
DEF FN_ReadLineS(length%, valid$, flags%)
     IF length% <= 0 THEN =""
     LOCAL temp%, buffer%
     DIM buffer% LOCAL length%
     temp% = FN_ReadLineF(buffer%, length%, "~", 1)
     = $buffer%

     REM Master line input routine
     
DEF FN_ReadLineF(buffer%, length%, valid$, flags%)

     PRIVATE insert%, init%, history{}, line{()}, valid%
     IF init% = FALSE THEN
       
init% = TRUE
       
insert% = TRUE
       DIM
history{cur%, top%, max%}
       history.cur% = 0
       history.top% = 0
       IF ((flags% AND &FF000000) >> 24) = 7 THEN
         
history.max% = (flags% AND &00FFFFFF)
       ELSE
         
history.max% = 260
       ENDIF
       DIM
line{(history.max%)text$, pos%}
       DIM valid% 255
     ENDIF

     
REM Read validation string
     
LOCAL mode%, c%, d%, e%, i%, j%, err%, esc%
     REM Invalidate all characters
     
FOR i% = 0 TO 255
       valid%?i% = 0
     NEXT i%
     IF valid$ = "" THEN valid$ = "~"
     IF LEFT$(valid$,1) = "~" THEN
       
REM Default: Allow all characters
       
FOR i% = 32 TO 126:REM ***
         
i% = i% - (i% = 127)
         valid%?i% = 1
       NEXT i%
     ENDIF
     
REM Parse validation string
     
mode% = 1 :REM Allow
     
FOR i% = 1 TO LEN valid$
       err%=FALSE:esc%=FALSE
       
c% = ASCMID$(valid$,i%)
       IF c%=ASC"\" THEN
         
i% += 1
         c% = ASCMID$(valid$,i%)
         esc% = TRUE:REM Escape character
         
IF c% = -1 THEN err% = -1:REM Escape string too short
       
ENDIF
       
d% = ASCMID$(valid$,i%+1)
       IF c% = ASC"-" AND NOT esc% THEN err% = -2:REM '-' outside range
       
IF c% = ASC"~" AND NOT esc% THEN mode% = 0:err% = 1:REM Disallow
       
IF d% = ASC"-" THEN
         
e% = ASCMID$(valid$,i%+2)
         IF e%=ASC"\" THEN
           
i% += 1
           e% = ASCMID$(valid$,i%+2)
           esc% = TRUE:REM Escape character
           
IF e% = -1 THEN err% = -1:REM Escape string too short
         
ENDIF
         IF
e% = -1 THEN
           
err% = -3:REM Validation missing a range end mark
           
i% += 1
         ELSE
           IF
c% > e% THEN
             
err% = -4:REM Invalid range, start character is greater than end character
           
ELSE
             FOR
j% = c% TO e%
               valid%?j% = mode%
             NEXT j%
           ENDIF
           
i% += 2
         ENDIF
       ELSE
         IF
err% = 0 THEN valid%?c% = mode%
       ENDIF
     NEXT
i%

     REM Pop history (Must be here for reason code 8)
     
IF history.top% = history.max% - 1 THEN
       FOR
i% = 0 TO history.top% - 1
         line{(i%)}.text$ = line{(i% + 1)}.text$
       NEXT i%
       history.top% -= 1
     ENDIF
     
history.cur% = history.top%

     REM Handle internal calls
     
LOCAL hfile%, temp%, ret%
     CASE (flags% AND &FF000000) >> 24 OF
       WHEN
1:REM Load history
         
hfile% = OPENIN$buffer%
         IF hfile% = 0 THEN
           
ret% = 1
         ELSE
           PROC
_ClearHistory
           i% = 0
           WHILE NOT EOF#hfile%
             line{(i%)}.text$ = GET$#hfile%
             temp% = BGET#hfile%
             i% += 1
           ENDWHILE
           
history.top% = i%
           CLOSE#hfile%
           hfile% = 0
         ENDIF

       WHEN
2:REM Save history
         
hfile% = OPENOUT$buffer%
         IF hfile% = 0 THEN
           
ret% = 1
         ELSE
           FOR
i% = 0 TO history.top% - 1
             BPUT#hfile%, line{(i%)}.text$ + CHR$13 + CHR$10;
           NEXT i%
           CLOSE#hfile%
           hfile% = 0
         ENDIF

       WHEN
3:REM Clear history
         
FOR i% = 0 TO history.top%
           line{(i%)}.text$ = ""
           line{(i%)}.pos%  = 0
         NEXT i%
         history.top% = 0
         history.cur% =0

       WHEN 4:REM Dump history
         
IF history.top% > 0 THEN
           FOR
i% = history.top% TO 0 STEP -1
             PRINT RIGHT$("000"+STR$i%,3)":";line{(i%)}.text$
           NEXT i%
         ELSE
           PRINT
"Line history is disabled."
         ENDIF

       WHEN
5:REM Enumerate lines
         
ret% = history.top%

       WHEN 6:REM Return specific line
         
temp% = flags% AND &FFFFFF
         IF temp% >= 0 AND temp% <= history.max% THEN
           
=line{(temp%)}.text$
         ELSE
           
=""
         ENDIF

       WHEN
7:REM Set line history buffer
         REM Must be the first call to ReadLineF
         
IF history.max% = flags% AND &00FFFFFF THEN
           
ret% = 0:REM Ok
         
ELSE
           
ret% = 1:REM Failed
         
ENDIF

       WHEN
8:REM Append a line to the history buffer, as if it was the last line typed
         
line{(history.top%)}.text$ = $buffer%
         history.top% += 1
         IF history.top% > history.max% THEN history.top% = history.max%

       WHEN 9:REM Remove history item specified by its position from the history
         
line% = flags% AND &FFFFFF
         FOR i% = line% + 1 TO history.top%
           IF i% <= history.max% + 1 THEN line{(i%-1)}.text$ = line{(i%)}.text$
         NEXT i%

       WHEN &A:REM Replace history item specified by its position with the given line
         
temp% = flags% AND &FFFFFF
         IF temp% >= 0 AND temp% <= history.max% THEN
           
line{(temp%)}.text$ = $buffer%
           ret% = 0
         ELSE
           
ret% = 1
         ENDIF

     ENDCASE
     IF
(flags% AND &FF000000) >> 24 THEN =ret%

     IF length% <= 0 THEN =0
     LOCAL ptr%, len%, x%, y%, cursor_enabled%, escape_enabled%, history_enabled%
     cursor_enabled%  = FN_CursorIsEnabled
     escape_enabled%  = FN_EscapeIsEnabled
     history_enabled% = ((flags% AND &200) >> 9) = 0

     REM Disable ESCape
     
*ESC OFF

     REM Set cursor to insert/overwrite
     
IF insert% THEN
       VDU
23,0,10,13,0;0;0;
     ELSE
       VDU
23,0,10,0,0;0;0;
     ENDIF

     
REM Clear buffer
     
FOR i%=0 TO length%
       buffer%?i% = 13
     NEXT

     
REM Main input loop
     
REPEAT

       ON
:temp% = GET:OFF

       
REM Recognise ALT+KeyPad combinations as characters
       
IF INKEY-3 = -1 AND temp% >= 32 AND temp% <> 127 THEN temp% += 256

       REM Insert - toggle edit mode
       
IF temp% = 134 THEN
         
insert% = NOT insert%
         REM Set cursor to insert/overwrite
         
IF insert% THEN
           VDU
23,0,10,13,0;0;0;
         ELSE
           VDU
23,0,10,0,0;0;0;
         ENDIF
         
temp% = 0
       ENDIF

       
REM CTRL-U - clear input buffer
       
IF temp% = 21 THEN
         PROC
_MoveCursor(-ptr%, flags%)
         PROC_WRSTR(STRING$(len%, CHR$31), flags%)
         PROC_MoveCursor(-len%, flags%)
         buffer%?0 = 13
         ptr% = 0
         len% = 0
         temp% = 0
       ENDIF

       
REM Delete character to the left
       
IF (temp% = 8 OR temp% = 127) AND ptr% > 0 THEN
         IF
ptr% = len% THEN
           
ptr% -= 1
           len% -= 1
           PROC_MoveCursor(-1, flags%)
           PROC_WRCH(31, flags%)
           PROC_MoveCursor(-1, flags%)
         ELSE
           
buffer%?len% = 13
           temp% = ptr%
           FOR temp% = ptr% - 1 TO len%-1
             buffer%?temp% = ?(buffer%+temp%+1)
           NEXT
           
ptr% -= 1
           len% -= 1
           PROC_MoveCursor(-1, flags%)
           PROC_WRSTR($(buffer%+ptr%)+CHR$31, flags%)
           PROC_MoveCursor(-LEN$(buffer%+ptr%)-1, flags%)
         ENDIF
         
temp% = 0
       ENDIF

       
REM Delete character to the right
       
IF (temp% = 135 AND ptr% < len%) THEN
         IF
ptr% = len%-1 THEN
           
len% -= 1
           PROC_WRCH(31, flags%)
           PROC_MoveCursor(-1, flags%)
         ELSE
           
buffer%?len% = 13
           temp% = ptr%
           FOR temp% = ptr% TO len%
             buffer%?temp% = ?(buffer%+temp%+1)
           NEXT
           
len% -= 1
           PROC_WRSTR($(buffer%+ptr%)+CHR$31, flags%)
           PROC_MoveCursor(-LEN$(buffer%+ptr%), flags%)
         ENDIF
         
temp% = 0
       ENDIF

       
REM User has finished inputting
       
IF temp% = 27 OR temp% = 10 OR temp% = 13 THEN
         
buffer%?len% = 13
         REM Move pointer to end of input
         
IF ptr% < len% THEN
           PROC
_MoveCursor(len% - ptr%, flags%)
           ptr% = len%
         ENDIF
         IF
temp% = 27 THEN buffer%?0 = 13
         IF temp% = 10 THEN history_enabled% = FALSE
         
temp% = -1: REM Signifies end of input
       
ENDIF

       
REM Left - Move left one char
       
IF temp% = 136 AND ptr% > 0 THEN
         
ptr% -= 1
         PROC_MoveCursor(-1, flags%)
         temp% = 0
       ENDIF

       
REM Right - Move right one char
       
IF temp% = 137 AND ptr% < len% THEN
         PROC
_MoveCursor(1, flags%)
         ptr% += 1
         temp% = 0
       ENDIF

       
REM Ctrl Left - Move one word left
       
IF temp% = 128 AND ptr% > 0 THEN
         REPEAT
           
ptr% -= 1
           PROC_MoveCursor(-1, flags%)
         UNTIL ptr% = 0 OR (?(buffer% + ptr% - 1) = 32 AND buffer%?ptr% <> 32)
         temp% = 0
       ENDIF

       
REM Ctrl Right - Move one word right
       
IF temp% = 129 AND ptr% < len% THEN
         REPEAT
           PROC
_MoveCursor(1, flags%)
           ptr% += 1
         UNTIL ptr% = len% OR (?(buffer% + ptr% - 1) = 32 AND buffer%?ptr% <> 32)
         temp% = 0
       ENDIF

       
REM Home - Move to start
       
IF temp% = 130 AND ptr% > 0 THEN
         PROC
_MoveCursor(-ptr%, flags%)
         ptr% = 0
         temp% = 0
       ENDIF

       
REM End - Move to end
       
IF temp% = 131 AND ptr% < len% THEN
         PROC
_MoveCursor(len% - ptr%, flags%)
         ptr% = len%
         temp% = 0
       ENDIF

       
REM Ctrl Home - Delete to start
       
IF temp% = 156 AND ptr% > 0 THEN
         PROC
_MoveCursor(-ptr%, flags%)
         buffer%?len% = 13
         PROC_WRSTR($(buffer% + ptr%) + STRING$(ptr%, CHR$31), flags%)
         PROC_MoveCursor(-len%, flags%)
         FOR i% = 0 TO len% - ptr%
           buffer%?i% = ?(buffer% + ptr%+i%)
         NEXT
         
len% -= ptr%
         ptr% = 0
         temp% = 0
       ENDIF

       
REM Ctrl End - Delete to end
       
IF temp% = 157 THEN
         
buffer%?ptr% = 13
         PROC_WRSTR(STRING$(len% - ptr%, CHR$31), flags%)
         PROC_MoveCursor(-(len% - ptr%), flags%)
         len% = ptr%
         temp% = 0
       ENDIF

       
REM Ctrl+S - Switch case
       
IF temp% = 19 AND ptr% < len% THEN
         
temp% = buffer%?ptr%
         CASE TRUE OF
           WHEN
temp% >= ASC"A" AND temp% <= ASC"Z" : temp% += 32
           WHEN temp% >= ASC"a" AND temp% <= ASC"z" : temp% -= 32
             REM TODO: Switch case of extended characters
         
ENDCASE
         IF
valid%?temp% THEN
           
buffer%?ptr% = temp%
           PROC_WRCH(buffer%?ptr%, flags%)
         ELSE
           PROC
_MoveCursor(1, flags%)
         ENDIF
         
ptr% += 1
         temp% = 0
       ENDIF

       
REM Ctrl+T - Transpose adjacent characters
       
IF temp% = 20 AND ptr% + 1 < len% THEN
         SWAP
buffer%?ptr%, buffer%?(ptr% + 1)
         PROC_WRCH(buffer%?ptr%, flags%)
         PROC_WRCH(buffer%?(ptr% + 1), flags%)
         PROC_MoveCursor(-2, flags%)
         temp% = 0
       ENDIF

       
REM Sound system bell
       
IF temp% = 7 AND (flags% AND 1024) = 0 THEN VDU 7

       REM Ctrl+C / Ctrl+X - Copy to clipboard
       
IF temp% = 3 OR temp% = 24 THEN
         SYS
"GlobalAlloc", &2000, LEN($buffer%)+3 TO i%
         SYS "GlobalLock", i% TO j%
         $j% = $buffer% + CHR$13 + CHR$10
         j%?(LEN($buffer%)+2) = 0
         SYS "GlobalUnlock", i%
         SYS "OpenClipboard", @hwnd%
         SYS "EmptyClipboard"
         SYS "SetClipboardData", 1, i%
         SYS "CloseClipboard"
         IF temp% = 24 THEN
           PROC
_MoveCursor(-ptr%, flags%)
           PROC_WRSTR(STRING$(len%, CHR$31), flags%)
           PROC_MoveCursor(-len%, flags%)
           buffer%?0 = 13
           ptr% = 0
           len% = 0
         ENDIF
         
temp% = 0
       ENDIF

       
REM Ctrl+V - Paste text from clipboard
       
IF temp% = 22 THEN
         SYS
"IsClipboardFormatAvailable", 1 TO i%
         IF i% = 1 THEN
           SYS
"OpenClipboard", @hwnd%
           SYS "GetClipboardData", 1 TO j%
           SYS "GlobalLock", j% TO temp%
           c% = 0
           i% = temp%
           temp$ = ""
           WHILE ?i% <> 0 AND (len% + c% + 1) < length%
             IF valid%?(?i%) THEN
               
c% += 1
               temp$ += CHR$?i%
             ENDIF
             
i% += 1
           ENDWHILE
           
$(buffer% + ptr%) = temp$ + $(buffer% + ptr%)
           SYS "GlobalUnlock", j%
           SYS "CloseClipboard"
           PROC_WRSTR($(buffer% + ptr%), flags%)
           len% += c%
           ptr% += c%
           IF len% <> ptr% THEN PROC_MoveCursor(-len% + ptr%, flags%)
         ENDIF
         
temp% = 0
       ENDIF

       
REM Up / Down - Navigate history
       
IF temp% = 139 OR temp% = 138 THEN
         
dir% = NOT((temp% - 138) + (temp% = 138)) + 1
         IF (history.cur% = history.top% AND dir% = -1) OR (history.cur% = 0 AND dir% = 1) OR (history.cur% > 0 AND history.cur% < history.top%) THEN
           IF
history.cur% = history.top% THEN
             IF
dir%=-1 THEN line{(history.top%)}.text$ = $buffer%
           ENDIF
           IF INKEY
-1 THEN dir% *= 16
           history.cur% += dir%
           IF history.cur% > history.top% OR (INKEY-2 AND dir% > 0) THEN history.cur% = history.top%
           IF history.cur% < 0 OR (INKEY-2 AND dir% < 0) THEN history.cur% = 0
           REM Clear current buffer
           
PROC_MoveCursor(-ptr%, flags%)
           PROC_WRSTR(STRING$(len%, CHR$31), flags%)
           PROC_MoveCursor(-len%, flags%)
           ptr% = 0
           len% = 0
           REM Copy string observing allowed characters
           
FOR i% = 1 TO LENline{(history.cur%)}.text$
             temp% = ASCMID$(line{(history.cur%)}.text$, i%, 1)
             IF valid%?temp% = 1 AND ptr% < length% THEN
               
buffer%?ptr% = temp%
               ptr% += 1
             ENDIF
           NEXT
i%
           REM Finish and display
           
len% = ptr%
           buffer%?len% = 13
           PROC_WRSTR($buffer%, flags%)
         ENDIF
         
temp% = 0
       ENDIF

       
REM Insert character into buffer
       
IF temp% >= 256 THEN temp% -= 256
       IF valid%?temp% = 1 THEN
         IF
(ptr% + 1) < length% AND (insert% AND len% + 1 < length%) THEN
           
REM Using insert mode
           
IF insert% AND ptr% <> len% THEN
             
buffer%?len% = 13
             FOR i% = len% TO ptr% - 1 STEP -1
               ?(buffer% + i% + 1) = buffer%?i%
             NEXT
             PROC
_MoveCursor(1, flags%)
             PROC_WRSTR($(buffer% + ptr% + 1), flags%)
             PROC_MoveCursor(-LEN$(buffer% + ptr%), flags%)
             len% += 1
           ENDIF
           
buffer%?ptr%=temp%
           ptr% += 1
           IF ptr% > len% THEN len% = ptr%
           PROC_WRCH(temp%, flags%)
         ELSE
           
REM Using overwrite mode
           
IF NOT insert% THEN
             
buffer%?ptr% = temp%
             ptr% += 1
             IF ptr% > len% THEN len% = ptr%
             PROC_WRCH(temp%, flags%)
           ELSE
             
REM Buffer is full
             
IF (flags% AND 1024) = 0 THEN VDU 7
           ENDIF
         ENDIF
       ENDIF
     UNTIL
temp% = -1


     REM Store line in history
     
line{(history.top%)}.text$ = ""
     IF history_enabled% AND LEN$buffer% > 0 THEN
       
line{(history.top%)}.text$ = $buffer%
       history.top% += 1
       IF history.top% > history.max% THEN history.top% = history.max%
     ENDIF

     
REM Restore cursor shape, cursor and *ESC status
     
VDU 23,0,10,13,0;0;0;
     IF cursor_enabled% THEN ON
     IF
escape_enabled% THEN
       
*ESC ON
     ELSE
       
*ESC OFF
     ENDIF

     
=LEN$buffer%

     REM Load line history from file$
     
DEF PROC_LoadHistory(file$)
     LOCAL temp%, buffer%
     DIM buffer% LOCAL LENfile$+1
     $buffer% = file$
     temp% = FN_ReadLineF(buffer%, LENfile$+1, "~", &01000000)
     ENDPROC

     
REM Save line history to file$
     
DEF PROC_SaveHistory(file$)
     LOCAL temp%, buffer%
     DIM buffer% LOCAL LENfile$+1
     $buffer% = file$
     temp% = FN_ReadLineF(buffer%, LENfile$+1, "~", &02000000)
     ENDPROC

     
REM Clear line history
     
DEF PROC_ClearHistory
     LOCAL temp%
     temp% = FN_ReadLineF(0, 0, "~", &03000000)
     ENDPROC

     
REM Dump line history
     
DEF PROC_DumpHistory
     LOCAL temp%
     temp% = FN_ReadLineF(0, 0, "~", &04000000)
     ENDPROC

     
REM Return number of items in history
     
DEF FN_EnumerateHistory
     =FN_ReadLineF(0, 0, "~", &05000000)

     REM Return specified line from history
     
DEF FN_GetHistoryItem(line%)
     =FN_ReadLineF(0, 0, "~", &06000000 + line%)

     REM Set number of lines remembered in the history
     
DEF PROC_SetHistory(lines%)
     LOCAL temp%
     temp% = FN_ReadLineF(0, 0, "~", &07000000 + lines%)
     ENDPROC

     
REM Append a line to the history buffer, as if it was the last line typed
     
DEF PROC_AddHistory(line$)
     LOCAL temp%, buffer%
     DIM buffer% LOCAL LENline$+1
     $buffer% = line$
     temp% = FN_ReadLineF(buffer%, LENline$+1, "~", &08000000)
     ENDPROC

     
REM Remove history item specified by its position from the history
     
DEF PROC_RemoveHistoryItem(line%)
     LOCAL temp%
     temp% = FN_ReadLineF(0, 0, "~", &09000000 + line%)
     ENDPROC

     
REM Replace history item specified by its position with the given line
     
DEF PROC_ReplaceHistoryItem(line%, line$)
     LOCAL temp%, buffer%
     DIM buffer% LOCAL LENline$+1
     $buffer% = line$
     temp% = FN_ReadLineF(buffer%, LENline$+1, "~", &0A000000 + line%)
     ENDPROC

     
REM Move cursor
     
DEF PROC_MoveCursor(offset%, flag%)
     IF SGNoffset% = -1 THEN
       PROC
_WRSTR(STRING$(ABSoffset%, CHR$8), flag%)
     ELSE
       PROC
_WRSTR(STRING$(offset%, CHR$9), flag%)
     ENDIF
     ENDPROC

     
REM Switch foreground and background text colours
     
DEF PROC_InvertVideo
     LOCAL fgc%,bgc%
     fgc% = @vdu%?70
     bgc% = @vdu%?71
     COLOUR bgc%
     COLOUR 128+fgc%
     ENDPROC

     
REM Return TRUE if text cursor is enabled
     
DEF FN_CursorIsEnabled
     =((@vdu%?68 AND 32) >> 5) = 0

     REM Return TRUE if *ESCape is enabled
     
DEF FN_EscapeIsEnabled
     =((@flags% AND &40000000) >> 30) = 0

     REM Write character observing flags
     
DEF PROC_WRCH(char%, flag%)
     IF (flag% AND &1FF) = 0 THEN ENDPROC
     IF
(flag% AND &1FF) = 2 THEN flag% = 256 + 42
     IF char% > 31 AND (flag% AND &100) THEN
       
char% = flag% AND &FF
       IF char% < 31 OR char% > 126 THEN char% = 42
     ENDIF
     IF
char% = 31 THEN char% = 32
     VDU char%
     ENDPROC

     
REM Write multiple characters observing flags
     
DEF PROC_WRSTR(string$, flag%)
     IF LENstring$ = 0 THEN ENDPROC
     LOCAL
i%
     FOR i% = 1 TO LEN string$
       PROC_WRCH(ASCMID$(string$, i%), flag%)
     NEXT i%
     ENDPROC