/* * edit.b * Editor. * BCPL port of my small nano-like editor. * * Copyright (c) 2020 Gordon Henderson ********************************************************************************* */ GET "libhdr.h" GET "rubyOs.h" GET "sys.h" GET "stringLib.h" GET "scb.h" GET "vdu.h" GET "rubyFnKeys.h" GLOBAL { screenLines:ug statusMessageThere killBuffer killBufferLines editLineNum editLines lines maxEditLines lineXpos screenY editTop fileEdited screen keyboard mainFilename unKilledLines whereSearch lastWhere movedUpDown tHeight tWidth } MANIFEST { DEFAULT_EDIT_LINES = 2000 EDIT_LINE_ALLOCS = 1000 MAX_KB_LINES = 50 MAX_LINE_LEN = 160 WHERE_LINE_SIZE = 20 DEFAULT_WIDTH = 80 DEFAULT_HEIGHT = 25 KEY_SAVE_FILE = 'X' - '@' // Ctrl-X } LET myVduWrch (ch) = VALOF { RESULTIS sys (Sys_sawrch, ch) } /* * readLineS: * Read in a line of text on the status line. * Returns the line without any trailing CR or LF. ********************************************************************************* */ AND readLineS (line, len) = VALOF { LET x = ? x := reads (line, len, FALSE) IF x < 2 THEN // reads returns trailing NL { line%0 := 0 RESULTIS line } WHILE line%x = '*n' | line%x = '*c' DO // Dump any reailing NL (or CR) x := x - 1 line%0 := x RESULTIS line } /* * clearStatusMsg: statusMsg: * Output/Clear text on the status line ********************************************************************************* */ AND clearStatusMsg () BE { UNLESS statusMessageThere THEN RETURN vduXY (0, tHeight - 1) vduDeleteEOL () statusMessageThere := FALSE deplete (screen) } AND statusMsg (message) BE { clearStatusMsg () vduXY (0, tHeight - 1) sawrites (message) deplete (screen) statusMessageThere := TRUE } /* * clearKillBuffer: * Delete the contents of the kill/paste buffer ********************************************************************************* */ AND clearKillBuffer () BE { FOR i = 0 TO killBufferLines - 1 DO { freevec (killBuffer!i) killBuffer!i := 0 } killBufferLines := 0 } /* * newEditLine: * Create a new line for the editor ********************************************************************************* */ AND newEditLine () = VALOF { LET x = getvec (MAX_LINE_LEN / bytesperword + 1) x%0 := 0 RESULTIS x } /* * newEditBuffer: * Initialise the main editor variables, etc. ********************************************************************************* */ AND newEditBuffer () BE { FOR i = 0 TO editLines - 1 DO UNLESS lines!i = 0 THEN { freevec (lines!i) lines!i := 0 } editLineNum := 0 lineXpos := 0 editTop := 0 screenY := 0 // Create a zero length first line editLines := 1 lines!0 := newEditLine () } /* * displayLine: * Display one line of text on the screen. yPos is the line where the * text will be displayed. ********************************************************************************* */ AND displayLine0 (line, yPos) BE { LET thisLine = ? LET len, c = ?, ? UNLESS 0 <= line < editLines THEN RETURN thisLine := lines!line len := thisLine%0 // Various printing strategies. // Line length is < tWidth. // simples - print the entire line. IF len < tWidth THEN { FOR i = 1 TO len DO sawrch (thisLine%i) vduDeleteEOL () RETURN } // Line length = tWidth, AND lineXpos < tWidth // relatively easy - just output the first tWidth characters, IF len = tWidth & lineXpos < tWidth THEN { FOR i = 1 TO tWidth DO sawrch (thisLine%i) RETURN } // Line length is >= tWidth, AND lineXpos < tWidth // relatively easy - just output the first tWidth characters, TEST lineXpos <= tWidth THEN { FOR i = 1 TO tWidth DO sawrch (thisLine%i) } // Line length is > tWidth, AND lineXpos > tWidth // Worst case - we need to "scroll" the line to the left. ELSE { FOR i = (tWidth - lineXpos) TO tWidth DO sawrch ('+') //(thisLine%i) deplete (screen) } } AND displayLine (line, yPos, updateNow) BE { vduXY (0, yPos) displayLine0 (line, yPos) IF updateNow THEN deplete (screen) } /* * displayPage: * Output a pagefull of program lines from the current editTop line ********************************************************************************* */ AND displayPage () BE { LET last = editLines - editTop vduXY (0,0) FOR y = 0 TO screenLines - 1 DO TEST (y + editTop) < editLines THEN displayLine (editTop + y, y, FALSE) ELSE { vduXY (0,y) sawrch ('~') vduDeleteEOL () } } /* * createEditBuffer * Create a new buffer for us to work with, or delete & free * and existing one first ********************************************************************************* */ AND createEditBuffer () = VALOF { LET newLines = ? newLines := getvec (maxEditLines) IF newLines = 0 THEN { statusMsg ("** Out of memory") RESULTIS FALSE } FOR i = 0 TO maxEditLines DO newLines!i := 0 lines := newLines RESULTIS TRUE } /*************** AND extendEditBuffer () = VALOF { LET currentMaxLines = maxEditLines LET newLines = ? // Extend maxEditLines := maxEditLines + EDIT_LINE_ALLOCS // Allocate space for more newLines := getvec (maxEditLines) IF newLines = 0 THEN { statusMsg ("** Out of memory") RESULTIS FALSE } FOR i = 0 TO maxEditLines DO newLines!i := 0 // Copy old to new UNLESS currentMaxLines = 0 THEN { FOR i = 0 TO currentMaxLines DO { newLines!i := lines!i freevec (lines!i) } freevec (lines) } lines := newLines RESULTIS TRUE } ****************/ /* * readFile: ********************************************************************************* */ AND readFile (filename, startLine) = VALOF { LET file = findinput (filename) LET inputLine = VEC (MAX_LINE_LEN / bytesperword + 1) LET newLine = ? LET len = ? UNLESS file THEN { statusMsg ("** File not found") RESULTIS FALSE } // read, not insert? // check, else we end up with a blank line at the end every time IF startLine = -1 THEN // Read new file { UNLESS lines!0 = 0 THEN freevec (lines!0) lines!0 := 0 editLines := 0 startLine := 0 } selectinput (file) { // Loop len := reads (inputLine, MAX_LINE_LEN - 1) IF len = endstreamch THEN BREAK // Remove any trailing NL or CR WHILE (inputLine%len = '*c' | inputLine%len = '*n') & len ~= 0 DO len := len - 1 // Hit the limit? /*** IF editLines = maxEditLines THEN UNLESS extendEditBuffer () THEN { statusMsg ("** Out of memory") endstream (file) selectinput (keyboard) RESULTIS FALSE } ***/ IF editLines = maxEditLines THEN { statusMsg ("** Too many lines") endstream (file) selectinput (keyboard) RESULTIS FALSE } // Pull lines down FOR i = editLines TO startLine + 1 BY -1 DO lines!(i+1) := lines!i // Make space for this new line newLine := newEditLine () IF newLine = 0 THEN { statusMsg ("** Out of memory") endstream (file) selectinput (keyboard) RESULTIS FALSE } // Copy inputLine to our newLine FOR i = 1 TO len DO newLine%i := inputLine%i newLine%0 := len // Store it lines!startLine := newLine editLines := editLines + 1 startLine := startLine + 1 } REPEAT endstream (file) selectinput (keyboard) RESULTIS TRUE } /* * insertFile: * Insert a file into the current buffer ********************************************************************************* */ AND insertFile () BE { LET filename = ? statusMsg ("File to insert? ") filename := readLineS (60) clearStatusMsg () IF filename%0 = 0 THEN { statusMsg ("** Insert abandoned") ; freevec (filename) RETURN } TEST readFile (filename, editLineNum) THEN { displayPage () statusMsg ("** Loaded OK") fileEdited := TRUE } ELSE statusMsg ("** Unable to load %s", filename) freevec (filename) } /* * whereis: * Handle the Ctrl-W command ********************************************************************************* */ AND whereis () BE { LET found = 0 LET lineFound = ? statusMsg ("WhereIs: ") UNLESS lastWhere%0 = 0 THEN sawritef ("[%s] ", lastWhere) readLineS (whereSearch, WHERE_LINE_SIZE) IF whereSearch%0 = 0 THEN TEST lastWhere%0 = 0 THEN { statusMsg ("** Nothing to find") RETURN } ELSE strcpy (whereSearch, lastWhere) // Start the search from the next line to the end ... FOR i = editLineNum + 1 TO editLines DO { found := strstr (lines!i, whereSearch) UNLESS found = 0 THEN { lineFound := i BREAK } } // ... and if we didn't find it then wrap round from the top IF found = 0 THEN { FOR i = 0 TO editLineNum DO { found := strstr (lines!i, whereSearch) UNLESS found = 0 THEN { lineFound := i BREAK } } IF found = 0 THEN { statusMsg ("** Not found") RETURN } } editLineNum := lineFound editTop := editLineNum - tHeight / 2 IF editTop < 0 THEN editTop := 0 screenY := editLineNum - editTop lineXpos := found - 1 // X starts at zero IF lastWhere = 0 THEN lastWhere := getvec (40) strcpy (lastWhere, whereSearch) displayPage () statusMsg ("** Found on line ") sawritef ("%d", lineFound + 1) } /* * gotoLine: * Handle the Ctrl-G/Go to command ********************************************************************************* */ AND gotoLine () BE { LET line = VEC 20 LET lineNum = ? statusMsg ("Go to line: ") readLineS (line, 20) lineNum := str2numb (line) IF lineNum < 1 THEN RETURN IF lineNum > editLines THEN lineNum := editLines editLineNum := lineNum - 1 editTop := editLineNum - tHeight / 2 IF editTop < 0 THEN editTop := 0 screenY := editLineNum - editTop lineXpos := 0 displayPage () statusMsg ("** Jumped to line ") sawritef ("%d", editLineNum + 1) } /* * finder: * Find a character in the current line ********************************************************************************* */ AND findNext (finder) BE { LET cf = capitalch (finder) FOR x = lineXpos + 2 TO lines!editLineNum%0 DO IF capitalch (lines!editLineNum%x) = cf { lineXpos := x - 1 RETURN } } /* * insertChar: * Insert the given character into the current line ********************************************************************************* */ AND insertChar (c) BE { LET line = lines!editLineNum LET f = line%0 // From LET t = line%0 + 1 // To UNLESS f < MAX_LINE_LEN RETURN WHILE f >= lineXpos DO { line%t := line%f t := t - 1 f := f - 1 } line%(lineXpos + 1) := c line%0 := line%0 + 1 lineXpos := lineXpos + 1 displayLine (editLineNum, screenY, TRUE) } AND deleteChar () BE { LET line = lines!editLineNum LET f = lineXpos + 2 // From LET t = lineXpos + 1 // To IF line%0 = 0 // Empty line RETURN IF line%0 = lineXpos // End of the line? RETURN WHILE f <= line%0 DO { line%t := line%f t := t + 1 f := f + 1 } line%0 := line%0 - 1 displayLine (editLineNum, screenY, TRUE) } /* * handleTabKey: * If at X=0 * move cursor to first non-space character on (non-blank) line above * else * move to next X/2 ********************************************************************************* */ AND tab2 () BE { insertChar (' ') IF (lineXpos & 1) = 1 THEN insertChar (' ') } AND handleTabKey () BE { LET tab = ? LET p, q = ?, ? IF editLineNum = 0 THEN // On the top line { tab2 () ; RETURN } // Find the indentation of the first non-blank line above FOR line = editLineNum - 1 TO 0 BY -1 DO { IF lines!line%0 = 0 THEN // Empty line LOOP p := lines!line q := 1 UNLESS p%q = ' ' THEN // Line doesn't start with a space { tab2 () ; RETURN } tab := 0 WHILE p%q = ' ' DO { q := q + 1 ; tab := tab + 1 } IF lineXpos >= tab THEN // Currently on or past it { tab2 () ; RETURN } tab := tab - lineXpos // Move up to it WHILE tab > 0 DO { tab := tab - 1 insertChar (' ') } RETURN } // Didn't find a suitable line above: tab2 () } /* * swapChar: * Swaps the character under the cursor with the one to the right ********************************************************************************* */ AND swapChar () BE { LET line = lines!editLineNum IF lineXpos < (line%0 - 1) { LET c = line%(lineXpos + 2) line%(lineXpos + 2) := line%(lineXpos + 1) line%(lineXpos + 1) := c displayLine (editLineNum, screenY, TRUE) } } /* * killLine: * Move the current line into the kill buffer and move the rest up one ********************************************************************************* */ AND killLine () BE { // Have we moved since we killed some lines? IF movedUpDown THEN { clearKillBuffer () movedUpDown := FALSE } // Have we done a paste since we killed some lines? IF unKilledLines THEN { clearKillBuffer () unKilledLines := FALSE } // Overkill? IF killBufferLines = MAX_KB_LINES THEN { statusMsg ("** Kill/Paste buffer full") RETURN } // Empty file? // Actually, this can't happen IF editLines = 0 THEN { statusMsg ("** Empty file error") RETURN } killBuffer!killBufferLines := lines!editLineNum killBufferLines := killBufferLines + 1 IF editLines = 1 // We killed the one and only line, so we need to allocate a new one { lines!0 := newEditLine () displayPage () RETURN } // Move all the lines up one FOR i = editLineNum TO editLines - 1 DO lines!i := lines!(i + 1) editLines := editLines - 1 IF editLineNum = editLines // We killed the bottom line { editLineNum := editLineNum - 1 TEST screenY > 0 THEN screenY := screenY - 1 ELSE editTop := editTop - 1 } fileEdited := TRUE displayPage () } /* * unKillUnder: unKillAbove: * Copy the paste buffer into the file ********************************************************************************* */ AND unKillCheckOK () = VALOF { IF (killBufferLines + editLines) >= maxEditLines THEN { statusMsg ("** Too many lines") RESULTIS FALSE } IF (killBufferLines = 0) { statusMsg ("** Nothing to un-kill") RESULTIS FALSE RETURN } RESULTIS TRUE } AND unKillAbove () BE { LET from, to, count = ?, ?, ? UNLESS unKillCheckOK () THEN RETURN IF (killBufferLines + editLines) >= maxEditLines THEN { statusMsg ("** Too many lines") RETURN } IF (killBufferLines = 0) { statusMsg ("** Nothing to un-kill") RETURN } // Move lines down from := editLines - 1 to := from + killBufferLines WHILE from >= editLineNum DO { lines!to := lines!from lines!from := 0 from := from - 1 to := to - 1 } editLines := editLines + killBufferLines // Copy the kill buffer lines into the gap count := killBufferLines from := 0 to := editLineNum WHILE count > 0 DO { /*** UNLESS lines!to = 0 THEN sawritef ("EEEEKKKKK!!!!!! To isn't ZERO*n") ***/ lines!to := newEditLine () FOR j = 0 TO killBuffer!from%0 DO lines!to%j := killBuffer!from%j count := count - 1 from := from + 1 to := to + 1 } displayPage () unKilledLines := TRUE fileEdited := TRUE } AND unKillUnder () BE { LET from, to, count = ?, ?, ? UNLESS unKillCheckOK () THEN RETURN // Move lines down from := editLines to := from + killBufferLines WHILE from > editLineNum DO { lines!to := lines!from lines!from := 0 from := from - 1 to := to - 1 } editLines := editLines + killBufferLines // Copy the kill buffer lines into the gap count := killBufferLines from := 0 to := editLineNum + 1 WHILE count > 0 DO { /*** UNLESS lines!to = 0 THEN sawritef ("EEEEKKKKK!!!!!! To isn't ZERO*n") ***/ lines!to := newEditLine () FOR j = 0 TO killBuffer!from%0 DO lines!to%j := killBuffer!from%j count := count - 1 from := from + 1 to := to + 1 } displayPage () unKilledLines := TRUE fileEdited := TRUE } /* * insertLineOver: insertLineAfter: * Insert a new blank line into the program ********************************************************************************* */ AND insertLineOver () BE { IF editLines >= maxEditLines THEN { statusMsg ("** Too many lines") RETURN } FOR i = editLines TO editLineNum BY -1 DO lines!i := lines!(i - 1) editLines := editLines + 1 lines!editLineNum := newEditLine () } AND insertLineAfter () BE { IF editLines >= maxEditLines THEN { statusMsg ("** Too many lines") RETURN } editLines := editLines + 1 editLineNum := editLineNum + 1 UNLESS editLineNum = editLines - 1 THEN // Not the last line? FOR i = editLines TO editLineNum BY -1 DO lines!i := lines!(i - 1) lines!editLineNum := newEditLine () TEST screenY = screenLines - 1 THEN // Already on the bottom line? editTop := editTop + 1 // Scroll up ELSE screenY := screenY + 1 // Cursor down } /* * splitLine: * Split the current line with everything from the cursor to the right * moving to the line below ********************************************************************************* */ AND splitLine () BE { LET lineToSplit, newLine = ?, ? IF editLines >= maxEditLines THEN { statusMsg ("** Too many lines") RETURN } lineToSplit := lines!editLineNum insertLineAfter (FALSE) newLine := lines!editLineNum FOR i = lineXpos + 1 TO lineToSplit%0 + 1 DO // Copy the rightmost bit in newLine%(i-lineXpos) := lineToSplit%i newLine%0 := lineToSplit%0 - lineXpos // Set length of new line lineToSplit%0 := lineXpos // Truncate current line lineXpos := 0 displayPage () } /* * joinLines: * We've typed a "delete left" or backspace at the start of a line, * so we join this to the one above. ********************************************************************************* */ AND joinLines () BE { LET len = ? LET upLine = ? LET thisLine = ? IF editLineNum = 0 THEN // Nothing to join to RETURN upLine := lines!(editLineNum-1) thisLine := lines!editLineNum len := upLine%0 + thisLine%0 IF len >= MAX_LINE_LEN THEN { statusMsg ("** Line would be too long") RETURN } lineXpos := upLine%0 // Length of line above FOR i = 1 TO thisLine%0 DO // Concatenate this line to the on above upLine%(lineXpos+i) := thisLine%i upLine%0 := len freevec (thisLine) FOR i = editLineNum TO editLines - 1 DO lines!i := lines!(i + 1) editLines := editLines - 1 editLineNum := editLineNum -1 screenY := screenY -1 displayPage () } /* * abortEdit: * See if we want to bail-out ********************************************************************************* */ AND abortEdit () = VALOF { LET c = ? UNLESS fileEdited THEN RESULTIS TRUE statusMsg ("** File has been edited. Really quit? ") c := sardch () IF c = 'y' | c = 'Y' THEN { sawrites ("Yes*n") RESULTIS TRUE } clearStatusMsg () RESULTIS FALSE } /* * saveFile: * Copy the program back to our disk file ********************************************************************************* */ AND saveFile () BE { LET backup = VEC (32) LET outfile = ? strcpy (backup, mainFilename) backup%0 := backup%0 + 1 backup%(backup%0) := '~' // Remove any backup (ignore result) deletefile (backup) // Rename, but bail if fails.. UNLESS renamefile (mainFilename, backup) THEN { statusMsg ("** Unable to create backup") RETURN } // Create file outfile := findoutput (mainFilename) IF outfile = 0 THEN { statusMsg ("** Unable to create file! (Backup is in-place)") RETURN } statusMsg ("** Writing ...") deplete (screen) selectoutput (outfile) FOR i = 0 TO editLines-1 DO writef ("%s*n", lines!i) endstream (outfile) selectoutput (screen) fileEdited := FALSE statusMsg ("** File written") sawritef (": %d line%s", editLines, editLines = 1 -> ".", "s.") RETURN //RESULTIS TRUE } /* * detabFile: * Scan the file in memory and "de-tab" it. Replace all TABs with spaces * rounded up to the next 8 column. ********************************************************************************* */ AND insertSpace (line, pos) BE { LET f = line%0 // From LET t = line%0 + 1 // To // Shuffle all one space right WHILE f >= pos DO { line%t := line%f t := t - 1 f := f - 1 } // Make the gap a space and add 1 to the line length line%pos := ' ' line%0 := line%0 + 1 } AND detabFile () BE { LET count = 0 FOR i = 0 TO editLines -1 DO { LET line = lines!i LET col = 0 LET p = 1 WHILE p <= line%0 DO { col := col + 1 IF line%p = '*t' THEN { count := count + 1 line%p := ' ' // Replace that TAB with a space UNTIL (col & 7) = 0 DO { insertSpace (line, p) p := p + 1 col := col + 1 } } p := p + 1 } } UNLESS count = 0 THEN fileEdited := TRUE ; displayPage () statusMsg ("** Detab: ") sawritef ("%d TABs expanded.", count) } /* * editor: * Our simple screen editor ********************************************************************************* */ AND editor (mainFilename) BE { LET len, key = ?, ? LET finding = FALSE LET findChar = ? vduCls () displayPage () statusMsg ("Editing: ") sawrites (mainFilename) // Main edit loop { // Adjust cursor X location if we've scrolled up/down to a shorter line len := lines!editLineNum%0 IF lineXpos >= len THEN lineXpos := len // Output status information vduXY (tWidth - 5, tHeight - 1) TEST fileEdited THEN sawrites ("<**>") ELSE sawrites ("< >") // Adjust cursor X location if the line is longer than the screen width TEST lineXpos >= tWidth THEN vduXY (tWidth - 1, screenY) ELSE vduXY (lineXpos, screenY) deplete (screen) key := sardch () IF key = 0 LOOP // Serial terminal input? clearStatusMsg () // Finding? IF finding THEN { finding := FALSE UNLESS key = KEY_FIND THEN findChar := key findNext (findChar) LOOP } // Printable? // Insert it IF (32 <= key <= 126) THEN { insertChar (key) fileEdited := TRUE LOOP } // Process all control keys, etc. SWITCHON key INTO { // F4 is abort edit CASE KEY_F4: CASE KEY_QUIT: IF abortEdit () THEN RETURN ENDCASE // TAB key handling CASE KEY_TAB: handleTabKey () ENDCASE // Ctrl _ // Inserts a new line after the current one CASE KEY_ADD_LINE_BELOW: insertLineAfter () lineXpos := 0 fileEdited := TRUE displayPage () ENDCASE // Ctrl-O // Add a new line Over (Before) the current one CASE KEY_ADD_LINE_ABOVE: insertLineOver () lineXpos := 0 fileEdited := TRUE displayPage () ENDCASE // Enter: // Insert a new line after the current one (split the line) CASE KEY_ENTER: splitLine () fileEdited := TRUE ENDCASE // F5: or Ctrl-X // Write file out CASE KEY_F5: CASE KEY_SAVE_FILE: saveFile () ENDCASE /*** // F8: // Load a new file CASE KEY_F8: if (fileEdited) statusMsg ("* File edited. Save (F5) or Revert (F9).") else loadProgram () ENDCASE // F9: // Undo all edits since last save CASE KEY_F9: if (!fileEdited) { statusMsg ("* No changes to undo") ENDCASE } statusMsg ("* File edited. Discard changes? ") key = cgetc () if ((key == 'y') || (key == 'Y')) { newEditBuffer () clearStatusMsg () // if (!readFile (currentFilename, -1)) // return (void)syntaxError ("EDIT: Unable to read program: %s", strerror (errno)) displayPage () statusMsg ("* Reverted to last saved version") fileEdited = FALSE } else cputs ("No") ENDCASE // F10: // Insert a file CASE KEY_F10: insertProgram () ENDCASE // F12: // Erase the file (in memory) CASE KEY_F12: if (fileEdited) statusMsg ("* File edited. Save (F5) or Revert (F9) first.") else { newEditBuffer () fileEdited = FALSE editLineNum = lineXpos = screenY = 0 displayPage () //updateCurrentFilename (NULL) } ENDCASE **/ // Find & Where is: CASE KEY_FIND: finding := TRUE ; ENDCASE CASE KEY_WHERE_IS: whereis () ; ENDCASE CASE KEY_GOTO_LINE: gotoLine () ; ENDCASE CASE KEY_STATUS: { statusMsg (">>> ") sawritef ("Line:%d of %d; Len: %d. [%d,%d] Killed: %d", editLineNum + 1, editLines, lines!editLineNum%0, lineXpos + 1, screenY, killBufferLines) deplete (screen) ENDCASE } // Swap this and next? CASE KEY_SWAP: swapChar () fileEdited := TRUE ENDCASE // Delete? CASE KEY_DEL_UNDER: deleteChar () fileEdited := TRUE ENDCASE CASE KEY_DEL_LEFT: CASE KEY_DEL_LEFT2: CASE KEY_DEL: TEST lineXpos = 0 THEN // Up we go! Join this to the one above. joinLines () ELSE { lineXpos := lineXpos - 1 deleteChar () } fileEdited := TRUE ENDCASE // Kill line(s) CASE KEY_KILL_LINE: killLine () ENDCASE // Paste (UnKill) line(s) // Above the current line CASE KEY_PASTE_ABOVE: unKillAbove () ENDCASE // Control-P: Prior to the current line CASE KEY_PASTE_BELOW: unKillUnder () ENDCASE // Cursor movement CASE KEY_CENTER_PAGE: editTop := editLineNum - tHeight / 2 IF editTop < 0 THEN editTop := 0 screenY := editLineNum - editTop displayPage () ENDCASE CASE KEY_ARROW_LEFT: IF lineXpos > 0 THEN lineXpos := lineXpos - 1 ENDCASE CASE KEY_ARROW_RIGHT: IF lineXpos < lines!editLineNum%0 THEN lineXpos := lineXpos + 1 ENDCASE CASE KEY_ARROW_UP: movedUpDown := TRUE IF editLineNum = 0 THEN // Already on the top line? ENDCASE editLineNum := editLineNum - 1 TEST screenY = 0 THEN // We need to scroll the screen down a line { editTop := editTop - 1 displayPage () } ELSE screenY := screenY - 1 ENDCASE CASE KEY_ARROW_DOWN: movedUpDown := TRUE IF editLineNum = editLines - 1 THEN // Already on the last line? ENDCASE editLineNum := editLineNum + 1 IF screenY = screenLines - 1 THEN // Already on the bottom line? { editTop := editTop + 1 // Scroll up vduScrollUp () displayLine (editLineNum, screenY, TRUE) // Draw in new bottom line ENDCASE } screenY := screenY + 1 ENDCASE CASE KEY_HOME: CASE KEY_START_OF_LINE: lineXpos := 0 ENDCASE CASE KEY_END: CASE KEY_END_OF_LINE: lineXpos := lines!editLineNum%0 ENDCASE CASE KEY_PG_DN: CASE KEY_PAGE_DOWN: TEST (editTop + screenLines) < editLines THEN { editTop := editTop + screenLines editLineNum := editLineNum + screenLines IF editLineNum >= editLines THEN { editLineNum := editLines - 1 screenY := editLines - editTop - 1 } } ELSE { editLineNum := editLines - 1 screenY := editLines - editTop - 1 } displayPage () ENDCASE CASE KEY_PG_UP: CASE KEY_PAGE_UP: TEST editTop < screenLines THEN { editTop := 0 editLineNum := 0 screenY := 0 } ELSE { editTop := editTop - screenLines editLineNum := editLineNum - screenLines } displayPage () ENDCASE /*** // Help CASE KEY_F1: //editHelp () displayPage () break default: break ***/ // De-Tab a file? CASE KEY_F10: detabFile () ; ENDCASE CASE KEY_REFRESH: displayPage () ENDCASE } } REPEAT } /* * cleanup: * Free everything that we have allocated * (hopefully!) ********************************************************************************* */ AND cleanup () BE { vduXY (0, tHeight - 1) deplete (screen) // Tidy up by deleting the edit and kill buffers FOR i = 0 TO editLines DO freevec (lines!i) freevec (lines) FOR i = 0 TO MAX_KB_LINES DO UNLESS killBuffer!i = 0 THEN freevec (killBuffer!i) freevec (killBuffer) UNLESS mainFilename = 0 THEN freevec (mainFilename) freevec (whereSearch) freevec (lastWhere) } AND failUsage () = VALOF { sawrites ("Usage: edit fileName [y=?] [x=?] [new]*n") RESULTIS 1 } /* * Start here: * We always start empty. ********************************************************************************* */ AND start (argc, argv) = VALOF { //LET argv = VEC 40 LET x,y,l,new = ?, ?, ?, ? UNLESS cis!scb_type = scbt_console THEN { sawrites ("edit can only be used from the interactive console*n") RESULTIS 1 } // Get arguments IF argc < 2 THEN RESULTIS failUsage () new := getargFlag (@argc, argv, "new") x := getarg (@argc, argv, "x") y := getarg (@argc, argv, "y") l := getarg (@argc, argv, "l") IF x THEN x := str2numb (x) IF y THEN y := str2numb (y) IF l THEN l := str2numb (l) TEST x THEN tWidth := x ELSE tWidth := sys (Sys_getTWidth) //DEFAULT_WIDTH TEST y THEN tHeight := y ELSE tHeight := sys (Sys_getTHeight) //DEFAULT_HEIGHT TEST l THEN maxEditLines := l ELSE maxEditLines := DEFAULT_EDIT_LINES UNLESS argc = 2 THEN RESULTIS failUsage () mainFilename := argv!1 UNLESS 80 <= tWidth <= 255 THEN { sawritef ("Terminal width (%d) is not in the range 80...255*n", tWidth) ; RESULTIS 1 } UNLESS 24 <= tHeight <= 255 THEN { sawritef ("Terminal height (%d) is not in the range 24...255*n", tHeight) ; RESULTIS 1 } IF new THEN // Check file doesn't already exist { LET fd = sys (Sys_openread, mainFilename, 0) TEST fd > 0 THEN // It exists { sawritef ("edit: Cowardly refusing to overwrite existing file: %s (%d)*n", mainFilename, fd) sys (Sys_close, fd) RESULTIS 1 } ELSE { fd := sys (Sys_openwrite, mainFilename, 0) IF fd < 0 THEN { sawritef ("edit: Unable to create new file: %s*n", mainFilename) RESULTIS 1 } sys (Sys_close, fd) } } // Initialisation of the globals killBuffer := 0 killBufferLines := 0 unKilledLines := FALSE movedUpDown := FALSE editLineNum := 0 editLines := 0 lines := 0 lineXpos := 0 screenY := 0 editTop := 0 fileEdited := FALSE screen := output () keyboard := input () screenLines := tHeight - 1 whereSearch := getvec (WHERE_LINE_SIZE / bytesperword + 1) ; whereSearch%0 := 0 lastWhere := getvec (WHERE_LINE_SIZE / bytesperword + 1) ; lastWhere%0 := 0 // Initialise lines and killLines //extendEditBuffer () createEditBuffer () newEditBuffer () killBuffer := getvec (MAX_KB_LINES) FOR i = 0 TO MAX_KB_LINES DO killBuffer!i := 0 UNLESS new THEN UNLESS readFile (mainFilename, -1) THEN { sawrites ("*n") cleanup () RESULTIS 1 } // Call the editor editor (mainFilename) // And we're done cleanup () RESULTIS 0 }