;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname editor-part-2) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) (require 2htdp/image) (require 2htdp/universe) (define BACKGROUND (empty-scene 1000 500 "white")) (define FSIZE-EDIT 20) ; the default text font size (can be changed at runtime) (define FSIZE-MENU 25) (define COLOR-EDIT "black") (define COLOR-MENU "red") (define LINE-SEP "\n") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; GENERAL UTILITY FUNCTIONS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; split : String 1String -> [List-of String] ; split the given string into a list of substrings defined by the given separator character. ; (The separator is not part of the substrings in the output list.) (check-expect (split "First|Second|Third|Fourth" "|") (list "First" "Second" "Third" "Fourth")) (check-expect (split "A|B|C|D" "|") (list "A" "B" "C" "D")) (check-expect (split "|A|B|C|D" "|") (list "" "A" "B" "C" "D")) (check-expect (split "|A|B|C|D|" "|") (list "" "A" "B" "C" "D" "")) (check-expect (split "" "|") (list "")) (check-expect (split "A" "|") (list "A")) (check-expect (split "|" "|") (list "" "")) (check-expect (split "||" "|") (list "" "" "")) (define (split str sep) (local [(define (add-char-or-sep c los) (cond [(and (string=? c sep) (empty? los)) (list "" "")] [(string=? c sep) (cons "" los)] [(empty? los) (list c)] [else (cons (string-append c (first los)) (rest los))]))] (foldr add-char-or-sep (list "") (explode str)))) ; last : [X] [NEList-of X] -> X ; the last element of a non-empty list (check-expect (last (list 1 2 3 4)) 4) (check-expect (last (list 1)) 1) (define (last lox) (list-ref lox (sub1 (length lox)))) ; subst : [X] X Nat [List-of X] -> [List-of X] ; substitute new for the element at pos in lox (error if does not exist) (check-expect (subst "!" 0 (list 1 2 3)) (list "!" 2 3)) (check-expect (subst "?" 3 (list 0 1 2 3 4 5 6)) (list 0 1 2 "?" 4 5 6)) (define (subst new pos lox) (if (= pos 0) (cons new (rest lox)) (cons (first lox) (subst new (sub1 pos) (rest lox))))) ; subst-two : [X] X X Nat [List-of X] -> [List-of X] ; substitute the *two* elements new1 and new2 for the element at pos (error if does not exist in lox) (check-expect (subst-two 1 2 0 '(0)) (list 1 2)) (check-expect (subst-two 3 4 3 '(0 1 2 ? 5 6 7)) (list 0 1 2 3 4 5 6 7)) (define (subst-two new1 new2 pos lox) (if (= pos 0) (cons new1 (cons new2 (rest lox))) (cons (first lox) (subst-two new1 new2 (sub1 pos) (rest lox))))) ; subst-for-two : [X] X Nat [List-of X]-> [List-of X] ; substitute new for the *two* elements as positions pos and pos+1 (error if they do not exist in lox) (check-expect (subst-for-two "X" 0 (list 0 1)) (list "X")) (check-expect (subst-for-two "X" 2 (list 0 1 2 3 4 5 6)) (list 0 1 "X" 4 5 6)) (define (subst-for-two new pos lox) (if (= pos 0) (cons new (rest (rest lox))) (cons (first lox) (subst-for-two new (sub1 pos) (rest lox))))) ; char-insert : 1String Nat String -> String ; insert character c into s at position pos, or at end if pos = (string-length s) (check-expect (char-insert "X" 0 "") "X") (check-expect (char-insert "X" 0 "A") "XA") (check-expect (char-insert "X" 2 "AB") "ABX") (define (char-insert c pos s) (string-append (substring s 0 pos) c (substring s pos))) ; char-delete : Nat String -> String ; delete the character at the given position (error if does not exist) (check-expect (char-delete 0 "Hello") "ello") (check-expect (char-delete 2 "Hello") "Helo") (define (char-delete pos s) (string-append (substring s 0 pos) (substring s (add1 pos)))) ; stack-images-left-aligned : [List-of Image] -> Image ; stack the images on top of each other, aligned on the left (check-expect (stack-images-left-aligned '()) empty-image) (check-expect (stack-images-left-aligned (list (text "A" 12 "black") (text "ABC" 13 "red"))) (above/align "left" (text "A" 12 "black") (text "ABC" 13 "red"))) (define (stack-images-left-aligned loi) (local [(define (stack-image image stack) (above/align "left" image stack))] (foldr stack-image empty-image loi))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; DATA DEFINITIONS FOR A TEXT BUFFER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; A Line is a String ; A line of text contained in some text buffer. ; No special characters (such as newlines) allowed. (define line-0 "When I came back from Munich,") (define (line-templ l) (... l ...)) ; A Text is a String ; The text contained in some text buffer, in single-string format. ; Lines of text are separated by LINE-SEP (define text-0 (string-append "About a hiring visit by Edsger Dijkstra (1930-2002)," LINE-SEP "a famous computer scientist:" LINE-SEP LINE-SEP "\"When I came back from Munich, it was September, and I was Professor of" LINE-SEP "Mathematics at the Eindhoven University of Technology. Later I learned that" LINE-SEP "I had been the Department's third choice, after two numerical analysts had" LINE-SEP "turned the invitation down; the decision to invite me had not been an easy" LINE-SEP "one, on the one hand because I had not really studied mathematics, and on" LINE-SEP "the other hand because of my sandals, my beard and my 'arrogance' (whatever" LINE-SEP "that may be).\"")) (define-struct buffer [lol lnum cnum fsize mode]) ; A Buffer is a (make-buffer [NEList-of Line] Nat Nat PosInt String) ; a buffer of a text editor: ; - lol is the list of lines of the text. ; This list is NEVER empty (see buffer-text-empty below) ; - lnum is the line number where the cursor is. Range: 0 .. len(lol)-1 ; - cnum is the column number where the cursor is. Range: 0 .. len(s) . s = string at line lnum. ; - fsize is the font size ; - mode is the mode of the buffer: ; * "EDIT" : normal text input ; * "MENU" : menu mode, in which the command menu is displayed (define buffer-text-empty (make-buffer (list "") 0 0 FSIZE-EDIT "EDIT")) (define buffer-text (make-buffer (list "Line 0" "Line 1") 1 3 FSIZE-EDIT "EDIT")) (define buffer-menu (make-buffer (list "Line 0" "Line 1") 1 3 FSIZE-EDIT "MENU")) (define (buffer-templ buffer) (... (buffer-lol buffer) (buffer-lnum buffer) (buffer-cnum buffer) (buffer-fsize buffer) (buffer-mode buffer))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; UTILITY FUNCTIONS OPERATING ON A BUFFER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; start-of-buffer? : Buffer -> Boolean ; are we at the top-left corner of the buffer? (check-expect (start-of-buffer? buffer-text-empty) #t) (check-expect (start-of-buffer? buffer-text) #f) (define (start-of-buffer? buffer) (= (buffer-lnum buffer) (buffer-cnum buffer) 0)) ; end-of-buffer? : Buffer -> Boolean ; are we at the lower right corner of the buffer, i.e. last line, last column? (check-expect (end-of-buffer? buffer-text-empty) #t) (check-expect (end-of-buffer? buffer-text) #f) (define (end-of-buffer? buffer) (local [(define lol (buffer-lol buffer))] (and (= (buffer-lnum buffer) (sub1 (length lol))) (= (buffer-cnum buffer) (string-length (last lol)))))) ; end-of-line : Buffer Nat -> Nat ; the end-of-line position of the given line in the buffer (check-expect (end-of-line buffer-text-empty 0) 0) (check-expect (end-of-line buffer-text 1) 6) (define (end-of-line buffer lnum) (local [(define lol (buffer-lol buffer)) (define line (list-ref lol lnum))] (string-length line))) ; end-of-line? : Buffer -> Boolean ; are we at the end of the current line in the buffer? (check-expect (end-of-line? buffer-text-empty) #t) (check-expect (end-of-line? buffer-text) #f) (define (end-of-line? buffer) (local [(define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer))] (= cnum (end-of-line buffer lnum)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; MAIN: THE BIG-BANG WORLD PROGRAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; editor : Text -> Text ; Launches the text editor, with text as the initial buffer contents. ; Returns the buffer contents as a Text. ; It can therefore be called in the form ; (define new-text (editor old-text)) ; so that the buffer content after the editor finishes is saved in variable new-text ; which can then be stored in a file. (define (editor initial-text) (lol->text (buffer-lol (big-bang (make-buffer (text->lol initial-text) 0 0 FSIZE-EDIT "EDIT") [name "A Simple Text Editor"] [to-draw draw-buffer] [on-key process-key])))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; CONVERSIONS Text <-> [NEList-of Line] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; text->lol : Text -> [NEList-of Line] ; the text converted into a list of lines (check-expect (text->lol "") (buffer-lol buffer-text-empty)) ; a special case (check-expect (text->lol (string-append "Line 0" LINE-SEP "Line 1")) (buffer-lol buffer-text)) (define (text->lol text) (split text LINE-SEP)) ; lol->text : [NEList-of Line] -> Text ; the buffer content converted into a Text, with LINE-SEP inserted as needed (check-expect (lol->text (buffer-lol buffer-text-empty)) "") (check-expect (lol->text (buffer-lol buffer-text)) (string-append "Line 0" LINE-SEP "Line 1")) (define (lol->text lol) (local [(define (line-combine l s) (string-append l LINE-SEP s)) (define text (foldr line-combine "" lol))] (substring text 0 (sub1 (string-length text))))) ; substring removes the trailing LINE-SEP ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; DRAWING THE BUFFER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; draw-buffer : Buffer -> Image ; render the buffer in text- or cmd mode, as the case may be (check-expect (draw-buffer buffer-text-empty) (draw-buffer-text-mode buffer-text-empty)) (define (draw-buffer buffer) (cond [(string=? (buffer-mode buffer) "EDIT") (draw-buffer-text-mode buffer)] [(string=? (buffer-mode buffer) "MENU") (draw-buffer-menu-mode buffer)])) ; draw-buffer-text-mode : Buffer -> Image ; render a buffer in EDIT mode: the buffer text (check-expect (draw-buffer-text-mode buffer-text-empty) (place-image/align (char->image-cursor " " FSIZE-EDIT COLOR-EDIT) 0 0 "left" "top" BACKGROUND)) (define (draw-buffer-text-mode buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer))] (place-image/align (stack-images-left-aligned (lines->images lol lnum cnum fsize COLOR-EDIT)) 0 0 "left" "top" BACKGROUND))) ; lines->images : [NEList-of Line] Nat Nat PosInt String -> [List-of Image] ; turn a list of lines into a list of images containing the text (check-expect (lines->images (list "Line of text" "") 1 0 FSIZE-EDIT COLOR-EDIT) (list (line->image "Line of text" FSIZE-EDIT COLOR-EDIT #f) (char->image-cursor " " FSIZE-EDIT COLOR-EDIT))) (define (lines->images lol lnum cnum fsize fcolor) (if (= lnum 0) (cons (line->image-cursor (first lol) cnum fsize fcolor) (map (lambda (l) (line->image l fsize fcolor #f)) (rest lol))) (cons (line->image (first lol) fsize fcolor #f) (lines->images (rest lol) (sub1 lnum) cnum fsize fcolor)))) ; line->image : Line PosInt String Boolean -> Image ; the given text rendered as an image (check-expect (line->image "Line of text" FSIZE-EDIT COLOR-EDIT #f) (text/font "Line of text" FSIZE-EDIT COLOR-EDIT "Monospace" "default" "normal" "normal" #f)) (define (line->image line fsize fcolor underline?) (text/font line fsize fcolor "Monospace" "default" "normal" "normal" underline?)) ; line->image-cursor : Line Nat PosInt String -> Image ; the given text rendered as an image in which the char at the given position is underlined (check-expect (line->image-cursor "Line of text" 0 FSIZE-EDIT COLOR-EDIT) (beside (text/font "L" FSIZE-EDIT COLOR-EDIT "Monospace" "default" "normal" "normal" #t) (text/font "ine of text" FSIZE-EDIT COLOR-EDIT "Monospace" "default" "normal" "normal" #f))) (define (line->image-cursor line cnum fsize fcolor) (if (< cnum (string-length line)) (beside (line->image (substring line 0 cnum) fsize fcolor #f) (char->image-cursor (string-ith line cnum) fsize fcolor) (line->image (substring line (add1 cnum)) fsize fcolor #f)) ; special case: cursor is post-end-of-line (beside (line->image line fsize fcolor #f) (char->image-cursor " " fsize fcolor)))) ; char->image-cursor : 1String PosInt String -> Image ; the 1-character string rendered as an image of an underlined character (check-expect (char->image-cursor "X" FSIZE-EDIT COLOR-EDIT) (text/font "X" FSIZE-EDIT COLOR-EDIT "Monospace" "default" "normal" "normal" #t)) (define (char->image-cursor c fsize fcolor) (text/font c fsize fcolor "Monospace" "default" "normal" "normal" #t)) ; a string announcing the list of available commands (define AVAIL-CMDS "Available commands:") ; a list of strings to be displayed in MENU mode (define MENU-LIST (list AVAIL-CMDS "" ": toggle Menu display" " : decrease text-font size" " : increase text-font size")) ; a list of text images, one per line of text describing the menu in MENU mode (define MENU-IMAGES (map (lambda (l) (line->image l FSIZE-MENU COLOR-MENU (string=? l AVAIL-CMDS))) MENU-LIST)) ; the entire menu text as a single image (define MENU-IMAGE (stack-images-left-aligned MENU-IMAGES)) ; draw-buffer-menu-mode : Buffer -> Image ; render a buffer in MENU mode: the menu (check-expect (draw-buffer-menu-mode buffer-text-empty) (overlay MENU-IMAGE BACKGROUND)) (define (draw-buffer-menu-mode _) (overlay MENU-IMAGE BACKGROUND)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; RESPONDING TO KEY EVENTS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; the following keys are ignored in EDIT mode ; IGNORE-KEYS-EDIT : -> [List-of Key] (define IGNORE-KEYS-EDIT (list "\t" ; tab "\u007F" ; delete key "shift" "rshift" ; typing "shift-a" still results in an "A" being processed "control" "rcontrol" "menu" "f3" "f4" "f5" "f6" "f7" "f8" "f9" "f10" "f11" "f12" "numlock" "scroll" "wheel-up" "wheel-down" "wheel-left" "wheel-right")) ; "mouse" key events ; ignore-key? : Key [List-of Key] -> Boolean ; is the key to be ignored, i.e. a member of the given key list? (check-expect (ignore-key? "menu" IGNORE-KEYS-EDIT) #t) (check-expect (ignore-key? "A" IGNORE-KEYS-EDIT) #f) (define (ignore-key? key lok) (ormap (lambda (ik) (key=? ik key)) lok)) ; process-key : Buffer Key -> Buffer ; respond to each key event (check-expect (process-key buffer-text "escape") buffer-menu) (check-expect (process-key buffer-menu "escape") buffer-text) (define (process-key buffer key) (cond [(string=? (buffer-mode buffer) "EDIT") (process-key-text-mode buffer key)] [(string=? (buffer-mode buffer) "MENU") (process-key-menu-mode buffer key)])) ; process-key-text-mode : Buffer Key -> Buffer ; respond to each key event in text mode (check-expect (process-key-text-mode buffer-text "escape") buffer-menu) (define (process-key-text-mode buffer key) (cond [(ignore-key? key IGNORE-KEYS-EDIT) buffer] [(key=? key "left") (process-text-left buffer)] [(key=? key "right") (process-text-right buffer)] [(key=? key "up") (process-text-up buffer)] [(key=? key "down") (process-text-down buffer)] [(key=? key "home") (process-text-home buffer)] [(key=? key "end") (process-text-end buffer)] [(key=? key "\r") (process-text-return buffer)] [(key=? key "\b") (process-text-backspace buffer)] [(key=? key "f1") (process-text-f1 buffer)] ; decrease font [(key=? key "f2") (process-text-f2 buffer)] ; increase font [(key=? key "escape") (process-text-escape buffer)] ; -> MENU mode [else (process-text-printable buffer key)])) ; process-text-left : Buffer -> Buffer ; process the "left" cursor key: move left, possibly up one line (check-expect (process-text-left buffer-text) (make-buffer (list "Line 0" "Line 1") 1 2 FSIZE-EDIT "EDIT")) (check-expect (process-text-left buffer-text-empty) buffer-text-empty) (define (process-text-left buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer))] (cond [(start-of-buffer? buffer) buffer] [(= cnum 0) (make-buffer lol (sub1 lnum) (end-of-line buffer (sub1 lnum)) fsize mode)] [else (make-buffer lol lnum (sub1 cnum) fsize mode)]))) ; process-text-right : Buffer -> Buffer ; process the "right" cursor key: move right, possibly down one line (check-expect (process-text-right buffer-text) (make-buffer (list "Line 0" "Line 1") 1 4 FSIZE-EDIT "EDIT")) (check-expect (process-text-right buffer-text-empty) buffer-text-empty) (define (process-text-right buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer))] (cond [(end-of-buffer? buffer) buffer] [(end-of-line? buffer) (make-buffer lol (add1 lnum) 0 fsize mode)] [else (make-buffer lol lnum (add1 cnum) fsize mode)]))) ; process-text-up : Buffer -> Buffer ; process the "up" cursor key (check-expect (process-text-up buffer-text-empty) buffer-text-empty) (check-expect (process-text-up buffer-text) (make-buffer (list "Line 0" "Line 1") 0 3 FSIZE-EDIT "EDIT")) (define (process-text-up buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer)) (define prev-lnum (sub1 lnum))] (if (= lnum 0) buffer (make-buffer lol prev-lnum (min cnum (end-of-line buffer prev-lnum)) fsize mode)))) ; process-text-down : Buffer -> Buffer ; process the "down" cursor key (check-expect (process-text-down buffer-text-empty) buffer-text-empty) (check-expect (process-text-down (make-buffer (list "Line 0" "Line 1") 0 3 FSIZE-EDIT "EDIT")) buffer-text) (define (process-text-down buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer)) (define next-lnum (add1 lnum))] (if (= lnum (sub1 (length lol))) buffer (make-buffer lol next-lnum (min cnum (end-of-line buffer next-lnum)) fsize mode)))) ; process-text-home : Buffer -> Buffer ; process the "home" key (check-expect (process-text-home buffer-text-empty) buffer-text-empty) (check-expect (process-text-home buffer-text) (make-buffer (list "Line 0" "Line 1") 1 0 FSIZE-EDIT "EDIT")) (define (process-text-home buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define new-lnum (if (= cnum 0) 0 lnum)) (define new-cnum 0)] (make-buffer lol new-lnum new-cnum (buffer-fsize buffer) (buffer-mode buffer)))) ; process-text-end : Buffer -> Buffer ; process the "end" key (check-expect (process-text-end buffer-text-empty) buffer-text-empty) (check-expect (process-text-end buffer-text) (make-buffer (list "Line 0" "Line 1") 1 6 FSIZE-EDIT "EDIT")) (define (process-text-end buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define new-lnum (if (end-of-line? buffer) (sub1 (length lol)) lnum)) (define new-cnum (end-of-line buffer new-lnum))] (make-buffer lol new-lnum new-cnum (buffer-fsize buffer) (buffer-mode buffer)))) ; process-return : Buffer -> Buffer ; process the "return" key, as follows: ; 1. replace current line by the current line up to and excluding the current position ; 2. insert a new line after the current one, ; consisting of the current line from the current position ; 3. lnum++, cnum = 0 (check-expect (process-text-return buffer-text) (make-buffer (list "Line 0" "Lin" "e 1") 2 0 FSIZE-EDIT "EDIT")) (define (process-text-return buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer)) (define line (list-ref lol lnum)) (define new-line-1 (substring line 0 cnum)) (define new-line-2 (substring line cnum)) (define new-lol (subst-two new-line-1 new-line-2 lnum lol))] (make-buffer new-lol (add1 lnum) 0 fsize mode))) ; process-text-backspace : Buffer -> Buffer ; process the "backspace" key, as follows: ; 1. if at the start of the buffer, do nothing. ; 2. if column > 0: just delete the current character and move left. ; 3. if column = 0: replace the lines at positions lnum-1 and lnum by their concatenation ; Note that case 3 precisely reverses the effect of the key (check-expect (process-text-backspace (make-buffer (list "Line 0" "Lin" "e 1") 2 0 FSIZE-EDIT "EDIT")) buffer-text) (check-expect (process-text-backspace buffer-text) (make-buffer (list "Line 0" "Lie 1") 1 2 FSIZE-EDIT "EDIT")) (define (process-text-backspace buffer) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer)) (define line (list-ref lol lnum))] (cond [(start-of-buffer? buffer) buffer] [(> cnum 0) (make-buffer (subst (char-delete (sub1 cnum) line) lnum lol) lnum (sub1 cnum) fsize mode)] [(= cnum 0) (make-buffer (subst-for-two (string-append (list-ref lol (sub1 lnum)) (list-ref lol lnum)) (sub1 lnum) lol) (sub1 lnum) (end-of-line buffer (sub1 lnum)) fsize mode)]))) ; process-text-printable : Buffer Key -> Buffer ; process the key event corresponding to a printable key: just insert it (check-expect (process-text-printable buffer-text "!") (make-buffer (list "Line 0" "Lin!e 1") 1 4 FSIZE-EDIT "EDIT")) (check-expect (process-text-printable buffer-text-empty "!") (make-buffer (list "!") 0 1 FSIZE-EDIT "EDIT")) (define (process-text-printable buffer key) (local [(define lol (buffer-lol buffer)) (define lnum (buffer-lnum buffer)) (define cnum (buffer-cnum buffer)) (define fsize (buffer-fsize buffer)) (define mode (buffer-mode buffer)) (define line (list-ref lol lnum))] (make-buffer (subst (char-insert key cnum line) lnum lol) lnum (add1 cnum) fsize mode))) ; process-text-f1 : Buffer -> Buffer ; reduce the text-font size (check-expect (process-text-f1 buffer-text-empty) (make-buffer (list "") 0 0 (sub1 FSIZE-EDIT) "EDIT")) (check-expect (process-text-f1 buffer-text) (make-buffer (list "Line 0" "Line 1") 1 3 (sub1 FSIZE-EDIT) "EDIT")) (define (process-text-f1 buffer) (local [(define fsize (buffer-fsize buffer))] (make-buffer (buffer-lol buffer) (buffer-lnum buffer) (buffer-cnum buffer) (max (sub1 fsize) 1) (buffer-mode buffer)))) ; process-text-f2 : Buffer -> Buffer ; increase the text-font size (check-expect (process-text-f2 buffer-text-empty) (make-buffer (list "") 0 0 (add1 FSIZE-EDIT) "EDIT")) (check-expect (process-text-f2 buffer-text) (make-buffer (list "Line 0" "Line 1") 1 3 (add1 FSIZE-EDIT) "EDIT")) (define (process-text-f2 buffer) (local [(define fsize (buffer-fsize buffer))] (make-buffer (buffer-lol buffer) (buffer-lnum buffer) (buffer-cnum buffer) (add1 fsize) (buffer-mode buffer)))) ; process-text-escape : Buffer -> Buffer ; switch from EDIT into MENU mode (check-expect (process-text-escape buffer-text) buffer-menu) (define (process-text-escape buffer) (make-buffer (buffer-lol buffer) (buffer-lnum buffer) (buffer-cnum buffer) (buffer-fsize buffer) "MENU")) ; process-key-menu-mode : Buffer Key -> Buffer ; respond to each key event in MENU mode (check-expect (process-key-menu-mode buffer-menu "escape") buffer-text) (define (process-key-menu-mode buffer key) (if (key=? key "escape") (make-buffer (buffer-lol buffer) (buffer-lnum buffer) (buffer-cnum buffer) (buffer-fsize buffer) "EDIT") buffer))