automatic parenthesis mode improvements
Handle close parentheses in a smarter way while in auto-parens mode and be a little more smart about inserting brace pairs in general. In summary: - Add some "smart-skip" behavior to insert-close-paren, described in the documentation. - When auto-parens mode is enabled, the existing "balance-parens" keybinding invokes insert-close-paren with a smart-skip argument of 'adjacent - A new "balance-parens-forward" keybinding invokes insert-close-paren with a smart-skip argument of 'forward (whether or not auto-parens mode is enabled) - Enable basic smart-skip behavior for strings ("...") and |...| pairs, specifically, typing a double-quote or bar character when the cursor immediately precedes one causes the cursor to simply skip over the existing one - Tweak auto-insertion of block comment pairs; i.e. typing hash and a bar results in a properly balanced #||# pair. Also, when you type a bar character when the cursor immediately precedes a closing bar and hash of a comment, then the cursor skips over both characters (this seems better than having it just skip over the bar, and then having to introduce a new keybinding to detect when a hash is typed while the cursor is between a bar and a hash) - In strings and line/block comments, auto-parens mode no longer has any effect (you can still use the M+.. keybindings to force insertion of a particular brace pair) - Detect when a character constant is being typed, and don't insert brace pairs if so; i.e. if the cursor is immediately after #\ , then typing any open parens, double quote, or bar, does _not_ result in the insertion of an open/close pair even in auto-parens mode - Add a bunch of tests related to auto-parens, matching pairs of braces, strings, comments, etc. to collects/tests/framework/racket.rkt
This commit is contained in:
parent
5197649cb7
commit
8f3343cd01
|
@ -8,6 +8,7 @@ added get-regions
|
||||||
|
|
||||||
(require racket/class
|
(require racket/class
|
||||||
racket/gui/base
|
racket/gui/base
|
||||||
|
(prefix-in r: racket/match) ; does 'match' conflict with something else
|
||||||
syntax-color/token-tree
|
syntax-color/token-tree
|
||||||
syntax-color/paren-tree
|
syntax-color/paren-tree
|
||||||
syntax-color/default-lexer
|
syntax-color/default-lexer
|
||||||
|
@ -892,6 +893,7 @@ added get-regions
|
||||||
(tokenize-to-pos ls position)))
|
(tokenize-to-pos ls position)))
|
||||||
|
|
||||||
;; See docs
|
;; See docs
|
||||||
|
;; Note: this doesn't seem to handle sexp-comments correctly .nah.
|
||||||
(define/public (skip-whitespace position direction comments?)
|
(define/public (skip-whitespace position direction comments?)
|
||||||
(when stopped?
|
(when stopped?
|
||||||
(error 'skip-whitespace "called on a color:text<%> whose colorer is stopped."))
|
(error 'skip-whitespace "called on a color:text<%> whose colorer is stopped."))
|
||||||
|
@ -951,38 +953,127 @@ added get-regions
|
||||||
(get-close-paren pos (cdr closers) #t)
|
(get-close-paren pos (cdr closers) #t)
|
||||||
#f)))))
|
#f)))))
|
||||||
c))))))
|
c))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; this returns the start/end positions
|
||||||
|
;; of the matching close paren of the first open paren to the left of pos,
|
||||||
|
;; if it is properly balanced. (this one assumes though that closers
|
||||||
|
;; really contains only 'parenthesis type characters)
|
||||||
|
;; find-next-outer-paren : number (list string)
|
||||||
|
;; -> (values (or #f number) (or #f number) (or #f string))
|
||||||
|
(define/private (find-next-outer-paren pos closers)
|
||||||
|
(cond
|
||||||
|
[(null? closers) (values #f #f #f)]
|
||||||
|
[else
|
||||||
|
(define c (car closers)) ; pick a close parens
|
||||||
|
(define l (string-length c))
|
||||||
|
(define ls (find-ls pos))
|
||||||
|
(cond
|
||||||
|
[(not ls) (values #f #f #f)]
|
||||||
|
[else
|
||||||
|
(define start-pos (lexer-state-start-pos ls))
|
||||||
|
(insert c pos) ; temporarily insert c
|
||||||
|
(define m (backward-match (+ l pos) start-pos)) ; find matching open parens
|
||||||
|
(delete pos (+ l pos)) ; delete c
|
||||||
|
(define n ; now from the open parens find the *real* matching close parens
|
||||||
|
(and m (forward-match m (last-position)))) ; n is the position *after* the close
|
||||||
|
#;(printf "outer: ~a~n" (list pos n m (and n m (let-values ([(a b) (get-token-range (- n l))])
|
||||||
|
(list a b)))))
|
||||||
|
(if n
|
||||||
|
(let-values ([(a b) (get-token-range (- n l))])
|
||||||
|
(values a b (get-text a b)))
|
||||||
|
(find-next-outer-paren pos (cdr closers)))])]))
|
||||||
|
|
||||||
(inherit insert delete flash-on on-default-char)
|
|
||||||
|
;; returns the start and end positions of the next token at or after
|
||||||
|
;; pos that matches any of the given list of closers, as well as
|
||||||
|
;; the string of the matching token itself and whether it
|
||||||
|
;; occurred immediately adjacent to pos, ignoring whitespace and comments
|
||||||
|
;; find-next-close-paren : number (list string) boolean
|
||||||
|
;; -> (values (or #f number) (or #f number) (or #f string) boolean)
|
||||||
|
(define/private (find-next-close-paren pos closers [adj? #t])
|
||||||
|
(define next-pos (skip-whitespace pos 'forward #t))
|
||||||
|
(define tree (lexer-state-tokens (find-ls next-pos)))
|
||||||
|
(define start-pos (begin (send tree search! next-pos)
|
||||||
|
(send tree get-root-start-position)))
|
||||||
|
(define end-pos (send tree get-root-end-position))
|
||||||
|
|
||||||
|
#;(printf "~a |~a| |~a|~n" (list pos next-pos start-pos end-pos (send tree get-root-data)) closers (get-text start-pos end-pos))
|
||||||
|
|
||||||
|
(cond
|
||||||
|
[(or (not (send tree get-root-data)) (<= end-pos pos))
|
||||||
|
(values #f #f #f #f)] ;; didn't find /any/ token ending after pos
|
||||||
|
[(and (<= pos start-pos)
|
||||||
|
(member (get-text start-pos end-pos) closers)) ; token at start-pos matches
|
||||||
|
(values start-pos end-pos (get-text start-pos end-pos) adj?)]
|
||||||
|
[else ; skip ahead
|
||||||
|
(find-next-close-paren end-pos closers #f)]))
|
||||||
|
|
||||||
|
|
||||||
|
;; given end-pos, a position right after a closing parens,
|
||||||
|
;; flash the matching open parens
|
||||||
|
(define/private (flash-from end-pos)
|
||||||
|
(let ((to-pos (backward-match end-pos 0)))
|
||||||
|
(when to-pos
|
||||||
|
(let ([ls (find-ls to-pos)])
|
||||||
|
(when ls
|
||||||
|
(let ([start-pos (lexer-state-start-pos ls)]
|
||||||
|
[parens (lexer-state-parens ls)])
|
||||||
|
(when (and (send parens is-open-pos? (- to-pos start-pos))
|
||||||
|
(send parens is-close-pos? (- end-pos 1 start-pos)))
|
||||||
|
(flash-on to-pos (+ 1 to-pos)))))))))
|
||||||
|
|
||||||
|
|
||||||
|
(inherit insert delete flash-on on-default-char set-position undo)
|
||||||
;; See docs
|
;; See docs
|
||||||
(define/public (insert-close-paren pos char flash? fixup?)
|
;; smart-skip : (or/c #f 'adjacent 'forward)
|
||||||
(let ((closer
|
(define/public (insert-close-paren pos char flash? fixup? [smart-skip #f])
|
||||||
(begin
|
(begin-edit-sequence #t #f) ;; to hide get-close-paren's temporary edits
|
||||||
(begin-edit-sequence #f #f)
|
(define closers (map symbol->string (map cadr pairs)))
|
||||||
(get-close-paren pos
|
(define closer
|
||||||
(if fixup?
|
(get-close-paren pos (if fixup? ;; Ensure preference for given character:
|
||||||
(let ([l (map symbol->string (map cadr pairs))])
|
(cons (string char) (remove (string char) closers))
|
||||||
;; Ensure preference for given character:
|
null)
|
||||||
(cons (string char) (remove (string char) l)))
|
;; If the inserted preferred (i.e., given) paren doesn't parse
|
||||||
null)
|
;; as a paren, then don't try to change it.
|
||||||
;; If the inserted preferred (i.e., given) paren doesn't parse
|
#f))
|
||||||
;; as a paren, then don't try to change it.
|
(define insert-str (if closer closer (string char)))
|
||||||
#f))))
|
(define-values (next-close-start next-close-end next-close-str next-close-adj?)
|
||||||
(end-edit-sequence)
|
(find-next-close-paren pos closers))
|
||||||
(let ((insert-str (if closer closer (string char))))
|
(define-values (outer-close-start outer-close-end outer-close-str)
|
||||||
(for-each (lambda (c)
|
(find-next-outer-paren pos closers))
|
||||||
(on-default-char (new key-event% (key-code c))))
|
(end-edit-sequence) ;; wraps up the net-zero editing changes done by get-close-paren etc.
|
||||||
(string->list insert-str))
|
(undo) ;; to avoid messing up the editor's modified state in case of a simple skip
|
||||||
(when flash?
|
|
||||||
(unless stopped?
|
;; an action is either '(insert) or '(skip p) where p is a position
|
||||||
(let ((to-pos (backward-match (+ (string-length insert-str) pos) 0)))
|
(define the-action
|
||||||
(when to-pos
|
(r:match smart-skip
|
||||||
(let ([ls (find-ls to-pos)])
|
[#f '(insert)]
|
||||||
(when ls
|
['adjacent (if (and next-close-start next-close-adj?
|
||||||
(let ([start-pos (lexer-state-start-pos ls)]
|
(string=? insert-str next-close-str))
|
||||||
[parens (lexer-state-parens ls)])
|
`(skip ,next-close-end)
|
||||||
(when (and (send parens is-open-pos? (- to-pos start-pos))
|
`(insert))]
|
||||||
(send parens is-close-pos? (- pos start-pos)))
|
['forward (cond
|
||||||
(flash-on to-pos (+ 1 to-pos)))))))))))))
|
[(and outer-close-start
|
||||||
|
(or fixup? (string=? insert-str outer-close-str)))
|
||||||
|
`(skip ,outer-close-end)]
|
||||||
|
[(and next-close-start
|
||||||
|
(or fixup? (string=? insert-str next-close-str)))
|
||||||
|
`(skip ,next-close-end)]
|
||||||
|
[else `(insert)])]
|
||||||
|
[_ (error 'insert-close-paren
|
||||||
|
(format "invalid smart-skip option: ~a" smart-skip))]))
|
||||||
|
|
||||||
|
(define end-pos
|
||||||
|
(r:match the-action
|
||||||
|
[(list 'insert)
|
||||||
|
(for-each (λ(c) (on-default-char (new key-event% (key-code c))))
|
||||||
|
(string->list insert-str))
|
||||||
|
(+ pos (string-length insert-str))]
|
||||||
|
[(list 'skip p) (set-position p) p]))
|
||||||
|
|
||||||
|
(when (and flash? (not stopped?)) (flash-from end-pos)))
|
||||||
|
|
||||||
|
|
||||||
(define/public (debug-printout)
|
(define/public (debug-printout)
|
||||||
(for-each
|
(for-each
|
||||||
|
|
|
@ -501,11 +501,14 @@
|
||||||
|
|
||||||
(define/public (get-limit pos) 0)
|
(define/public (get-limit pos) 0)
|
||||||
|
|
||||||
(define/public (balance-parens key-event)
|
(define/public (balance-parens key-event [smart-skip #f])
|
||||||
(insert-close-paren (get-start-position)
|
(insert-close-paren (get-start-position)
|
||||||
(send key-event get-key-code)
|
(send key-event get-key-code)
|
||||||
(preferences:get 'framework:paren-match)
|
(preferences:get 'framework:paren-match)
|
||||||
(preferences:get 'framework:fixup-parens)))
|
(preferences:get 'framework:fixup-parens)
|
||||||
|
(or smart-skip
|
||||||
|
(and (preferences:get 'framework:automatic-parens)
|
||||||
|
'adjacent))))
|
||||||
|
|
||||||
(define/public (tabify-on-return?) #t)
|
(define/public (tabify-on-return?) #t)
|
||||||
(define/public (tabify [pos (get-start-position)])
|
(define/public (tabify [pos (get-start-position)])
|
||||||
|
@ -1312,6 +1315,47 @@
|
||||||
[(and lam-reg (regexp-match lam-reg text)) 'lambda]
|
[(and lam-reg (regexp-match lam-reg text)) 'lambda]
|
||||||
[else #f])))))
|
[else #f])))))
|
||||||
|
|
||||||
|
|
||||||
|
(define (position-type-when-inserted text char)
|
||||||
|
(define selection-start (send text get-start-position))
|
||||||
|
(define selection-end (send text get-end-position))
|
||||||
|
(send text begin-edit-sequence #t #f)
|
||||||
|
(send text insert char selection-start)
|
||||||
|
(define actual-type (send text classify-position selection-start))
|
||||||
|
(send text delete selection-start (+ 1 selection-start))
|
||||||
|
(send text end-edit-sequence)
|
||||||
|
(send text undo) ; to avoid messing up the editor's modified state
|
||||||
|
;(printf "check: |~a| actual: ~a~n" char actual-type)
|
||||||
|
actual-type)
|
||||||
|
|
||||||
|
|
||||||
|
;; determines if the cursor is currently sitting in a string
|
||||||
|
;; literal or a comment. To do this more accurately, first
|
||||||
|
;; insert a space at the current cursor start position, then
|
||||||
|
;; check what classification of that space character itself
|
||||||
|
(define (in-string/comment? text)
|
||||||
|
(define selection-start (send text get-start-position))
|
||||||
|
(define selection-end (send text get-end-position))
|
||||||
|
(send text begin-edit-sequence #t #f)
|
||||||
|
(send text insert " " selection-start)
|
||||||
|
(define type (send text classify-position selection-start))
|
||||||
|
(send text delete selection-start (add1 selection-start))
|
||||||
|
(send text end-edit-sequence)
|
||||||
|
(send text undo) ; to avoid messing up the editor's modified state
|
||||||
|
; in case of a simple skip
|
||||||
|
(and (member type '(comment string)) #t))
|
||||||
|
|
||||||
|
;; produces the 1 character string immediately following
|
||||||
|
;; the cursor, if there is one and if there is not a current
|
||||||
|
;; selection, in which case produces #f
|
||||||
|
(define (immediately-following-cursor text)
|
||||||
|
(define selection-start (send text get-start-position))
|
||||||
|
(and (= selection-start (send text get-end-position)) ; nothing selected
|
||||||
|
(< selection-start (send text last-position))
|
||||||
|
(send text get-text selection-start (+ selection-start 1))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define set-mode-mixin
|
(define set-mode-mixin
|
||||||
(mixin (-text<%> mode:host-text<%>) ()
|
(mixin (-text<%> mode:host-text<%>) ()
|
||||||
(super-new)
|
(super-new)
|
||||||
|
@ -1425,6 +1469,9 @@
|
||||||
(send keymap add-function "balance-parens"
|
(send keymap add-function "balance-parens"
|
||||||
(λ (edit event)
|
(λ (edit event)
|
||||||
(send edit balance-parens event)))
|
(send edit balance-parens event)))
|
||||||
|
(send keymap add-function "balance-parens-forward"
|
||||||
|
(λ (edit event)
|
||||||
|
(send edit balance-parens event 'forward)))
|
||||||
|
|
||||||
(send keymap map-function "TAB" "tabify-at-caret")
|
(send keymap map-function "TAB" "tabify-at-caret")
|
||||||
|
|
||||||
|
@ -1444,17 +1491,62 @@
|
||||||
|
|
||||||
(send keymap map-function "leftbuttondouble" "paren-double-select")
|
(send keymap map-function "leftbuttondouble" "paren-double-select")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define (insert-brace-pair text open-brace close-brace)
|
(define (insert-brace-pair text open-brace close-brace)
|
||||||
(define selection-start (send text get-start-position))
|
(define selection-start (send text get-start-position))
|
||||||
|
(define hash-before? ; tweak to detect and correctly close block comments #| ... |#
|
||||||
|
(and (< 0 selection-start)
|
||||||
|
(string=? "#" (send text get-text (- selection-start 1) selection-start))))
|
||||||
|
(send text begin-edit-sequence)
|
||||||
(send text set-position (send text get-end-position))
|
(send text set-position (send text get-end-position))
|
||||||
(send text insert close-brace)
|
(send text insert close-brace)
|
||||||
|
(when (and (char=? #\| open-brace) hash-before?) (send text insert #\#))
|
||||||
(send text set-position selection-start)
|
(send text set-position selection-start)
|
||||||
(send text insert open-brace))
|
(send text insert open-brace)
|
||||||
|
(send text end-edit-sequence))
|
||||||
|
|
||||||
|
;; only insert a pair if:
|
||||||
|
;; - automatic-parens is on, and
|
||||||
|
;; - cursor is not in a string or line/block comment, and
|
||||||
|
;; - cursor is not preceded by #\ or \ escape characters
|
||||||
(define (maybe-insert-brace-pair text open-brace close-brace)
|
(define (maybe-insert-brace-pair text open-brace close-brace)
|
||||||
|
(define open-parens
|
||||||
|
(for/list ([x (racket-paren:get-paren-pairs)]) (string-ref (car x) 0)))
|
||||||
(cond
|
(cond
|
||||||
[(preferences:get 'framework:automatic-parens)
|
[(preferences:get 'framework:automatic-parens)
|
||||||
(insert-brace-pair text open-brace close-brace)]
|
(define c (immediately-following-cursor text))
|
||||||
|
(define when-inserted (position-type-when-inserted text (string open-brace)))
|
||||||
|
(cond
|
||||||
|
; insert paren pair if it results valid parenthesis token...
|
||||||
|
[(member open-brace open-parens)
|
||||||
|
(if (eq? (position-type-when-inserted text (string open-brace)) 'parenthesis)
|
||||||
|
(insert-brace-pair text open-brace close-brace)
|
||||||
|
(send text insert open-brace))]
|
||||||
|
|
||||||
|
; ASSUME: from here on, open-brace is either " or |
|
||||||
|
; is there a token error at current position which would
|
||||||
|
; be fixed by inserting the character...
|
||||||
|
[(and (eq? 'error (send text classify-position (send text get-start-position)))
|
||||||
|
(not (eq? 'error when-inserted)))
|
||||||
|
(send text insert open-brace)]
|
||||||
|
|
||||||
|
; smart-skip over a " | or |# ...
|
||||||
|
[(and c (char=? #\" open-brace) (string=? c "\""))
|
||||||
|
(send text set-position (+ 1 (send text get-end-position)))]
|
||||||
|
[(and c (char=? #\| open-brace) (string=? c "|"))
|
||||||
|
(send text set-position (+ 1 (send text get-end-position)))
|
||||||
|
(define d (immediately-following-cursor text))
|
||||||
|
(when (and d (string=? d "#")) ; a block comment?
|
||||||
|
(send text set-position (+ 1 (send text get-end-position))))]
|
||||||
|
|
||||||
|
; are we in a string or comment...
|
||||||
|
[(in-string/comment? text) (send text insert open-brace)]
|
||||||
|
|
||||||
|
; otherwise if open-brace would result in some literal
|
||||||
|
[(eq? 'constant when-inserted) (send text insert open-brace)]
|
||||||
|
|
||||||
|
[else (insert-brace-pair text open-brace close-brace)])]
|
||||||
[else
|
[else
|
||||||
(send text insert open-brace)]))
|
(send text insert open-brace)]))
|
||||||
|
|
||||||
|
@ -1549,6 +1641,10 @@
|
||||||
|
|
||||||
;(map-meta "c:m" "mark-matching-parenthesis")
|
;(map-meta "c:m" "mark-matching-parenthesis")
|
||||||
; this keybinding doesn't interact with the paren colorer
|
; this keybinding doesn't interact with the paren colorer
|
||||||
|
|
||||||
|
(map-meta ")" "balance-parens-forward")
|
||||||
|
(map-meta "]" "balance-parens-forward")
|
||||||
|
(map-meta "}" "balance-parens-forward")
|
||||||
|
|
||||||
(map-meta "(" "insert-()-pair")
|
(map-meta "(" "insert-()-pair")
|
||||||
(map-meta "[" "insert-[]-pair")
|
(map-meta "[" "insert-[]-pair")
|
||||||
|
@ -1673,7 +1769,9 @@
|
||||||
(send text delete pos (+ pos 1) #f)
|
(send text delete pos (+ pos 1) #f)
|
||||||
(send text end-edit-sequence)
|
(send text end-edit-sequence)
|
||||||
(cond
|
(cond
|
||||||
[(preferences:get 'framework:automatic-parens)
|
[(and (preferences:get 'framework:automatic-parens)
|
||||||
|
(not (in-string/comment? text))
|
||||||
|
(eq? (position-type-when-inserted text real-char) 'parenthesis))
|
||||||
(send text insert (case real-char
|
(send text insert (case real-char
|
||||||
[(#\() #\)]
|
[(#\() #\)]
|
||||||
[(#\[) #\]]
|
[(#\[) #\]]
|
||||||
|
|
|
@ -217,16 +217,40 @@
|
||||||
|
|
||||||
Must only be called while the tokenizer is started.
|
Must only be called while the tokenizer is started.
|
||||||
}
|
}
|
||||||
@defmethod*[(((insert-close-paren (position natural-number/c) (char char?) (flash? boolean?) (fixup? boolean?)) void?))]{
|
@defmethod*[(((insert-close-paren (position natural-number/c) (char char?)
|
||||||
|
(flash? boolean?) (fixup? boolean?)
|
||||||
The @racket[position] is the place to put the parenthesis, and
|
(smart-skip? (or/c #f 'adjacent 'forward) #f)) void?))]{
|
||||||
|
Inserts a close parentheses, or, under scenarios described further below, skips
|
||||||
|
past a subsequent one. The @racket[position] is the place to put the parenthesis, or
|
||||||
|
from which to start searching for a subsequent one, and
|
||||||
@racket[char] is the parenthesis to be added (e.g., that the user typed).
|
@racket[char] is the parenthesis to be added (e.g., that the user typed).
|
||||||
If @racket[fixup?] is true, the right kind of closing parenthesis will be
|
If @racket[fixup?] is true, the right kind of closing parenthesis will be
|
||||||
chosen from the set previously passed to @method[color:text<%> start-colorer]---but only
|
chosen from the set previously passed to @method[color:text<%> start-colorer]---but only
|
||||||
if an inserted @racket[char] would be colored as a parenthesis (i.e., with
|
if an inserted @racket[char] would be colored as a parenthesis (i.e., with
|
||||||
the @racket['parenthesis] classification). Otherwise, @racket[char] will
|
the @racket['parenthesis] classification). Otherwise, @racket[char] will
|
||||||
be inserted, even if it is not the right kind. If @racket[flash?] is true,
|
be inserted (or skipped past), even if it is not the right kind.
|
||||||
the matching open parenthesis will be flashed.
|
If @racket[flash?] is true, the matching open parenthesis will be flashed when
|
||||||
|
the insertion or skip is done.
|
||||||
|
|
||||||
|
The "smart skipping" behavior of this function is determined by
|
||||||
|
@racket[smart-skip?]. If @racket[smart-skip?] is false, no skip will
|
||||||
|
take place. A parenthesis will simply be inserted as described in the
|
||||||
|
paragraph above. When @racket[smart-skip?] is @racket['adjacent], if
|
||||||
|
the next token after @racket[position], ignoring whitespace and
|
||||||
|
comments (see @racket[skip-whitespace]), is a properly matched closing
|
||||||
|
parenthesis (which may not necessarily match @racket[char] if
|
||||||
|
@racket[fixup?] is true) then simply move the cursor to the position
|
||||||
|
immediately after that already present closing parenthesis. When
|
||||||
|
@racket[smart-skip?] is @racket['forward], this function attempts to
|
||||||
|
determine the closest pair of properly balanced parentheses around
|
||||||
|
@racket[position]. If that exists, then the cursor position skips
|
||||||
|
to the position immediately after the closing parenthesis of that
|
||||||
|
outer pair. If a properly balanced outer pair is not present, then
|
||||||
|
the cursor attempts to skip immediately after the next closing
|
||||||
|
parenthesis that occurs after @racket[position], ignoring whitespace,
|
||||||
|
comments, and all other tokens. In both non-false cases of
|
||||||
|
@racket[smart-skip?], if there is no subsequent parenthesis, then
|
||||||
|
a parenthesis is simply inserted, as previously described.
|
||||||
}
|
}
|
||||||
@defmethod*[(((classify-position (position exact-nonnegative-integer?)) (or/c symbol? #f)))]{
|
@defmethod*[(((classify-position (position exact-nonnegative-integer?)) (or/c symbol? #f)))]{
|
||||||
|
|
||||||
|
|
|
@ -91,3 +91,257 @@
|
||||||
(test-magic-square-bracket 'local1 "(local " "(local [")
|
(test-magic-square-bracket 'local1 "(local " "(local [")
|
||||||
(test-magic-square-bracket 'local2 "(local [" "(local [(")
|
(test-magic-square-bracket 'local2 "(local [" "(local [(")
|
||||||
(test-magic-square-bracket 'local2 "(local [(define x 1)] " "(local [(define x 1)] (")
|
(test-magic-square-bracket 'local2 "(local [(define x 1)] " "(local [(define x 1)] (")
|
||||||
|
|
||||||
|
|
||||||
|
;; tests what happens when a given key/s is/are typed in an editor with initial
|
||||||
|
;; text and cursor position, under different settings of the auto-parentheses and
|
||||||
|
;; smart-skip-parentheses preferences .nah.
|
||||||
|
|
||||||
|
;; test-auto-parens-behavior
|
||||||
|
;; : any string [or num (list num num)] [or char symbol 1string (list char) (list key-event%)] [or num (list num num)] string
|
||||||
|
(define (test-auto-parens-behavior which initial-text initial-pos keys final-text final-pos
|
||||||
|
[auto-parens? #f])
|
||||||
|
(test
|
||||||
|
(string->symbol (format "racket:test-auto-parens-behavior ~a" which))
|
||||||
|
(λ (x) (if (list? final-pos)
|
||||||
|
(equal? x (car final-pos) (cadr final-pos) final-text)
|
||||||
|
(equal? x (list final-pos final-pos final-text))))
|
||||||
|
(λ ()
|
||||||
|
(queue-sexp-to-mred
|
||||||
|
`(let* ([t (new racket:text%)]
|
||||||
|
[f (new frame% [label ""] [width 600] [height 600])]
|
||||||
|
[ec (new editor-canvas% [parent f] [editor t])])
|
||||||
|
(preferences:set 'framework:automatic-parens ,auto-parens?)
|
||||||
|
(send f reflow-container)
|
||||||
|
(send t insert ,initial-text)
|
||||||
|
,(if (number? initial-pos)
|
||||||
|
`(send t set-position ,initial-pos)
|
||||||
|
`(send t set-position ,(car initial-pos) ,(cadr initial-pos)))
|
||||||
|
,@(map
|
||||||
|
(lambda (k)
|
||||||
|
(cond [(char? k)
|
||||||
|
`(send (racket:get-keymap)
|
||||||
|
handle-key-event t (new key-event% [key-code ,k]))]
|
||||||
|
[(string? k)
|
||||||
|
`(send (racket:get-keymap)
|
||||||
|
handle-key-event t (new key-event% [key-code ,(car (string->list k))]))]
|
||||||
|
[(symbol? k)
|
||||||
|
`(send (racket:get-keymap)
|
||||||
|
handle-key-event t (new key-event% [key-code (quote ,k)]))]
|
||||||
|
[else `(send (racket:get-keymap) handle-key-event t ,k)]))
|
||||||
|
(if (list? keys) keys (list keys)))
|
||||||
|
(list (send t get-start-position) (send t get-end-position) (send t get-text)))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; this takes an initial editor state (specified by the text before the cursor,
|
||||||
|
;; some selected text (may be blank string), and text after the cursor), and
|
||||||
|
;; a key(s), and runs tests to check what happens when that key(s) is/are
|
||||||
|
;; typed - in both possible settings of the 'automatic-parens preference
|
||||||
|
;;
|
||||||
|
;; final-states is a list of 2 pairs of strings. each pair is the final text before
|
||||||
|
;; and after the cursor, for auto-parens disabled and enabled respectively
|
||||||
|
(define (test-parens-behavior/full which
|
||||||
|
init-text-before init-text-selected init-text-after
|
||||||
|
keys
|
||||||
|
final-states)
|
||||||
|
(define initial-text (string-append init-text-before init-text-selected init-text-after))
|
||||||
|
(define initial-start-pos (string-length init-text-before))
|
||||||
|
(define initial-end-pos (+ initial-start-pos (string-length init-text-selected)))
|
||||||
|
(for-each
|
||||||
|
(lambda (label auto? final-pair)
|
||||||
|
(test-auto-parens-behavior (format "~a-~a" which label)
|
||||||
|
initial-text (list initial-start-pos initial-end-pos) keys
|
||||||
|
(apply string-append final-pair) (string-length (car final-pair))
|
||||||
|
auto?))
|
||||||
|
'("no-auto-parens" "with-auto-parens")
|
||||||
|
'(#f #t)
|
||||||
|
final-states))
|
||||||
|
|
||||||
|
|
||||||
|
(define SPECIAL-CHARS '(#\( #\) #\[ #\] #\" #\| #\{ #\}))
|
||||||
|
|
||||||
|
(for ([k SPECIAL-CHARS])
|
||||||
|
;; test that character literals never result in a pair of characters typed...
|
||||||
|
(test-parens-behavior/full (format "literal-~a" k)
|
||||||
|
"(list 1 #\\" "" ")"
|
||||||
|
k
|
||||||
|
`([,(string-append "(list 1 #\\" (string k)) ")"]
|
||||||
|
[,(string-append "(list 1 #\\" (string k)) ")"]))
|
||||||
|
;; test that auto-parens has no effect in strings
|
||||||
|
(test-parens-behavior/full (format "~a-in-string" k)
|
||||||
|
"\" abc def " "" " \""
|
||||||
|
k
|
||||||
|
`([,(string-append "\" abc def " (string k)) " \""]
|
||||||
|
[,(string-append "\" abc def " (string k)) " \""]))
|
||||||
|
|
||||||
|
;; test that auto-parens has no effect in various comment situations
|
||||||
|
(define scenarios
|
||||||
|
; description before-cursor after-cursor
|
||||||
|
'(("in-line-comment" ";; abc def " " ghi ")
|
||||||
|
("end-of-line-comment" ";; abc def " "")
|
||||||
|
("in-block-comment" "#| abc def " " ghi |#")
|
||||||
|
))
|
||||||
|
(for ([s scenarios])
|
||||||
|
(let* ([before (cadr s)]
|
||||||
|
[after (caddr s)]
|
||||||
|
[before-final (string-append before (string k))]
|
||||||
|
[result (list before-final after)])
|
||||||
|
(test-parens-behavior/full (format "~a-~a" k (car s))
|
||||||
|
before "" after k `(,result ,result)))))
|
||||||
|
|
||||||
|
;;; assorted other scenarios...
|
||||||
|
(test-parens-behavior/full 'open-parens
|
||||||
|
"abcd" "" "efg" ; editor state: before, selected, after
|
||||||
|
#\( ; key(s) pressed
|
||||||
|
'(["abcd(" "efg"] ; result state sep by cursor, no auto-parens
|
||||||
|
["abcd(" ")efg"])) ; result state with auto-parens
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'close-1
|
||||||
|
"abcd" "" "efg"
|
||||||
|
#\)
|
||||||
|
'(["abcd)" "efg"] ["abcd)" "efg"]))
|
||||||
|
(test-parens-behavior/full 'close-2
|
||||||
|
"(abcd" "" "efg"
|
||||||
|
#\)
|
||||||
|
'(["(abcd)" "efg"] ["(abcd)" "efg"]))
|
||||||
|
(test-parens-behavior/full 'close-3
|
||||||
|
"(abcd" "" ")efg"
|
||||||
|
#\)
|
||||||
|
'(["(abcd)" ")efg"] ["(abcd)" "efg"]))
|
||||||
|
(test-parens-behavior/full 'close-4
|
||||||
|
"(abcd efg " "" " ) efg"
|
||||||
|
#\)
|
||||||
|
'(["(abcd efg )" " ) efg"]
|
||||||
|
["(abcd efg )" " efg"]))
|
||||||
|
(test-parens-behavior/full 'close-5
|
||||||
|
"(define before+afters `([\"\" abc \"efg\" 12345 xyz]) [84])"
|
||||||
|
""
|
||||||
|
""
|
||||||
|
#\)
|
||||||
|
'(["(define before+afters `([\"\" abc \"efg\" 12345 xyz]) [84]))" ""]
|
||||||
|
["(define before+afters `([\"\" abc \"efg\" 12345 xyz]) [84]))" ""]))
|
||||||
|
(test-parens-behavior/full 'close-6
|
||||||
|
"(define before+afters `([\"\" abc \"efg\""
|
||||||
|
""
|
||||||
|
" 12345 xyz]) [84])"
|
||||||
|
#\)
|
||||||
|
'(["(define before+afters `([\"\" abc \"efg\"]" " 12345 xyz]) [84])"]
|
||||||
|
["(define before+afters `([\"\" abc \"efg\"]" " 12345 xyz]) [84])"]))
|
||||||
|
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'close-skip-1
|
||||||
|
"(define before+afters `([\"\" abc \"efg\" 12345 xyz]"
|
||||||
|
""
|
||||||
|
" ) [84])"
|
||||||
|
#\)
|
||||||
|
'(["(define before+afters `([\"\" abc \"efg\" 12345 xyz])" " ) [84])"]
|
||||||
|
["(define before+afters `([\"\" abc \"efg\" 12345 xyz] )" " [84])"]))
|
||||||
|
(test-parens-behavior/full 'close-skip-fixup-1
|
||||||
|
"(define before+afters `{[abc 123]"
|
||||||
|
""
|
||||||
|
" ) [84])"
|
||||||
|
#\) ; here the next close after ) doesn't match the {, so no skip happens
|
||||||
|
'(["(define before+afters `{[abc 123]}" " ) [84])"]
|
||||||
|
["(define before+afters `{[abc 123]}" " ) [84])"]))
|
||||||
|
(test-parens-behavior/full 'close-skip-fixup-2
|
||||||
|
"(define before+afters `{[abc 123]"
|
||||||
|
""
|
||||||
|
" } [84])"
|
||||||
|
#\) ; here the next close does match the {, so skip
|
||||||
|
'(["(define before+afters `{[abc 123]}" " } [84])"]
|
||||||
|
["(define before+afters `{[abc 123] }" " [84])"]))
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'surround-open-1
|
||||||
|
"abcd" "ef" "g"
|
||||||
|
#\(
|
||||||
|
'(["abcd(" "g"] ["abcd(" "ef)g"]))
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'double-quote-1
|
||||||
|
"" "" ""
|
||||||
|
#\"
|
||||||
|
'(["\"" ""] ["\"" "\""]))
|
||||||
|
(test-parens-behavior/full 'double-quote-2
|
||||||
|
"abc " "" ""
|
||||||
|
#\"
|
||||||
|
'(["abc \"" ""] ["abc \"" "\""]))
|
||||||
|
(test-parens-behavior/full 'double-quote-selection-1
|
||||||
|
"(abc " "def 123" " xyz]"
|
||||||
|
#\"
|
||||||
|
'(["(abc \"" " xyz]"] ["(abc \"" "def 123\" xyz]"]))
|
||||||
|
(test-parens-behavior/full 'double-quote-skip-1
|
||||||
|
"\"abc def " "" "\" 123"
|
||||||
|
#\"
|
||||||
|
'(["\"abc def \"" "\" 123"] ["\"abc def \"" " 123"]))
|
||||||
|
(test-parens-behavior/full 'double-quote-escaped-1
|
||||||
|
"\"abcd \\" "" ""
|
||||||
|
#\"
|
||||||
|
'(["\"abcd \\\"" ""]
|
||||||
|
["\"abcd \\\"" "\""])) ; this one inserts a pair
|
||||||
|
; because the string wasn't closed anyway
|
||||||
|
; (it's a hard case to distinguish)
|
||||||
|
(test-parens-behavior/full 'double-quote-escaped-2
|
||||||
|
"\"abcd \\" "" "\""
|
||||||
|
#\"
|
||||||
|
'(["\"abcd \\\"" "\""]
|
||||||
|
["\"abcd \\\"" "\""]))
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'bar
|
||||||
|
"abc " "" "123"
|
||||||
|
#\|
|
||||||
|
'(["abc |" "123"] ["abc |" "|123"]))
|
||||||
|
(test-parens-behavior/full 'bar-literal
|
||||||
|
"(list 1 #\\" "" ")"
|
||||||
|
#\|
|
||||||
|
'(["(list 1 #\\|" ")"] ["(list 1 #\\|" ")"]))
|
||||||
|
(test-parens-behavior/full 'bar-skip
|
||||||
|
"abc |def" "" "|123"
|
||||||
|
#\|
|
||||||
|
'(["abc |def|" "|123"] ["abc |def|" "123"]))
|
||||||
|
(test-parens-behavior/full 'bar-selection
|
||||||
|
"abc |def " "hij" "|123"
|
||||||
|
#\|
|
||||||
|
'(["abc |def |" "|123"] ["abc |def |" "hij||123"]))
|
||||||
|
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'block-comment-1
|
||||||
|
" #" "" ""
|
||||||
|
#\|
|
||||||
|
'([" #|" ""]
|
||||||
|
[" #|" "|#"]))
|
||||||
|
(test-parens-behavior/full 'block-comment-2
|
||||||
|
"(123 abc#" "" " def 456)"
|
||||||
|
#\|
|
||||||
|
'(["(123 abc#|" " def 456)"]
|
||||||
|
["(123 abc#|" "|# def 456)"]))
|
||||||
|
(test-parens-behavior/full 'block-comment-skip-1
|
||||||
|
"#| (123 abc" "" "|# def 456)"
|
||||||
|
#\|
|
||||||
|
'(["#| (123 abc|" "|# def 456)"]
|
||||||
|
["#| (123 abc|#" " def 456)"]))
|
||||||
|
|
||||||
|
#| for these, the key-event with meta-down doesn't seem to work... maybe a Mac OS
|
||||||
|
issue; and may cause problems with these tests on another platform? .nah. |#
|
||||||
|
(when (equal? 'macosx (system-type))
|
||||||
|
(test-parens-behavior/full 'meta-open-1
|
||||||
|
"abcd" "" "efg"
|
||||||
|
'(escape #\() ; '((new key-event% [key-code #\(] [meta-down #t]))
|
||||||
|
'(["abcd(" ")efg"] ["abcd(" ")efg"]))
|
||||||
|
|
||||||
|
(test-parens-behavior/full 'meta-close-skip-1
|
||||||
|
"(define before (list 1 2" "" " 3 4)"
|
||||||
|
'(escape #\)) ; '((new key-event% [key-code #\)] [meta-down #t]))
|
||||||
|
'(["(define before (list 1 2 3 4)" ""]
|
||||||
|
["(define before (list 1 2 3 4)" ""]))
|
||||||
|
(test-parens-behavior/full 'meta-close-skip-2
|
||||||
|
"#lang racket\n(define before+afters `([\"\" abc \"efg\""
|
||||||
|
""
|
||||||
|
" 12345 xyz] [84])"
|
||||||
|
'(escape #\)) ;'((new key-event% [key-code #\)] [meta-down #t]))
|
||||||
|
'(["#lang racket\n(define before+afters `([\"\" abc \"efg\" 12345 xyz]" " [84])"]
|
||||||
|
["#lang racket\n(define before+afters `([\"\" abc \"efg\" 12345 xyz]" " [84])"]))
|
||||||
|
(test-parens-behavior/full 'meta-close-skip-3
|
||||||
|
"(define before" "" " (list 1 2 3 4)"
|
||||||
|
'(escape #\)) ; '((new key-event% [key-code #\)] [meta-down #t]))
|
||||||
|
'(["(define before (list 1 2 3 4)" ""]
|
||||||
|
["(define before (list 1 2 3 4)" ""]))
|
||||||
|
)
|
||||||
|
|
|
@ -8,6 +8,36 @@
|
||||||
more to use the #lang language (unless they are using the teaching
|
more to use the #lang language (unless they are using the teaching
|
||||||
languages)
|
languages)
|
||||||
|
|
||||||
|
. Automatic-parenthesis mode improvements:
|
||||||
|
- Added some "smart-skip" behavior to insert-close-paren,
|
||||||
|
described in the documentation.
|
||||||
|
- When auto-parens mode is enabled,
|
||||||
|
the existing "balance-parens" keybinding invokes
|
||||||
|
insert-close-paren with a smart-skip argument of
|
||||||
|
'adjacent
|
||||||
|
- A new "balance-parens-forward" keybinding invokes
|
||||||
|
insert-close-paren with a smart-skip argument of
|
||||||
|
'forward (whether or not auto-parens mode is
|
||||||
|
enabled)
|
||||||
|
|
||||||
|
- Some basic smart-skip behavior is also enabled for
|
||||||
|
strings ("...") and |...| pairs, specifically, typing
|
||||||
|
a double-quote or bar character when the cursor
|
||||||
|
immediately precedes one causes the cursor to simply
|
||||||
|
skip over the existing one
|
||||||
|
|
||||||
|
- Tweaked insertion of block comment pairs in
|
||||||
|
auto-parens mode
|
||||||
|
|
||||||
|
- In strings and line/block comments, auto-parens mode
|
||||||
|
no longer has any effect (you can still use the M+..
|
||||||
|
keybindings to force insertion of a particular brace
|
||||||
|
pair)
|
||||||
|
|
||||||
|
- Detect when a character constant is being typed, and
|
||||||
|
don't insert brace pairs if so
|
||||||
|
|
||||||
|
|
||||||
------------------------------
|
------------------------------
|
||||||
Version 5.3.1
|
Version 5.3.1
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
Loading…
Reference in New Issue
Block a user