#lang racket/base

(require "test-suite-utils.rkt"
         (for-syntax racket/base))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; testing inserting parens and the automatic-parens prefs
;;

(define (test-type-string/proc line to-type expected-result [control-down #f])
  (test
   (string->symbol (format "test-type-string line ~a" line))
   (λ (x) (equal? x expected-result))
   (λ ()
     (queue-sexp-to-mred
      `(let ()
         (define f (new frame:basic% [label ""]))
         (define t (new racket:text%))
         (define ec (new canvas:basic%
                         [parent (send f get-area-container)]
                         [editor t]))
         (send t on-char (new key-event% [key-code ,to-type] [control-down ,control-down]))
         (send t get-text))))))

(define-syntax (test-type-string stx)
  (syntax-case stx ()
    [(_ . rst)
     (with-syntax ([line (syntax-line stx)])
       #'(test-type-string/proc line . rst))]))

(begin
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-parens #f))
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-open-parens #f))
  
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #f))
  (test-type-string #\( "(")
  (test-type-string #\[ "[")
  (test-type-string #\" "\"")
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #t))
  (test-type-string #\( "()")
  (test-type-string #\[ "[]")
  (test-type-string #\" "\"\""))


(begin
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-parens #f))
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-open-parens #t))
  
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #f))
  (test-type-string #\( "(")
  (test-type-string #\[ "(")
  (test-type-string #\[ "[" #t)
  (test-type-string #\" "\"")
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #t))
  (test-type-string #\( "()")
  (test-type-string #\[ "()")
  (test-type-string #\[ "[]" #t)
  (test-type-string #\" "\"\""))

(begin
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-parens #t))
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-open-parens #f))
  
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #f))
  (test-type-string #\( "(")
  (test-type-string #\[ "[")
  (test-type-string #\" "\"")
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #t))
  (test-type-string #\( "()")
  (test-type-string #\[ "[]")
  (test-type-string #\" "\"\""))

(begin
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-parens #t))
  (queue-sexp-to-mred `(preferences:set 'framework:fixup-open-parens #t))
  
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #f))
  (test-type-string #\( "(")
  (test-type-string #\[ "(")
  (test-type-string #\" "\"")
  (queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #t))
  (test-type-string #\( "()")
  (test-type-string #\[ "()")
  (test-type-string #\" "\"\""))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; testing highlight-range method
;;


(define (test-text-balanced? number str start end expected)
  (test
   (string->symbol (format "racket:text-balanced?-~a" number))
   (lambda (x) 
     (equal? x expected))
   (λ ()
     (queue-sexp-to-mred
      `(let ([t (new racket:text%)])
         (send t insert ,str)
         (racket:text-balanced? t ,start ,end))))))

(test-text-balanced? 0 "" 0 #f #f)
(test-text-balanced? 1 "  \n " 0 #f #f)
(test-text-balanced? 2 "foo)" 0 #f #t)
(test-text-balanced? 3 "(foo" 0 #f #f)
(test-text-balanced? 4 "(foo)" 0 #f #t)
(test-text-balanced? 5 "(foo 'bar))" 0 #f #t)
(test-text-balanced? 6 "(foo) bar ([buz])" 0 #f #t)
(test-text-balanced? 7 "(foo]" 0 #f #t)
(test-text-balanced? 8 "{foo} ((bar) [5.9])" 0 #f #t)
(test-text-balanced? 9 "#(1 2 . 3)" 0 #f #t)

(define-syntax (test-indentation stx) 
  (syntax-case stx () 
    [(_ . args) 
     (with-syntax ([line (syntax-line stx)])
       #'(test-indentation/proc line . args))]))

(define (test-indentation/proc line before after)
  (test
   (string->symbol (format "racket:test-indentation-line-~a" line))
   (λ (x) (equal? x after))
   (λ ()
     (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])])
         (send f reflow-container)
         (send t insert ,before)
         (send t tabify-all)
         (send t get-text))))))

(test-indentation "a" "a")
(test-indentation "(a\n b)" "(a\n b)")
(test-indentation "(a\nb)" "(a\n b)")
(test-indentation "(a b\nc)" "(a b\n   c)")
(test-indentation "(a ...\nb)" "(a ...\n b)")
(test-indentation "(lambda (x)\nb)" "(lambda (x)\n  b)")
(test-indentation "(lambdaa (x)\nb)" "(lambdaa (x)\n         b)")
(test-indentation "(define x\n  (let/ec return\n    (when 1\n      (when 2\n\t\t      3))\n    2))"
                  "(define x\n  (let/ec return\n    (when 1\n      (when 2\n        3))\n    2))")
(test-indentation "(for ([x 1])\nx)"
                  "(for ([x 1])\n  x)")
(test-indentation "(for/list ([x 1])\nx)"
                  "(for/list ([x 1])\n  x)")
(test-indentation "(for/anything ([x 1])\nx)"
                  "(for/anything ([x 1])\n  x)")
(test-indentation "(for*/anything ([x 1])\nx)"
                  "(for*/anything ([x 1])\n  x)")
(test-indentation "(for-anything ([x 1])\nx)"
                  "(for-anything ([x 1])\n              x)")
(test-indentation "(for/fold ([x 1])\n([y 2])\n3\n4)"
                  "(for/fold ([x 1])\n          ([y 2])\n  3\n  4)")
(test-indentation "a\na\na\n" "a\na\na\n")
(test-indentation "(begin\n1)" "(begin\n  1)")
(test-indentation "(lambda\n(x) x)" "(lambda\n    (x) x)")
(test-indentation "(some-random-function x\nx)"
                  "(some-random-function x\n                      x)")
(test-indentation "(some-random-function x y\nx)"
                  "(some-random-function x y\n                      x)")
(test-indentation "(x ...\nx)"
                  "(x ...\n x)")
(test-indentation "(x\n;;; hello\ny)"
                  "(x\n ;;; hello\n y)")
(test-indentation "(require racket/contract\nracket/unit\nracket/class"
                  "(require racket/contract\n         racket/unit\n         racket/class")
(test-indentation "(r a\nb\nc\nd\ne\nf\ng"
                  "(r a\n   b\n   c\n   d\n   e\n   f\n   g")
(test-indentation "(r a b\nc d\ne f\ng h"
                  "(r a b\n   c d\n   e f\n   g h")
(test-indentation "(#:x\n1)"
                  "(#:x\n 1)")
(test-indentation "(#:x 0\n1)"
                  "(#:x 0\n 1)")
(test-indentation "(a b c d\n---)"
                  "(a b c d\n ---)")
(test-indentation "[---- \"β\"\na"
                  "[---- \"β\"\n a")


(define (test-magic-square-bracket which before after)
  (test
   (string->symbol (format "racket:test-magic-square-bracket-~a" which))
   (λ (x) (equal? x after))
   (λ ()
     (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])])
         (send f reflow-container)
         (send t insert ,before)
         (send (racket:get-keymap) call-function "maybe-insert-[]-pair-maybe-fixup-[]" t (new event%))
         (send t get-text))))))

(queue-sexp-to-mred `(preferences:set 'framework:automatic-parens #f))
(queue-sexp-to-mred `(preferences:set 'framework:fixup-open-parens #t))
(test-magic-square-bracket 'mt "" "(")
(test-magic-square-bracket 'mt2 "(() " "(() (")
(test-magic-square-bracket 'mt3 "([] " "([] [")
(test-magic-square-bracket 'mt4 "(\"" "(\"[")
(test-magic-square-bracket 'mt4 "(#\\" "(#\\[")
(test-magic-square-bracket 'let1 "(let " "(let (")
(test-magic-square-bracket 'let2 "(let (" "(let ([")
(test-magic-square-bracket 'let3 "(let loop " "(let loop (")
(test-magic-square-bracket 'let3 "(let loop (" "(let loop ([")
(test-magic-square-bracket 'let4 "(let rec (" "(let rec ([")
(test-magic-square-bracket 'cond1 "(cond " "(cond [")
(test-magic-square-bracket 'cond2 "(cond [" "(cond [(")
(test-magic-square-bracket 'with-syntax1 "(syntax-case x " "(syntax-case x (")
(test-magic-square-bracket 'with-syntax2 "(syntax-case x () " "(syntax-case x () [")
(test-magic-square-bracket 'with-syntax3 "(syntax-case 'x " "(syntax-case 'x (")
(test-magic-square-bracket 'with-syntax4 "(syntax-case 'x () " "(syntax-case 'x () [")
(test-magic-square-bracket 'with-syntax3 "(syntax-case #'x " "(syntax-case #'x (")
(test-magic-square-bracket 'with-syntax4 "(syntax-case #'x () " "(syntax-case #'x () [")
(test-magic-square-bracket 'local1 "(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 'for/fold1 "(for/fold (" "(for/fold ([")
(test-magic-square-bracket 'for/fold2 "(for/fold ([x 1]) (" "(for/fold ([x 1]) ([")

(define (test-insert-return/proc line before-txt before-pos after-txt after-pos #:tabify? [tabify? #t])
  (test
   (string->symbol (format "racket:test-insert-return ~a" line))
   (λ (x)
     (and (equal? (car x) after-pos)
          (equal? (cadr x) after-txt)))
   (λ ()
    (queue-sexp-to-mred
     `(let ()
       (define t (new (class racket:text%
                            (define/override (tabify-on-return?) ,tabify?)
                            (super-new))))
       (send t insert ,before-txt)
       (send t set-position ,before-pos)
       (send t insert-return)
       (list (send t get-start-position)
             (send t get-text)))))))

(define-syntax (test-insert-return stx)
  (syntax-case stx ()
    [(_  before-txt before-pos after-txt after-pos . args)
     (with-syntax ([line (syntax-line stx)])
       #'(test-insert-return/proc line before-txt before-pos after-txt after-pos . args))]))

(test-insert-return "" 0 "\n" 1)
(test-insert-return "" 0 "\n" 1 #:tabify? #f)
(test-insert-return " " 1 "\n" 1)
(test-insert-return " " 1 "\n" 1 #:tabify? #f)
(test-insert-return "( " 2 "(\n " 3)
(test-insert-return "( " 2 "(\n" 2 #:tabify? #f)
(test-insert-return "hellothere" 5 "hello\nthere" 6)
(test-insert-return "hellothere" 5 "hello\nthere" 6 #:tabify? #f)
(test-insert-return "#lang racket\n(+ 123 456)\n 4"      20 "#lang racket\n(+ 123\n  456)\n 4" 22)
(test-insert-return "#lang racket\n(+ 123 456)\n 4"      20 "#lang racket\n(+ 123\n456)\n 4" 20 #:tabify? #f)
(test-insert-return "#lang racket\n(+ 123      456)\n 4" 22 "#lang racket\n(+ 123\n  456)\n 4" 22)
(test-insert-return "#lang racket\n(+ 123      456)\n 4" 22 "#lang racket\n(+ 123\n   456)\n 4" 20 #:tabify? #f)
(test-insert-return "#lang racket\n(+ 123 \n   456)\n 4" 22 "#lang racket\n(+ 123 \n\n  456)\n 4" 24)
(test-insert-return "#lang racket\n(+ 123 \n   456)\n 4" 22 "#lang racket\n(+ 123 \n\n  456)\n 4" 22 #:tabify? #f)

(define (test-message-send/proc line before expected pos msg
                                #:check-result? [check-result? #f] 
                                . args)
  (define (maybe-quote x)
    (cond
      [(or (number? x) (boolean? x) (string? x)) x]
      [else `',x]))
  (test
   (string->symbol (format "line ~a: ~s"
                           line
                           `(,msg ,@(map maybe-quote args))))
   (λ (x) (equal? x expected))
   (λ ()
     (queue-sexp-to-mred
      `(let ()
         (define f (new frame% [label ""]))
         (define t (new racket:text%))
         (define ec (new editor-canvas% [parent f] [editor t]))
         (send t insert ,before)
         (send t set-position ,pos)
         (define ans (send t ,msg ,@(map maybe-quote args)))
         ,(if check-result?
              'ans
              '(send t get-text)))))))
(define-syntax (test-message-send stx)
  (syntax-case stx ()
    [(_ before expected pos mth . args)
     (with-syntax ([line (syntax-line stx)])
       #'(test-message-send/proc line before expected pos 'mth . args))]))

(test-message-send ""  "]"  0 insert-close-paren 0 #\] #t #t 'adjacent)
(test-message-send ""  "]"  0 insert-close-paren 0 #\] #t #t #f)
(test-message-send "(" "()" 1 insert-close-paren 1 #\] #t #t #f)
(test-message-send "(" "(]" 1 insert-close-paren 1 #\] #f #f #f)
(test-message-send ""  "]"  0 insert-close-paren 0 #\] #t #t 'forward)

(test-message-send "(1)" "1" 1 kill-enclosing-parens 1)
(test-message-send "(1 2 3)" "1 2 3" 3 kill-enclosing-parens 3)
(test-message-send "()" "" 1 kill-enclosing-parens 1)
(test-message-send "(1\n 2\n 3)" "1\n2\n3" 1 kill-enclosing-parens 1) ;; test tabify call

(test-message-send "abc" #f 1 backward-containing-sexp #:check-result? #t 1 3)

;; 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 (list (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:fixup-parens #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
;;  (NB. final-states could also contain 3-pairs of strings, the middle portion 
;;       representing text that is selected after the insertion)
(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)
                             (if (= 3 (length final-pair)) 
                                 ; final-pair could actually be a triplet 
                                 ; to indicate residual selection after insertion
                                 (list (string-length (car final-pair))
                                       (string-length (string-append (car final-pair)
                                                                     (cadr 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 escaped characters in a string never result in a pair of characters typed...
  ;; except for | which is a hard case to detect, because the tokenizer ends up
  ;; in an error state
  (unless (or (eq? #\| k))
      (test-parens-behavior/full (format "literal-~a-in-string" k)
                             "\"abc \\" "" "def\""
                             k
                             `([,(string-append "\"abc \\" (string k)) "def\""]
                               [,(string-append "\"abc \\" (string k)) "def\""])))
  ;; test that auto-parens has no effect in strings, *except for double quotes*
  (unless (eq? #\" k)
    (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 " "")
      ("end-of-line-comment-with-close-paren" ";; abc def " "   ) \n )")
      ("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 inserts double since string was not closed
(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)"]))

(test-parens-behavior/full 'close-adjusts-properly-when-space-follows-paren
                           "( x" "" ""
                           #\]
                           '(["( x)" "" ""]
                             ["( x)" "" ""]))
(test-parens-behavior/full 'close-adjusts-properly-when-inside-a-comment
                           "[();" "" ""
                           #\)
                           '(["[();)" "" ""]
                             ["[();)" "" ""]))
(test-parens-behavior/full 'close-adjusts-properly-when-inside-a-comment.2
                           "[;" "" "\n"
                           #\)
                           '(["[;)" "" "\n"]
                             ["[;)" "" "\n"]))
(test-parens-behavior/full 'close-adjusts-properly-when-inside-an-unclosed-string
                           "[()\"" "" ""
                           #\)
                           '(["[()\")" "" ""]
                             ["[()\")" "" ""]))
(test-parens-behavior/full 'close-adjusts-properly-when-inside-a-string
                           "[()\"" "" "\""
                           #\)
                           '(["[()\")" "" "\""]
                             ["[()\")" "" "\""]))

(test-parens-behavior/full 'close-adjusts-properly-when-no-containing-sexp
                           ")" "" ""
                           #\]
                           '([")]" "" ""]
                             [")]" "" ""]))

(test-parens-behavior/full '|"-splits-string|
                           " \"abcd" "" "efg\" "
                           #\"
                           '([" \"abcd\""     "efg\" "]
                             [" \"abcd\" "    "\"efg\" "]))
(test-parens-behavior/full '|"-splits-string-at-beginning|
                           " \"" "" "abcdefg\" "
                           #\"
                           '([" \"\""     "abcdefg\" "]
                             [" \"\" "    "\"abcdefg\" "]))
(test-parens-behavior/full '|"-splits-out-selected-string|
                           " \"abc" "def" "ghi\" "
                           #\"
                           '([" \"abc\"" "" "ghi\" "]
                             ; test that "def" remains selected afterwards...
                             [" \"abc\" "   "\"def\""    " \"ghi\" "]))



#| 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)" ""])))