
(load-relative "loadtest.ss")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                            Editor Tests                                    ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;; File and port save/load tests

(define (run-save/load-tests editor% insert reset?)
  (when reset?
    (map (lambda (f)
	   (when (file-exists? f) (delete-file f)))
	 '("tmp99" "tmp98" "tmp97" "tmp96" "tmp95")))
  (let* ([mode #f]
	 [editor+% (class editor%
		     (define/augment (on-load-file path -mode)
		       (set! mode -mode))
		     (define/augment (on-save-file path -mode)
		       (set! mode -mode))
		     (super-new))]
	 [e (make-object editor+%)]
	 [ck-text (lambda (path)
		    (when (eq? editor% text%)
		      (st 'text e get-file-format)
		      (when path
			(test "Hello" 'content (with-input-from-file path
						 (lambda ()
						   (read-string 100)))))))]
	 [ck-binary (lambda (path)
		      (when (eq? editor% text%)
			(st 'standard e get-file-format))
		      (when path
			(test #"WXME" 'content (with-input-from-file path
						 (lambda ()
						   (read-bytes 4))))))]
	 [ck-mode (lambda (-mode)
		    (test (if (eq? editor% pasteboard%) 
			      (if (eq? -mode 'copy)
				  'copy
				  'standard) 
			      -mode) 
			  'mode mode))])
    (insert e "Hello")
    (st #t e is-modified?)
    (st #f e get-filename)
    (when (eq? editor% text%)
      (st 'standard e get-file-format))
    (st #t e save-file "tmp99" 'text)
    (ck-mode 'text)
    (st (string->path "tmp99") e get-filename)
    (st #f e is-modified?)
    (ck-text "tmp99")
    (insert e "Ack")
    (st #t e is-modified?)
    (st #t e load-file "tmp99" 'guess)
    (ck-mode 'guess)
    (ck-text #f)
    (st #f e is-modified?)
    (st "Hello" e get-flattened-text)
    (let ([now (file-or-directory-modify-seconds "tmp99")])
      (st #t e save-file "tmp99" 'same)
      (ck-text "tmp99")
      (st (string->path "tmp99") e get-filename)
      (let ([later (file-or-directory-modify-seconds "tmp99")])
	(test #t 'file-date (now . <= . later))
	(st #t e save-file "tmp98" 'standard)
	(test #f 'file-date (later . < . (file-or-directory-modify-seconds "tmp99")))
	(ck-binary "tmp98")
	(st #t e load-file "tmp98" 'guess)
	(ck-mode 'guess)
	(ck-binary #f)
	(st #t e load-file "tmp98" 'text)
	(ck-mode 'text)
	(ck-text #f)
	(when (eq? editor% text%)
	  (st "WXME" e get-text 0 4))
	(st #t e load-file "tmp98" 'same)
	(ck-mode 'same)
	(ck-text #f)
	(when (eq? editor% text%)
	  (st "WXME" e get-text 0 4))
	(st #t e load-file "tmp98" 'guess)
	(ck-mode 'guess)
	(ck-binary #f)
	(st "Hello" e get-flattened-text)))
    (let ([target "tmp97"])
      ;; Check [non-]temporary file names
      (set! e (make-object (class editor+%
			     (define/override (put-file file dir)
			       (string->path target))
			     (super-new))))
      (insert e "Howdy")
      (st #t e is-modified?)
      (set! mode #f)
      (st #t e save-file #f 'copy)
      (ck-mode 'copy)
      (set! target "tmp95")
      (st #t e is-modified?)
      (ck-binary "tmp97")
      (stv e set-filename "tmp96")
      (st #t e save-file #f)
      (st #f e is-modified?)
      (st (string->path "tmp96") e get-filename)
      (ck-binary "tmp96")
      (stv e set-filename "tmp96" #t)
      (st #t e save-file #f)
      (ck-mode 'same)
      (st (string->path "tmp95") e get-filename)
      (stv e set-filename "tmp96" #t)
      (st #t e save-file "")
      (st (string->path "tmp95") e get-filename)
      (ck-binary "tmp95")
      (st "Howdy" e get-flattened-text)
      (when (eq? editor% text%)
	(stv e set-position 100))
      (st #t e insert-file "tmp98")
      (st "HowdyHello" e get-flattened-text)
      (st #t e insert-file "tmp99")
      (st "HowdyHelloHello" e get-flattened-text)
      (st (string->path "tmp95") e get-filename))))
    
(define text-insert (lambda (e t) (send e insert t)))
(define pb-insert (lambda (e t) (send e insert (make-object string-snip% t))))

(map (lambda (reset?)
       (run-save/load-tests text% text-insert reset?)
       (run-save/load-tests pasteboard% pb-insert reset?))
     '(#t #f))

;; Test DrScheme-style format change in `on-save':
(define (run-on-save-tests editor% insert)
  (let* ([editor+% (if (eq? editor% text%)
		       (class editor%
			 (inherit set-file-format)
			 (define/augment (on-save-file path -mode)
			   (set-file-format 'standard))
			 (super-new))
		       editor%)]
	 [e (make-object editor+%)])
    (insert e "Hello")
    (st #t e is-modified?)
    (st #f e get-filename)
    (when (eq? editor% text%)
      (stv e set-file-format 'text)
      (st 'text e get-file-format))
    (st #t e save-file "tmp99" 'same)
    (when (eq? editor% text%)
      (st 'standard e get-file-format))
    (send e load-file "tmp99" 'guess)
    (st "Hello" e get-flattened-text)))
(run-on-save-tests text% text-insert)
(run-on-save-tests pasteboard% pb-insert)

;;;;;; Undo tests

(define e (make-object text%))

(stv e insert "Hello")
(st #t e is-modified?)
(stv e undo)
(st #f e is-modified?)
(stv e redo)
(st #t e is-modified?)
(stv e set-modified #f)
(st #f e is-modified?)
(stv e undo)
(st #t e is-modified?)
(stv e redo)
(st #f e is-modified?)
(stv e undo)
(st #t e is-modified?)
(stv e redo)
(st #f e is-modified?)
(stv e undo)
(stv e set-modified #f)
(st #f e is-modified?)
(stv e redo)
(st #t e is-modified?)
(st "Hello" e get-text)
(define undone? #f)
(stv e add-undo (letrec ([f (lambda ()
			      (set! undone? #t)
			      (send e add-undo f) ; reinstall self!
			      #f)])
		  f))
(stv e undo)
(st "Hello" e get-text)
(test #t 'undone? undone?)
(stv e undo)
(st "" e get-text)
(set! undone? #f)
(stv e redo)
(st "Hello" e get-text)
(test #f 'undone? undone?)
(stv e redo)
(st "Hello" e get-text)
(test #t 'undone? undone?)
(set! undone? #f)
(stv e redo)
(st "Hello" e get-text)
(test #f 'undone? undone?)
(stv e insert "x")
(st "Hellox" e get-text)
(stv e add-undo (lambda ()
		  (set! undone? #t)
		  #t)) ; do next one, too
(stv e undo)
(test #t 'undone? undone?)
(st "Hello" e get-text)

;; Editor ports

(let ([e (make-object text%)]
      [res-mode? #f])
  (stv e insert "hello")
  (let ([p (open-input-text-editor e)])
    (test 'hello 'read (read p))
    (test eof 'read (read p)))
  (stv e insert " there")
  (let ([p (open-input-text-editor e)])
    (test 'hello 'read (read p))
    (test 'there 'read (read p))
    (test eof 'read (read p)))
  (stv e insert (make-object
		 (class* snip% 
		   (readable-snip<%>)
		   (define/public (read-special src line col pos)
		     (if res-mode?
			 'res
			 (error 'ack)))
		   (super-new))))
  (let ([p (open-input-text-editor e)])
    (port-count-lines! p)
    (test '(1 0 1) 'pos (call-with-values (lambda () (port-next-location p)) list))
    (test 'hello 'read (read p))
    (test '(1 5 6) 'pos (call-with-values (lambda () (port-next-location p)) list))
    (test 'there 'read (read p))
    (test '(1 11 12) 'pos (call-with-values (lambda () (port-next-location p)) list))
    (test 'got-ack 'read (with-handlers ([exn:fail? (lambda (x)
						      'got-ack)])
			   (read p)))
    (test '(1 12 13) 'pos (call-with-values (lambda () (port-next-location p)) list))
    (test eof 'read (read p)))
  (set! res-mode? #t)
  (let ([p (open-input-text-editor e)])
    (port-count-lines! p)
    (test 'hello 'read (read p))
    (test 'there 'read (read p))
    (test 'res 'read (read p))
    (test '(1 12 13) 'pos (call-with-values (lambda () (port-next-location p)) list))
    (test eof 'read (read p)))
  (stv e insert (make-object image-snip% (build-path
					  (collection-path "icons") 
					  "plt.gif")))
  (let ([p (open-input-text-editor e)])
    (test 'hello 'read (read p))
    (test 'there 'read (read p))
    (test 'res 'read (read p))
    (test #t 'read (is-a? (read p) image-snip%))))

(let ()
  (define x (new text%))
  (define s (make-object image-snip% "no-such-image.jpg" 
			 'unknown #f #f))
  (define t (make-object image-snip% "no-such-image.jpg" 
			 'unknown #f #f))
  (send x insert s 0 'same #t)
  (send x insert t 1 'same #t)
  (send x insert "1" 2 'same #t)
  
  (let ([i (open-input-text-editor x 0 'end (lambda (x) (eq? x s)))])
    (test #t 'peek-s (peek-byte-or-special i 0))
    (test #t 'read-s (read-byte-or-special i))
    (test #f 'peek-t (peek-byte-or-special i 0))
    (test 49 'read-1 (peek-byte-or-special i 1))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                            Snips and Streams                               ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (mk-number-snip-class% term?)
  (class snip-class%
    (define/override (read f)
      (let* ([number-str (if term?
			     (send f get-bytes)
			     (send f get-unterminated-bytes))]
             [number (string->number (bytes->string/utf-8 number-str))]
             [decimal-prefix (bytes->string/utf-8 (if term?
						      (send f get-bytes)
						      (send f get-unterminated-bytes)))]
             [snip
              (instantiate number-snip% ()
                [number number]
                [decimal-prefix decimal-prefix])])
        snip))
    (super-instantiate ())))

(define snip-class (make-object (mk-number-snip-class% #t)))
(send snip-class set-classname (format "~s" `(lib "number-snip.ss" "drscheme" "private")))
(send (get-the-snip-class-list) add snip-class)

(define snip-class2 (make-object (mk-number-snip-class% #f)))
(send snip-class2 set-classname (format "~s" `(lib "number-snip-two.ss" "drscheme" "private")))
(send (get-the-snip-class-list) add snip-class2)

(define (mk-number-snip% snip-class term?)
  (define self%
    (class snip%
      (init-field number)
      (define/public (get-number) number)
      (define/public (get-prefix) decimal-prefix)
      (init-field [decimal-prefix ""])
      (define/override (write f)
	(let ([num (string->bytes/utf-8 (number->string number))]
	      [pfx (string->bytes/utf-8 decimal-prefix)])
	  (if term?
	      (begin
		(send f put num)
		(send f put pfx))
	      (begin
		(unless (eq? 'ok
			     (with-handlers ([exn:fail? (lambda (x) 'ok)])
			       (send f put 5 #"123")
			       'not-ok))
		  (error "too-long write should have failed"))
		(send f put (bytes-length num) num)
		(send f put (bytes-length pfx) pfx)))))
      (define/override (copy)
	(instantiate self% ()
		     [number number]
		     [decimal-prefix decimal-prefix]))
      (inherit get-style)
      (super-instantiate ())
      (inherit set-snipclass set-flags get-flags)
      (set-snipclass snip-class)))
  self%)

(define number-snip% (mk-number-snip% snip-class #t))
(define number-snip2% (mk-number-snip% snip-class2 #f))

(define (snip-test term?)
  (define t (new text%))
  (define t2 (new text%))
  (send t insert (new (if term? number-snip% number-snip2%)
		      [number 1/2]))
  (send t set-position 0 1)
  (send t copy)
  ;; Under X, force snip to be marshalled:
  (let ([s (send the-clipboard get-clipboard-data "WXME" 0)])
    (send the-clipboard set-clipboard-client
	  (make-object (class clipboard-client%
			 (define/override (get-data fmt)
			   (and (string=? fmt "WXME")
				s))
			 (inherit add-type)
			 (super-new)
			 (add-type "WXME")))
	  0))
  (send t2 paste)
  (let ([s (send t2 find-first-snip)])
    (st 1/2 s get-number)
    (st "" s get-prefix)))

(snip-test #t)
(snip-test #f)

(let ()
  (define orig-snip (make-object string-snip% "hello"))
  
  (define out (make-object editor-stream-out-bytes-base%))
  (define out-stream (make-object editor-stream-out% out))
  
  (define _ (send orig-snip write out-stream))
  
  (define in (make-object editor-stream-in-bytes-base% (send out get-bytes)))
  (define in-stream (make-object editor-stream-in% in))
  
  (define new-snip
    (send (send (get-the-snip-class-list) 
		find 
		(send (send (new string-snip%) get-snipclass) get-classname))
	  read in-stream))
  
  (st "hello" new-snip get-text 0 10))

;; ----------------------------------------
;; Check CRLF conversion

(let ([crlf-s (apply
	       string-append
	       (let loop ([n 100])
		 (if (zero? n)
		     null
		     (cons (make-string (random 40) #\a)
			   (cons "\r\n"
				 (loop (sub1 n)))))))]
      [e (new text%)])
  (let ([lf-s (regexp-replace* #rx"\r\n" crlf-s "\n")]
	[lflf-s (regexp-replace* #rx"\r\n" crlf-s "\n\n")])
    (st "" e get-text 0 'eof)
    (send e insert-port (open-input-string crlf-s))
    (st lf-s e get-text 0 'eof)
    (send e erase)
    (send e insert crlf-s)
    (st lflf-s e get-text 0 'eof)))

;; ----------------------------------------
;; Check lines and paras without display, but with a max width

(define t (new text%))
(send t insert "abc\ndef\nghi\n")
(send t set-max-width 955)
(st 0 t line-start-position 0)
(st 4 t line-start-position 1)
(st 8 t line-start-position 2)
(st 0 t paragraph-start-position 0)
(st 4 t paragraph-start-position 1)
(st 8 t paragraph-start-position 2)
(st 3 t paragraph-end-position 0)
(st 7 t paragraph-end-position 1)
(st 11 t paragraph-end-position 2)
(st 0 t paragraph-start-line 0)
(st 1 t paragraph-start-line 1)
(st 2 t paragraph-start-line 2)
(st 0 t line-paragraph 0)
(st 1 t line-paragraph 1)
(st 2 t line-paragraph 2)
(st 0 t position-paragraph 0)
(st 0 t position-paragraph 3)
(st 1 t position-paragraph 4)
(st 1 t position-paragraph 7)
(st 2 t position-paragraph 8)
(st 2 t position-paragraph 11)

;; ----------------------------------------

(report-errs)

