racket/collects/framework/private/keymap.rkt
Robby Findler 6a69da76e2 adjust ascii-box => unicode-box algorithm so that
it only looks left and right at hyphens and only
up and down at pipes, etc. This better handles the
case where you have something like this:

  +--------------+
  | (<= a-x b-y) |
  +--------------+

Before this commit, it would have adjusted the hypens
inside the identifiers
2012-12-24 13:49:32 -06:00

1617 lines
68 KiB
Racket

#lang racket/base
(require string-constants
racket/class
racket/match
racket/list
mred/mred-sig
"../preferences.rkt"
mrlib/tex-table
(only-in srfi/13 string-prefix? string-prefix-length)
"sig.rkt"
racket/unit)
(provide keymap@)
(define-unit keymap@
(import mred^
[prefix finder: framework:finder^]
[prefix handler: framework:handler^]
[prefix frame: framework:frame^]
[prefix editor: framework:editor^]
[prefix text: framework:text^])
(export (rename framework:keymap^
[-get-file get-file]))
(init-depend mred^)
;; if I put this in main.rkt with the others, it doesn't happen
;; early enough... ? JBC, 2011-07-12
(preferences:set-default 'framework:automatic-parens #f boolean?)
(define user-keybindings-files (make-hash))
(define (add-user-keybindings-file spec)
(hash-ref
user-keybindings-files
spec
(λ ()
(let* ([path (spec->path spec)]
[sexp (and (file-exists? path)
(parameterize ([read-accept-reader #t])
(call-with-input-file path read)))])
(match sexp
[`(module ,name ,lang ,x ...)
(cond
[(valid-keybindings-lang? lang)
(let ([km (dynamic-require spec '#%keymap)])
(hash-set! user-keybindings-files spec km)
(send user-keymap chain-to-keymap km #t))]
[else
(error 'add-user-keybindings-file
(string-constant user-defined-keybinding-malformed-file/found-lang)
(path->string path)
lang)])]
[else (error 'add-user-keybindings-file
(string-constant user-defined-keybinding-malformed-file)
(path->string path))])))))
(define (valid-keybindings-lang? x)
(member x
(list `(lib "keybinding-lang.ss" "framework")
`(lib "keybinding-lang.rkt" "framework")
`(lib "framework/keybinding-lang.ss")
`(lib "framework/keybinding-lang.rkt")
`framework/keybinding-lang)))
(define (spec->path p)
(cond
[(path? p) p]
[else
(let* ([mod-name ((current-module-name-resolver) p #f #f)]
[str (symbol->string mod-name)]
[pth (substring str 1 (string-length str))])
(let-values ([(base name _) (split-path pth)])
(let ([filenames
(sort
(filter (λ (x) (substring? (path->string name) x))
(map path->string (directory-list base)))
(λ (x y) (> (string-length x) (string-length y))))])
(when (null? filenames)
(error 'spec->path "could not convert ~s, found no filenames for ~s" p mod-name))
(build-path base (car filenames)))))]))
(define (substring? s1 s2)
(and (<= (string-length s1)
(string-length s2))
(string=? s1 (substring s2 0 (string-length s1)))))
(define (remove-user-keybindings-file spec)
(let/ec k
(let ([km (hash-ref user-keybindings-files spec (λ () (k (void))))])
(send global remove-chained-keymap km)
(hash-remove! user-keybindings-files spec))))
(define (remove-chained-keymap ed keymap-to-remove)
(let ([ed-keymap (send ed get-keymap)])
(when (eq? keymap-to-remove ed-keymap)
(error 'keymap:remove-keymap "cannot remove initial keymap from editor"))
(let p-loop ([parent-keymap ed-keymap])
(unless (is-a? parent-keymap aug-keymap<%>)
(error 'keymap:remove-keymap
"found a keymap that is not a keymap:aug-keymap<%> ~e"
parent-keymap))
(let c-loop ([child-keymaps (send parent-keymap get-chained-keymaps)])
(cond
[(null? child-keymaps)
(void)]
[else
(let ([child-keymap (car child-keymaps)])
(cond
[(eq? child-keymap keymap-to-remove)
(send parent-keymap remove-chained-keymap child-keymap)
(c-loop (cdr child-keymaps))]
[else
(p-loop child-keymap)
(c-loop (cdr child-keymaps))]))])))))
(define (set-chained-keymaps parent-keymap children-keymaps)
(for-each (λ (orig-sub) (send parent-keymap remove-chained-keymap))
(send parent-keymap get-chained-keymaps))
(for-each (λ (new-sub) (send parent-keymap chain-to-keymap new-sub #f))
children-keymaps))
(define aug-keymap<%> (interface ((class->interface keymap%))
get-chained-keymaps
get-map-function-table
get-map-function-table/ht))
(define aug-keymap-mixin
(mixin ((class->interface keymap%)) (aug-keymap<%>)
(define chained-keymaps null)
(define/public (get-chained-keymaps) chained-keymaps)
(define/override (chain-to-keymap keymap prefix?)
(super chain-to-keymap keymap prefix?)
(set! chained-keymaps
(if prefix?
(cons keymap chained-keymaps)
(append chained-keymaps (list keymap)))))
(define/override (remove-chained-keymap keymap)
(super remove-chained-keymap keymap)
(set! chained-keymaps (remq keymap chained-keymaps)))
(define function-table (make-hasheq))
(define/public (get-function-table) function-table)
(define/override (map-function keyname fname)
(super map-function (canonicalize-keybinding-string keyname) fname)
(hash-set! function-table (string->symbol keyname) fname))
(define/public (get-map-function-table)
(get-map-function-table/ht (make-hasheq)))
(define/public (get-map-function-table/ht table)
(for ([(keyname fname) (in-hash function-table)])
(define cs (canonicalize-keybinding-string (format "~a" keyname)))
(define key (string->symbol cs))
(unless (hash-ref table key #f)
(when (on-this-platform? cs)
(hash-set! table key fname))))
(for ([chained-keymap (in-list chained-keymaps)])
(when (is-a? chained-keymap aug-keymap<%>)
(send chained-keymap get-map-function-table/ht table)))
table)
(define/private (on-this-platform? cs)
(let* ([splits (map (λ (x) (all-but-last (split-out #\: x))) (split-out #\; (string->list cs)))]
[has-key? (λ (k) (ormap (λ (x) (member (list k) x)) splits))])
(cond
[(eq? (system-type) 'windows)
(cond
[(or (regexp-match #rx"a:c" cs)
(regexp-match #rx"c:m" cs))
#f]
[(or (has-key? #\a) (has-key? #\d))
#f]
[else #t])]
[(eq? (system-type) 'macosx)
(cond
[(has-key? #\m)
#f]
[else #t])]
[(eq? (system-type) 'unix)
(cond
[(or (has-key? #\a) (has-key? #\d))
#f]
[else #t])]
[else
;; just in case new platforms come along ....
#t])))
(define/private (all-but-last l)
(cond
[(null? l) l]
[(null? (cdr l)) l]
[else (cons (car l) (all-but-last (cdr l)))]))
(super-new)))
(define aug-keymap% (aug-keymap-mixin keymap%))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; ;;;;;;;;
;;;;;;; canonicalize-keybinding-string ;;;;;;;;
;;;;;;; ;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; canonicalize-keybinding-string : string -> string
;; The result can be used with string=? to determine
;; if two key bindings refer to the same key.
;; Assumes a well-formed keystring.
(define (canonicalize-keybinding-string str)
(let* ([chars (map char-downcase (string->list str))]
[separated-keys
(map
canonicalize-single-keybinding-string
(split-out #\; chars))])
(join-strings ";" separated-keys)))
;; join-strings : string (listof string) -> string
;; concatenates strs with sep between each of them
(define (join-strings sep strs)
(if (null? strs)
""
(apply
string-append
(cons
(car strs)
(let loop ([sepd-strs (cdr strs)])
(cond
[(null? sepd-strs) null]
[else (list*
sep
(car sepd-strs)
(loop (cdr sepd-strs)))]))))))
;; canonicalize-single-keybinding-string : (listof char) -> string
(define (canonicalize-single-keybinding-string chars)
(let* ([neg? (char=? (car chars) #\:)]
[mods/key (split-out #\: (if neg? (cdr chars) chars))]
[mods
(let loop ([mods mods/key])
(cond
[(null? mods) null]
[(null? (cdr mods)) null]
[else (cons (car mods) (loop (cdr mods)))]))]
[key (apply string (car (last-pair mods/key)))]
[canon-key
(cond
[(string=? key "enter") "return"]
[(string=? key "del") "delete"]
[(string=? key "ins") "insert"]
[else key])]
[shift (if neg? #f 'd/c)]
[control (if neg? #f 'd/c)]
[alt (if neg? #f 'd/c)]
[meta (if neg? #f 'd/c)]
[command (if neg? #f 'd/c)]
[do-key
(λ (char val)
(cond
[(eq? val #t) (string char)]
[(eq? val #f) (string #\~ char)]
[(eq? val 'd/c) #f]))])
(for-each (λ (mod)
(let ([val (not (char=? (car mod) #\~))])
(case (if (char=? (car mod) #\~)
(cadr mod)
(car mod))
[(#\s) (set! shift val)]
[(#\c) (set! control val)]
[(#\a) (set! alt val)]
[(#\d) (set! command val)]
[(#\m) (set! meta val)])))
mods)
(join-strings ":"
(filter
values
(list
(do-key #\a alt)
(do-key #\c control)
(do-key #\d command)
(do-key #\m meta)
(do-key #\s shift)
canon-key)))))
;; split-out : char (listof char) -> (listof (listof char))
;; splits a list of characters at its first argument
;; if the last character is the same as the first character,
;; it is not split into an empty list, but returned.
(define (split-out split-char chars)
(let loop ([chars chars]
[this-split null]
[all-split null])
(cond
[(null? chars)
(reverse (cons (reverse this-split) all-split))]
[else (let ([char (car chars)])
(cond
[(char=? split-char char)
(if (null? (cdr chars))
(loop null
(cons char this-split)
all-split)
(loop (cdr chars)
null
(cons (reverse this-split) all-split)))]
[else
(loop (cdr chars)
(cons char this-split)
all-split)]))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;; ;;;;;;;;
;;;;;;; end canonicalize-keybinding-string ;;;;;;;;
;;;;;;; ;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-meta-prefix-list key)
(list (string-append "m:" key)
(string-append "ESC;" key)))
(define send-map-function-meta
(λ (keymap key func)
(for-each (λ (key) (send keymap map-function key func))
(make-meta-prefix-list key))))
(define add-to-right-button-menu (make-parameter void))
(define add-to-right-button-menu/before (make-parameter void))
(define setup-global
; Define some useful keyboard functions
(let* ([ring-bell
(λ (edit event)
(bell))]
[mouse-popup-menu
(λ (edit event)
(when (send event button-up?)
(let ([a (send edit get-admin)])
(when a
(let ([m (make-object popup-menu%)])
((add-to-right-button-menu/before) m edit event)
(append-editor-operation-menu-items
m #:popup-position
(list edit
(send edit find-position (send event get-x) (send event get-y))))
(for-each
(λ (i)
(when (is-a? i selectable-menu-item<%>)
(send i set-shortcut #f)))
(send m get-items))
((add-to-right-button-menu) m edit event)
(let-values ([(x y) (send edit
dc-location-to-editor-location
(send event get-x)
(send event get-y))])
(send a popup-menu m (+ x 1) (+ y 1))))))))]
[toggle-anchor
(λ (edit event)
(send edit set-anchor
(not (send edit get-anchor))))]
[center-view-on-line
(λ (edit event)
(let ([new-mid-line (send edit position-line
(send edit get-start-position))]
[bt (box 0)]
[bb (box 0)])
(send edit get-visible-line-range bt bb #f)
(let* ([half (sub1 (quotient (- (unbox bb) (unbox bt)) 2))]
[last-pos (send edit position-line (send edit last-position))]
[top-pos (send edit line-start-position
(max (min (- new-mid-line half) last-pos) 0))]
[bottom-pos (send edit line-start-position
(max 0
(min (+ new-mid-line half)
last-pos)))])
(send edit scroll-to-position
top-pos
#f
bottom-pos)))
#t)]
[collapse-variable-space
;; As per emacs: collapse tabs & spaces around the point,
;; perhaps leaving a single space.
;; drscheme bonus: if at end-of-line, collapse into the next line.
(λ (leave-one? edit event)
(letrec ([last-pos (send edit last-position)]
[sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)]
[collapsible? (λ (c) (and (char-whitespace? c)
(not (char=? #\newline c))))]
[find-noncollapsible
; Return index of next non-collapsible char,
; starting at pos in direction dir.
; NB returns -1 or last-pos, if examining
; initial/final whitespace
; (or, when initial pos is outside of [0,last-pos).)
(λ (pos dir)
(let loop ([pos pos])
(cond [(< pos 0) -1]
[(>= pos last-pos) last-pos]
[(collapsible? (send edit get-character pos))
(loop (+ pos dir))]
[else pos])))])
(when (= sel-start sel-end) ; Only when no selection:
(let* ([start (add1 (find-noncollapsible (sub1 sel-start) -1))]
[end-heeding-eol (find-noncollapsible sel-start +1)]
; This is the end of the range, were we to always heed newlines.
; Special case: if we're sitting at EOL,
; and we're not affecting much else,
; then delete that EOL and collapse spaces
; at the start of next line, too:
[end (if (and (<= (- end-heeding-eol start)
(if leave-one? 1 0))
(char=? #\newline (send edit get-character end-heeding-eol))
; If you wish to avoid deleting an newline at EOF, do so here.
)
(find-noncollapsible (add1 end-heeding-eol) +1)
end-heeding-eol)]
[making-no-difference?
; Don't introduce edits into undo-chain, if no effect.
(if leave-one?
(and (= (- end start) 1)
(char=? #\space (send edit get-character start)))
(= (- end start) 0))])
(unless making-no-difference?
(send edit begin-edit-sequence)
(send edit set-position end) ; Even after delete, caret will be at "end".
(send edit delete start end)
(when leave-one? (send edit insert #\space start))
(send edit end-edit-sequence))))))]
[collapse-space
(λ (edit event)
(collapse-variable-space #t edit event))]
[remove-space
(λ (edit event)
(collapse-variable-space #f edit event))]
[collapse-newline
(λ (edit event)
(letrec ([find-nonwhite
(λ (pos d offset)
(let/ec escape
(let ([max (if (> offset 0)
(send edit last-position)
0)])
(let loop ([pos pos])
(if (= pos max)
(escape pos)
(let ([c (send edit get-character (+ pos offset))])
(cond
[(char=? #\newline c)
(loop (+ pos d))
(escape pos)]
[(char-whitespace? c)
(loop (+ pos d))]
[else pos])))))))])
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(when (= sel-start sel-end)
(let* ([pos-line (send edit position-line sel-start #f)]
[pos-line-start (send edit line-start-position pos-line)]
[pos-line-end (send edit line-end-position pos-line)]
[whiteline?
(let loop ([pos pos-line-start])
(if (>= pos pos-line-end)
#t
(and (char-whitespace? (send edit get-character pos))
(loop (add1 pos)))))]
[start (find-nonwhite pos-line-start -1 -1)]
[end (find-nonwhite pos-line-end 1 0)]
[start-line
(send edit position-line start #f)]
[start-line-start
(send edit line-start-position start-line)]
[end-line
(send edit position-line end #f)]
[end-line-start
(send edit line-start-position (add1 end-line))])
(cond
[(and whiteline?
(= start-line pos-line)
(= end-line pos-line))
; Special case: just delete this line
(send edit delete pos-line-start (add1 pos-line-end))]
[(and whiteline? (< start-line pos-line))
; Can delete before & after
(send* edit
(begin-edit-sequence)
(delete (add1 pos-line-end) end-line-start)
(delete start-line-start pos-line-start)
(end-edit-sequence))]
[else
; Only delete after
(send edit delete (add1 pos-line-end)
end-line-start)]))))))]
[open-line
(λ (edit event)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(when (= sel-start sel-end)
(send* edit
(insert #\newline)
(set-position sel-start)))))]
[transpose-chars
(λ (edit event)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(when (and (= sel-start sel-end)
(not (= sel-start 0)))
(let ([sel-start
(if (= sel-start
(send edit line-end-position
(send edit position-line sel-start)))
(sub1 sel-start)
sel-start)])
(let ([s (send edit get-text
sel-start (add1 sel-start))])
(send* edit
(begin-edit-sequence)
(delete sel-start (add1 sel-start))
(insert s (- sel-start 1))
(set-position (add1 sel-start))
(end-edit-sequence)))))))]
[transpose-words
(λ (edit event)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(when (= sel-start sel-end)
(let ([word-1-start (box sel-start)])
(send edit find-wordbreak word-1-start #f 'caret)
(let ([word-1-end (box (unbox word-1-start))])
(send edit find-wordbreak #f word-1-end 'caret)
(let ([word-2-end (box (unbox word-1-end))])
(send edit find-wordbreak #f word-2-end 'caret)
(let ([word-2-start (box (unbox word-2-end))])
(send edit find-wordbreak word-2-start #f 'caret)
(let ([text-1 (send edit get-text
(unbox word-1-start)
(unbox word-1-end))]
[text-2 (send edit get-text
(unbox word-2-start)
(unbox word-2-end))])
(send* edit
(begin-edit-sequence)
(insert text-1
(unbox word-2-start)
(unbox word-2-end))
(insert text-2
(unbox word-1-start)
(unbox word-1-end))
(set-position (unbox word-2-end))
(end-edit-sequence))))))))))]
[capitalize-it
(λ (edit char-case1 char-case2)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)]
[real-end (send edit last-position)])
(when (= sel-start sel-end)
(let ([word-end (let ([b (box sel-start)])
(send edit find-wordbreak #f b 'caret)
(min real-end (unbox b)))])
(send edit begin-edit-sequence)
(let loop ([pos sel-start]
[char-case char-case1])
(when (< pos word-end)
(let ([c (send edit get-character pos)])
(cond
[(char-alphabetic? c)
(send edit insert
(list->string
(list (char-case c)))
pos (add1 pos))
(loop (add1 pos) char-case2)]
[else
(loop (add1 pos) char-case)]))))
(send* edit
(end-edit-sequence)
(set-position word-end))))))]
[capitalize-word
(λ (edit event)
(capitalize-it edit char-upcase char-downcase))]
[upcase-word
(λ (edit event)
(capitalize-it edit char-upcase char-upcase))]
[downcase-word
(λ (edit event)
(capitalize-it edit char-downcase char-downcase))]
[kill-word
(λ (edit event)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(let ([end-box (box sel-end)])
(send edit find-wordbreak #f end-box 'caret)
(send edit kill 0 sel-start (unbox end-box)))))]
[backward-kill-word
(λ (edit event)
(let ([sel-start (send edit get-start-position)]
[sel-end (send edit get-end-position)])
(let ([start-box (box sel-start)])
(send edit find-wordbreak start-box #f 'caret)
(send edit kill 0 (unbox start-box) sel-end))))]
[copy-click-region
(λ (edit event)
(region-click/internal edit event
(λ (click eol start end)
(send edit flash-on start end)
(send edit copy #f 0 start end))))]
[cut-click-region
(λ (edit event)
(region-click/internal edit event
(λ (click eol start end)
(send edit cut #f 0 start end))))]
[paste-click-region
(λ (edit event)
(region-click/internal edit event
(λ (click eol start end)
(send edit set-position click)
(send edit paste-x-selection 0 click))))]
[mouse-copy-clipboard
(λ (edit event)
(send edit copy #f (send event get-time-stamp)))]
[mouse-paste-clipboard
(λ (edit event)
(send edit paste (send event get-time-stamp)))]
[mouse-cut-clipboard
(λ (edit event)
(send edit cut #f (send event get-time-stamp)))]
[select-click-word
(λ (edit event)
(region-click edit event
(λ (click eol)
(let ([start-box (box click)]
[end-box (box click)])
(send edit find-wordbreak
start-box
end-box
'selection)
(send edit set-position
(unbox start-box)
(unbox end-box))))))]
[select-click-line
(λ (edit event)
(region-click edit event
(λ (click eol)
(let* ([line (send edit position-line
click eol)]
[start (send edit line-start-position
line #f)]
[end (send edit line-end-position
line #f)])
(send edit set-position start end)))))]
[goto-line
(λ (edit event)
(let ([num-str
(call/text-keymap-initializer
(λ ()
(get-text-from-user
(string-constant goto-line)
(string-constant goto-line))))])
(when (string? num-str)
(let* ([possible-num (string->number num-str)]
[line-num (and possible-num (inexact->exact possible-num))])
(cond
[(and (number? line-num)
(integer? line-num)
(<= 1 line-num (+ (send edit last-paragraph) 1)))
(let ([pos (send edit paragraph-start-position
(sub1 line-num))])
(send edit set-position pos))]
[else
(message-box
(string-constant goto-line)
(format
(string-constant goto-line-invalid-number)
num-str
(+ (send edit last-line) 1)))]))))
#t)]
[repeater
(λ (n edit)
(let* ([km (send edit get-keymap)]
[done
(λ ()
(send km set-break-sequence-callback void)
(send km remove-grab-key-function))])
(send km set-grab-key-function
(λ (name local-km edit event)
(if name
(begin
(done)
(dynamic-wind
(λ ()
(send edit begin-edit-sequence))
(λ ()
(let loop ([n n])
(unless (zero? n)
(send local-km call-function name edit event)
(loop (sub1 n)))))
(λ ()
(send edit end-edit-sequence))))
(let ([k (send event get-key-code)])
(if (and (char? k) (char<=? #\0 k #\9))
(set! n (+ (* n 10) (- (char->integer k)
(char->integer #\0))))
(begin
(done)
(dynamic-wind
(λ ()
(send edit begin-edit-sequence))
(λ ()
(let loop ([n n])
(unless (zero? n)
(send edit on-char event)
(loop (sub1 n)))))
(λ ()
(send edit end-edit-sequence)))))))
#t))
(send km set-break-sequence-callback done)
#t))]
[make-make-repeater
(λ (n)
(λ (edit event)
(repeater n edit)))]
[current-macro '()]
[building-macro #f] [build-macro-km #f] [build-protect? #f]
[show/hide-keyboard-macro-icon
(λ (edit on?)
(when (is-a? edit editor:basic<%>)
(let ([frame (send edit get-top-level-window)])
(when (is-a? frame frame:text-info<%>)
(send frame set-macro-recording on?)
(send frame update-shown)))))]
[do-macro
(λ (edit event)
; If c:x;e during record, copy the old macro
(when building-macro
(set! building-macro (append (reverse current-macro)
(cdr building-macro))))
(let ([bm building-macro]
[km (send edit get-keymap)])
(dynamic-wind
(λ ()
(set! building-macro #f)
(send edit begin-edit-sequence))
(λ ()
(let/ec escape
(for-each
(λ (f)
(let ([name (car f)]
[event (cdr f)])
(if name
(unless (send km call-function name edit event #t)
(escape #t))
(send edit on-char event))))
current-macro)))
(λ ()
(send edit end-edit-sequence)
(set! building-macro bm))))
#t)]
[start-macro
(λ (edit event)
(if building-macro
(send build-macro-km break-sequence)
(letrec ([km (send edit get-keymap)]
[done
(λ ()
(if build-protect?
(send km set-break-sequence-callback done)
(begin
(set! building-macro #f)
(show/hide-keyboard-macro-icon edit #f)
(send km set-break-sequence-callback void)
(send km remove-grab-key-function))))])
(set! building-macro '())
(show/hide-keyboard-macro-icon edit #t)
(set! build-macro-km km)
(send km set-grab-key-function
(λ (name local-km edit event)
(dynamic-wind
(λ ()
(set! build-protect? #t))
(λ ()
(if name
(send local-km call-function name edit event)
(send edit on-default-char event)))
(λ ()
(set! build-protect? #f)))
(when building-macro
(set! building-macro
(cons (cons name event)
building-macro)))
#t))
(send km set-break-sequence-callback done)))
#t)]
[end-macro
(λ (edit event)
(when building-macro
(set! current-macro (reverse building-macro))
(set! build-protect? #f)
(send build-macro-km break-sequence))
#t)]
[delete-key
(λ (edit event)
(let ([kmap (send edit get-keymap)])
(send kmap call-function
(if (preferences:get 'framework:delete-forward?)
"delete-next-character"
"delete-previous-character")
edit event #t)))]
[toggle-overwrite
(λ (edit event)
(when (preferences:get 'framework:overwrite-mode-keybindings)
(send edit set-overwrite-mode
(not (send edit get-overwrite-mode)))))]
[down-into-embedded-editor
(λ (text event)
(let ([start (send text get-start-position)]
[end (send text get-end-position)])
(when (= start end)
(let* ([bx (box 0)]
[after-snip (send text find-snip start 'after-or-none bx)])
(cond
[(and (= (unbox bx) start)
after-snip
(is-a? after-snip editor-snip%))
(let ([embedded-editor (send after-snip get-editor)])
(when (is-a? embedded-editor text%)
(send embedded-editor set-position 0))
(send embedded-editor set-caret-owner #f 'global))]
[else
(let ([before-snip (send text find-snip start 'before-or-none bx)])
(when (and (= (+ (unbox bx) 1) start)
before-snip
(is-a? before-snip editor-snip%))
(let ([embedded-editor (send before-snip get-editor)])
(when (is-a? embedded-editor text%)
(send embedded-editor set-position
(send embedded-editor last-position)))
(send embedded-editor set-caret-owner #f 'global))))]))))
#t)]
[forward-to-next-embedded-editor
(λ (text event)
(let ([start-pos (send text get-start-position)]
[end-pos (send text get-end-position)])
(when (= start-pos end-pos)
(let loop ([snip (send text find-snip start-pos 'after-or-none)])
(cond
[(not snip) (void)]
[(is-a? snip editor-snip%)
(send text set-position (send text get-snip-position snip))]
[else (loop (send snip next))]))))
#t)]
[back-to-prev-embedded-editor
(λ (text event)
(let ([start-pos (send text get-start-position)]
[end-pos (send text get-end-position)])
(when (= start-pos end-pos)
(let loop ([snip (send text find-snip start-pos 'before-or-none)])
(cond
[(not snip) (void)]
[(is-a? snip editor-snip%)
(send text set-position (+ (send text get-snip-position snip) 1))]
[else (loop (send snip previous))]))))
#t)]
[up-out-of-embedded-editor
(λ (text event)
(let ([start (send text get-start-position)]
[end (send text get-end-position)])
(when (= start end)
(let ([editor-admin (send text get-admin)])
(when (is-a? editor-admin editor-snip-editor-admin<%>)
(let* ([snip (send editor-admin get-snip)]
[snip-admin (send snip get-admin)])
(when snip-admin
(let ([editor (send snip-admin get-editor)])
(when (is-a? editor text%)
(let ([new-pos (send editor get-snip-position snip)])
(send editor set-position new-pos new-pos))
(send editor set-caret-owner #f 'display)))))))))
#t)]
[make-read-only
(λ (text event)
(send text lock #t)
#t)]
[newline
(λ (text event)
(send text insert "\n")
#t)]
[shift-focus
(λ (adjust)
(λ (text event)
(when (is-a? text editor:basic<%>)
(let ([frame (send text get-top-level-window)])
(let ([found-one? #f])
(let/ec k
(let ([go
(λ ()
(let loop ([obj frame])
(cond
[(and found-one?
(is-a? obj editor-canvas%)
(is-a? (send obj get-editor) editor:keymap<%>))
(send obj focus)
(k (void))]
[(and (is-a? obj window<%>) (send obj has-focus?))
(set! found-one? #t)]
[(is-a? obj area-container<%>)
(for-each loop (adjust (send obj get-children)))])))])
(go)
;;; when we get here, we either didn't find the focus anywhere,
;;; or the last editor-canvas had the focus. either way,
;;; the next thing should get the focus
(set! found-one? #t)
(go))))))))]
[TeX-compress
(let* ([biggest (apply max (map (λ (x) (string-length (car x))) tex-shortcut-table))])
(define (meet s t)
(substring s 0 (string-prefix-length s t 0)))
(λ (text event)
(let ([pos (send text get-start-position)])
(when (= pos (send text get-end-position))
(let ([slash (send text find-string "\\" 'backward pos (max 0 (- pos biggest 1)))])
(when slash
(define entered (send text get-text slash pos))
(define completions
(filter (λ (shortcut) (string-prefix? entered (first shortcut)))
tex-shortcut-table))
(unless (empty? completions)
(define-values (replacement partial?)
(let ([complete-match
(findf (λ (shortcut) (equal? entered (first shortcut)))
completions)])
(if complete-match
(values (second complete-match) #f)
(if (= 1 (length completions))
(values (second (first completions)) #f)
(let ([tex-names (map first completions)])
(values (foldl meet (first tex-names) (rest tex-names)) #t))))))
(send text begin-edit-sequence)
(send text delete (if partial? slash (- slash 1)) pos)
(send text insert replacement)
(send text end-edit-sequence))))))))]
[greek-letters "αβγδεζηθι κλμνξοπρςστυφχψω"]
[Greek-letters "ΑΒΓΔΕΖΗΘΙ ΚΛΜΝΞΟΠΡ ΣΤΥΦΧΨΩ"]
;; don't have a capital ς, just comes out as \u03A2 (or junk)
[find-beginning-of-line
(λ (txt)
(define pos-to-start-with
(cond
[(= (send txt get-extend-start-position)
(send txt get-start-position))
(send txt get-end-position)]
[else
(send txt get-start-position)]))
(cond
[(is-a? txt text:basic<%>)
(send txt get-start-of-line pos-to-start-with)]
[(is-a? txt text%)
(send txt line-start-position (send txt position-line pos-to-start-with))]
[else #f]))]
[beginning-of-line
(λ (txt event)
(define pos (find-beginning-of-line txt))
(when pos
(send txt set-position pos pos)))]
[select-to-beginning-of-line
(λ (txt event)
(define pos (find-beginning-of-line txt))
(when pos
(send txt extend-position pos)))]
[unicode-ascii-art-boxes
(λ (txt evt)
(define start (send txt get-start-position))
(when (= start (send txt get-end-position))
(do-unicode-ascii-art-boxes txt start)
(send txt set-position start)))])
(λ (kmap)
(let* ([map (λ (key func)
(send kmap map-function key func))]
[map-meta (λ (key func)
(send-map-function-meta kmap key func))]
[add (λ (name func)
(send kmap add-function name func))]
[add-m (λ (name func)
(send kmap add-function name func))])
; Map names to keyboard functions
(for-each
(λ (c)
(unless (equal? c #\space)
(add (format "insert ~a" c)
(λ (txt evt) (send txt insert c)))))
(string->list (string-append greek-letters Greek-letters)))
(add "unicode-ascii-art-boxes" unicode-ascii-art-boxes)
(add "shift-focus" (shift-focus values))
(add "shift-focus-backwards" (shift-focus reverse))
(add "TeX compress" TeX-compress)
(add "newline" newline)
(add "down-into-embedded-editor" down-into-embedded-editor)
(add "up-out-of-embedded-editor" up-out-of-embedded-editor)
(add "forward-to-next-embedded-editor" forward-to-next-embedded-editor)
(add "back-to-prev-embedded-editor" back-to-prev-embedded-editor)
(add "toggle-overwrite (when enabled in prefs)" toggle-overwrite)
(add "exit" (λ (edit event)
(let ([frame (send edit get-frame)])
(if (and frame
(is-a? frame frame:standard-menus<%>))
(send frame file-menu:quit)
(bell)))))
(add "ring-bell" ring-bell)
(add "toggle-anchor" toggle-anchor)
(add "center-view-on-line" center-view-on-line)
(add "collapse-space" collapse-space)
(add "remove-space" remove-space)
(add "collapse-newline" collapse-newline)
(add "open-line" open-line)
(add "transpose-chars" transpose-chars)
(add "transpose-words" transpose-words)
(add "capitalize-word" capitalize-word)
(add "upcase-word" upcase-word)
(add "downcase-word" downcase-word)
(add "kill-word" kill-word)
(add "backward-kill-word" backward-kill-word)
(let loop ([n 9])
(unless (negative? n)
(let ([s (number->string n)])
(add (string-append "command-repeat-" s)
(make-make-repeater n))
(loop (sub1 n)))))
(add "keyboard-macro-run-saved" do-macro)
(add "keyboard-macro-start-record" start-macro)
(add "keyboard-macro-end-record" end-macro)
(add-m "copy-clipboard" mouse-copy-clipboard)
(add-m "cut-clipboard" mouse-cut-clipboard)
(add-m "paste-clipboard" mouse-paste-clipboard)
(add-m "copy-click-region" copy-click-region)
(add-m "cut-click-region" cut-click-region)
(add-m "paste-click-region" paste-click-region)
(add-m "select-click-word" select-click-word)
(add-m "select-click-line" select-click-line)
(add "goto-line" goto-line)
(add "delete-key" delete-key)
(add "mouse-popup-menu" mouse-popup-menu)
(add "make-read-only" make-read-only)
(add "beginning-of-line" beginning-of-line)
(add "select-to-beginning-of-line" select-to-beginning-of-line)
; Map keys to functions
(let ([setup-mappings
(λ (greek-chars shift?)
(let loop ([i 0])
(when (< i (string-length greek-chars))
(let ([greek-char (string-ref greek-chars i)])
(unless (equal? greek-char #\space)
(let ([roman-char
(integer->char
(+ (char->integer #\a) i))])
(map (format "a:g;~a~a"
(if shift? "s:" "")
roman-char)
(format "insert ~a" greek-char))
(map (format "m:x;c:g;~a~a"
(if shift? "s:" "")
roman-char)
(format "insert ~a" greek-char))
(map (format "c:x;c:g;~a~a"
(if shift? "s:" "")
roman-char)
(format "insert ~a" greek-char)))))
(loop (+ i 1)))))])
(setup-mappings greek-letters #f)
(setup-mappings Greek-letters #t))
(map "c:x;r;a" "unicode-ascii-art-boxes")
(map "~m:c:\\" "TeX compress")
(map "~c:m:\\" "TeX compress")
(map "c:x;t" "TeX compress")
(map "c:j" "newline")
(map-meta "c:down" "down-into-embedded-editor")
(map "a:c:down" "down-into-embedded-editor")
(map-meta "c:up" "up-out-of-embedded-editor")
(map "a:c:up" "up-out-of-embedded-editor")
(map-meta "c:right" "forward-to-next-embedded-editor")
(map "a:c:right" "forward-to-next-embedded-editor")
(map-meta "c:left" "back-to-prev-embedded-editor")
(map "a:c:left" "back-to-prev-embedded-editor")
(map "c:c;c:g" "ring-bell")
(map "c:p" "previous-line")
(map "up" "previous-line")
(map "s:c:p" "select-up")
(map "s:up" "select-up")
(map "c:n" "next-line")
(map "down" "next-line")
(map "s:c:n" "select-down")
(map "s:down" "select-down")
(map "c:e" "end-of-line")
(map "d:right" "end-of-line")
(map "end" "end-of-line")
(map "s:end" "select-to-end-of-line")
(map "s:c:e" "select-to-end-of-line")
(map "s:d:right" "select-to-end-of-line")
(map "c:a" "beginning-of-line")
(map "d:left" "beginning-of-line")
(map "home" "beginning-of-line")
(map "s:home" "select-to-beginning-of-line")
(map "s:c:a" "select-to-beginning-of-line")
(map "s:d:left" "select-to-beginning-of-line")
(map "c:f" "forward-character")
(map "right" "forward-character")
(map "s:c:f" "forward-select")
(map "s:right" "forward-select")
(map "c:b" "backward-character")
(map "left" "backward-character")
(map "s:c:b" "backward-select")
(map "s:left" "backward-select")
(map-meta "f" "forward-word")
(map "c:right" "forward-word")
(map-meta "s:f" "forward-select-word")
(map "c:s:right" "forward-select-word")
(map-meta "b" "backward-word")
(map "c:left" "backward-word")
(map-meta "s:b" "backward-select-word")
(map "c:s:left" "backward-select-word")
(map-meta "<" "beginning-of-file")
(map "d:up" "beginning-of-file")
(map "c:home" "beginning-of-file")
(map "s:c:home" "select-to-beginning-of-file")
(map "s:d:up" "select-to-beginning-of-file")
(map-meta ">" "end-of-file")
(map "d:down" "end-of-file")
(map "c:end" "end-of-file")
(map "s:c:end" "select-to-end-of-file")
(map "s:d:down" "select-to-end-of-file")
(map "c:v" "next-page")
(map "pagedown" "next-page")
(map "c:down" "next-page")
(map "s:c:v" "select-page-down")
(map "s:pagedown" "select-page-down")
(map "s:c:down" "select-page-down")
(map-meta "v" "previous-page")
(map "pageup" "previous-page")
(map "c:up" "previous-page")
(map-meta "s:v" "select-page-up")
(map "s:pageup" "select-page-up")
(map "s:c:up" "select-page-up")
(map "c:h" "delete-previous-character")
(map "c:d" "delete-next-character")
(map "del" "delete-key")
(map-meta "d" "kill-word")
(map-meta "del" "kill-word")
(map-meta "backspace" "backward-kill-word")
(map-meta "c" "capitalize-word")
(map-meta "u" "upcase-word")
(map-meta "l" "downcase-word")
(map "c:l" "center-view-on-line")
(map "c:k" "delete-to-end-of-line")
(map "c:y" "paste-clipboard")
(map-meta "y" "paste-next")
(map "a:v" "paste-clipboard")
(map "d:v" "paste-clipboard")
(map "c:_" "undo")
(map "c:/" "undo")
(map (format "~a" (integer->char 31)) "undo") ; for Windows - strange
(map "c:+" "redo")
(map "a:z" "undo")
(map "d:z" "undo")
(map "c:x;u" "undo")
(map "c:w" "cut-clipboard")
(map "a:x" "cut-clipboard")
(map "d:x" "cut-clipboard")
(map-meta "w" "copy-clipboard")
(map "a:c" "copy-clipboard")
(map "d:c" "copy-clipboard")
(map "s:delete" "cut-clipboard")
(map "c:insert" "copy-clipboard")
(map "s:insert" "paste-clipboard")
(map-meta "space" "collapse-space")
(when (eq? (system-type) 'macosx)
(map "a:space" "collapse-space"))
;(map-meta "\\" "remove-space") ; Conflicts with european keyboards.
(map "c:x;c:o" "collapse-newline")
(map "c:o" "open-line")
(map "c:t" "transpose-chars")
(map-meta "t" "transpose-words")
(map "c:space" "toggle-anchor")
(map "insert" "toggle-overwrite (when enabled in prefs)")
(map-meta "o" "toggle-overwrite (when enabled in prefs)")
(map-meta "g" "goto-line")
(map "c:u" "command-repeat-0")
(let loop ([n 9])
(unless (negative? n)
(let ([s (number->string n)])
(map-meta s (string-append "command-repeat-" s))
(loop (sub1 n)))))
(map "c:x;e" "keyboard-macro-run-saved")
(map "c:x;(" "keyboard-macro-start-record")
(map "c:x;)" "keyboard-macro-end-record")
(map "leftbuttontriple" "select-click-line")
(map "leftbuttondouble" "select-click-word")
;; the "roller ball" mice map clicking the ball to button 2.
(unless (eq? (system-type) 'windows)
(map "middlebutton" "paste-click-region"))
(map ":rightbuttonseq" "mouse-popup-menu")
(map "c:c;c:r" "make-read-only")
(map "c:x;o" "shift-focus")
(map "c:x;p" "shift-focus-backwards")
(map "c:f6" "shift-focus")
(map "a:tab" "shift-focus")
(map "a:s:tab" "shift-focus-backwards")))))
(define setup-search
(let* ([send-frame
(λ (invoke-method)
(λ (edit event)
(let ([frame
(cond
[(is-a? edit editor<%>)
(let ([canvas (send edit get-active-canvas)])
(and canvas
(send canvas get-top-level-window)))]
[(is-a? edit area<%>)
(send edit get-top-level-window)]
[else #f])])
(if frame
(invoke-method frame)
(bell)))
#t))])
(λ (kmap)
(let* ([map (λ (key func)
(send kmap map-function key func))]
[map-meta (λ (key func)
(send-map-function-meta kmap key func))]
[add (λ (name func)
(send kmap add-function name func))]
[add-m (λ (name func)
(send kmap add-function name func))])
(add "search forward"
(send-frame (λ (f) (send f search 'forward))))
(add "search backward"
(send-frame (λ (f) (send f search 'backward))))
(add "replace & search forward"
(send-frame (λ (f) (send f replace&search 'forward))))
(add "replace & search backward"
(send-frame (λ (f) (send f replace&search 'backward))))
(add "unhide search and toggle focus"
(send-frame (λ (f) (send f unhide-search-and-toggle-focus))))
(add "hide-search"
(send-frame (λ (f) (send f hide-search))))
(map "c:g" "hide-search")
(map "f3" "unhide search and toggle focus")
(map "c:s" "unhide search and toggle focus")
(map "c:r" "search backward")
(case (system-type)
[(unix)
(map-meta "%" "search forward")])))))
(define setup-file
(let* ([get-outer-editor ;; : text% -> text%
;; returns the outermost editor, if this editor is nested in an editor snip.
(λ (edit)
(let loop ([edit edit])
(let ([admin (send edit get-admin)])
(cond
[(is-a? admin editor-snip-editor-admin<%>)
(loop (send (send (send admin get-snip) get-admin) get-editor))]
[else edit]))))]
[save-file-as
(λ (this-edit event)
(let ([edit (get-outer-editor this-edit)])
(parameterize ([finder:dialog-parent-parameter
(and (is-a? edit editor:basic<%>)
(send edit get-top-level-window))])
(let ([file (finder:put-file)])
(when file
(send edit save-file/gui-error file)))))
#t)]
[save-file
(λ (this-edit event)
(let ([edit (get-outer-editor this-edit)])
(if (send edit get-filename)
(send edit save-file/gui-error)
(save-file-as edit event)))
#t)]
[load-file
(λ (edit event)
(let ([fn (send edit get-filename)])
(handler:open-file
(and fn
(let-values ([(base name dir) (split-path fn)])
base))))
#t)])
(λ (kmap)
(let* ([map (λ (key func)
(send kmap map-function key func))]
[map-meta (λ (key func)
(send-map-function-meta kmap key func))]
[add (λ (name func)
(send kmap add-function name func))]
[add-m (λ (name func)
(send kmap add-function name func))])
(add "save-file" save-file)
(add "save-file-as" save-file-as)
(add "load-file" load-file)
(map "c:x;c:s" "save-file")
(map "d:s" "save-file")
(map "c:x;c:w" "save-file-as")
(map "c:x;c:f" "load-file")))))
(define (setup-editor kmap)
(let ([add/map
(λ (func op key)
(send kmap add-function
func
(λ (editor evt)
(send editor do-edit-operation op)))
(send kmap map-function
(string-append
(case (system-type)
[(macosx macos) "d:"]
[(windows unix) "c:"]
[else (error 'keymap.rkt "unknown platform: ~s" (system-type))])
key)
func))])
(add/map "editor-undo" 'undo "z")
(unless (eq? (system-type) 'macosx)
(add/map "editor-redo" 'redo "y"))
(add/map "editor-cut" 'cut "x")
(add/map "editor-copy" 'copy "c")
(add/map "editor-paste" 'paste "v")
(add/map "editor-select-all" 'select-all "a")))
(define (generic-setup keymap)
(add-editor-keymap-functions keymap)
(add-pasteboard-keymap-functions keymap)
(add-text-keymap-functions keymap))
(define user-keymap (make-object aug-keymap%))
(define (get-user) user-keymap)
(define global (make-object aug-keymap%))
(define global-main (make-object aug-keymap%))
(send global chain-to-keymap global-main #f)
(generic-setup global-main)
(setup-global global-main)
(define (get-global) global)
(define file (make-object aug-keymap%))
(generic-setup file)
(setup-file file)
(define (-get-file) file)
(define search (make-object aug-keymap%))
(generic-setup search)
(setup-search search)
(define (get-search) search)
(define editor (make-object aug-keymap%))
(setup-editor editor)
(define (get-editor) editor)
(define (call/text-keymap-initializer thunk)
(let ([ctki (current-text-keymap-initializer)])
(parameterize ([current-text-keymap-initializer
(λ (keymap)
(send keymap chain-to-keymap global #t)
(ctki keymap))])
(thunk))))
(define (region-click text event f)
(region-click/internal text event
(λ (click-pos eol start end) (f click-pos eol))))
(define (region-click/internal text event f)
(when (and (is-a? event mouse-event%)
(send event button-down?)
(is-a? text text%))
(define x-box (box (send event get-x)))
(define y-box (box (send event get-y)))
(define eol-box (box #f))
(send text global-to-local x-box y-box)
(define click-pos (send text find-position
(unbox x-box)
(unbox y-box)
eol-box))
(define start-pos (send text get-start-position))
(define end-pos (send text get-end-position))
(define eol (unbox eol-box))
(if (< start-pos click-pos)
(f click-pos eol start-pos click-pos)
(f click-pos eol click-pos end-pos)))))
(define (do-unicode-ascii-art-boxes t pos)
(when (i? t pos)
(define visited (make-hash))
(send t begin-edit-sequence)
(let loop ([pos pos])
(unless (hash-ref visited pos #f)
(hash-set! visited pos #t)
(define-values (x y) (pos->xy t pos))
(define c (send t get-character pos))
(define up (xy->pos t x (- y 1)))
(define dn (xy->pos t x (+ y 1)))
(define lt (xy->pos t (- x 1) y))
(define rt (xy->pos t (+ x 1) y))
(define i-up? (and (i? t up) (member c up-chars)))
(define i-dn? (and (i? t dn) (member c dn-chars)))
(define i-lt? (and (i? t lt) (member c lt-chars)))
(define i-rt? (and (i? t rt) (member c rt-chars)))
(cond
[(and i-up? i-dn? i-lt? i-rt?) (set t pos "")]
[(and i-dn? i-lt? i-rt?) (set t pos "")]
[(and i-up? i-lt? i-rt?) (set t pos "")]
[(and i-up? i-dn? i-rt?) (set t pos "")]
[(and i-up? i-dn? i-lt?) (set t pos "")]
[(and i-up? i-lt?) (set t pos "")]
[(and i-up? i-rt?) (set t pos "")]
[(and i-dn? i-lt?) (set t pos "")]
[(and i-dn? i-rt?) (set t pos "")]
[(or i-up? i-dn?) (set t pos "")]
[else (set t pos "")])
(when i-up? (loop up))
(when i-dn? (loop dn))
(when i-lt? (loop lt))
(when i-rt? (loop rt))))
(send t end-edit-sequence)))
(define (i? t pos)
(and pos
(member (send t get-character pos)
adjustable-chars)))
(define (set t pos s)
(unless (equal? (string-ref s 0) (send t get-character pos))
(send t delete pos (+ pos 1))
(send t insert s pos pos)))
(define (pos->xy text pos)
(define para (send text position-paragraph pos))
(define start (send text paragraph-start-position para))
(values (- pos start) para))
(define up-chars
'(#\╬
#\╩ #\╣ #\╠
#\╝ #\╚
#\║
#\+ #\|))
(define dn-chars
'(#\╬
#\╦ #\╣ #\╠
#\╗ #\╔
#\║
#\+ #\|))
(define lt-chars
'(#\╬
#\╩ #\╦ #\╣
#\╝ #\╗
#\═
#\+ #\-))
(define rt-chars
'(#\╬
#\╩ #\╦ #\╠
#\╔ #\╚
#\═
#\+ #\-))
(define adjustable-chars
(remove-duplicates
(append up-chars dn-chars lt-chars rt-chars)))
(define (xy->pos text x y)
(cond
[(and (<= 0 x) (<= 0 y (send text last-paragraph)))
(define para-start (send text paragraph-start-position y))
(define para-end (send text paragraph-end-position y))
(define pos (+ para-start x))
(and (< pos para-end)
;; the newline at the end of the
;; line is not on the line, so use this guard
pos)]
[else #f]))
(module+ test
(require rackunit
racket/gui/base)
(define sa string-append)
(let ([t (new text%)])
(send t insert (sa "abc\n"
"d\n"
"ghi\n"))
(check-equal? (xy->pos t 0 0) 0)
(check-equal? (xy->pos t 1 0) 1)
(check-equal? (xy->pos t 0 1) 4)
(check-equal? (xy->pos t 3 0) #f)
(check-equal? (xy->pos t 0 3) #f)
(check-equal? (xy->pos t 1 1) #f)
(check-equal? (xy->pos t 2 1) #f)
(check-equal? (xy->pos t 0 2) 6)
(check-equal? (xy->pos t 1 2) 7)
(check-equal? (xy->pos t 2 -1) #f)
(check-equal? (xy->pos t -1 0) #f)
(check-equal? (xy->pos t 2 2) 8)
(check-equal? (xy->pos t 2 3) #f))
(let ([t (new text%)])
(send t insert (sa "abc\n"
"d\n"
"ghi"))
(check-equal? (xy->pos t 2 2) 8)
(check-equal? (xy->pos t 2 3) #f)))