This repository has been archived on 2022-08-10. You can view files and clone it, but cannot push or open issues or pull requests.
chez-openbsd/s/expeditor.ss
2022-07-29 15:12:07 +02:00

3055 lines
116 KiB
Scheme

;;; expeditor.ss
;;; R. Kent Dybvig
;;; August 2007
;;; This code is based on David Boyer's command-line editor, which has the
;;; following copyright:
;;;
;;; Copyright (c) 1989, 1993, 1994 C. David Boyer
;;;
;;; Permission to copy this software, in whole or in part, to use this
;;; software for any lawful purpose, and to redistribute this software is
;;; granted subject to the restriction that all copies made of this software
;;; must include this copyright notice in full.
;;;
;;; The present implementation retains some of the basic design but little
;;; of the original code.
;;; The expression editor module is organized into sections:
;;;
;;; 1. screen-management routines
;;; 2. exported parameters
;;; 3. eestate and pos record definitions
;;; 4. current entry management routines
;;; 5. the reader and prompt-and-reader
;;; 6. history management routines
;;; 7. key function definitions
;;; 8. key binding code
;;;
;;; Also contained within this file are a few system entry points:
;;; the $enable-expeditor and $expeditor-history-file parameters and
;;; the main entry point into the expression editor, $expeditor.
(when-feature expeditor
(define $enable-expeditor (make-parameter #f))
(define $expeditor-history-file
(make-parameter #f
(lambda (s)
(cond
[(not s) s]
[(string? s)
(if (string=? s "")
(if-feature windows
(cond
[(getenv "APPDATA") =>
(lambda (appdata)
(let ([dir (format "~a\\Chez Scheme" appdata)])
(unless (file-exists? dir)
(guard (c [#t (void)]) (mkdir dir)))
(format "~a\\History" dir)))]
[(getenv "HOME") =>
(lambda (home)
(format "~a\\.chezscheme_history" home))]
[else ".chezscheme_history"])
"~/.chezscheme_history")
s)]
[else ($oops '$expeditor-history-file "~s is not #f or a string" s)]))))
(define $expeditor)
(module expression-editor
(
; parameters
ee-auto-indent ee-auto-paren-balance ee-common-identifiers
ee-default-repeat ee-flash-parens ee-noisy
ee-paren-flash-delay ee-history-limit ee-standard-indent
; establishing key bindings
ee-bind-key ee-compose
; built-in operators
ee-next-id-completion
ee-next-id-completion/indent
ee-id-completion ee-id-completion/indent
ee-insert-self ee-command-repeat
ee-history-bwd ee-history-fwd
ee-history-fwd-prefix ee-history-bwd-prefix
ee-history-fwd-contains ee-history-bwd-contains
ee-newline ee-accept ee-newline/accept ee-open-line
ee-indent ee-indent-all ee-backward-char
ee-forward-char ee-next-line ee-previous-line
ee-end-of-line ee-beginning-of-line
ee-beginning-of-entry ee-end-of-entry
ee-delete-to-eol ee-delete-line
ee-delete-between-point-and-mark ee-set-mark
ee-delete-entry ee-reset-entry ee-delete-sexp ee-backward-delete-sexp
ee-redisplay ee-yank-kill-buffer ee-yank-selection
ee-string-macro ee-eof ee-delete-char ee-eof/delete-char
ee-backward-delete-char ee-insert-paren
ee-flash-matching-delimiter ee-goto-matching-delimiter
ee-exchange-point-and-mark ee-forward-sexp
ee-backward-sexp ee-forward-word
ee-backward-word ee-forward-page
ee-backward-page ee-suspend-process
)
(define-syntax assert*
(syntax-rules ()
[(_ expr ...)
(begin (assert expr) ...)]))
(define-syntax on-error
(syntax-rules ()
[(on-error e0 e1 e2 ...)
(guard (c [#t e0]) e1 e2 ...)]))
(define-syntax defopt
(syntax-rules ()
[(_ (p x ... [y e]) b1 b2 ...)
(define p
(case-lambda
[(x ...) (p x ... e)]
[(x ... y) b1 b2 ...]))]))
; screen initialization and manipulation routines
(module (init-screen raw-mode no-raw-mode
screen-resize! screen-rows screen-cols
ee-winch? ee-char-ready? ee-peek-char ee-read-char
ee-write-char ee-display-string ee-flush
move-cursor-up move-cursor-right move-cursor-left move-cursor-down
scroll-reverse clear-eol clear-eos clear-screen
carriage-return line-feed
bell pause get-clipboard wait)
; screen state
(define cols)
(define rows)
(define cursor-col)
(define the-unread-char)
(define winch)
; we use terminfo routines directly, rather than going through curses,
; because curses requires initscr(), which clears the screen, discarding
; the current context. this is a shell, not a full-screen user interface.
(define init-term (foreign-procedure "(cs)ee_init_term" () boolean))
(define $ee-read-char (foreign-procedure "(cs)ee_read_char" (boolean) scheme-object))
(define $ee-write-char (foreign-procedure "(cs)ee_write_char" (wchar_t) void))
(define ee-flush (foreign-procedure "(cs)ee_flush" () void))
(define get-screen-size (foreign-procedure "(cs)ee_get_screen_size" () scheme-object))
(define raw-mode (foreign-procedure "(cs)ee_raw" () void))
(define no-raw-mode (foreign-procedure "(cs)ee_noraw" () void))
(define enter-am-mode (foreign-procedure "(cs)ee_enter_am_mode" () void))
(define exit-am-mode (foreign-procedure "(cs)ee_exit_am_mode" () void))
(define nanosleep (foreign-procedure "(cs)ee_nanosleep" (unsigned-32 unsigned-32) void))
(define pause (foreign-procedure "(cs)ee_pause" () void))
(define get-clipboard (foreign-procedure "(cs)ee_get_clipboard" () scheme-object))
(define move-cursor-up (foreign-procedure "(cs)ee_up" (integer-32) void))
(define move-cursor-down (foreign-procedure "(cs)ee_down" (integer-32) void))
(define $move-cursor-left (foreign-procedure "(cs)ee_left" (integer-32) void))
(define $move-cursor-right (foreign-procedure "(cs)ee_right" (integer-32) void))
(define clear-eol (foreign-procedure "(cs)ee_clr_eol" () void))
(define clear-eos (foreign-procedure "(cs)ee_clr_eos" () void))
(define $clear-screen (foreign-procedure "(cs)ee_clear_screen" () void))
(define scroll-reverse (foreign-procedure "(cs)ee_scroll_reverse" (integer-32) void))
(define bell (foreign-procedure "(cs)ee_bell" () void))
(define $carriage-return (foreign-procedure "(cs)ee_carriage_return" () void))
(define line-feed (foreign-procedure "(cs)ee_line_feed" () void))
(define (screen-resize!)
(let ([p (get-screen-size)])
(set! rows (car p))
(set! cols (cdr p))))
(define (screen-rows) rows)
(define (screen-cols) cols)
(define (init-screen)
(and (init-term)
(begin
(set! cursor-col 0)
(set! the-unread-char #f)
(set! winch #f)
#t)))
(define (clear-screen)
($clear-screen)
(set! cursor-col 0))
(define (ee-winch?)
(and (not the-unread-char)
(if winch
(begin (set! winch #f) #t)
(begin
(ee-flush)
(let ([c ($ee-read-char #t)])
(or (eq? c #t)
(begin (set! the-unread-char c) #f)))))))
(define (ee-char-ready?)
(if the-unread-char
#t
(let f ()
(ee-flush)
(let ([c ($ee-read-char #f)])
(cond
[(eq? c #f) #f]
[(eq? c #t) (set! winch #t) (f)]
[else (set! the-unread-char c) #t])))))
(define (ee-read-char)
(if the-unread-char
(let ([c the-unread-char]) (set! the-unread-char #f) c)
(let f ()
(ee-flush)
(let ([c ($ee-read-char #t)])
(if (eq? c #t)
(begin (set! winch #t) (f))
c)))))
(define (ee-peek-char)
(or the-unread-char
(let ([c (ee-read-char)])
(set! the-unread-char c)
c)))
; we assume that ee-write-char receives only characters that occupy one
; screen cell. it should never be passed #\return, #\newline, or #\tab.
; furthermore, ee-write-char should never be used to write past the end
; of a screen line.
(define (ee-write-char c)
(set! cursor-col (fx+ cursor-col 1))
(if (fx= cursor-col cols)
(begin
(exit-am-mode)
($ee-write-char c)
(enter-am-mode))
($ee-write-char c)))
; comments regarding ee-write-char above apply also to ee-display-string
(define (ee-display-string s)
(let ([n (string-length s)])
(do ([i 0 (fx+ i 1)])
((fx= i n))
(ee-write-char (string-ref s i)))))
(define (carriage-return)
(set! cursor-col 0)
($carriage-return))
(define (move-cursor-right n)
(cond
[(fx< (fx+ cursor-col n) cols)
($move-cursor-right n)
(set! cursor-col (fx+ cursor-col n))]
[else
(move-cursor-down (quotient (fx+ cursor-col n) cols))
(let ([new-cursor-col (remainder (fx+ cursor-col n) cols)])
(if (fx>= new-cursor-col cursor-col)
(move-cursor-right (fx- new-cursor-col cursor-col))
(move-cursor-left (fx- cursor-col new-cursor-col))))]))
(define (move-cursor-left n)
(when (and (fx= cursor-col cols) (fx> n 0))
(set! n (fx- n 1))
(set! cursor-col (fx- cursor-col 1)))
(cond
[(fx<= n cursor-col)
($move-cursor-left n)
(set! cursor-col (fx- cursor-col n))]
[else
(move-cursor-up (fx1+ (quotient (fx- n cursor-col 1) cols)))
(let ([new-cursor-col (remainder
(fx- cols (remainder (fx- n cursor-col) cols))
cols)])
(if (fx>= new-cursor-col cursor-col)
(move-cursor-right (fx- new-cursor-col cursor-col))
(move-cursor-left (fx- cursor-col new-cursor-col))))]))
(define wait
(lambda (ms)
(unless (or (<= ms 0) (ee-char-ready?))
(nanosleep 0 (* 10 1000 1000)) ; 10ms granularity is best we can assume
(wait (- ms 10)))))
)
;;; parameters
(define ee-common-identifiers
(make-parameter
; general theory: exclude short ids and ids that will come up early
; in an alphabetical search with short prefix. include common ids that
; come up annoyingly late in such a search.
'(append apply call/cc call-with-values define display display-string
define-syntax define-record null? quote quotient reverse read-char
substring string-ref string-length string? string=? string-set!
syntax-case syntax-rules unless vector-ref vector-length vector?
vector-set! vector)
(lambda (x)
(unless (and (list? x) (andmap symbol? x))
($oops 'ee-common-identifiers "~s is not a list of symbols" x))
x)))
;;; default repeat value for ^U
(define ee-default-repeat
(make-parameter 4
(lambda (x)
(unless (and (fixnum? x) (fxnonnegative? x))
($oops 'ee-default-repeat "~s is not an integer" x))
x)))
(define ee-auto-indent (make-parameter #t (lambda (x) (and x #t))))
(define ee-auto-paren-balance (make-parameter #t (lambda (x) (and x #t))))
(define ee-flash-parens (make-parameter #t (lambda (x) (and x #t))))
;;; paren balance delay factor in milliseconds
(define ee-paren-flash-delay
(make-parameter 100
(lambda (x)
(unless (and (fixnum? x) (fxnonnegative? x))
($oops 'ee-paren-flash-delay "~s is not an integer" x))
x)))
;;; enable/disable bell
(define ee-noisy (make-parameter #f (lambda (x) (and x #t))))
;;; standard indent length
(define ee-standard-indent
(make-parameter 2
(lambda (x)
(unless (and (fixnum? x) (fxnonnegative? x))
($oops 'ee-standard-indent "~s is not an integer" x))
x)))
(define ee-history-limit
(make-parameter 256
(lambda (x)
(unless (and (fixnum? x) (fxnonnegative? x))
($oops 'ee-history-length "~s is not a nonnegative fixnum" x))
x)))
;;; eestate holds the state of the expression editor.
(define-record-type eestate
(fields
(mutable last-op)
(mutable rt-last-op)
(mutable prompt)
(mutable repeat-count)
(mutable killbuf)
(mutable histnew)
(mutable histbwd)
(mutable histnow)
(mutable histfwd)
(mutable histkey)
(mutable last-suffix*)
(mutable cc?))
(nongenerative)
(sealed #t)
(protocol
(lambda (new)
(lambda ()
(new #f ; last-op
'(0 . 0) ; rt-last-op
"" ; prompt
1 ; repeat-count
'() ; killbuf
0 ; histnew
'() ; histbwd
"" ; histnow
'() ; hisfwd
"" ; histkey
'() ; last-suffix*
#f))))) ; cc?
(module ()
(record-writer (type-descriptor eestate)
(lambda (x p wr)
(display "#<ee-state>" p))))
;;; pos is used for two different but related purposes: for row, col
;;; positions and for row, physical-line positions. see the comment
;;; about the entry top-line and bot-line fields below.
(module (make-pos pos? pos-row pos-col pos=? pos<? pos<=? pos>? pos>=? index->pos)
(define-record-type pos
(fields (immutable row) (immutable col))
(nongenerative)
(sealed #t))
(define (pos=? p1 p2)
(and (fx= (pos-row p1) (pos-row p2))
(fx= (pos-col p1) (pos-col p2))))
(define (pos<? p1 p2)
(or (fx< (pos-row p1) (pos-row p2))
(and (fx= (pos-row p1) (pos-row p2))
(fx< (pos-col p1) (pos-col p2)))))
(define (pos<=? p1 p2)
(or (fx< (pos-row p1) (pos-row p2))
(and (fx= (pos-row p1) (pos-row p2))
(fx<= (pos-col p1) (pos-col p2)))))
(define (pos>? p1 p2)
(or (fx> (pos-row p1) (pos-row p2))
(and (fx= (pos-row p1) (pos-row p2))
(fx> (pos-col p1) (pos-col p2)))))
(define (pos>=? p1 p2)
(or (fx> (pos-row p1) (pos-row p2))
(and (fx= (pos-row p1) (pos-row p2))
(fx>= (pos-col p1) (pos-col p2)))))
(define (index->pos s n r c)
; convert index in single-string representation of entry
; into pos. r and c are row and col at which string
; starts in the entry
(let f ([i 0] [r r] [c c])
(if (fx= i n)
(make-pos r c)
(if (char=? (string-ref s i) #\newline)
(f (fx+ i 1) (fx+ r 1) 0)
(f (fx+ i 1) r (fx+ c 1))))))
(record-writer (type-descriptor pos)
(lambda (x p wr)
(fprintf p "#<pos ~a ~a>" (pos-row x) (pos-col x))))
)
(define lpchar #\()
(define rpchar #\))
(define lbchar #\[)
(define rbchar #\])
(define beep
(lambda (str . arg*)
#;(with-output-to-file "/tmp/ee.log"
(lambda () (apply printf str arg*) (newline))
'append)
(when (ee-noisy) (bell))))
(module (string->entry
entry->string
string->lines
; primtiive and derived record accessors and mutators: no ee argument
entry-col
entry-nsr
entry-row
entry-mark
entry-point
null-entry?
entry-mark-set!
entry-row-set!
entry-col-set!
; normal entry procedures: first two arguments are ee and entry
add-char
beginning-of-line?
clear-entry
id-completions
correct&flash-matching-delimiter
yank-entry
delete-char
delete-forward
delete-to-eol
echo-entry
end-of-line?
find-matching-delimiter
find-next-sexp-backward
find-next-sexp-forward
find-next-word
find-previous-word
first-line?
flash
goto
handle-winch
indent
indent-all
insert-string-before
insert-strings-before
join-rows
last-line?
last-line-displayed?
move-bol
move-down
move-eoe
move-eol
move-left
move-right
move-up
only-whitespace-left?
page-down
page-up
redisplay
should-auto-indent?)
; NB. top-line and bot-line aren't really positions.
; the row does identify the logical row, but the col identifies the
; physical row of the logical row, i.e., 0 for the first physical
; row, 1 for the second, etc.
(define-record-type entry
(fields
(immutable lns) ; logical lines
(mutable row) ; point (logical cursor) row
(mutable col) ; point (logical cursor) column
(mutable screen-cols) ; cached screen columns
(mutable screen-rows) ; cached screen rows
(mutable top-line) ; first displayed line
(mutable bot-line) ; last displayed line
(mutable mark)) ; current mark pos
(nongenerative)
(sealed #t)
(protocol
(lambda (new)
(lambda (lns)
(new lns 0 0 (screen-cols) (screen-rows)
(make-pos 0 0) (make-pos 0 0) #f)))))
(module ()
(record-writer (type-descriptor entry)
(lambda (x p wr)
(display "#<ee entry>" p))))
(define (entry-point entry)
(make-pos (entry-row entry) (entry-col entry)))
;;; an lns is a nonempty list of logical lines, each of which may span
;;; multiple screen lines. each line consists of an integer that records
;;; the number of screen rows spanned along with a string containing
;;; the text of the line. lines are implicitly separated by newlines; no
;;; newlines appear in the strings themselves.
;;;
;;; lns := (ln ln ...) ;;; list of "ln"s
;;; ln := [nsr, str]
;;; nsr := integer ;;; number of screen rows occupied by the line
;;; str := string ;;; contents of the line
; arrange for nsr to be updated whenever str is changed
(module (make-ln ln? ln-str ln-nsr ln-str-set! ln-nsr-set!)
(define-record-type (ln make-ln ln?)
(fields
(mutable str ln-str $ln-str-set!)
(mutable nsr ln-nsr ln-nsr-set!))
(nongenerative)
(sealed #t)
(protocol
(lambda (new)
(lambda (ee str)
(new str (str->nsr ee str))))))
(define (ln-str-set! ee ln str)
($ln-str-set! ln str)
(ln-nsr-set! ln (str->nsr ee str))))
; extract nsr or str from selected row of lns
(define (lns->nsr lns row) (ln-nsr (list-ref lns row)))
(define (lns->str lns row) (ln-str (list-ref lns row)))
; replace str in selected row of lns
(define (lns->str! ee lns row str)
(ln-str-set! ee (list-ref lns row) str))
(define (lns->char lns row col)
(let ([str (lns->str lns row)])
(if (fx< col (string-length str))
(string-ref str col)
#f)))
(define (yank-entry ee entry)
(map ln-str (entry-lns entry)))
(define (entry->string entry)
(let* ([lns (entry-lns entry)] [n (length lns)])
(let ([sop (open-output-string)])
(let loop ([i 0] [sep ""])
(unless (fx= i n)
(fprintf sop "~a~a" sep (lns->str lns i))
(loop (fx+ i 1) "\n")))
(get-output-string sop))))
(define (echo-entry ee entry tp)
(display-string (eestate-prompt ee) tp)
(let ([lns (entry-lns entry)])
(fprintf tp "~a\n" (ln-str (car lns)))
(for-each
(let ([pad (phantom-prompt ee)])
(lambda (ln) (fprintf tp "~a~a\n" pad (ln-str ln))))
(cdr lns)))
(flush-output-port tp))
(define (string->lines s)
; break string into list of lines while expanding tabs
(let ([n (string-length s)] [op (open-output-string)])
(let f ([i 0] [col 0])
(if (fx= i n)
(list (get-output-string op))
(let ([c (string-ref s i)])
(case c
[(#\newline)
(let ([line (get-output-string op)])
(cons line (f (fx+ i 1) 0)))]
[(#\tab)
(do ([i (fx- 8 (fxmodulo col 8)) (fx- i 1)])
((fx= i 0))
(write-char #\space op))
(f (fx+ i 1) 0)]
[(#\return) (f (fx+ i 1) col)]
[else (write-char c op) (f (fx+ i 1) (fx+ col 1))]))))))
(define (string->entry ee s)
(let ([ln* (map (lambda (str) (make-ln ee str)) (string->lines s))])
(make-entry (if (null? ln*) (list (make-ln ee "")) ln*))))
(define (null-entry? entry)
(let ([lns (entry-lns entry)])
(and (fx= (length lns) 1)
(equal? (ln-str (car lns)) ""))))
(define (entry-nsr entry) (apply fx+ (map ln-nsr (entry-lns entry))))
;;; split a logical row into a list of strings each of which will fit
;;; on a screen line, starting at logical column col.
(define split-string
(lambda (ee str col)
(let ([str-len (string-length str)])
(let f ([col col] [width (fx- (screen-cols) (col->screen-col ee col))])
(if (fx< (fx- str-len col) width)
(list (substring str col str-len))
(cons (substring str col (fx+ col width))
(f (fx+ col width) (screen-cols))))))))
(define (screen-lines-between ee entry toprow topoff nextrow nextoff)
; returns distance in physical screen lines between physical line
; topoff of toprow and nextoff of nextrow
(let ([lns (entry-lns entry)])
(let f ([i toprow] [off topoff] [lns (list-tail lns toprow)])
(if (fx= i nextrow)
(fx- nextoff off)
(fx+ (fx- (ln-nsr (car lns)) off)
(f (fx+ i 1) 0 (cdr lns)))))))
(define (str->nsr ee str)
(fx+ (col->line-offset ee (string-length str)) 1))
;;; return the line offset based on the column and screen size
;;; ||-offset=2 (prompt)
;;; example: if: col = 15 vv
;;; offset = 2 -----------
;;; scrn-cols = 10 |> line-000| line-offset 0
;;; then: |line-11111| line-offset 1
;;; line-offset = 1 ^column = 15
(define col->line-offset
(lambda (ee col)
(fxquotient (fx+ (string-length (eestate-prompt ee)) col) (screen-cols))))
;;; return the actual screen column based on the logical row column
;;; example: if: col = 15 vv-offset=2 (prompt)
;;; offset = 2 -----------
;;; scrn-cols = 10 |> line-000| line-offset 0
;;; then: |line-11111| line-offset 1
;;; scrn-col = 7 ^column = 15
(define col->screen-col
(lambda (ee col)
(fxremainder (fx+ col (string-length (eestate-prompt ee))) (screen-cols))))
(define (clear-entry ee entry)
; like clear-screen, but clears only from top line of entry
(if (visible? ee entry 0 0)
(begin
(carriage-return)
(move-cursor-up
(let ([top-line (entry-top-line entry)])
(screen-lines-between ee entry
(pos-row top-line) (pos-col top-line)
(entry-row entry) (col->line-offset ee (entry-col entry)))))
(clear-eos))
(clear-screen)))
;;; given bottom line displayed, determines top line that will fill
;;; the screen to the extent possible
(defopt (calc-top-line-displayed entry last-row-pos [nrows (screen-rows)])
(let ([lns (entry-lns entry)])
(let loop ([n nrows]
[r (pos-row last-row-pos)]
[off (pos-col last-row-pos)])
(if (or (fx= n 1) (and (fx= r 0) (fx= off 0)))
(make-pos r off)
(if (fx= off 0)
(loop (fx- n 1) (fx- r 1) (fx- (lns->nsr lns (fx- r 1)) 1))
(loop (fx- n 1) r (fx- off 1)))))))
;;; given first line displayed, determines bottom line that will fill
;;; the screen to the extent possible
(defopt (calc-bot-line-displayed entry first-row-pos [nrows (screen-rows)])
(let* ([lns (entry-lns entry)]
[last-row (fx- (length lns) 1)]
[last-off (fx- (lns->nsr lns last-row) 1)]
[first-row (pos-row first-row-pos)])
(let loop ([n nrows]
[r first-row]
[off (pos-col first-row-pos)]
[off-max (fx- (lns->nsr lns first-row) 1)])
(if (or (fx= n 1) (and (fx= r last-row) (fx= off last-off)))
(make-pos r off)
(if (fx= off off-max)
(loop (fx- n 1) (fx+ r 1) 0 (fx- (lns->nsr lns (fx+ r 1)) 1))
(loop (fx- n 1) r (fx+ off 1) off-max))))))
; NB. the macos x terminal app distinguishes between empty screen
; positions (e.g., after clr_eos or clr_eol) and screen positions filled
; with spaces. attempts to move past and clear after the former result
; in strange behavior. (For example, the sequence clr_eos, cursor_right,
; clr_eol, 'a', clr_eol, and 'b' doesn't print the b but does cause the
; terminal to send back some characters. Using ' ' in place of the
; cursor_right works as expected.) For this reason, we display spaces
; and avoid using move-cursor-right to pad the front of each row after
; the first, which gets the actual prompt.
(define (phantom-prompt ee)
(make-string (string-length (eestate-prompt ee)) #\space))
(module (display-rest/goto)
(define (display-rest-of-line ee entry row col clear?)
; display as much of the rest of row as will fit on the screen
(let ([lns (entry-lns entry)] [bot-line (entry-bot-line entry)])
; n = number of lines to display beyond the first
(let loop ([n (fx- (if (fx= row (pos-row bot-line))
(pos-col bot-line)
(fx- (lns->nsr lns row) 1))
(col->line-offset ee col))]
[str-lst (split-string ee (lns->str lns row) col)]
[new-col col])
(when clear? (clear-eol))
(let ([str (car str-lst)])
(ee-display-string (car str-lst))
(let ([new-col (fx+ new-col (string-length str))])
(if (fx= n 0)
new-col
(begin
(carriage-return)
(line-feed)
(loop (fx- n 1) (cdr str-lst) new-col))))))))
(define (display-rest-of-entry ee entry)
(let ([row (entry-row entry)]
[col (entry-col entry)]
[bot-row (pos-row (entry-bot-line entry))])
(let loop ([new-row row] [start-col col])
(let ([new-col (display-rest-of-line ee entry new-row start-col #f)])
(if (fx= new-row bot-row)
(values new-row new-col)
(begin
(carriage-return)
(line-feed)
(ee-display-string (phantom-prompt ee))
(loop (fx+ new-row 1) 0)))))))
(define (display-rest/goto ee entry just-row? clear? to-row to-col)
; display rest of entry and go directly from there to (to-row, to-col)
; just-row? => only remainder of current logical row needed by displayed
; clear? => clear-eos or clear-eol needed
(let-values ([(cur-row cur-col)
(if just-row?
(values
(entry-row entry)
(display-rest-of-line ee entry
(entry-row entry) (entry-col entry) clear?))
(begin
(entry-bot-line-set! entry
(calc-bot-line-displayed entry
(entry-top-line entry)))
(when clear? (clear-eos))
(display-rest-of-entry ee entry)))])
(unless (and (fx= cur-row (entry-row entry))
(fx= cur-col (entry-col entry)))
(entry-row-set! entry cur-row)
; if the last character written was in the last column of a screen
; line, move back one so that the cursor is pointing at that character
; to avoid returning a column value that would wrongly indicate that
; the cursor is at the start of the next screen line
(if (and (fx> cur-col 0) (fx= (col->screen-col ee cur-col) 0))
(begin
(move-cursor-left 1)
(entry-col-set! entry (fx- cur-col 1)))
(entry-col-set! entry cur-col)))
(goto ee entry (make-pos to-row to-col)))))
(module (display-partial-entry)
(define (display-partial-row ee row str start end)
; displays physical lines of str from start (inclusive) to end (inclusive)
; assumes cursor is at column zero of start line; leaves cursor at
; column zero of end line
(let ([ls (list-tail (split-string ee str 0) start)])
(when (fx= start 0)
(ee-display-string
(if (fx= row 0)
(eestate-prompt ee)
(phantom-prompt ee))))
(ee-display-string (car ls))
(carriage-return)
(do ([i start (fx+ i 1)] [ls (cdr ls) (cdr ls)])
((fx= i end))
(line-feed)
(ee-display-string (car ls))
(carriage-return))))
(define (display-partial-entry ee entry toprow topoff botrow botoff)
; displays physical screen lines between physical line topoff of
; toprow (inclusive) and botoff of botrow (inclusive)
; assumes cursor is at column zero of first physical line to be displayed;
; leaves cursor at column zero of last line displayed
(let ([lns (entry-lns entry)])
(let loop ([r toprow] [start topoff] [lns (list-tail lns toprow)])
(display-partial-row ee r (ln-str (car lns)) start
(if (fx= r botrow) botoff (fx- (ln-nsr (car lns)) 1)))
(unless (fx= r botrow)
(line-feed)
(loop (fx+ r 1) 0 (cdr lns)))))))
(define (goto-backward ee entry new-row new-col)
(assert* (fx>= new-row 0)
(fx>= new-col 0)
(fx<= new-col (string-length (lns->str (entry-lns entry) new-row))))
(let* ([lns (entry-lns entry)]
[row (entry-row entry)]
[col (entry-col entry)]
[top-line (entry-top-line entry)]
[new-str (lns->str lns new-row)]
[new-len (string-length new-str)]
[new-row-offset (col->line-offset ee new-col)]
[new-row-pos (make-pos new-row new-row-offset)]
[new-bot-line (calc-bot-line-displayed entry new-row-pos)])
(cond
; case 1: destination on screen, no scrolling necessary
; ------------------
; | (define fact | <--top-line
; | (lambda (n) | <--new-row
; | (if (zero? n)| <--point
; | 1 |
; | (* n (fac|
; |t (sub1 n)))))) | <--bot-line
; | |
; ------------------
[(pos>=? new-row-pos (entry-top-line entry))
(move-cursor-up
(screen-lines-between ee entry
new-row new-row-offset
(entry-row entry) (col->line-offset ee (entry-col entry))))
(let ([screen-col (col->screen-col ee col)]
[new-screen-col (col->screen-col ee new-col)])
(cond
[(fx> new-screen-col screen-col)
(move-cursor-right (fx- new-screen-col screen-col))]
[(fx< new-screen-col screen-col)
(move-cursor-left (fx- screen-col new-screen-col))]))
(entry-row-set! entry new-row)
(entry-col-set! entry new-col)]
; case 2: a portion of the old screen overlaps the new screen.
; we will scroll down and keep the overlap instead of
; redrawing
; + = new screen border
; - = old-screen border
; ++++++++++++++++++
; | (define f | <--new-row 0 }extra-top-
; | (lambda (n) | }lines
; ------------------
; | (if (zero? n)| <--top-line (2 . 0)
; | 1 | <--point (row . col)
; | (* n | <--new-bot-line (4 . 0)
; ++++++++++++++++++
; | (f | <--bot-line (5 . 0)
; | (1- |
; ------------------
; n))))))
[(pos>? new-bot-line (entry-top-line entry))
; move cursor to physical column 0 of top screen line
(move-cursor-up
(screen-lines-between ee entry
(pos-row top-line) (pos-col top-line)
row (col->line-offset ee col)))
(carriage-return)
(let ([extra-top-lines
(screen-lines-between ee entry
new-row new-row-offset
(pos-row top-line) (pos-col top-line))])
; reverse scroll to open up space at the top
; if we're not actually at the top of the physical display, e.g.,
; if we only partially displayed the entry after an error or tab-tab,
; we hope that this goes up a line and clears to end of line. if
; this ever gives us problems, we'll have avoid getting into this
; case when less than a screenful of lines has been displayed.
(scroll-reverse extra-top-lines)
; display the extra lines
(let ([r (pos-row top-line)] [off (fx- (pos-col top-line) 1)])
(if (fx>= off 0)
(display-partial-entry ee entry new-row new-row-offset r off)
(display-partial-entry ee entry new-row new-row-offset
(fx- r 1) (fx- (lns->nsr lns (fx- r 1)) 1))))
; move cursor back to top
(move-cursor-up (fx- extra-top-lines 1)))
(move-cursor-right (col->screen-col ee new-col))
(entry-col-set! entry new-col)
(entry-row-set! entry new-row)
(entry-top-line-set! entry new-row-pos)
(when (pos<? new-bot-line (entry-bot-line entry))
(entry-bot-line-set! entry new-bot-line))]
; case 3: no overlap between old screen area and new screen
; area. we will redraw the entire screen
; + = new screen border
; - = old-screen border
; ++++++++++++++++++
; | (define f | <--new-row 0
; | (lambda (n) |
; | (if (zero? n)| <--new-bot-line (4 . 0)
; ++++++++++++++++++
; ------------------
; | 1 | <--top-line (2 . 0)
; | (* n |
; | (f | <--bot-line (4 . 1)
; ------------------
; (1-
; n))))))
[else
(clear-screen)
(display-partial-entry ee entry
new-row new-row-offset
(pos-row new-bot-line) (pos-col new-bot-line))
(move-cursor-up
(screen-lines-between ee entry
new-row new-row-offset
(pos-row new-bot-line) (pos-col new-bot-line)))
(move-cursor-right (col->screen-col ee new-col))
(entry-col-set! entry new-col)
(entry-row-set! entry new-row)
(entry-top-line-set! entry new-row-pos)
(entry-bot-line-set! entry new-bot-line)])))
(define (goto-forward ee entry new-row new-col)
(assert* (fx< new-row (length (entry-lns entry)))
(fx>= new-col 0)
(fx<= new-col (string-length (lns->str (entry-lns entry) new-row))))
(let* ([lns (entry-lns entry)]
[row (entry-row entry)]
[col (entry-col entry)]
[bot-line (entry-bot-line entry)]
[new-str (lns->str lns new-row)]
[new-len (string-length new-str)]
[new-row-offset (col->line-offset ee new-col)]
[new-row-pos (make-pos new-row new-row-offset)]
[new-top-line (calc-top-line-displayed entry new-row-pos)])
(cond
; case 1: destination on screen, no scrolling necessary
; ------------------
; | (define fact | <--top-line
; | (lambda (n) | <--point
; | (if (zero? n)| <--new-row
; | 1 |
; | (* n (fac|
; |t (sub1 n)))))) | <--bot-line
; | |
; ------------------
[(pos<=? new-row-pos bot-line)
(move-cursor-down
(screen-lines-between ee entry
row (col->line-offset ee col)
new-row new-row-offset))
(let ([screen-col (col->screen-col ee col)]
[new-screen-col (col->screen-col ee new-col)])
(cond
[(fx> new-screen-col screen-col)
(move-cursor-right (fx- new-screen-col screen-col))]
[(fx< new-screen-col screen-col)
(move-cursor-left (fx- screen-col new-screen-col))]))
(entry-row-set! entry new-row)
(entry-col-set! entry new-col)]
; case 2: a portion of the old screen overlaps the new screen.
; we will scroll up and keep the overlap
;
; + = new screen border
; - = old-screen border
; ------------------
; | (define f | <--top-line (0 . 0)
; | (lambda (n) |
; ++++++++++++++++++
; | (if (zero? n)| <--new-top-line } scrn-
; | 1 | <--point (row . col) } draw-
; | (* n | <--bot-line (4 . 0) } lines
; ------------------
; | (f |
; | (1- | <--new-row 6
; ++++++++++++++++++
; n))))))
[(pos>=? bot-line new-top-line)
; move cursor to physical col 0 of first line after old bot-line
(move-cursor-down
(screen-lines-between ee entry
row (col->line-offset ee col)
(pos-row bot-line) (pos-col bot-line)))
(carriage-return)
(line-feed)
(let ([r (pos-row bot-line)] [off (fx+ (pos-col bot-line) 1)])
(if (fx< off (lns->nsr lns r))
(display-partial-entry ee entry r off
new-row new-row-offset)
(display-partial-entry ee entry (fx+ r 1) 0
new-row new-row-offset)))
(move-cursor-right (col->screen-col ee new-col))
(entry-col-set! entry new-col)
(entry-row-set! entry new-row)
(when (pos>? new-top-line (entry-top-line entry))
(entry-top-line-set! entry new-top-line))
(entry-bot-line-set! entry new-row-pos)]
; case 3: no overlap between old screen area and new screen
; area. we will redraw the entire screen
; + = new screen border
; - = old-screen border
; ++++++++++++++++++
; | (define f | <--top-line (0 . 0)
; | (lambda (n) |
; | (if (zero? n)| <--bot-line (2 . 0)
; ++++++++++++++++++
; ------------------
; | 1 | <--new-top-line
; | (* n |
; | (f | <--new-row, new-row-offset
; ------------------
; (1-
; n))))))
[else
(clear-screen)
(display-partial-entry ee entry
(pos-row new-top-line) (pos-col new-top-line)
new-row new-row-offset)
(move-cursor-right (col->screen-col ee new-col))
(entry-col-set! entry new-col)
(entry-row-set! entry new-row)
(entry-top-line-set! entry new-top-line)
(entry-bot-line-set! entry new-row-pos)])))
(define (goto ee entry p)
(let ([new-row (pos-row p)] [new-col (pos-col p)])
(assert* (fx< new-row (length (entry-lns entry)))
(fx<= new-col (string-length (lns->str (entry-lns entry) new-row))))
(if (or (fx< new-row (entry-row entry))
(and (fx= new-row (entry-row entry))
(fx< new-col (entry-col entry))))
(goto-backward ee entry new-row new-col)
(goto-forward ee entry new-row new-col))))
(defopt (move-up ee entry [n 1])
(assert* (fx>= (fx- (entry-row entry) n) 0))
(let ([new-row (fx- (entry-row entry) n)])
(goto-backward ee entry new-row
(fxmin (entry-col entry)
(string-length (lns->str (entry-lns entry) new-row))))))
(defopt (move-down ee entry [n 1])
(assert* (fx< (fx+ (entry-row entry) n) (length (entry-lns entry))))
(let ([new-row (fx+ (entry-row entry) n)])
(goto-forward ee entry new-row
(fxmin (entry-col entry)
(string-length (lns->str (entry-lns entry) new-row))))))
(defopt (move-left ee entry [n 1])
(let ([new-col (fx- (entry-col entry) n)])
(assert* (fx>= new-col 0))
(goto-backward ee entry (entry-row entry) new-col)))
(defopt (move-right ee entry [n 1])
(let ([new-col (fx+ (entry-col entry) n)])
(assert* (fx<= new-col (string-length (lns->str (entry-lns entry) (entry-row entry)))))
(goto-forward ee entry (entry-row entry) new-col)))
(define (page-down ee entry)
(let* ([last-row (fx- (length (entry-lns entry)) 1)]
[row (entry-row entry)]
[col (entry-col entry)]
[point-line-offset (col->line-offset ee col)]
[top-line (entry-top-line entry)]
[bot-line (entry-bot-line entry)]
[n (screen-lines-between ee entry
(pos-row top-line) (pos-col top-line)
(pos-row bot-line) (pos-col bot-line))])
(let f ([r (fxmin (fx+ row n) last-row)])
(if (fx= r row)
(unless (fx= r last-row)
(goto-forward ee entry (fx+ r 1)
(fxmin col (string-length (lns->str (entry-lns entry) (fx+ r 1))))))
(let ([c (fxmin col (string-length (lns->str (entry-lns entry) r)))])
(if (<= (screen-lines-between ee entry
row point-line-offset
r (col->line-offset ee c))
n)
(goto-forward ee entry r c)
(f (fx- r 1))))))))
(define (page-up ee entry)
(let* ([row (entry-row entry)]
[col (entry-col entry)]
[point-line-offset (col->line-offset ee col)]
[top-line (entry-top-line entry)]
[bot-line (entry-bot-line entry)]
[n (screen-lines-between ee entry
(pos-row top-line) (pos-col top-line)
(pos-row bot-line) (pos-col bot-line))])
(let f ([r (max (fx- row n) 0)])
(if (fx= r row)
(unless (fx= r 0)
(goto-backward ee entry (fx- r 1)
(fxmin col (string-length (lns->str (entry-lns entry) (fx- r 1))))))
(let ([c (fxmin col (string-length (lns->str (entry-lns entry) r)))])
(if (<= (screen-lines-between ee entry
r (col->line-offset ee c)
row point-line-offset)
n)
(goto-backward ee entry r c)
(f (fx+ r 1))))))))
(define (move-eol ee entry)
(move-right ee entry
(fx- (string-length (lns->str (entry-lns entry) (entry-row entry)))
(entry-col entry))))
(define (move-bol ee entry)
(move-left ee entry (entry-col entry)))
(define (move-eoe ee entry)
(let ([lns (entry-lns entry)])
(let ([r (fx- (length lns) 1)])
(goto-forward ee entry r (string-length (lns->str lns r))))))
(define (move-to-col-pos ee entry new-col)
(let ([col (entry-col entry)])
(if (fx< new-col col)
(move-left ee entry (fx- col new-col))
(move-right ee entry (fx- new-col col)))))
(define (adjust-mark/delete ee entry r1 c1 r2 c2)
(let ([mark (entry-mark entry)])
(when mark
(let ([mrow (pos-row mark)] [mcol (pos-col mark)])
(unless (or (fx< mrow r1) (and (fx= mrow r1) (fx< mcol c1)))
(entry-mark-set! entry
(and (not (or (fx< mrow r2) (and (fx= mrow r2) (fx< mcol c2))))
(make-pos
(fx- mrow (fx- r2 r1))
(if (fx= mrow r2) (fx+ c1 (fx- mcol c2)) mcol)))))))))
(define (adjust-mark/insert ee entry r1 c1 r2 c2)
(let ([mark (entry-mark entry)])
(when mark
(let ([mrow (pos-row mark)] [mcol (pos-col mark)])
(unless (or (fx< mrow r1) (and (fx= mrow r1) (fx< mcol c1)))
(entry-mark-set! entry
(make-pos
(fx+ mrow (fx- r2 r1))
(if (fx= mrow r1) (fx+ c2 (fx- mcol c1)) mcol))))))))
(define (delete-forward ee entry r2 c2)
; deletes from point, aka r1, c1 (inclusive) to r2, c2 (exclusive)
; and returns the deleted content as a list of strings
(let ([r1 (entry-row entry)] [c1 (entry-col entry)])
(assert* (or (fx< r1 r2) (and (fx= r1 r2) (fx<= c1 c2))))
(adjust-mark/delete ee entry r1 c1 r2 c2)
(if (fx= r1 r2)
(let* ([ln (list-ref (entry-lns entry) r1)]
[s (ln-str ln)]
[old-nsr (ln-nsr ln)])
(ln-str-set! ee ln
(string-append
(substring s 0 c1)
(substring s c2 (string-length s))))
(display-rest/goto ee entry (fx= (ln-nsr ln) old-nsr) #t r1 c1)
(list (substring s c1 c2)))
(let* ([lns (entry-lns entry)]
[ls1 (list-tail lns r1)]
[ls2 (list-tail ls1 (fx- r2 r1))]
[s1 (ln-str (car ls1))]
[s2 (ln-str (car ls2))])
(ln-str-set! ee (car ls1)
(string-append
(substring s1 0 c1)
(substring s2 c2 (string-length s2))))
(let ([deleted
(cons (substring s1 c1 (string-length s1))
(let f ([ls (cdr ls1)])
(if (eq? ls ls2)
(list (substring s2 0 c2))
(cons (ln-str (car ls)) (f (cdr ls))))))])
(set-cdr! ls1 (cdr ls2))
(display-rest/goto ee entry #f #t r1 c1)
deleted)))))
(define (delete-char ee entry)
(assert* (not (end-of-line? ee entry)))
(let ([row (entry-row entry)] [col (entry-col entry)])
(delete-forward ee entry row (fx+ col 1))))
(define (delete-to-eol ee entry)
(let ([row (entry-row entry)])
(delete-forward ee entry row
(string-length (lns->str (entry-lns entry) row)))))
(define (join-rows ee entry)
(assert* (end-of-line? ee entry) (not (last-line? ee entry)))
(delete-forward ee entry (fx+ (entry-row entry) 1) 0))
(define (insert-string-before ee entry new-str)
(let* ([row (entry-row entry)]
[col (entry-col entry)]
[lns (entry-lns entry)]
[ln (list-ref lns row)]
[str (ln-str ln)]
[str-len (string-length str)]
[new-col (fx+ col (string-length new-str))]
[nsr (ln-nsr ln)]
[eoe? (end-of-entry? ee entry)])
(ln-str-set! ee ln
(string-append
(substring str 0 col)
new-str
(substring str col (string-length str))))
(let ([just-row? (fx= (ln-nsr ln) nsr)])
(display-rest/goto ee entry just-row?
; avoid clear-eol/eos if insertion takes place at end of entry or
; if rewriting just the current row
(and (not eoe?) (not just-row?))
row new-col))
(adjust-mark/insert ee entry row col row new-col)))
(define (add-char ee entry c)
; add character after point, then move point forward one character
(assert* (char? c))
(insert-string-before ee entry (string c)))
(define (insert-strings-before ee entry strs)
(unless (null? strs)
(if (fx= (length strs) 1)
(insert-string-before ee entry (car strs))
(let* ([row (entry-row entry)]
[col (entry-col entry)]
[lns (entry-lns entry)]
[ls (list-tail lns row)]
[ln (car ls)]
[point-str (ln-str ln)]
[eoe? (end-of-entry? ee entry)])
(ln-str-set! ee ln
(string-append (substring point-str 0 col) (car strs)))
(set-cdr! ls
(let f ([str (cadr strs)] [strs (cddr strs)])
(if (null? strs)
(cons (make-ln ee
(string-append str
(substring point-str col
(string-length point-str))))
(cdr ls))
(cons (make-ln ee str)
(f (car strs) (cdr strs))))))
(let* ([n (fx- (length strs) 1)]
[new-row (fx+ row n)]
[new-col (string-length (list-ref strs n))])
(display-rest/goto ee entry #f (not eoe?) new-row new-col)
(adjust-mark/insert ee entry row col new-row new-col))))))
(define (first-line? ee entry) (fxzero? (entry-row entry)))
(define (last-line? ee entry)
(fx= (entry-row entry) (fx1- (length (entry-lns entry)))))
(define (last-line-displayed? ee entry)
(pos=? (make-pos (entry-row entry) (col->line-offset ee (entry-col entry)))
(entry-bot-line entry)))
(define (visible? ee entry row col)
(let ([line (make-pos row (col->line-offset ee col))])
(and (pos<=? (entry-top-line entry) line)
(pos<=? line (entry-bot-line entry)))))
(define (end-of-line? ee entry)
(fx= (entry-col entry)
(string-length (lns->str (entry-lns entry) (entry-row entry)))))
(define (end-of-entry? ee entry)
(and (fx= (entry-row entry) (fx- (length (entry-lns entry)) 1))
(end-of-line? ee entry)))
(define (beginning-of-line? ee entry) (fx= (entry-col entry) 0))
; returns #t iff only spaces and newlines are left after point
(define (only-whitespace-left? ee entry)
(let f ([ls (list-tail (entry-lns entry) (entry-row entry))]
[col (entry-col entry)])
(or (null? ls)
(let* ([s (ln-str (car ls))] [n (string-length s)])
(let g ([col col])
(if (fx= col n)
(f (cdr ls) 0)
(and (char=? (string-ref s col) #\space)
(g (fx+ col 1)))))))))
(define (handle-winch ee entry)
(screen-resize!)
(unless (and (fx= (screen-rows) (entry-screen-rows entry))
(fx= (screen-cols) (entry-screen-cols entry)))
(clear-entry ee entry)
(redisplay ee entry)))
(module (redisplay)
(define (set-screen-size! ee entry)
(screen-resize!)
(unless (and (fx= (entry-screen-cols entry) (screen-cols))
(fx= (entry-screen-rows entry) (screen-rows)))
(for-each
(lambda (ln) (ln-nsr-set! ln (str->nsr ee (ln-str ln))))
(entry-lns entry))
(entry-screen-cols-set! entry (screen-cols))
(entry-screen-rows-set! entry (screen-rows))))
(defopt (redisplay ee entry [nrows #f])
(set-screen-size! ee entry)
(let* ([nrows (or nrows (screen-rows))] ; want new screen-rows
[row (entry-row entry)]
[col (entry-col entry)]
[point-line (make-pos row (col->line-offset ee col))])
(entry-bot-line-set! entry
(calc-bot-line-displayed entry (entry-top-line entry) nrows))
(when (pos>? point-line (entry-bot-line entry))
(entry-bot-line-set! entry point-line))
(entry-top-line-set! entry
(calc-top-line-displayed entry (entry-bot-line entry) nrows))
(when (pos<? point-line (entry-top-line entry))
(entry-top-line-set! entry point-line)
(entry-bot-line-set! entry
(calc-bot-line-displayed entry (entry-top-line entry) nrows)))
(let ([top-line (entry-top-line entry)] [bot-line (entry-bot-line entry)])
(display-partial-entry ee entry
(pos-row top-line) (pos-col top-line)
(pos-row bot-line) (pos-col bot-line))
(move-cursor-up
(screen-lines-between ee entry
(pos-row point-line) (pos-col point-line)
(pos-row bot-line) (pos-col bot-line)))
(move-cursor-right (col->screen-col ee col))))))
(define (flash ee entry mpos)
(let ([point-pos (entry-point entry)])
(cond
[(visible? ee entry (pos-row mpos) (pos-col mpos))
(goto ee entry mpos)
(ee-flush)
(wait (ee-paren-flash-delay))
(goto ee entry point-pos)]
[(pos<? mpos point-pos)
(let ([nlines
(screen-lines-between ee entry
(pos-row (entry-top-line entry))
(pos-col (entry-top-line entry))
(pos-row point-pos)
(col->line-offset ee (pos-col point-pos)))]
[ncols (col->screen-col ee (pos-col point-pos))])
(move-cursor-left ncols)
(move-cursor-up nlines)
(ee-flush)
(wait (ee-paren-flash-delay))
(move-cursor-down nlines)
(move-cursor-right ncols))]
[else
(let ([nlines
(screen-lines-between ee entry
(pos-row point-pos)
(col->line-offset ee (pos-col point-pos))
(pos-row (entry-bot-line entry))
(pos-col (entry-top-line entry)))]
[ncols (col->screen-col ee (pos-col point-pos))])
(move-cursor-left ncols)
(move-cursor-down nlines)
(ee-flush)
(wait (ee-paren-flash-delay))
(move-cursor-up nlines)
(move-cursor-right ncols))])))
(define (correct&flash-matching-delimiter ee entry)
(define (expected left) (if (eqv? left lbchar) rbchar rpchar))
(move-left ee entry 1) ; move over delim
(let ([lns (entry-lns entry)])
(let* ([row (entry-row entry)]
[col (entry-col entry)]
[str (lns->str lns row)]
[c (string-ref str col)])
(if (or (char=? c lpchar) (char=? c lbchar))
; don't correct close delimiter when inserting open delimiter
; since doing so often leads to surprising results
(when (ee-flash-parens)
(cond
[(find-matching-delim-forward ee entry row col #f) =>
(lambda (mpos) (flash ee entry mpos))]))
(cond
[(find-matching-delim-backward ee entry row col
(ee-auto-paren-balance)) =>
(lambda (mpos)
(let ([cexp (expected
(string-ref
(lns->str lns (pos-row mpos))
(pos-col mpos)))])
(unless (eqv? c cexp)
(string-set! str col cexp)
(ee-write-char cexp)
(move-cursor-left 1)))
(when (ee-flash-parens) (flash ee entry mpos)))]))))
(move-right ee entry 1))
(define (find-matching-delimiter ee entry)
(let ([row (entry-row entry)]
[col (entry-col entry)]
[str (lns->str (entry-lns entry) (entry-row entry))])
(and (fx< col (string-length str))
(let ([c (string-ref str col)])
(if (or (char=? c lpchar) (char=? c lbchar))
(find-matching-delim-forward ee entry row col #f)
(and (or (char=? c rpchar) (char=? c rbchar))
(find-matching-delim-backward ee entry row col #f)))))))
(define (find-matching-delim-backward ee entry row col lax?)
(let ([lns (entry-lns entry)])
; 1. create string representing current entry through row, col
; 2. search forward, stacking left/right delimiters and their indices
; 3. if matching delimiter found, convert string index to pos
(let* ([s (let ([op (open-output-string)])
(let loop ([i 0] [sep ""])
(let ([str (lns->str lns i)])
(if (= i row)
(fprintf op "~a~a" sep (substring str 0 (fx+ col 1)))
(begin
(fprintf op "~a~a" sep str)
(loop (fx+ i 1) "\n")))))
(get-output-string op))]
[ip (open-input-string s)])
(let loop ([stack '()])
(on-error (loop '())
(let-values ([(type value start end) (read-token ip)])
(case type
[(atomic box dot insert mark quote) (loop stack)]
[(lbrack record-brack)
(loop (cons (cons 'rbrack end) stack))]
[(lparen vfxnparen vfxparen vnparen vparen vu8nparen vu8paren)
(loop (cons (cons 'rparen end) stack))]
[(rbrack rparen)
(if (= end (string-length s))
(and (not (null? stack))
(or lax? (eq? (caar stack) type))
(index->pos s (fx- (cdar stack) 1) 0 0))
(if (and (not (null? stack)) (eq? (caar stack) type))
(loop (cdr stack))
(loop '())))]
[(eof fasl) #f]
[else
(warningf 'expeditor "unexpected token type ~s from read-token" type)
#f])))))))
(define (find-matching-delim-forward ee entry row col lax?)
(let ([lns (entry-lns entry)])
; should be sitting on left paren or bracket
(assert* (or (char=? (lns->char lns row col) lpchar)
(char=? (lns->char lns row col) lbchar)))
; 1. create string representing current entry starting at col, row
; 2. search forward until matching delimiter, eof, or error
; 3. if matching delimiter found, convert string index to pos
(let* ([s (let ([op (open-output-string)] [l-lns (length lns)])
(let ([s (lns->str lns row)])
(display (substring s col (string-length s)) op))
(let loop ([i (fx+ row 1)])
(unless (fx= i l-lns)
(fprintf op "\n~a" (lns->str lns i))
(loop (fx+ i 1))))
(get-output-string op))]
[ip (open-input-string s)])
(on-error #f
(let loop ([stack '()])
(let-values ([(type value start end) (read-token ip)])
(case type
[(atomic box dot insert mark quote) (loop stack)]
[(lbrack record-brack)
(loop (cons 'rbrack stack))]
[(lparen vfxnparen vfxparen vnparen vparen vu8nparen vu8paren)
(loop (cons 'rparen stack))]
[(rbrack rparen)
(if (fx= (length stack) 1)
(and (or lax? (eq? (car stack) type))
(index->pos s start row col))
(and (eq? (car stack) type) (loop (cdr stack))))]
[(eof fasl) #f]
[else
(warningf 'expeditor "unexpected token type ~s from read-token" type)
#f])))))))
(define (find-next-sexp-backward ee entry row col)
(let* ([lns (entry-lns entry)]
[s (let ([op (open-output-string)])
(let loop ([i 0] [sep ""])
(let ([str (lns->str lns i)])
(if (= i row)
(fprintf op "~a~a" sep (substring str 0 col))
(begin
(fprintf op "~a~a" sep str)
(loop (fx+ i 1) "\n")))))
(get-output-string op))]
[ip (open-input-string s)])
(on-error #f
(let loop ([stack '()] [last-start 0])
(let-values ([(type value start end) (read-token ip)])
(case type
[(atomic dot insert mark)
(if (and (not (null? stack)) (eq? (caar stack) 'qubx))
(loop (cdr stack) (cdar stack))
(loop stack start))]
[(box quote)
(if (and (not (null? stack)) (eq? (caar stack) 'qubx))
(loop stack #f)
(loop (cons (cons 'qubx start) stack) #f))]
[(eof) (and last-start (index->pos s last-start 0 0))]
[(lbrack record-brack)
(if (and (not (null? stack)) (eq? (caar stack) 'qubx))
(loop (cons (cons 'rbrack (cdar stack)) (cdr stack)) #f)
(loop (cons (cons 'rbrack start) stack) #f))]
[(lparen vfxnparen vfxparen vnparen vparen vu8nparen vu8paren)
(if (and (not (null? stack)) (eq? (caar stack) 'qubx))
(loop (cons (cons 'rparen (cdar stack)) (cdr stack)) #f)
(loop (cons (cons 'rparen start) stack) #f))]
[(rbrack rparen)
(if (and (not (null? stack)) (eq? (caar stack) type))
(loop (cdr stack) (cdar stack))
(loop '() #f))]
[else
(warningf 'expeditor "unexpected token type ~s from read-token" type)
#f]))))))
(define (find-next-sexp-forward ee entry row col ignore-whitespace?)
; ordinarily stops at first s-expression if it follows whitespace (or
; comments), but always moves to second if ignore-whitespace? is true
(let* ([lns (entry-lns entry)]
[s (let ([op (open-output-string)] [l-lns (length lns)])
(let ([s (lns->str lns row)])
(display (substring s col (string-length s)) op))
(let loop ([i (fx+ row 1)])
(unless (fx= i l-lns)
(fprintf op "\n~a" (lns->str lns i))
(loop (fx+ i 1))))
(get-output-string op))]
[ip (open-input-string s)])
(define (skip start)
(index->pos s
(on-error start
(let-values ([(type value start end) (read-token ip)])
start))
row col))
(on-error #f
(let loop ([stack '()] [first? #t] [ignore? #f])
(let-values ([(type value start end) (read-token ip)])
(if (and first? (not ignore-whitespace?) (fx> start 0))
(and (not ignore?) (index->pos s start row col))
(case type
[(atomic dot insert mark)
(if (null? stack)
(and (not ignore?) (skip start))
(loop stack #f ignore?))]
[(box) (loop stack #f ignore?)]
[(quote)
(when (and ignore-whitespace?
(eq? value 'datum-comment)
(null? stack))
(loop '() #f #t))
(loop stack #f ignore?)]
[(eof fasl) #f]
[(lbrack record-brack) (loop (cons 'rbrack stack) #f ignore?)]
[(lparen vfxnparen vfxparen vnparen vparen vu8nparen vu8paren)
(loop (cons 'rparen stack) #f ignore?)]
[(rbrack rparen)
(and (not (null? stack))
(eq? (car stack) type)
(let ([stack (cdr stack)])
(if (null? stack)
(and (not ignore?) (skip start))
(loop stack #f ignore?))))]
[else
(warningf 'expeditor "unexpected token type ~s from read-token" type)
#f])))))))
(module (find-next-word find-previous-word)
(define separator?
(lambda (c)
(memq c '(#\space #\; #\( #\) #\[ #\] #\" #\' #\`))))
(define (find-next-word ee entry row col)
; always returns a position
(let ([lns (entry-lns entry)])
; skip past separators
(let loop ([row row] [col col])
(cond
[(fx= col (string-length (lns->str lns row)))
(if (fx= row (fx1- (length lns)))
(make-pos row col)
(loop (fx1+ row) 0))]
[(separator? (lns->char lns row col))
(loop row (fx1+ col))]
; now we are past initial separators, find next separator
[else
(let loop ([col col])
(cond
[(or (fx= col (string-length (lns->str lns row)))
(separator? (lns->char lns row col)))
(make-pos row col)]
[else (loop (fx1+ col))]))]))))
(define (find-previous-word ee entry row col)
; always returns a position
(let ([lns (entry-lns entry)])
; skip past separators space (starts at char left of current)
(let loop ([row row] [col col])
(cond
[(fx= col 0)
(if (fx= row 0)
(make-pos row col)
(loop
(fx1- row)
(string-length (lns->str lns (fx1- row)))))]
[(separator? (lns->char lns row (fx1- col)))
(loop row (fx1- col))]
; now we are past initial separators, find next separator
[else
(let loop ([col col])
(cond
[(or (fx= col 0)
(separator? (lns->char lns row (fx1- col))))
(make-pos row col)]
[else (loop (fx1- col))]))])))))
(module (indent indent-all)
(define (calc-indent ee entry row)
(define (find-unmatched-left-delim row)
(let* ([ln (list-ref (entry-lns entry) row)]
[s (ln-str ln)])
(ln-str-set! ee ln (string rpchar))
(let ([pos (find-matching-delim-backward ee entry row 0 #t)])
(ln-str-set! ee ln s)
pos)))
(let ([lns (entry-lns entry)])
(cond
[(find-unmatched-left-delim row) =>
(lambda (mpos)
(let ([mrow (pos-row mpos)] [mcol (pos-col mpos)])
(or
; if some intervening line has same unmatched left
; delimiter, use its indentation
(let f ([xrow (fx- row 1)])
(and (not (fx= xrow mrow))
(cond
[(find-unmatched-left-delim xrow) =>
(lambda (xmpos)
(if (pos=? xmpos mpos)
(current-indent lns xrow)
(f (fx- xrow 1))))]
[else (f (fx- xrow 1))])))
; otherwise, if left paren is followed by a symbol,
; indent under second item or use standard indent if
; second item is too far out or not present
(let ([ip (open-input-string
(let ([s (lns->str lns mrow)])
(substring s mcol (string-length s))))])
(on-error #f
(and (char=? (read-char ip) lpchar)
(let-values ([(t1 v1 s1 e1) (read-token ip)])
(and (and (eq? t1 'atomic) (symbol? v1))
(let-values ([(t2 v2 s2 e2) (read-token ip)])
(if (and (not (eq? t2 'eof))
(fx< s2 6)
; use standard indent for let and rec
(not (memq v1 '(let rec))))
(fx+ mcol s2)
(fx+ mcol (ee-standard-indent)))))))))
; otherwise, indent one space in. this handles, among
; other things, bracketed let bindings and cond clauses.
(fx+ mcol 1))))]
[else 0])))
(define current-indent
(lambda (lns row)
(let* ([s (lns->str lns row)]
[n (string-length s)])
(let f ([i 0])
(if (and (fx< i n) (char=? (string-ref s i) #\space))
(f (fx+ i 1))
i)))))
(define (indent-row! ee entry row n)
(cond
[(fx< n 0)
(adjust-mark/delete ee entry row 0 row (fx- n))
(let ([lns (entry-lns entry)])
(lns->str! ee lns row
(let ([s (lns->str lns row)])
(substring s (fx- n) (string-length s)))))]
[(fx> n 0)
(adjust-mark/insert ee entry row 0 row n)
(let ([lns (entry-lns entry)])
(lns->str! ee lns row
(string-append
(make-string n #\space)
(lns->str lns row))))]))
(define (indent ee entry)
(let* ([row (entry-row entry)]
[lns (entry-lns entry)]
[n (fx- (calc-indent ee entry row) (current-indent lns row))])
(unless (fx= n 0)
(let* ([ln (list-ref lns row)]
[nsr (ln-nsr ln)]
[eoe? (end-of-entry? ee entry)])
(indent-row! ee entry row n)
(move-bol ee entry)
(let ([just-row? (fx= (ln-nsr ln) nsr)])
(display-rest/goto ee entry just-row?
; avoid clear-eol/eos if inserting and either at end of entry or
; rewriting just the current row
(or (fx< n 0) (and (not eoe?) (not just-row?)))
row (fxmax (fx+ (entry-col entry) n) 0)))))))
(define (indent-all ee entry)
(let* ([lns (entry-lns entry)]
[row (entry-row entry)]
[col (entry-col entry)]
[top-line (entry-top-line entry)]
[point-ln (list-ref lns row)]
[point-strlen (string-length (ln-str point-ln))]
[lines-to-top ; compute before we muck with indentation
(screen-lines-between ee entry
(pos-row top-line) (pos-col top-line)
row (col->line-offset ee col))])
(let loop ([ls lns] [i 0] [firstmod (length lns)] [lastmod -1])
(if (null? ls)
(unless (and (fx< lastmod (pos-row top-line))
(fx> firstmod (pos-row (entry-bot-line entry))))
; move to first physical column of first displayed line
(move-cursor-up lines-to-top)
(carriage-return)
(clear-eos)
(entry-col-set! entry
(fxmax 0
(fx+ col
(fx- (string-length (ln-str point-ln))
point-strlen))))
(redisplay ee entry))
(let ([n (fx- (calc-indent ee entry i) (current-indent lns i))])
(if (fx= n 0)
(loop (cdr ls) (fx+ i 1) firstmod lastmod)
(begin
(indent-row! ee entry i n)
(loop (cdr ls) (fx+ i 1)
(fxmin i firstmod)
(fxmax i lastmod)))))))))
)
(define (id-completions ee entry)
(define (idstring<? prefix)
(let ([common (ee-common-identifiers)]
[scheme-syms (environment-symbols (scheme-environment))])
(lambda (s1 s2)
(let ([x1 (string->symbol (string-append prefix s1))]
[x2 (string->symbol (string-append prefix s2))])
; prefer common
(let ([m1 (memq x1 common)] [m2 (memq x2 common)])
(if m1
(or (not m2) (< (length m2) (length m1)))
(and (not m2)
; prefer user-defined
(let ([u1 (not (memq x1 scheme-syms))]
[u2 (not (memq x2 scheme-syms))])
(if u1
(or (not u2) (string<? s1 s2))
(and (not u2) (string<? s1 s2)))))))))))
(define (completion str1 str2)
(let ([n1 (string-length str1)] [n2 (string-length str2)])
(and (fx>= n2 n1)
(string=? (substring str2 0 n1) str1)
(substring str2 n1 n2))))
(define (fn-completions prefix)
(values prefix
(sort string<?
(fold-left
(let ([last (path-last prefix)])
(lambda (suffix* s)
(cond
[(completion last s) =>
(lambda (suffix)
(cons (if (file-directory? (string-append prefix suffix))
(string-append suffix (string (directory-separator)))
suffix)
suffix*))]
[else suffix*])))
'()
(on-error '()
(directory-list
(let ([dir (path-parent prefix)])
(if (string=? dir "") "." dir))))))))
(let loop ([c 0])
(if (fx>= c (entry-col entry))
(values #f '())
(let ([s (let ([s (lns->str (entry-lns entry) (entry-row entry))])
(substring s c (string-length s)))])
((on-error
(lambda ()
(if (and (fx> (string-length s) 0) (char=? (string-ref s 0) #\"))
(fn-completions (substring s 1 (string-length s)))
(loop (fx+ c 1))))
(let-values ([(type value start end) (read-token (open-input-string s))])
(lambda ()
(cond
[(and (fx= (fx+ c end) (entry-col entry))
(eq? type 'atomic)
(symbol? value))
(let ([prefix (symbol->string value)])
(values prefix
(sort (idstring<? prefix)
(fold-left (lambda (suffix* x)
(cond
[(completion prefix (symbol->string x)) =>
(lambda (suffix) (cons suffix suffix*))]
[else suffix*]))
'() (environment-symbols (interaction-environment))))))]
[(and (fx= (fx+ c end -1) (entry-col entry))
(eq? type 'atomic)
(string? value))
(fn-completions value)]
[else (loop (fx+ c end))])))))))))
(define (should-auto-indent? ee)
(and (ee-auto-indent)
; don't autoindent if the characters are coming so fast that we're
; probably dealing with paste input
(> (- (real-time) (car (eestate-rt-last-op ee))) 50)))
)
(module (ee-read)
(define (accept ee entry kf)
(let* ([str (entry->string entry)]
[sip (open-input-string str)])
(define (fail c)
(define (report sop)
(cond
[(and (message-condition? c)
(irritants-condition? c)
(equal? (condition-message c) "~? at char ~a of ~s")
(let ([irritants (condition-irritants c)])
(and (list? irritants)
(fx= (length irritants) 4)
irritants))) =>
(lambda (irritants)
(apply
(lambda (?msg ?args fp ip)
(fprintf sop "read: ~?" ?msg ?args)
(let ([pos (index->pos str fp 0 0)])
(entry-row-set! entry (pos-row pos))
(entry-col-set! entry (pos-col pos))))
irritants))]
[else (display-condition c sop)]))
; clear entry before report has a chance to muck with point position
(clear-entry ee entry)
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed)
(let* ([s (let ([sop (open-output-string)])
(report sop)
(get-output-string sop))]
[n (string-length s)])
(let loop ([i 0] [msg-lines 0])
(if (= i n)
(begin
(unless (fx< (screen-rows) 3)
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed))
(redisplay ee entry (max (fx- (screen-rows) msg-lines 2) 1)))
(let ([m (min (fx+ i (screen-cols)) n)])
(ee-display-string (substring s i m))
(when (fx< (screen-rows) 2) (wait 2000))
(carriage-return)
(line-feed)
(loop m (fx+ msg-lines 1))))))
(kf))
(define (succeed result)
(move-eoe ee entry)
(no-raw-mode)
(ee-write-char #\newline)
(ee-flush)
(update-history! ee entry)
; skip close delimiters, whitespace, and comments, then
; save remainder of entry, if any, as histnow
(eestate-histnow-set! ee
(substring str
(let skip ([fp (file-position sip)])
(on-error fp
(let-values ([(type value start end) (read-token sip)])
(case type
[(rparen rbrack) (skip end)]
[else start]))))
(string-length str)))
(eestate-last-op-set! ee #f)
; inform encapsulated transcript port(s) if any
(let loop ([op (console-output-port)])
(when ($xscript-port? op)
(let-values ([(ip op xp) ($constituent-ports op)])
(unless (port-closed? xp) (echo-entry ee entry xp))
(loop op))))
result)
((guard (c [#t (lambda () (fail c))])
(let ([x (read sip)])
(lambda () (succeed x)))))))
(define (dispatch ee entry table)
(if (ee-winch?)
(begin
(handle-winch ee entry)
(dispatch ee entry table))
(let ([c (ee-read-char)])
(let ([x (if (eof-object? c)
(lambda (ee entry c) #f)
(hashtable-ref table c ee-insert-self))])
(cond
[(procedure? x)
(let ([n (eestate-repeat-count ee)])
(eestate-repeat-count-set! ee 1)
(if (= n 0)
(dispatch ee entry base-dispatch-table)
(let loop ([n n] [entry entry])
(cond
[(x ee entry c) =>
(lambda (entry)
(if (> n 1)
(loop (- n 1) entry)
(begin
(eestate-rt-last-op-set! ee
(cons (cdr (eestate-rt-last-op ee))
(real-time)))
(eestate-last-op-set! ee x)
(dispatch ee entry base-dispatch-table))))]
[else
(accept ee entry
(lambda ()
(dispatch ee entry base-dispatch-table)))]))))]
[(dispatch-table? x) (dispatch ee entry x)]
[else
(eestate-repeat-count-set! ee 1)
(eestate-last-op-set! ee #f)
(beep "unbound key")
(dispatch ee entry base-dispatch-table)])))))
(define (ee-read ee)
(screen-resize!)
(let ([entry (let ([s (eestate-histnow ee)])
; set to "" so that entry will appear modified if nonempty,
; i.e., if a partial entry is left over from last read
(eestate-histnow-set! ee "")
(string->entry ee s))])
(raw-mode)
(carriage-return)
(redisplay ee entry)
(move-eol ee entry)
(guard (c [#t (carriage-return)
(line-feed)
(clear-eos)
(ee-flush)
(no-raw-mode)
(ee-display-string
(call-with-string-output-port
(lambda (p)
(display-condition c p))))
(ee-write-char #\newline)
(update-history! ee entry)
(void)])
(dispatch ee entry base-dispatch-table)))))
(define (ee-prompt-and-read ee n)
(unless (and (integer? n) (>= n 0))
($oops 'ee-prompt-and-read
"nesting level ~s is not a positive integer"
n))
(if (and (let f ([ip (console-input-port)])
(or (eq? ip #%$console-input-port)
(and ($xscript-port? ip)
(let-values ([(ip op xp) ($constituent-ports ip)])
(f ip)))))
(let f ([op (console-output-port)])
(or (eq? op #%$console-output-port)
(and ($xscript-port? op)
(let-values ([(ip op xp) ($constituent-ports op)])
(f op))))))
(begin
; fresh-line doesn't take into account output written to the console
; through some other port or external means, so this might not emit a
; fresh line when one is needed, but the user can always redisplay
(fresh-line (console-output-port))
(flush-output-port (console-output-port))
(eestate-prompt-set! ee
(let ([wps (waiter-prompt-string)])
(if (string=? wps "")
""
(string-append
(apply string-append (make-list n wps))
" "))))
(ee-read ee))
(default-prompt-and-read n)))
;;; history functions
(module (history-search-bwd history-search-fwd
update-history! history-fast-forward! entry-modified?
ee-save-history ee-load-history)
(define search
(lambda (ee pred? get-bwd set-bwd! get-fwd set-fwd!)
(let loop ([bwd (get-bwd ee)]
[now (eestate-histnow ee)]
[fwd (get-fwd ee)])
(and (not (null? bwd))
(let ([s (car bwd)])
(if (pred? s)
(begin
(set-bwd! ee (cdr bwd))
(eestate-histnow-set! ee s)
(set-fwd! ee (cons now fwd))
s)
(loop (cdr bwd) s (cons now fwd))))))))
(define history-search-bwd
(lambda (ee pred?)
(search ee pred? eestate-histbwd eestate-histbwd-set!
eestate-histfwd eestate-histfwd-set!)))
(define history-search-fwd
(lambda (ee pred?)
(search ee pred? eestate-histfwd eestate-histfwd-set!
eestate-histbwd eestate-histbwd-set!)))
(define history->list
(lambda (ee)
(cdr `(,@(reverse (eestate-histfwd ee))
,(eestate-histnow ee)
,@(eestate-histbwd ee)))))
(define trim-history
(lambda (ls)
(let ([n (ee-history-limit)])
(if (> (length ls) n)
(list-head ls n)
ls))))
(define update-history!
(lambda (ee entry)
(define (all-whitespace? s)
(let ([n (string-length s)])
(let f ([i 0])
(or (fx= i n)
(and (memv (string-ref s i) '(#\space #\newline))
(f (fx+ i 1)))))))
(let ([s (entry->string entry)] [ls (history->list ee)])
(eestate-histbwd-set! ee
(if (or (all-whitespace? s)
(and (not (null? ls))
(equal? s (car ls))))
ls
(begin
(eestate-histnew-set! ee (fx+ (eestate-histnew ee) 1))
(trim-history (cons s ls))))))
(eestate-histnow-set! ee "")
(eestate-histfwd-set! ee '())))
(define history-fast-forward!
(lambda (ee)
(eestate-histbwd-set! ee (history->list ee))
(eestate-histnow-set! ee "")
(eestate-histfwd-set! ee '())))
(define (entry-modified? ee entry)
(not (string=? (entry->string entry) (eestate-histnow ee))))
(module (ee-save-history ee-load-history)
(define read-history
(lambda (ip)
(on-error #f
(let loop ([ls '()])
(let ([x (read ip)])
(if (eof-object? x)
ls
(begin
(unless (string? x) ($oops #f "oops"))
(loop (cons x ls)))))))))
(define ee-save-history
(lambda (ee filename)
(unless (string? filename)
($oops 'ee-save "~s is not a string" filename))
(let* ([iop ($open-file-input/output-port 'expeditor filename
(file-options exclusive no-fail no-truncate)
(buffer-mode block)
(make-transcoder (utf-8-codec)))]
[ls (let ([curls (history->list ee)])
(cond
[(read-history iop) =>
(lambda (savls)
(trim-history
(append
(list-head curls (eestate-histnew ee))
savls)))]
[else curls]))])
(truncate-file iop)
(fprintf iop "~
;;; This file contains a saved history for the (Petite) Chez Scheme~@
;;; expression editor. The history is represented as a sequence of~@
;;; strings, each representing a history entry, with the most recent~@
;;; entries listed last.~@
~@
;;; Exit each Scheme session running the expression editor before~@
;;; saving changes so they aren't wiped out when the session ends.\n\n")
(for-each (lambda (s) (fprintf iop "~s\n" s)) (reverse ls))
(close-port iop))))
(define ee-load-history
(lambda (ee filename)
(unless (string? filename)
($oops 'ee-load-history "~s is not a string" filename))
(let* ([iop ($open-file-input/output-port 'expeditor filename
(file-options exclusive no-fail no-truncate)
(buffer-mode block)
(make-transcoder (utf-8-codec)))]
[ls (read-history iop)])
(close-port iop)
(unless ls
($oops 'ee-load-history "missing or malformed history file ~s"
filename))
(eestate-histnew-set! ee 0)
(eestate-histbwd-set! ee ls)
(eestate-histnow-set! ee "")
(eestate-histfwd-set! ee '())))))
)
;;; editing functions
(module (ee-next-id-completion ee-next-id-completion/indent)
(define complete
(lambda (ee entry suffix*)
(eestate-last-suffix*-set! ee suffix*)
(if (null? suffix*)
(beep "id-completion: no completion found")
(insert-string-before ee entry (car suffix*)))))
(define next-completion
(lambda (ee entry)
(if (fx<= (length (eestate-last-suffix* ee)) 1)
(beep "id-completion: no completion found")
(let ([suffix (car (eestate-last-suffix* ee))])
(let ([n (string-length suffix)])
(move-left ee entry n)
(delete-forward ee entry (entry-row entry) (fx+ (entry-col entry) n)))
(complete ee entry
(append (cdr (eestate-last-suffix* ee)) (list suffix)))))))
(define ee-next-id-completion
(lambda (ee entry c)
(if (eq? (eestate-last-op ee) ee-next-id-completion)
(next-completion ee entry)
(let-values ([(prefix suffix*) (id-completions ee entry)])
(if prefix
(complete ee entry suffix*)
(begin
(eestate-last-suffix*-set! ee '())
(beep "id-completion: no identifier to complete")))))
entry))
(define ee-next-id-completion/indent
(lambda (ee entry c)
(cond
[(and (eq? (eestate-last-op ee) ee-next-id-completion/indent)
(eestate-cc? ee))
(next-completion ee entry)
entry]
[(and (or (eq? (eestate-last-op ee) ee-insert-self)
(eq? (eestate-last-op ee) ee-next-id-completion/indent))
(let-values ([(prefix suffix*) (id-completions ee entry)])
(and prefix suffix*))) =>
(lambda (suffix*)
(eestate-cc?-set! ee #t)
(complete ee entry suffix*)
entry)]
[else
(eestate-cc?-set! ee #f)
(eestate-last-suffix*-set! ee '())
(ee-indent ee entry c)])))
)
(module (ee-id-completion ee-id-completion/indent)
(define (display-completions prefix suffix*)
(let* ([s* (map (lambda (suffix) (string-append prefix suffix)) suffix*)]
[width (fx+ (apply fxmax (map string-length s*)) 2)]
[tcols (fxmax 1 (fxquotient (screen-cols) width))]
[trows (fxquotient (length s*) tcols)]
[nlong (fxremainder (length s*) tcols)])
(define (display-row v last)
(let loop ([j 0])
(let ([s (vector-ref v j)])
(if (fx= j last)
(ee-display-string s)
(begin
(ee-display-string (format "~va" width s))
(loop (fx+ j 1))))))
(carriage-return)
(line-feed))
(let ([v (make-vector (if (fx= nlong 0) trows (fx+ trows 1)))])
(do ([i 0 (fx+ i 1)])
((fx= i (vector-length v)))
(vector-set! v i (make-vector tcols #f)))
(let f ([s* s*] [i 0] [j 0] [nlong nlong])
(unless (null? s*)
(if (fx= i (if (fx> nlong 0) (fx+ trows 1) trows))
(f s* 0 (fx+ j 1) (fx- nlong 1))
(begin
(vector-set! (vector-ref v i) j (car s*))
(f (cdr s*) (fx+ i 1) j nlong)))))
(do ([i 0 (fx+ i 1)])
((fx= i trows))
(display-row (vector-ref v i) (fx- tcols 1)))
(unless (fx= nlong 0)
(display-row (vector-ref v trows) (fx- nlong 1)))
(if (fx= nlong 0) trows (fx+ trows 1)))))
(define (common-prefix s*)
(let outer ([s1 (car s*)] [s* (cdr s*)])
(if (null? s*)
s1
(let ([s2 (car s*)])
(let ([n1 (string-length s1)] [n2 (string-length s2)])
(let inner ([i 0])
(if (or (fx= i n1)
(fx= i n2)
(not (char=? (string-ref s1 i) (string-ref s2 i))))
(outer (substring s1 0 i) (cdr s*))
(inner (fx+ i 1)))))))))
(define ee-id-completion
(lambda (ee entry c)
(let-values ([(prefix suffix*) (id-completions ee entry)])
(if prefix
(if (not (null? suffix*))
(if (eq? (eestate-last-op ee) ee-id-completion)
(begin
(clear-entry ee entry)
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed)
(let ([nrows (display-completions prefix suffix*)])
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed)
(redisplay ee entry (max (fx- (screen-rows) nrows 1) 1))))
(insert-string-before ee entry (common-prefix suffix*)))
(beep "id-completion: no completions found"))
(beep "id-completion: no identifier to complete")))
entry))
(define ee-id-completion/indent
(lambda (ee entry c)
(cond
[(and (eq? (eestate-last-op ee) ee-id-completion/indent)
(eestate-cc? ee))
(let-values ([(prefix suffix*) (id-completions ee entry)])
(if (not (null? suffix*))
(begin
(clear-entry ee entry)
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed)
(let ([nrows (display-completions prefix suffix*)])
(ee-display-string (make-string (screen-cols) #\-))
(carriage-return)
(line-feed)
(redisplay ee entry (max (fx- (screen-rows) nrows 1) 1))))
(beep "id-completion: no completions found")))
entry]
[(and (or (eq? (eestate-last-op ee) ee-insert-self)
(eq? (eestate-last-op ee) ee-id-completion/indent))
(let-values ([(prefix suffix*) (id-completions ee entry)])
(and prefix suffix*))) =>
(lambda (suffix*)
(eestate-cc?-set! ee #t)
(if (not (null? suffix*))
(insert-string-before ee entry (common-prefix suffix*))
(beep "id-completion: no completions found"))
entry)]
[else
(eestate-cc?-set! ee #f)
(ee-indent ee entry c)])))
)
(define ee-insert-self
(lambda (ee entry c)
(add-char ee entry c)
entry))
(define ee-command-repeat
(lambda (ee entry c)
(define (digit-value c) (char- c #\0))
(eestate-repeat-count-set! ee
(let ([c (ee-peek-char)])
(if (and (not (eof-object? c)) (char-numeric? c))
(let loop ([n (digit-value (ee-read-char))])
(let ([c (ee-peek-char)])
(if (and (not (eof-object? c)) (char-numeric? c))
(loop (+ (* n 10) (digit-value (ee-read-char))))
n)))
(ee-default-repeat))))
entry))
(module (ee-history-bwd ee-history-fwd
ee-history-bwd-prefix ee-history-fwd-prefix
ee-history-bwd-contains ee-history-fwd-contains)
(define contains?
(lambda (key str)
(let ([key-len (string-length key)]
[str-len (string-length str)])
(let loop ([idx 0])
(cond
[(fx> key-len (fx- str-len idx)) #f]
[(string=? key (substring str idx (fx+ idx key-len))) #t]
[else (loop (add1 idx))])))))
(define prefix?
(lambda (key str)
(let ([nkey (string-length key)] [nstr (string-length str)])
; if key doesn't start with space, skip leading spaces in str
(let ([i (if (or (fx= nkey 0) (char=? (string-ref key 0) #\space))
0
(let f ([i 0])
(if (or (fx= i nstr) (not (char=? (string-ref str i) #\space)))
i
(f (fx+ i 1)))))])
(let ([n (fx+ nkey i)])
(and (fx<= n nstr)
(string=? key (substring str i n))))))))
(define new-entry
(lambda (ee entry s)
(clear-entry ee entry)
(let ([entry (string->entry ee s)])
(redisplay ee entry 1)
(move-eol ee entry)
entry)))
(define ee-history-bwd
(lambda (ee entry c)
(cond
[(and (not (null-entry? entry)) (entry-modified? ee entry))
(beep "cannot leave nonempty modified entry")
entry]
[(history-search-bwd ee (lambda (s) #t)) =>
(lambda (s)
; clear histkey when null as favor to search commands
(when (null-entry? entry) (eestate-histkey-set! ee ""))
(new-entry ee entry s))]
[else
(beep "invalid history movement")
entry])))
(define ee-history-fwd
(lambda (ee entry c)
(cond
[(and (not (null-entry? entry)) (entry-modified? ee entry))
(beep "cannot leave nonempty modified entry")
entry]
[(history-search-fwd ee (lambda (s) #t)) =>
(lambda (s)
; clear histkey when null as favor to search commands
(when (null-entry? entry) (eestate-histkey-set! ee ""))
(new-entry ee entry s))]
[else
(beep "invalid history movement")
entry])))
(define history-search-bwd-key
(lambda (ee entry match?)
(if (or (entry-modified? ee entry) (null-entry? entry))
(begin
(history-fast-forward! ee)
(eestate-histkey-set! ee (entry->string entry))
(cond
[(history-search-bwd ee
(lambda (s) (match? (eestate-histkey ee) s))) =>
(lambda (s) (new-entry ee entry s))]
[else
(beep "invalid history movement")
entry]))
; if nonempty and unmodified, we must already have moved via one
; of the history commands, so eestate-histkey should be valid
(cond
[(history-search-bwd ee
(lambda (s) (match? (eestate-histkey ee) s))) =>
(lambda (s) (new-entry ee entry s))]
[else
(beep "invalid history movement")
entry]))))
(define history-search-fwd-key
; similar to history-search-bwd-key but "finds" key at forward extreme
(lambda (ee entry match?)
(if (or (entry-modified? ee entry) (null-entry? entry))
(begin
(history-fast-forward! ee)
(eestate-histkey-set! ee (entry->string entry))
(cond
[(history-search-fwd ee
(lambda (s) (prefix? (eestate-histkey ee) s))) =>
(lambda (s) (new-entry ee entry s))]
[else
(beep "invalid history movement")
entry]))
; if nonempty and unmodified, we must already have moved via one
; of the history commands, so eestate-histkey should be valid
(cond
[(history-search-fwd ee
(lambda (s) (match? (eestate-histkey ee) s))) =>
(lambda (s) (new-entry ee entry s))]
[else
(let ([entry (new-entry ee entry (eestate-histkey ee))])
(history-fast-forward! ee)
entry)]))))
(define ee-history-fwd-prefix
(lambda (ee entry c)
(history-search-fwd-key ee entry prefix?)))
(define ee-history-bwd-prefix
(lambda (ee entry c)
(history-search-bwd-key ee entry prefix?)))
(define ee-history-fwd-contains
(lambda (ee entry c)
(history-search-fwd-key ee entry contains?)))
(define ee-history-bwd-contains
(lambda (ee entry c)
(history-search-bwd-key ee entry contains?)))
)
(define ee-newline/accept
(lambda (ee entry c)
(cond
[(null-entry? entry) entry]
; #f tells ee-read to return expr
[(and (find-next-sexp-forward ee entry 0 0 #t)
(only-whitespace-left? ee entry))
(let loop ()
(delete-to-eol ee entry)
(unless (last-line? ee entry)
(join-rows ee entry)
(loop)))
#f]
[else
(insert-strings-before ee entry '("" ""))
(when (should-auto-indent? ee) (indent ee entry))
entry])))
(define ee-newline
(lambda (ee entry c)
(cond
[(null-entry? entry) entry]
[else
(insert-strings-before ee entry '("" ""))
(when (should-auto-indent? ee) (indent ee entry))
entry])))
(define ee-accept
(lambda (ee entry c)
; force ee-read to attempt read even if not at end of expr and not balanced
(on-error #f
(let ([sip (open-input-string (entry->string entry))])
(let loop ()
(let-values ([(type value start end) (read-token sip)])
(cond
[(eq? type 'eof)
; entry contains only whitespace and comments. pretend to accept
; but don't really, or ee-read will return eof, causing cafe to exit
(update-history! ee entry)
(move-eoe ee entry)
(no-raw-mode)
(ee-write-char #\newline)
(ee-flush)
(raw-mode)
(let ([entry (string->entry ee "")])
(redisplay ee entry)
entry)]
[(and (eq? type 'quote) (eq? value 'datum-comment))
(read sip)
(loop)]
[else #f])))))))
(define ee-open-line
(lambda (ee entry c)
(let ([point (entry-point entry)])
(insert-strings-before ee entry '("" ""))
(when (should-auto-indent? ee) (indent ee entry))
(goto ee entry point)
entry)))
(define ee-indent
(lambda (ee entry c)
(indent ee entry)
entry))
(define ee-indent-all
(lambda (ee entry c)
(indent-all ee entry)
entry))
(define ee-backward-char
(lambda (ee entry c)
(if (beginning-of-line? ee entry)
(unless (first-line? ee entry)
(move-up ee entry)
(move-eol ee entry))
(move-left ee entry))
entry))
(define ee-forward-char
(lambda (ee entry c)
(if (end-of-line? ee entry)
(unless (last-line? ee entry)
(move-down ee entry)
(move-bol ee entry))
(move-right ee entry))
entry))
(define ee-next-line
(lambda (ee entry c)
(if (last-line? ee entry)
(ee-history-fwd ee entry c)
(begin
(move-down ee entry)
entry))))
(define ee-previous-line
(lambda (ee entry c)
(if (first-line? ee entry)
(ee-history-bwd ee entry c)
(begin
(move-up ee entry)
entry))))
(define ee-end-of-line
(lambda (ee entry c)
(move-eol ee entry)
entry))
(define ee-beginning-of-line
(lambda (ee entry c)
(move-bol ee entry)
entry))
(define ee-beginning-of-entry
(lambda (ee entry c)
(goto ee entry (make-pos 0 0))
entry))
(define ee-end-of-entry
(lambda (ee entry c)
(move-eoe ee entry)
entry))
(define ee-delete-to-eol
(lambda (ee entry c)
(if (end-of-line? ee entry)
(unless (last-line? ee entry)
(join-rows ee entry)
(eestate-killbuf-set! ee
(if (eq? (eestate-last-op ee) ee-delete-to-eol)
(append (eestate-killbuf ee) '(""))
'(""))))
(eestate-killbuf-set! ee
(let ([killbuf (delete-to-eol ee entry)])
(if (eq? (eestate-last-op ee) ee-delete-to-eol)
; last addition must have been ("") representing newline
(append (reverse (cdr (reverse (eestate-killbuf ee))))
killbuf)
killbuf))))
entry))
(define ee-delete-line
(lambda (ee entry c)
(if (and (first-line? ee entry)
(not (last-line? ee entry))
(last-line-displayed? ee entry))
(ee-delete-entry ee entry c)
(begin
(move-bol ee entry)
(let ([killbuf (delete-to-eol ee entry)])
(unless (equal? killbuf '(""))
(eestate-killbuf-set! ee killbuf)))
entry))))
(define ee-delete-between-point-and-mark
(lambda (ee entry c)
(let ([point (entry-point entry)] [mark (entry-mark entry)])
(if mark
(unless (pos=? mark point)
(eestate-killbuf-set! ee
(if (pos<? mark (entry-point entry))
(begin
(goto ee entry mark)
(delete-forward ee entry (pos-row point) (pos-col point)))
(delete-forward ee entry (pos-row mark) (pos-col mark)))))
(beep "mark not set")))
entry))
(define ee-set-mark
(lambda (ee entry c)
(entry-mark-set! entry (entry-point entry))
entry))
(define ee-delete-entry
(lambda (ee entry c)
(unless (null-entry? entry)
(eestate-killbuf-set! ee (yank-entry ee entry)))
(clear-entry ee entry)
(let ([entry (string->entry ee "")])
(redisplay ee entry)
entry)))
(define ee-reset-entry
(lambda (ee entry c)
(history-fast-forward! ee)
(ee-delete-entry ee entry c)))
(define ee-delete-sexp
(lambda (ee entry c)
(let ([pos (find-next-sexp-forward ee entry
(entry-row entry) (entry-col entry) #f)])
(if pos
(eestate-killbuf-set! ee
(delete-forward ee entry (pos-row pos) (pos-col pos)))
(beep "end of s-expression not found")))
entry))
(define ee-backward-delete-sexp
(lambda (ee entry c)
(let ([row (entry-row entry)] [col (entry-col entry)])
(let ([pos (find-next-sexp-backward ee entry row col)])
(if pos
(begin
(goto ee entry pos)
(eestate-killbuf-set! ee (delete-forward ee entry row col)))
(beep "start of s-expression not found"))))
entry))
(define ee-redisplay
(lambda (ee entry c)
(if (eq? (eestate-last-op ee) ee-redisplay)
(clear-screen)
(clear-entry ee entry))
(redisplay ee entry)
entry))
(define ee-yank-kill-buffer
(lambda (ee entry c)
(insert-strings-before ee entry (eestate-killbuf ee))
entry))
(define ee-yank-selection
(lambda (ee entry c)
(insert-strings-before ee entry
(string->lines
(let* ([s (get-clipboard)]
[n (fx- (string-length s) 1)])
(if (and (fx>= n 0) (char=? (string-ref s n) #\newline))
(substring s 0 n)
s))))
entry))
(define ee-string-macro
(lambda (str)
(lambda (ee entry c)
(insert-string-before ee entry str)
entry)))
(define ee-eof
(lambda (ee entry c)
(cond
[(null-entry? entry) #f]
[else (beep "eof ignored except in null entry")])))
(define ee-delete-char
(lambda (ee entry c)
(cond
[(end-of-line? ee entry)
(unless (last-line? ee entry) (join-rows ee entry))
entry]
[else (delete-char ee entry) entry])))
(define ee-eof/delete-char
(lambda (ee entry c)
(cond
[(null-entry? entry)
(if (eq? (eestate-last-op ee) ee-eof/delete-char)
entry ; assume attempt to continue deleting chars
#f)]
[(end-of-line? ee entry)
(unless (last-line? ee entry) (join-rows ee entry))
entry]
[else (delete-char ee entry) entry])))
(define ee-backward-delete-char
(lambda (ee entry c)
(if (beginning-of-line? ee entry)
(unless (first-line? ee entry)
(move-up ee entry)
(move-eol ee entry)
(join-rows ee entry))
(begin
(move-left ee entry)
(delete-char ee entry)))
entry))
(define ee-insert-paren
(lambda (ee entry c)
(add-char ee entry c)
(when (or (ee-flash-parens) (ee-auto-paren-balance))
(correct&flash-matching-delimiter ee entry))
entry))
(define ee-goto-matching-delimiter
(lambda (ee entry c)
(let ([pos (find-matching-delimiter ee entry)])
(if pos
(goto ee entry pos)
(beep "matching delimiter not found")))
entry))
(define ee-flash-matching-delimiter
(lambda (ee entry c)
(let ([pos (find-matching-delimiter ee entry)])
(if pos
(flash ee entry pos)
(beep "matching delimiter not found")))
entry))
(define ee-exchange-point-and-mark
(lambda (ee entry c)
(let ([mark (entry-mark entry)])
(if mark
(begin
(entry-mark-set! entry (entry-point entry))
(goto ee entry mark))
(beep "mark not set")))
entry))
(define ee-forward-sexp
(lambda (ee entry c)
(let ([pos (find-next-sexp-forward ee entry
(entry-row entry) (entry-col entry) #f)])
(if pos
(goto ee entry pos)
(beep "end of s-expression not found")))
entry))
(define ee-backward-sexp
(lambda (ee entry c)
(let ([pos (find-next-sexp-backward ee entry
(entry-row entry) (entry-col entry))])
(if pos
(goto ee entry pos)
(beep "start of s-expression not found")))
entry))
(define ee-forward-word
(lambda (ee entry c)
(goto ee entry
(find-next-word ee entry
(entry-row entry)
(entry-col entry)))
entry))
(define ee-backward-word
(lambda (ee entry c)
(goto ee entry
(find-previous-word ee entry
(entry-row entry)
(entry-col entry)))
entry))
(define ee-forward-page
(lambda (ee entry c)
(page-down ee entry)
entry))
(define ee-backward-page
(lambda (ee entry c)
(page-up ee entry)
entry))
(define ee-suspend-process
(lambda (ee entry c)
(carriage-return)
(line-feed)
(clear-eos)
(ee-flush)
(no-raw-mode)
(pause)
(raw-mode)
(carriage-return)
(clear-eos)
(redisplay ee entry)
entry))
(define (ee-compose . p*)
(rec ee-composition
(lambda (ee entry c)
(let f ([p* p*] [entry entry])
(if (null? p*)
entry
(let ([entry ((car p*) ee entry c)])
(and entry (f (cdr p*) entry))))))))
;;; key bindings
;;; (ee-bind-key key ee-xxx)
;;; key must evaluate to a <key>, where:
;;;
;;; <key> = <char> | <key-string>
;;;
;;; <key-string> -> "<key-char>+"
;;; <key-char> ->
;;; \e escape character
;;; ^x control is applied to character x
;;; \\ backslash
;;; \^ caret
;;; <plain-char> any character other than \ or ^
;;;
;;; <char> examples:
;;;
;;; input key description byte sequence
;;; --------- ----------- -------------
;;; #\a letter 'a' 97
;;; #\^ caret 94
;;;
;;; <key-string> examples:
;;;
;;; input key contents description byte sequence
;;; --------- -------- ----------- -------------
;;; "\\ex" \ex Esc-x 27 120
;;; "^a" ^a Ctrl-A 1
;;; "\\\\" \\ backslash 92
;;; "\\^" \^ caret 94
;;; "a" a letter 'a' 97
(module (dispatch-table? base-dispatch-table ee-bind-key)
(define make-dispatch-table
(lambda ()
(make-eqv-hashtable 256)))
(define dispatch-table? hashtable?)
(define ee-bind-key
(lambda (key proc)
(unless (or (char? key)
(and (string? key) (fx> (string-length key) 0)))
($oops 'ee-bind-key "~s is not a valid key (character or nonempty string)" key))
(unless (procedure? proc)
($oops 'ee-bind-key "~s is not a procedure" proc))
(if (string? key)
(let* ([n (string-length key)])
(define (s0 table i)
(let ([c (string-ref key i)])
(case c
[(#\\) (s-backslash table (fx+ i 1))]
[(#\^) (s-caret table (fx+ i 1))]
[else (s-lookup table (fx+ i 1) c)])))
(define (s-backslash table i)
(when (fx= i n)
($oops 'ee-bind-key
"malformed key ~s (nothing following \\)"
key))
(let ([c (string-ref key i)])
(case c
[(#\e) (s-lookup table (fx+ i 1) #\esc)]
[(#\\ #\^) (s-lookup table (fx+ i 1) c)]
[else ($oops 'ee-bind-key
"malformed key ~s (unexpected character following \\)"
key)])))
(define (s-caret table i)
(define (^char c)
(integer->char (fxlogand (char->integer c) #b11111)))
(when (fx= i n)
($oops 'ee-bind-key
"malformed key ~s (nothing following ^)"
key))
(s-lookup table (fx+ i 1) (^char (string-ref key i))))
(define (s-lookup table i key)
(let ([x (hashtable-ref table key #f)])
(cond
[(fx= i n)
(when (dispatch-table? x)
(warningf 'ee-bind-key
"definition for key ~s disables its use as a prefix"
key))
(hashtable-set! table key proc)]
[(dispatch-table? x) (s0 x i)]
[else
(when (procedure? x)
(warningf 'ee-bind-key
"definition for key ~s disables its use as a prefix"
key))
(let ([x (make-dispatch-table)])
(hashtable-set! table key x)
(s0 x i))])))
(s0 base-dispatch-table 0))
(begin
(when (dispatch-table? (hashtable-ref base-dispatch-table key #f))
(warningf 'ee-bind-key
"definition for key ~s disables its use as a prefix"
key))
(hashtable-set! base-dispatch-table key proc)))))
(define base-dispatch-table (make-dispatch-table))
; set up self-insertion for space and all printing characters
(for-each
(lambda (c) (ee-bind-key c ee-insert-self))
(string->list " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"))
)
(let ([ebk ee-bind-key])
; newline operations
(ebk #\return ee-newline/accept) ; Enter, ^M
(ebk "^J" ee-accept) ; ^J
(ebk "^O" ee-open-line) ; ^O
; indenting operations
(ebk "\\e\t" ee-indent) ; Esc-Tab
(ebk "\\eq" ee-indent-all) ; Esc-q
(ebk "\\eQ" ee-indent-all) ; Esc-Q
(ebk "\\e^Q" ee-indent-all) ; Esc-^Q
; command completion
(ebk "\t" ee-id-completion/indent) ; Tab
(ebk "^R" ee-next-id-completion) ; ^R
; cursor movement keys
(ebk "^B" ee-backward-char) ; ^B
(ebk "\\e[D" ee-backward-char) ; Left ; ]
(ebk "^F" ee-forward-char) ; ^F
(ebk "\\e[C" ee-forward-char) ; Right ; ]
(ebk "^N" ee-next-line) ; ^N
(ebk "\\e[B" ee-next-line) ; Down
(ebk "^P" ee-previous-line) ; ^P
(ebk "\\e[A" ee-previous-line) ; Up
(ebk "\\ef" ee-forward-word) ; Esc-f
(ebk "\\eF" ee-forward-word) ; Esc-F
(ebk "\\e^F" ee-forward-sexp) ; Esc-^F
(ebk "\\eb" ee-backward-word) ; Esc-b
(ebk "\\eB" ee-backward-word) ; Esc-B
(ebk "\\e^B" ee-backward-sexp) ; Esc-^B
(ebk "^X^X" ee-exchange-point-and-mark) ; ^X^X
(ebk "^X[" ee-backward-page) ; ^X[
(ebk "^X]" ee-forward-page) ; ^X]
(ebk "\\e[5~" ee-backward-page) ; Page-Up
(ebk "\\e[6~" ee-forward-page) ; Page-Down
(ebk "^E" ee-end-of-line) ; ^E
(ebk "\\e[F" ee-end-of-line) ; End key
; terminals are supposed to default to "normal" (aka "cursor") rather than
; "application" mode and in normal mode send ANSI \\e[F and \\e[H for End
; and Home. although gnome terminal apparently starts in normal mode, it
; sends the application-mode sequences for this. we capitulate reluctantly,
; since by defining Esc-OF and Esc-OH to do End and Home we prevent people
; from binding Esc-O by itself to a command.
(ebk "\\eOF" ee-end-of-line) ; End key (gnome terminal)
(ebk "\\e[4~" ee-end-of-line) ; End key (cygwin)
(ebk "^A" ee-beginning-of-line) ; ^A
(ebk "\\e[H" ee-beginning-of-line) ; Home key
(ebk "\\eOH" ee-beginning-of-line) ; Home key (gnome terminal)
(ebk "\\e[1~" ee-beginning-of-line) ; Home key (cygwin)
(ebk "\\e<" ee-beginning-of-entry) ; Esc-<
(ebk "\\e>" ee-end-of-entry) ; Esc-> ; [[
(ebk "\\e]" ee-goto-matching-delimiter) ; Esc-]
(ebk #\( ee-insert-paren) ; (
(ebk #\) ee-insert-paren) ; )
(ebk #\[ ee-insert-paren) ; [
(ebk #\] ee-insert-paren) ; ]
(ebk "^]" ee-flash-matching-delimiter) ; ^]
; destructive functions
(ebk "^U" ee-delete-line) ; ^U
(ebk "^K" ee-delete-to-eol) ; ^K
(ebk "\\ek" ee-delete-to-eol) ; Esc-k
(ebk "^W" ee-delete-between-point-and-mark) ; ^W
(ebk "^G" ee-delete-entry) ; ^G
(ebk "^C" ee-reset-entry) ; ^C
(ebk "\\e^K" ee-delete-sexp) ; Esc-^K
(ebk "\\e\\e[3~" ee-delete-sexp) ; Esc-Delete
(ebk "\\e\177" ee-backward-delete-sexp) ; Esc-Backspace
(ebk "\\e^H" ee-backward-delete-sexp) ; Esc-^H
(ebk "^V" ee-yank-selection) ; ^V
(ebk "^Y" ee-yank-kill-buffer) ; ^Y
(ebk "^D" ee-eof/delete-char) ; ^D
(ebk #\rubout ee-backward-delete-char) ; Backspace (<--)
(ebk "\\e[3~" ee-delete-char) ; Delete
(ebk "^H" ee-backward-delete-char) ; ^H
(ebk "^@" ee-set-mark) ; ^@ (or ^Space)
(ebk "^^" ee-set-mark) ; ^^
; display functions
(ebk "^L" ee-redisplay) ; ^L
; string macros
(ebk "\\ed" (ee-string-macro "(define ")) ; Esc-d ; )
(ebk "\\el" (ee-string-macro "(lambda ")) ; Esc-l ; )
; history keys
(ebk "\\e^P" ee-history-bwd) ; Esc-^P
(ebk "\\e\\e[A" ee-history-bwd) ; Esc-Up
(ebk "\\e^N" ee-history-fwd) ; Esc-^N
(ebk "\\e\\e[B" ee-history-fwd) ; Esc-Down
(ebk "\\ep" ee-history-bwd-prefix) ; Esc-p
(ebk "\\eP" ee-history-bwd-contains) ; Esc-P
(ebk "\\en" ee-history-fwd-prefix) ; Esc-n
(ebk "\\eN" ee-history-fwd-contains) ; Esc-N
; misc
(ebk "\\e^U" ee-command-repeat) ; Esc-^U
(ebk "^Z" ee-suspend-process) ; ^Z
)
(set! $expeditor
(lambda (thunk)
(let ([ee #f])
(define (expeditor-prompt-and-read n)
(if (cond
[(eestate? ee) #t]
[(eq? ee 'failed) #f]
[(init-screen)
(set! ee (make-eestate))
(let ([histfile ($expeditor-history-file)])
(when histfile
(on-error (void) (ee-load-history ee histfile))))
#t]
[else (set! ee 'failed) #f])
(ee-prompt-and-read ee n)
(default-prompt-and-read n)))
(let-values ([val* (parameterize ([waiter-prompt-and-read expeditor-prompt-and-read])
(thunk))])
(when (eestate? ee)
(let ([histfile ($expeditor-history-file)])
(when histfile
(on-error (void) (ee-save-history ee histfile)))))
(apply values val*)))))
)
) ; when-feature expeditor