SUBROUTINE editor(output.string, input.string)
***
*
*  Editor like INFORMATION EDITOR with some limitations for non-
*    privileged user use.  Each line should be separated by a @FM.
*
*  D. Armatage                11-4-88         Original Coding
*
*  Local variables and assignments
*
***
LastUpdated... = "Rev: 14:09 30APR90 ANDREW <KSF.V2>EMAIL 160 Z <KSF.V2>EMAIL>BP>EDITOR"
     rev = FIELD(LastUpdated...," ",3,1)

     COMMON init, help, valid.delim, EmailHelpFile, HelpAvailable

     IF rev NE init THEN
        init = rev
        HelpAvailable = 1
        OPEN '','EMAIL.HELP' TO EmailHelpFile  THEN
           READ help FROM EmailHelpFile, "EDITOR" ELSE
              CRT "*** Unable to read help record, helps will not be available."
              HelpAvailable = 0
           END
           READ valid.delim FROM EmailHelpFile, 'VALID.DELIMITERS' ELSE
              CRT "*** Unable to get valid delimiter list, please call MIS.":@SYS.BELL
              RETURN
           END
        END ELSE
           CRT "*** Unable to open HELP file, helps will not be available."
           HelpAvailable = 0
        END
     END

     EQU command.li TO "----: "         ;*command line
     EQU msg.space LIT "( IF LEN( msg ) THEN '  ' ELSE '' )"

     nopage = @(0,0)
     sofin = "$SOFIN.MAX"
     justify = "TEXT.JUSTIFIER"
     PROMPT ''
     CONVERT @IM:@VM:@SM TO '' IN input.string
     CALL !PTERM("HALF -NOLF")
     !
     crt.valid.delim = valid.delim
     CONVERT @VM TO '' IN crt.valid.delim

     select.start      = 0              ;*begin of selected text
     last.select.start = 0              ;*for 'OOPS'
     select.end        = 0              ;*end of selected text
     last.select.end   = 0              ;*for 'OOPS'
     output.string     = ''
     exit              = 0
     idx               = 0
     last.idx          = 0
     modified          = 0

     matrix.max = 500                   ;*default size
     DIM record(matrix.max), last.record(matrix.max)
     IF INMAT() THEN
        CRT "Insufficient memory space. This operation cannot be done.":
        CRT @SYS.BELL
        RETURN
     END
     MAT record = '' ; MAT last.record = ''
     IF input.string NE '' THEN
        MATPARSE record FROM input.string , @FM
        max.idx = INMAT()
        last.max.idx = max.idx
        ! ... if (record) matrix not large enough then enlarge.
        IF NOT(INMAT()) THEN
           max.idx = COUNT(input.string,@FM) + (input.string # '')
           last.max.idx = max.idx
           matrix.max = max.idx + 500
           DIM record(matrix.max), last.record(matrix.max)
           IF INMAT() THEN
              CRT "Insufficient memory space. This operation cannot be done.":
              CRT @SYS.BELL
              RETURN
           END
           MAT record = ''
           MATPARSE record FROM input.string , @FM
        END
        CRT max.idx: " lines."
     END ELSE
        max.idx = 0
        last.max.idx = 0
        CRT "New record."
     END
     MAT last.record = MAT record
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*   main processor
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
enter.command:
*
     new.string = ''
     CRT command.li:
     GOSUB read.line:
     command.line = new.string
     uc.command.line = OCONV(command.line,'MCU')
     old.string = record(idx)
     command.type = OCONV(new.string[1,1],'MCU')
     command = OCONV(FIELD(new.string,' ',1),'MCU')
     new.string = new.string[COL2()+1,LEN(new.string)]
     msg = ''

     BEGIN CASE
        CASE command.type EQ 'J' AND NOT(new.string) ;*justifier
           GOSUB command.j:
        CASE command EQ 'I'
           GOSUB command.i:
        CASE command EQ 'CAT'
           back.idx = idx
           select.start = idx + 1
           select.end = select.start
           GOSUB copy.record:
           IF idx LT max.idx THEN
              record(idx) := new.string: record(idx+1)
              GOSUB crunch.record:
           END
           idx = back.idx
        CASE command.type EQ 'C'        ;*change
           GOSUB command.c:
        CASE command.type EQ '+' OR command.type EQ '-' ;*+- line(s)
           x = command
           IF NUM(x) THEN
              IF x+idx GE 0 AND x+idx LE max.idx THEN
                 idx += x
              END ELSE
                 idx = (IF x+idx LT 0 THEN 0 ELSE max.idx)
              END
           END ELSE
              msg := msg.space: "Numeric required.": @SYS.BELL
           END
        CASE command EQ 'B' AND new.string EQ '' ;*bottom
           idx = max.idx
        CASE command EQ 'B' AND new.string NE '' ;*break line
           GOSUB command.b:
        CASE command EQ 'T' AND new.string EQ '' ;*top
           idx = 0
        CASE command EQ 'DROP' AND new.string EQ ''
           GOSUB select.text:
           IF msg EQ '' THEN
              GOSUB copy.record:
              GOSUB crunch.record:
           END
        CASE command.type EQ 'D' AND new.string EQ '' ;*delete
           GOSUB command.d:
        CASE command EQ 'SP' AND new.string EQ ''
           IF select.start AND select.end AND select.start LE select.end THEN
              CRT "Text selected from ":select.start:" to ":select.end
           END ELSE
              CRT "Invalid text select.  ":
              CRT "from: ": select.start: " to: ": select.end
           END
        CASE command.type EQ 'P' AND new.string EQ '' ;*CRT line(s)
           GOSUB command.p:
        CASE command EQ 'R'             ;*replace with 'any'
           GOSUB copy.record:
           record(idx) = new.string
        CASE command EQ 'A'             ;*append to line
           GOSUB copy.record:
           record(idx) := new.string
        CASE command EQ '<>' AND new.string EQ ''
           select.start = idx
           select.end = idx
           msg := msg.space:"Text selected from ": idx: " to ": idx: "."
        CASE command EQ '<' AND new.string EQ '' ;*start SELECTED text
           IF NOT(idx) THEN idx = 1
           select.start = idx
           msg := msg.space: "Text selected from ": idx
        CASE command EQ '>' AND new.string EQ '' ;*end of SELECTED text
           IF NOT(idx) THEN idx = 1
           select.end = idx
           msg := msg.space: "Text selected to ": idx
        CASE command EQ ''              ;*advance one line
           IF idx + 1 GT max.idx THEN idx = 0 ELSE idx += 1
        CASE command EQ 'OOPS' AND new.string EQ ''
           idx = last.idx
           max.idx = last.max.idx
           MAT record = MAT last.record
           select.start = last.select.start
           select.end = last.select.end
        CASE command EQ 'FI' OR command EQ 'FILE' AND new.string = ''
           exit = 1                     ;*exit and update
        CASE command EQ 'Q' OR command EQ 'QUIT' AND new.string = ''
           GOSUB command.q:
        CASE NUM(command) AND new.string = '' ;*goto (command) line#
           IF command LE max.idx AND command GT -1 THEN
              idx = INT(command)
           END ELSE
              idx = (IF command GT max.idx THEN max.idx ELSE 0)
           END
        CASE command EQ 'UC' AND new.string EQ ''
           GOSUB copy.record:
           record(idx) = OCONV(record(idx),'MCU')
        CASE command EQ 'LC' AND new.string EQ ''
           GOSUB copy.record:
           record(idx) = OCONV(record(idx),'MCL')
        CASE command EQ 'HELP'          ;*display help(s)
           GOSUB command.help:
        CASE command EQ 'SIZE'
           GOSUB command.size:
        CASE uc.command.line EQ 'PRINT HELP'
           GOSUB print.help:
        CASE 1
           msg := msg.space: "What?  Try help. ": @SYS.BELL
     END CASE
     !
     IF NOT(idx) THEN
        msg = "Top.": msg.space: msg
     END ELSE
        CRT (('000':idx:': ')[6]): record(idx)
        IF idx = max.idx THEN
           msg = "Bottom at line ": max.idx: ".": msg.space: msg
        END
     END
     IF msg THEN
        CRT msg
     END
     !
     IF NOT(exit) THEN
        GOTO enter.command:
     END ELSE
        IF exit GT 0 THEN
           FOR i = 1 TO max.idx
              output.string<i> = record(i)
           NEXT i
        END ELSE
           output.string = ''
        END
        CALL !PTERM("FULL")
        RETURN
     END
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*   command subroutines
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
command.j:
*
     GOSUB select.text:
     delim = command[2,1]
     IF delim MATCHES valid.delim THEN
        type = FIELD(command,delim,2)   ;*justify type
        indent = FIELD(command,delim,3) ;*indent spaces
        length = FIELD(command,delim,4) ;*length of line
        IF NOT(msg) THEN
           result = ''
           flag = 0
           CALL @justify(result, selected.text, length,
              type, indent, flag)
           IF NOT(flag) THEN
              new.string = result
              GOSUB copy.record:
              GOSUB crunch.record:
              ! ... next 2 code lines necessary for correct expanding positioning.
              idx -= (idx GT 0 AND idx LE max.idx)
              ! ... ... next line necessary if select.end equals max.idx.
              idx += (last.max.idx EQ last.select.end)
              GOSUB expand.record:
           END
        END
     END ELSE
        CRT "Invalid delimiter.  Must match ":crt.valid.delim:@SYS.BELL
     END
     RETURN
*
command.i:
*
     IF new.string THEN leave.loop = -2 ELSE leave.loop = 0
     ! ... (-2) will exit loop if insert data on command.line.
     LOOP
        leave.loop += 1
        IF leave.loop THEN
           IF new.string EQ '' THEN
              CRT (('000':(idx+1):'> ')[6]):
              GOSUB read.line:
           END
        END
     UNTIL new.string EQ '' DO
        GOSUB copy.record:
        GOSUB expand.record:
        new.string = ''
     REPEAT
     RETURN
*
command.c:
*
     delim = command.line[2,1]
     IF delim MATCHES valid.delim THEN
        zz = COUNT(command.line,delim)
        IF zz NE 2 AND zz NE 3 THEN
           msg := msg.space: "Must have 2 to 3 delimiters": @SYS.BELL
           msg := " to use 'C'hange option."
        END ELSE
           drop.text = FIELD(command.line,delim,2)
           add.text = FIELD(command.line,delim,3)
           x = INDEX(old.string,drop.text,1)
           IF x THEN
              y = old.string[1,x-1]
              y := add.text
              y := old.string[x+LEN(drop.text),LEN(old.string)]
              GOSUB copy.record:
              record(idx) = y
           END ELSE
              msg := msg.space: "'": drop.text: "' is not in this line."
           END
        END
     END ELSE
        CRT "Invalid delimiter.  Must match ":crt.valid.delim:@SYS.BELL
     END
     RETURN
*
command.b:
*
     x = new.string
     y = INDEX(old.string,x,1)
     IF y THEN
        z = y + LEN(x)
        GOSUB copy.record:
        record(idx) = old.string[1,z-1]
        new.string = old.string[z,LEN(old.string)]
        GOSUB expand.record:
        idx -= 1
     END
     RETURN
*
command.d:
*
     drop.cnt = (command[2,LEN(command)])
     IF NUM(drop.cnt) THEN
        IF NOT(drop.cnt) THEN drop.cnt = 1
        z = idx + drop.cnt - 1
        IF z GT max.idx THEN drop.cnt = max.idx - idx + 1
        IF drop.cnt GT 0 THEN
           select.start = idx
           select.end = idx + drop.cnt - 1
           GOSUB copy.record:
           GOSUB crunch.record:
        END ELSE
           msg := msg.space: "negative deletion invalid."
        END
     END ELSE
        msg := msg.space: "Invalid use of 'D'elete option."
        msg := "   Numeric required.":  @SYS.BELL
     END
     RETURN
*
command.p:
*
     display.lines = command[2,LEN(command)]
     IF NUM(display.lines) THEN
        IF NOT(display.lines) THEN display.lines = 21
        IF display.lines LT 0 THEN
           ! ... next line deals with (P-n) option
           idx = (IF idx + display.lines LT 0 THEN 0 ELSE idx + display.lines)
           display.lines = 21
        END
        x = 0                           ;*# lines printed
        FOR i = idx TO (idx + display.lines)
        WHILE i LT max.idx
           IF i THEN
              CRT (('000':i:': ')[6]): record(i)
              x += 1
           END
        NEXT i
        idx += x
        idx += (idx LT max.idx)         ;*for line # CRT below
     END ELSE
        msg := msg.space: "Numeric required." : @SYS.BELL
     END
     RETURN
*
command.help:
*
     IF NOT(HelpAvailable) THEN
        CRT "*** Helps not available."
        RETURN
     END
     help = help                        ;*for additional REMOVEs
     delim = 1                          ;*to begin loop
     y = 1                              ;*# lines printed on CRT
     z = LEN(new.string)
     new.string = OCONV(new.string,'MCU')
     cont = ''                          ;*screen page break prompt var
     more = 0                           ;*toggle for 'HELP abcd' option
     !
     LOOP
     WHILE delim AND cont NE 'Q' DO
        x = REMOVE(help,delim)
        !
        IF x[1,1] NE '*' THEN
           IF z THEN
              IF x[1,z] EQ new.string OR (x[1,1] EQ ' ' AND more) THEN
                 CRT x
                 y += 1
                 more = 1
              END ELSE
                 more = 0
              END
           END ELSE
              CRT x
              y += 1
           END
        END
        !
        IF NOT(MOD(y,20)) THEN
           CRT "any key to continue, or 'Q'...":
           INPUT cont,1
           cont = OCONV(cont,'MCU')
           CRT                          ;*CRT due to (PTERM -HALF -NOLF)
        END
        !
     REPEAT
     !
     IF y LE 1 AND new.string THEN
        CRT "No helps for ": new.string: ".  Try 'HELP'.": @SYS.BELL
     END
     RETURN
*
print.help:
*
     help = help                        ;*for additional REMOVEs
     !
     EXECUTE "SETUP T"
     HEADING "Editor Helps                     PAGE 'S' 'TL'"
     nopage = @(0,0)
     !
     LOOP
        x = REMOVE(help,delim)
        IF x[1,1] NE '*' THEN
           CRT x
        END
     WHILE delim DO
     REPEAT
     !
     EXECUTE "SETUP"
     CALL !BPIOCP
     nopage = @(0,0)
     RETURN
*
command.size:
*
     x = 0
     z = 0
     FOR i = 1 TO max.idx
        x += LEN( record(i) )
        z += COUNT(record(i),' ')
     NEXT i
     ! ... include the resulting @FM ( x + max.idx)
     CRT "# lines = ": max.idx," # bytes = ": x + max.idx:
     CRT ,"bytes - spaces = ": ( x + max.idx - z)
     RETURN
*
command.q:
*
     IF modified THEN
        CRT "***** Record changed --- O.K. to Quit (N) ? ":
        CRT @SYS.BELL:
        GOSUB read.line:
        new.string = OCONV(new.string,'MCU')
        IF new.string EQ 'Y' OR new.string EQ 'YES' THEN
           exit = -1
        END ELSE
           exit = 0
        END
     END ELSE
        exit = -1                       ;*exit no update
     END
     RETURN
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*   miscellaneous subroutines
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
***************
select.text:
     !
     selected.text = ''
     BEGIN CASE
        CASE NOT(select.start) OR NOT(select.end)
           msg := msg.space: 'No text selected.'
           msg := msg.space: '  This option requires selected text.'
        CASE select.end LT select.start
           msg := msg.space: "Select end is ":select.end
           msg := msg.space: "  Select start is ":select.start
           msg := msg.space: "  End point s/b GE start point."
        CASE 1
           x = 0
           FOR i = select.start TO select.end
              x += 1
              selected.text<x> = record(i)
           NEXT i
     END CASE
     RETURN
***************
expand.record:
     !
     xx = COUNT(new.string,@FM) + (new.string # '')
     IF xx + max.idx GT matrix.max THEN
        DIM record(xx+max.idx), last.record(xx+max.idx)
        IF INMAT() THEN
           CRT "Insufficient memory space. This operation cannot be done.":
           CRT @SYS.BELL
           RETURN
        END
        matrix.max = xx + max.idx
     END
     FOR x = max.idx TO idx + 1 STEP -1
        record(x+xx) = record(x)
     NEXT x
     FOR x = 1 TO xx
        record(idx+x) = new.string<x>
     NEXT x
     max.idx += xx
     idx += xx
     RETURN
***************
crunch.record:
     !
     FOR i = select.start TO select.end
        record(i) = ''
     NEXT i
     lose.no. = select.end - select.start + 1
     FOR i = select.end + 1 TO max.idx
        x = i - lose.no.
        record(x) = record(i)
        record(i) = ''
     NEXT i
     max.idx -= lose.no.
     IF max.idx LT 0 THEN max.idx = 0
     idx = (IF select.start LT max.idx THEN select.start ELSE max.idx)
     last.select.start = select.start
     last.select.end = select.end
     select.start = 0
     select.end = 0
     RETURN
***************
read.line:
     BREAK KEY OFF
     sofin$buf = SPACE(80)
     sofin$max = 80
     sofin$len = 0
     sofin$trm = 'x'
     sofin$flg = 1 + 2
read.loop:
     CALL @sofin(sofin$buf, sofin$max, sofin$len,
        sofin$trm, sofin$flg)
     !
     sofin$cmd = SEQ(sofin$trm)
     BEGIN CASE
        CASE sofin$trm GE ' ' AND sofin$trm LE '~'
           ! ... 32767 is GCI max characters
           IF sofin$max+80 LE 32000 THEN
              sofin$buf = sofin$buf[1,sofin$max]: sofin$trm: SPACE(79)
              sofin$len += 1
              sofin$max += 80
              CRT sofin$trm:
              GOTO read.loop:
           END ELSE
              CRT
              CRT "String max length reached.": @SYS.BELL
           END
        CASE sofin$cmd EQ 5
           CRT @(-1):
           BREAK KEY ON
           RETURN
        CASE sofin$cmd EQ 10
           new.string = sofin$buf[1,sofin$len]
           CRT
        CASE sofin$cmd EQ 255
           CRT "Forced logout. (sofin)"
           EXECUTE "LOGOUT"
           ABORT
        CASE 1
           CRT @SYS.BELL:
           GOTO read.loop:
     END CASE
     BREAK KEY ON
     RETURN
***************
copy.record:
     !
     last.idx = idx
     last.max.idx = max.idx
     MAT last.record = MAT record
     !
     last.select.start = select.start
     last.select.end = select.end
     !
     modified = 1
     RETURN
*
  END
