(module snipfile racket/base (require racket/class racket/port syntax/moddep (prefix-in wx: "kernel.rkt") (prefix-in wx: racket/snip/private/snip) "check.rkt" "editor.rkt") (provide open-input-text-editor open-input-graphical-file text-editor-load-handler open-output-text-editor) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define empty-string (make-bytes 0)) ;; open-input-text-editor : (instanceof text%) num num -> input-port ;; creates a user port whose input is taken from the text%, ;; starting at position `start-in' ;; and ending at position `end'. (define open-input-text-editor (lambda (text [start 0] [end 'end] [snip-filter values] [port-name text] [expect-to-read-all? #f] #:lock-while-reading? [lock-while-reading? #f]) ;; Check arguments: (unless (text . is-a? . text%) (raise-argument-error 'open-input-text-editor "(is-a?/c text%)" text)) (check-non-negative-integer 'open-input-text-editor start) (unless (or (eq? end 'end) (and (integer? end) (exact? end) (not (negative? end)))) (raise-argument-error 'open-input-text-editor "(or/c exact-nonnegative-integer? 'end)" end)) (let ([last (send text last-position)]) (when (start . > . last) (raise-range-error 'open-input-text-editor "editor" "starting " start text 0 last #f)) (unless (eq? end 'end) (unless (<= start end last) (raise-range-error 'open-input-text-editor "editor" "ending " end text start last 0)))) (let ([end (if (eq? end 'end) (send text last-position) end)] [snip (send text find-snip start 'after-or-none)]) ;; If the region is small enough, and if the editor contains ;; only string snips, then it's probably better to move ;; all of the text into a string port: (if (or (not snip) (and (is-a? snip wx:string-snip%) (let ([s (send text find-next-non-string-snip snip)]) (or (not s) ((send text get-snip-position s) . >= . end))))) (if (or expect-to-read-all? ((- end start) . < . 4096)) ;; It's all text, and it's short enough: just read it into a string (open-input-string (send text get-text start end) port-name) ;; It's all text, so the reading process is simple: (let ([start start]) (when lock-while-reading? (send text begin-edit-sequence) (send text lock #t)) (let-values ([(pipe-r pipe-w) (make-pipe)]) (make-input-port/read-to-peek port-name (lambda (s) (let ([v (read-bytes-avail!* s pipe-r)]) (if (eq? v 0) (let ([n (min 4096 (- end start))]) (if (zero? n) (begin (close-output-port pipe-w) (when lock-while-reading? (set! lock-while-reading? #f) (send text lock #f) (send text end-edit-sequence)) eof) (begin (write-string (send text get-text start (+ start n)) pipe-w) (set! start (+ start n)) (let ([ans (read-bytes-avail!* s pipe-r)]) (when lock-while-reading? (when (eof-object? ans) (set! lock-while-reading? #f) (send text lock #f) (send text edit-edit-sequence))) ans)))) v))) (lambda (s skip general-peek) (let ([v (peek-bytes-avail!* s skip #f pipe-r)]) (if (eq? v 0) (general-peek s skip) v))) void)))) ;; General case, which handles non-text context: (with-method ([gsp (text get-snip-position)] [grn (text get-revision-number)] [fs (text find-snip)]) (let-values ([(pipe-r pipe-w) (make-pipe)]) (let* ([get-text-generic (generic wx:snip% get-text)] [get-count-generic (generic wx:snip% get-count)] [next-generic (generic wx:snip% next)] [revision (grn)] [next? #f] [snip-end-position (+ (gsp snip) (send-generic snip get-count-generic))] [update-str-to-snip (lambda (skip to-str) (if snip (let ([snip-start (gsp snip)]) (cond [(snip-start . >= . end) (set! snip #f) (set! next? #f) 0] [(is-a? snip wx:string-snip%) (set! next? #t) (let ([c (min (- (send-generic snip get-count-generic) skip) (- end snip-start))]) (write-string (send-generic snip get-text-generic skip c) pipe-w) (read-bytes-avail!* to-str pipe-r))] [else (set! next? #f) 0])) (begin (set! next? #f) 0)))] [next-snip (lambda (to-str) (cond [(= revision (grn)) (set! snip (send-generic snip next-generic)) (set! snip-end-position (and snip (+ (gsp snip) (send-generic snip get-count-generic)))) (update-str-to-snip 0 to-str)] [else (set! revision (grn)) (define old-snip-end-position snip-end-position) (set! snip (fs snip-end-position 'after-or-none)) (define snip-start-position (and snip (gsp snip))) (set! snip-end-position (and snip (+ snip-start-position (send-generic snip get-count-generic)))) (update-str-to-snip (if snip (- old-snip-end-position snip-start-position) 0) to-str)]))] [read-chars (lambda (to-str) (cond [next? (next-snip to-str)] [snip (let ([the-snip (snip-filter snip)]) (next-snip empty-string) (lambda (file line col ppos) (if (is-a? the-snip wx:snip%) (if (is-a? the-snip wx:readable-snip<%>) (send the-snip read-special file line col ppos) (send the-snip copy)) the-snip)))] [else eof]))] [close (lambda () (void))] [port (make-input-port/read-to-peek port-name (lambda (s) (let* ([v (read-bytes-avail!* s pipe-r)] [res (if (eq? v 0) (read-chars s) v)]) (when (eof-object? res) (when lock-while-reading? (set! lock-while-reading? #f) (send text lock #f) (send text end-edit-sequence))) res)) (lambda (s skip general-peek) (let ([v (peek-bytes-avail!* s skip #f pipe-r)]) (if (eq? v 0) (general-peek s skip) v))) close)]) (when lock-while-reading? (send text begin-edit-sequence) (send text lock #t)) (if (is-a? snip wx:string-snip%) ;; Special handling for initial snip string in ;; case it starts too early: (let* ([snip-start (gsp snip)] [skip (- start snip-start)] [c (min (- (send-generic snip get-count-generic) skip) (- end snip-start))]) (set! next? #t) (display (send-generic snip get-text-generic skip c) pipe-w)) (update-str-to-snip 0 empty-string)) port))))))) (define (jump-to-submodule in-port expected-module k) (let ([header (bytes-append #"^#~" (bytes (string-length (version))) (regexp-quote (string->bytes/utf-8 (version))) #"D")]) (cond [(regexp-match-peek header in-port) ;; The input has a submodule table: (define encoded-expected (apply bytes-append (for/list ([n (in-list (if (pair? expected-module) (cdr expected-module) '()))]) (define s (string->bytes/utf-8 (symbol->string n))) (define l (bytes-length s)) (bytes-append (if (l . < . 255) (bytes l) (bytes 255 (bitwise-and l 255) (bitwise-and (arithmetic-shift l -8) 255) (bitwise-and (arithmetic-shift l -16) 255) (bitwise-and (arithmetic-shift l -24) 255))) s)))) (define (skip-bytes amt) (if (file-stream-port? in-port) (file-position in-port (+ (file-position in-port) amt)) (read-bytes amt in-port))) (define len (+ 2 1 (string-length (version)) 1 4)) ; 4 for table count (skip-bytes len) (let loop ([pos len]) ;; Each node in the table's btree is (define (read-num) (integer-bytes->integer (read-bytes 4 in-port) #f #f)) (define len (read-num)) (define new-pos (+ pos 4)) (define name (read-bytes len in-port)) (define code-start (read-num)) (define code-len (read-num)) (define left (read-num)) (define right (read-num)) (define after-pos (+ new-pos len 16)) (cond [(bytes=? encoded-expected name) (skip-bytes (- code-start after-pos)) (k #f)] [(bytescomplete-path filename))]) (if expected-module (with-module-reading-parameterization (lambda () (jump-to-submodule in-port expected-module (lambda (check-second?) (with-module-reading-parameterization (lambda () (let* ([first (read-syntax src in-port)] [module-ized-exp (check-module-form first expected-module filename)] [second (if check-second? (read in-port) eof)]) (unless (eof-object? second) (raise-syntax-error 'text-editor-load-handler (format "expected only a `module' declaration for `~s', but found an extra expression" expected-module) second)) (eval module-ized-exp)))))))) (let loop ([last-time-values (list (void))]) (let ([exp (read-syntax src in-port)]) (if (eof-object? exp) (apply values last-time-values) (call-with-values (lambda () (call-with-continuation-prompt (lambda () (eval (datum->syntax #f (cons '#%top-interaction exp) exp))) (default-continuation-prompt-tag) (lambda args (apply abort-current-continuation (default-continuation-prompt-tag) args)))) (lambda x (loop x))))))))) (lambda () (close-input-port in-port))) (begin (close-input-port in-port) (original-load-handler filename expected-module))))) ;; build-input-port : string -> (values input any) ;; constructs an input port for the load handler. Also ;; returns a value representing the source of code read from the file. (define (build-input-port filename expected-module) (let ([p (open-input-file filename #:for-module? expected-module)]) (port-count-lines! p) (define-values (new-p changed?) (cond [(regexp-match-peek #rx#"^(?:#reader[(]lib\"read[.]ss\"\"wxme\"[)])?WXME01[0-9][0-9] ##[ \r\n]" p) (let ([t (make-object text%)]) (send t insert-port p 'standard) (close-input-port p) (values (open-input-text-editor t 0 'end values filename) #t))] [else (values p #f)])) (when changed? (port-count-lines! new-p)) ; in case it's new (values new-p filename changed?))) (define (open-input-graphical-file filename) (let-values ([(p name wxme?) (build-input-port filename #f)]) p)) (define open-output-text-editor (lambda (text [start 'end] [special-filter values] [port-name text] #:eventspace [eventspace (wx:current-eventspace)]) (define pos (if (eq? start 'end) (send text last-position) (min start (send text last-position)))) (define-values (in out) (make-pipe)) (define cvt (bytes-open-converter "UTF-8-permissive" "UTF-8")) (define raw-buffer (make-bytes 128)) (define utf8-buffer (make-bytes 128)) (define (show s) (define (insert) (send text begin-edit-sequence) (send text insert s pos) (send text end-edit-sequence)) (if (and eventspace (and (not (eq? (current-thread) (wx:eventspace-handler-thread eventspace))))) (parameterize ([wx:current-eventspace eventspace]) (wx:queue-callback insert #f)) (insert)) (set! pos (+ (string-length s) pos))) (define (flush-text) (let ([cnt (peek-bytes-avail!* raw-buffer 0 #f in)]) (when (positive? cnt) (let-values ([(got used status) (bytes-convert cvt raw-buffer 0 cnt utf8-buffer)]) (cond [(positive? got) (read-bytes-avail!* raw-buffer in 0 used) (show (bytes->string/utf-8 utf8-buffer #\? 0 got)) (flush-text)] [(eq? status 'error) (read-byte in) (show "?") (flush-text)]))))) (define (force-text) (when (byte-ready? in) (show "?") (read-byte in) (flush-text) (force-text))) (define port (make-output-port text always-evt (lambda (s start end nonblock? breakable?) ;; Put bytes into pipe: (write-bytes s out start end) ;; Extract as many string characters as are ready: (flush-text) (- end start)) (lambda () (force-text)) (lambda (special nonblock? breakable?) (let ([special (special-filter special)]) (cond [(special . is-a? . wx:snip%) (force-text) (send text insert special pos) (set! pos (+ pos (send special get-count)))] [else (display special port)])) #t) #f #f (lambda () (let ([line (send text position-line pos)]) (values (add1 line) (- pos (send text line-start-position line)) (add1 pos)))) void (add1 pos))) port)))