'
' Simple console based editor with basic syntax highlighting.
'
' June/July/August 2013, PvE.
' -----------------------------------------------------------

PRAGMA OPTIONS -O2 -march=native -s

' Parse arguments to program
SPLIT ARGUMENT$ BY " " TO arg$ SIZE dim
IF dim < 2 THEN
    PRINT "Usage: editor <file>"
    END
END IF

' Create a 0-byte file if source does not exist
Src_File$ = arg$[1]
IF RIGHT$(Src_File$, 4) != ".bac" THEN Src_File$ = Src_File$ & ".bac"

IF NOT(FILEEXISTS(Src_File$)) THEN
    OPEN arg$[1] FOR WRITING AS tmp
    CLOSE FILE tmp
END IF

' Define statements
CONST Stat$ = "ALARM|ALIAS|APPENDING|AS|ASSOC|BACK|BASE|BG|BLACK|BLUE|BREAK|BROADCAST|" & \
"BY|CALL|CASE|CATCH|CHANGEDIR|CHUNK|CLEAR|CLOSE|COLLAPSE|COLOR|CONST|COMPARE|COMPILER|" & \
"CONTINUE|COPY|CURRENT|CURSOR|CYAN|DATA|DECLARE|DECR|DEFAULT|DEF|DELETE|DIRECTORY|DO|" & \
"DOWN|ELIF|ELSE|ENDFUNCTION|ENDIF|END|ENDSELECT|ENDSUB|ENDUSEC|ENDWITH|EPRINT|EQ|EXIT|FG|FI|" & \
"FILE|LDFLAGS|FN|FOR|FORMAT|FORWARD|FREE|FROM|FUNCTION|GE|GETBYTE|GETFILE|GETLINE|GLOBAL|" & \
"GOSUB|GOTO|GOTOXY|GREEN|GT|IF|IMPORT|IN|INCLUDE|INCR|INPUT|INTENSE|INTERNATIONAL|IS|ISNOT|" & \
"JOIN|LABEL|LE|LET|LOCAL|LOOKUP|LT|MAGENTA|MAKEDIR|MEMREWIND|MEMSTREAM|MEMTYPE|MULTICAST|" & \
"NE|NETWORK|NEXT|NORMAL|OFF|OFFSET|ON|OPEN|OPTION|OPTIONS|POKE|PRAGMA|PRINT|PROTO|PULL|" & \
"PUSH|PUTBYTE|PUTLINE|READING|READ|READLN|READWRITE|RECEIVE|RECORD|RECURSIVE|RED|REDIM|" & \
"RELATE|RENAME|REPEAT|RESET|RESIZE|RESTORE|RESUME|RETURN|REWIND|SCROLL|SCTP|SEED|SEEK|" & \
"SELECT|SEND|SERVER|SETENVIRON|SIZE|SLEEP|SOCKET|SORT|SPLIT|START|STARTPOINT|STEP|STOP|SUB|SWAP|" & \
"SYSTEM|TCP|TEXTDOMAIN|THEN|TO|TRACE|TRAP|TYPE|UDP|UNTIL|UP|USEC|USEH|VAR|WEND|WHENCE|WHILE|" & \
"WHITE|WITH|WRITELN|WRITING|YELLOW"
CONST Stat_Col = 2
CONST Stat_Check$ = " " & REPLACE$(Stat$, "|", " ") & " "

' Define functions
CONST Func$ = "ABS|ACOS|ADDRESS|AND|ASC|ASIN|ATN|COLUMNS|COS|DAY|DEC|ENDFILE|EQUAL|ERROR|EVEN|EXP|" & \
"FILEEXISTS|FILELEN|FILETIME|FILETYPE|FLOOR|FP|GETKEY|GETX|GETY|HOUR|INSTR|INSTRREV|INT|" & \
"ISFALSE|ISTRUE|LEN|LOG|MEMCHECK|MEMORY|MEMTELL|MINUTE|MOD|MONTH|NOT|NOW|ODD|OR|PEEK|POW|" & \
"RANDOM|REGEX|RND|ROUND|ROWS|SEARCH|SECOND|SGN|SIN|SIZEOF|SQR|TAN|TELL|TIMER|TIMEVALUE|" & \
"VAL|WAIT|WEEK|YEAR|ARGUMENT\\$|CHOP\\$|CHR\\$|CONCAT\\$|CURDIR\\$|ERR\\$|EXEC\\$|EXTRACT\\$|" & \
"FILL\\$|GETENVIRON\\$|GETPEER\\$|HEX\\$|HOST\\$|INTL\\$|LCASE\\$|LEFT\\$|MID\\$|MONTH\\$|NNTL\\$|" & \
"OS\\$|REPLACE\\$|REVERSE\\$|RIGHT\\$|SPC\\$|STR\\$|TAB\\$|UCASE\\$|WEEKDAY\\$"
CONST Func_Col = 6
CONST Func_Check$ = " " & REPLACE$(EXTRACT$(Func$, "\\"), "|", " ") & " "

' Numbers
CONST Num$ = "0|1|2|3|4|5|6|7|8|9"
CONST Num_Col = 1
CONST Num_Check$ = " " & REPLACE$(Num$, "|", " ") & " "

' Variables
CONST Var$ = "PI|TRUE|FALSE|REGLEN|RETVAL|MAXRANDOM|NL\\$|VERSION\\$"
CONST Var_Col = 3
CONST Var_Check$ = " " & REPLACE$(EXTRACT$(Var$, "\\"), "|", " ") & " "

' Comments
CONST Comm$ = "REM|'"
CONST Comm_Col = 4
CONST Comm_Check$ = " " & REPLACE$(Comm$, "|", " ") & " "

' Quoted text
CONST Quot_Col = 5

' Array size
CONST Dimension = 10240

LET Rows = ROWS
LET Columns = COLUMNS

LET Spos = 1

' Temporary file settings
Tmp_File$ = Src_File$ & ".tmp"

' Position of cursor
DECLARE Xpos, Ypos, Total_Lines, curline

Xpos = 1
Ypos = 1

' Insert mode 0=ON, 1=OFF
Insert_Mode = 0

' Compile flags
compile = FALSE

' Get file
OPEN Src_File$ FOR READING AS prog
OPEN Tmp_File$ FOR WRITING AS tmp

' Replace tabs and count lines
WHILE NOT(ENDFILE(prog))
    READLN line$ FROM prog
    IF NOT(ENDFILE(prog)) THEN
        WRITELN REPLACE$(line$, CHR$(9), SPC$(4)) TO tmp
        INCR Total_Lines
    END IF
WEND

CLOSE FILE tmp
CLOSE FILE prog

' Replace file
DELETE FILE Src_File$
RENAME Tmp_File$ TO Src_File$

CALL Show_Lines(0)
GOTOXY Xpos, Ypos

'------------------------------------------------------------------

REPEAT
    refresh = TRUE

    ' Scan for key and delay at the same time
    key = WAIT(STDIN_FILENO, 40)

    SELECT key
        ' No key pressed? Skip
        CASE 0
            CONTINUE

        ' Special sequence like <cursor up>, <down>, <alt>+<e> etc
        CASE 27
            curs = WAIT(STDIN_FILENO, 10)

            SELECT curs
                ' <ALT> + Q key
                CASE 113
                    BREAK

                ' <ALT> + C key
                CASE 99
                    compile = TRUE
                    BREAK

                ' <ALT> + A key
                CASE 97
                    message$ =  "      KEYS                  " & NL$ \
                                " ALT Q / ESC = quit         " & NL$ \
                                " ALT C = compile and quit   " & NL$ \
                                " ALT A = show help          " & NL$ \
                                " Insert = toggle insert mode" & NL$
                    CALL Show_Message(message$, Columns/2, Rows/2-5, 2000)
                    key = -1

                CASE 91
                ' One of the cursor keys
                    curs = WAIT(STDIN_FILENO, 10)

                    SELECT curs
                        ' Cursor down
                        CASE 66
                            IF Ypos = Rows THEN
                                IF curline < Total_Lines-Rows THEN:INCR curline:FI
                            ELSE
                                IF Ypos < Total_Lines THEN INCR Ypos
                                refresh = FALSE
                            END IF
                        ' Cursor up
                        CASE 65
                            IF Ypos = 1 THEN
                                IF curline > 0 THEN:DECR curline:FI
                            ELSE
                                DECR Ypos
                                refresh = FALSE
                            END IF
                        ' Cursor right
                        CASE 67
                            IF Xpos = Columns THEN
                                IF Spos < Columns THEN:INCR Spos:FI
                            ELSE
                                INCR Xpos
                                refresh = FALSE
                            END IF
                        ' Cursor left
                        CASE 68
                            IF Xpos = 1 THEN
                                IF Spos > 1 THEN:DECR Spos:FI
                            ELSE
                                DECR Xpos
                                refresh = FALSE
                            END IF
                        ' Insert key
                        CASE 50
                            curs = WAIT(STDIN_FILENO, 10)
                            IF curs = 126 THEN Insert_Mode = 1 - Insert_Mode
                            IF Insert_Mode THEN
                                CALL Show_Message("INSERT mode: OFF", Columns/2, Rows/2, 500)
                            ELSE
                                CALL Show_Message("INSERT mode: ON", Columns/2, Rows/2, 500)
                            ENDIF
                        ' Delete key
                        CASE 51
                            curs = WAIT(STDIN_FILENO, 10)
                            'IF curs = 126 THEN
                        ' Page up key
                        CASE 53
                            curs = WAIT(STDIN_FILENO, 10)
                            IF curs = 126 THEN
                                IF curline > Rows THEN
                                    DECR curline, Rows
                                ELSE
                                    curline = 0
                                END IF
                            END IF
                        ' Page down
                        CASE 54
                            curs = WAIT(STDIN_FILENO, 10)
                            IF curs = 126 THEN
                                IF curline < Total_Lines-2*Rows THEN
                                    INCR curline, Rows
                                ELSE
                                    curline = Total_Lines-Rows
                                END IF
                            END IF
                    END SELECT
                    key = -1

                CASE 79
                    curs = WAIT(STDIN_FILENO, 10)
                    SELECT curs
                        ' Home key
                        CASE 72
                            Xpos = 1
                        ' End key
                        CASE 70
                            Xpos = Columns
                    END SELECT
                    key = -1
                    refresh = FALSE
            END SELECT

        ' All other keys
        DEFAULT
            OPEN Src_File$ FOR READING AS prog
            OPEN Tmp_File$ FOR WRITING AS tmp
            ' Find current line
            count = 0
            WHILE count < curline+Ypos-1
                READLN line$ FROM prog
                WRITELN line$ TO tmp
                INCR count
            WEND
            ' Edit line
            READLN line$ FROM prog
            ' Backspace
            IF key = 127 THEN
                IF LEN(line$) > 0 THEN
                    IF Xpos > 1 THEN
                        WRITELN LEFT$(line$, Xpos-2+Spos-1), MID$(line$, Xpos+Spos-1) TO tmp
                    ELSE
                        WRITELN line$ TO tmp
                    ENDIF
                ENDIF
                IF Xpos > 1 THEN:DECR Xpos:FI
            ' CR key
            ELIF key = 10 THEN
                IF Xpos = 1 THEN
                    WRITELN "" TO tmp
                ELSE
                    WRITELN line$ TO tmp
                FI
                IF Xpos = 1 THEN
                    WRITELN line$ TO tmp
                ELSE
                    WRITELN "" TO tmp
                FI
                IF Ypos < Rows THEN
                    INCR Ypos
                ELSE
                    IF curline < Total_Lines-Rows THEN:INCR curline:FI
                END IF
                Xpos = 1
            ' Other keys
            ELSE
                ' Replace TAB
                IF key = 9 THEN
                    ch$ = SPC$(4)
                ELSE
                    ch$ = CHR$(key)
                FI
                ' Write the character
                IF LEN(line$) >= Xpos THEN
                    WRITELN LEFT$(line$, Xpos-1+Spos-1), ch$, MID$(line$, Xpos+Spos-1+Insert_Mode) TO tmp
                ELSE
                    WRITELN line$, SPC$(Xpos-1-LEN(line$)), ch$ TO tmp
                FI
                ' Move position
                IF Xpos < Columns THEN
                    IF key = 9 THEN
                        INCR Xpos, 4
                    ELSE
                        INCR Xpos
                    FI
                FI
            ENDIF
            ' Write rest of the file
            WHILE NOT(ENDFILE(prog))
                READLN line$ FROM prog
                IF NOT(ENDFILE(prog)) THEN WRITELN line$ TO tmp
            WEND
            ' Replace
            DELETE FILE Src_File$
            RENAME Tmp_File$ TO Src_File$
            ' End
            CLOSE FILE tmp
            CLOSE FILE prog
    END SELECT

    IF refresh THEN CALL Show_Lines(curline)

    ' Set position of cursor
    GOTOXY Xpos, Ypos

UNTIL key = 27

' Place back tabs
OPEN Src_File$ FOR READING AS prog
OPEN Tmp_File$ FOR WRITING AS tmp

' Create list of all lines in the file and replace tabs
WHILE NOT(ENDFILE(prog))
    READLN line$ FROM prog
    IF NOT(ENDFILE(prog)) THEN WRITELN REPLACE$(line$, SPC$(4), CHR$(9)) TO tmp
WEND

CLOSE FILE tmp
CLOSE FILE prog

' Replace file
DELETE FILE Src_File$
RENAME Tmp_File$ TO Src_File$

' Exit gracefully
CURSOR ON
PRINT
GOTOXY 1, Rows

' If compile
IF compile THEN SYSTEM "bacon " & CURDIR$ & "/" & Src_File$

END

'------------------------------------------------------------------

SUB Show_Lines(NUMBER where)

    ' Reserve array with filepointers
    LOCAL fp[Dimension]

    LOCAL txt$
    LOCAL pos, col, padding, len, total

    GOTOXY 1,1
    CURSOR OFF

    OPEN Src_File$ FOR READING AS prog

    ' Recreate index and recount lines
    Total_Lines = 0
    WHILE NOT(ENDFILE(prog))
        fp[Total_Lines] = TELL(prog)
        READLN txt$ FROM prog
        INCR Total_Lines
    WEND

    REWIND prog
    SEEK prog OFFSET fp[where]

    ' Show code
    FOR x = 1 TO Rows

        PUSH Spos

        READLN txt$ FROM prog
        txt$ = REPLACE$(txt$, CHR$(9), SPC$(4))

        ' How many spaces we need to fill up the line
        padding = Columns - LEN(txt$)

        REPEAT
            ' See if we have a keyword or number
            pos = REGEX(MID$(txt$, Spos), Stat$ & "|" & Func$ & "|" & Num$ & "|" & Var$ & "|" & Comm$ & "|\"[^\"]+\"")

            ' Keyword found?
            IF pos > 0 THEN
                len = REGLEN
                ' Determine color
                IF INSTR(Stat_Check$, " " & MID$(MID$(txt$, Spos), pos, REGLEN) & " ") THEN
                    col = Stat_Col
                ELIF INSTR(Num_Check$, " " & MID$(MID$(txt$, Spos), pos, REGLEN) & " ") THEN
                    col = Num_Col
                ELIF INSTR(Func_Check$, " " & MID$(MID$(txt$, Spos), pos, REGLEN) & " ") THEN
                    col = Func_Col
                ELIF INSTR(Var_Check$, " " & MID$(MID$(txt$, Spos), pos, REGLEN) & " ") THEN
                    col = Var_Col
                ELIF INSTR(Comm_Check$, " " & MID$(MID$(txt$, Spos), pos, REGLEN) & " ") THEN
                    col = Comm_Col
                    len = LEN(MID$(txt$, Spos)) - pos+1
                ELSE
                    col = Quot_Col
                FI
                ' Render the keyword with the colors
                PRINT MID$(txt$, Spos, pos-1);
                COLOR FG TO col
                PRINT MID$(txt$, Spos+pos-1, len);
                COLOR RESET
                txt$ = MID$(txt$, Spos+pos-1+len)
                Spos = 1
            ELSE
                ' Text without highlighting
                PRINT MID$(txt$, Spos, Columns), SPC$(padding);
                PULL Spos
                IF x < Rows THEN PRINT
            END IF
        UNTIL pos = 0
    NEXT

    CLOSE FILE prog

    CURSOR ON

END SUB

'------------------------------------------------------------------

SUB Show_Message(STRING txt$, NUMBER x, NUMBER y, NUMBER delay)

    LOCAL l$
    LOCAL len

    ' Determine max length
    FOR l$ IN txt$ STEP NL$
        IF LEN(l$) > len THEN len = LEN(l$)
    NEXT

    COLOR FG TO GREEN

    GOTOXY x-len/2-1, y
    PRINT "+", FILL$(len+2, ASC("-")), "+"

    FOR l$ IN txt$ STEP NL$
        INCR y
        GOTOXY x-len/2-1, y
        PRINT "| ", l$, " |"
    NEXT

    GOTOXY x-len/2-1, y+1
    PRINT "+", FILL$(len+2, ASC("-")), "+"

    CURSOR OFF
    COLOR RESET

    SLEEP delay

END SUB

'------------------------------------------------------------------