
I started from tabs that are not on the beginning of lines, and in several places I did further cleanings. If you're worried about knowing who wrote some code, for example, if you get to this commit in "git blame", then note that you can use the "-w" flag in many git commands to ignore whitespaces. For example, to see per-line authors, use "git blame -w <file>". Another example: to see the (*much* smaller) non-whitespace changes in this (or any other) commit, use "git log -p -w -1 <sha1>".
2090 lines
90 KiB
Racket
2090 lines
90 KiB
Racket
#lang racket/base
|
|
|
|
#|
|
|
|
|
TODO
|
|
- should a GC should happen on each execution? (or perhaps better, each kill?)
|
|
- front-end methods have new signature
|
|
|
|
|
|
|#
|
|
; =Kernel= means in DrRacket's thread and parameterization
|
|
;
|
|
; =User= means the user's thread and parameterization
|
|
;
|
|
; =Handler= means in the handler thread of some eventspace; it must
|
|
; be combined with either =Kernel= or =User=
|
|
|
|
(require racket/class
|
|
racket/path
|
|
racket/pretty
|
|
racket/unit
|
|
racket/list
|
|
racket/port
|
|
|
|
string-constants
|
|
setup/xref
|
|
racket/gui/base
|
|
framework
|
|
browser/external
|
|
"drsig.rkt"
|
|
"local-member-names.rkt"
|
|
"stack-checkpoint.rkt"
|
|
|
|
;; the dynamic-require below loads this module,
|
|
;; so we make the dependency explicit here, even
|
|
;; tho nothing is used from this module.
|
|
planet/terse-info)
|
|
|
|
(provide rep@ with-stack-checkpoint)
|
|
|
|
(define orig-output-port (current-output-port))
|
|
(define (oprintf . args) (apply fprintf orig-output-port args))
|
|
|
|
(define no-breaks-break-parameterization
|
|
(parameterize-break #f (current-break-parameterization)))
|
|
|
|
(define-unit rep@
|
|
(import [prefix drracket:init: drracket:init^]
|
|
[prefix drracket:language-configuration: drracket:language-configuration/internal^]
|
|
[prefix drracket:language: drracket:language^]
|
|
[prefix drracket:app: drracket:app^]
|
|
[prefix drracket:frame: drracket:frame^]
|
|
[prefix drracket:unit: drracket:unit^]
|
|
[prefix drracket:text: drracket:text^]
|
|
[prefix drracket:help-desk: drracket:help-desk^]
|
|
[prefix drracket:debug: drracket:debug^]
|
|
[prefix drracket:eval: drracket:eval^]
|
|
[prefix drracket:module-language: drracket:module-language/int^]
|
|
[prefix drracket: drracket:interface^])
|
|
(export (rename drracket:rep^
|
|
[-text% text%]
|
|
[-text<%> text<%>]))
|
|
|
|
(define -text<%>
|
|
(interface ((class->interface text%)
|
|
text:ports<%>
|
|
editor:file<%>
|
|
racket:text<%>
|
|
color:text<%>)
|
|
reset-highlighting
|
|
highlight-errors
|
|
highlight-errors/exn
|
|
|
|
get-user-custodian
|
|
get-user-eventspace
|
|
get-user-thread
|
|
get-user-namespace
|
|
|
|
get-definitions-text
|
|
|
|
kill-evaluation
|
|
|
|
display-results
|
|
|
|
run-in-evaluation-thread
|
|
after-many-evals
|
|
on-execute
|
|
|
|
shutdown
|
|
|
|
get-error-ranges
|
|
reset-error-ranges
|
|
|
|
reset-console
|
|
|
|
copy-prev-previous-expr
|
|
copy-next-previous-expr
|
|
copy-previous-expr
|
|
|
|
|
|
initialize-console
|
|
|
|
get-prompt
|
|
insert-prompt
|
|
get-context))
|
|
|
|
(define sized-snip<%>
|
|
(interface ((class->interface snip%))
|
|
;; get-character-width : -> number
|
|
;; returns the number of characters wide the snip is,
|
|
;; for use in pretty printing the snip.
|
|
get-character-width))
|
|
|
|
;; current-language-settings : (parameter language-setting)
|
|
;; set to the current language and its setting on the user's thread.
|
|
(define current-language-settings (make-parameter #f))
|
|
|
|
;; current-rep : (parameter (union #f (instanceof rep:text%)))
|
|
;; the repl that controls the evaluation in this thread.
|
|
(define current-rep (make-parameter #f))
|
|
|
|
;; a port that accepts values for printing in the repl
|
|
(define current-value-port (make-parameter #f))
|
|
|
|
;; drracket-error-display-handler : (string (union #f exn) -> void
|
|
;; =User=
|
|
;; the timing is a little tricky here.
|
|
;; the file icon must appear before the error message in the text, so that happens first.
|
|
;; the highlight must be set after the error message, because inserting into the text resets
|
|
;; the highlighting.
|
|
(define (drracket-error-display-handler msg exn)
|
|
(drracket:debug:error-display-handler/stacktrace msg exn))
|
|
|
|
(define (main-user-eventspace-thread?)
|
|
(let ([rep (current-rep)])
|
|
(and rep
|
|
(eq? (eventspace-handler-thread (send rep get-user-eventspace))
|
|
(current-thread)))))
|
|
|
|
(define drs-bindings-keymap (make-object keymap:aug-keymap%))
|
|
|
|
(let* ([get-frame
|
|
(λ (obj)
|
|
(and (is-a? obj editor<%>)
|
|
(let ([canvas (send obj get-canvas)])
|
|
(and canvas
|
|
(let ([frame (send canvas get-top-level-window)])
|
|
(and (is-a? frame drracket:unit:frame%)
|
|
frame))))))]
|
|
[add-drs-function
|
|
(λ (name f)
|
|
(send drs-bindings-keymap add-function name
|
|
(λ (obj evt) (cond [(get-frame obj) => f]))))]
|
|
[show-tab
|
|
(λ (i)
|
|
(λ (obj evt)
|
|
(let ([fr (get-frame obj)])
|
|
(and fr
|
|
(is-a? fr drracket:unit:frame<%>)
|
|
(< i (send fr get-tab-count))
|
|
(begin (send fr change-to-nth-tab i)
|
|
#t)))))])
|
|
(for ([i (in-range 1 10)])
|
|
(send drs-bindings-keymap add-function (format "show-tab-~a" i) (show-tab (- i 1))))
|
|
(send drs-bindings-keymap add-function "search-help-desk"
|
|
(λ (obj evt)
|
|
(if (not (and (is-a? obj text%) (get-frame obj))) ; is `get-frame' needed?
|
|
(drracket:help-desk:help-desk)
|
|
(let* ([start (send obj get-start-position)]
|
|
[end (send obj get-end-position)]
|
|
[str (if (= start end)
|
|
(drracket:unit:find-symbol obj start)
|
|
(send obj get-text start end))])
|
|
(if (or (not str) (equal? "" str))
|
|
(drracket:help-desk:help-desk)
|
|
(let* ([l (send obj get-canvas)]
|
|
[l (and l (send l get-top-level-window))]
|
|
[l (and l (is-a? l drracket:unit:frame<%>) (send l get-definitions-text))]
|
|
[l (and l (send l get-next-settings))]
|
|
[l (and l (drracket:language-configuration:language-settings-language l))]
|
|
[ctxt (and l (send l capability-value 'drscheme:help-context-term))]
|
|
[name (and l (send l get-language-name))])
|
|
(drracket:help-desk:help-desk
|
|
str (and ctxt (list ctxt name)))))))))
|
|
|
|
;; keep this in case people use it in their keymaps
|
|
(add-drs-function "execute" (λ (frame) (send frame execute-callback)))
|
|
|
|
(add-drs-function "run" (λ (frame) (send frame execute-callback)))
|
|
(add-drs-function "next-tab" (λ (frame) (send frame next-tab)))
|
|
(add-drs-function "prev-tab" (λ (frame) (send frame prev-tab)))
|
|
(add-drs-function "collapse" (λ (frame) (send frame collapse)))
|
|
(add-drs-function "split" (λ (frame) (send frame split)))
|
|
|
|
(add-drs-function "jump-to-previous-error-loc"
|
|
(λ (frame) (send frame jump-to-previous-error-loc)))
|
|
(add-drs-function "jump-to-next-error-loc"
|
|
(λ (frame) (send frame jump-to-next-error-loc)))
|
|
|
|
(add-drs-function "send-toplevel-form-to-repl" (λ (frame) (send frame send-toplevel-form-to-repl #f)))
|
|
(add-drs-function "send-selection-to-repl" (λ (frame) (send frame send-selection-to-repl #f)))
|
|
(add-drs-function "send-toplevel-form-to-repl-and-go" (λ (frame) (send frame send-toplevel-form-to-repl #t)))
|
|
(add-drs-function "send-selection-to-repl-and-go" (λ (frame) (send frame send-selection-to-repl #t)))
|
|
(add-drs-function "move-to-interactions" (λ (frame) (send frame move-to-interactions))))
|
|
|
|
(send drs-bindings-keymap map-function "m:p" "jump-to-previous-error-loc")
|
|
(send drs-bindings-keymap map-function "m:n" "jump-to-next-error-loc")
|
|
(send drs-bindings-keymap map-function "esc;p" "jump-to-previous-error-loc")
|
|
(send drs-bindings-keymap map-function "esc;n" "jump-to-next-error-loc")
|
|
(send drs-bindings-keymap map-function "c:x;`" "jump-to-next-error-loc")
|
|
|
|
(send drs-bindings-keymap map-function "f5" "run")
|
|
(send drs-bindings-keymap map-function "f1" "search-help-desk")
|
|
(send drs-bindings-keymap map-function "c:tab" "next-tab")
|
|
(send drs-bindings-keymap map-function "c:s:tab" "prev-tab")
|
|
(send drs-bindings-keymap map-function "c:pagedown" "next-tab")
|
|
(send drs-bindings-keymap map-function "c:pageup" "prev-tab")
|
|
|
|
(send drs-bindings-keymap map-function "c:x;0" "collapse")
|
|
(send drs-bindings-keymap map-function "c:x;2" "split")
|
|
|
|
(send drs-bindings-keymap map-function "c:c;c:z" "move-to-interactions")
|
|
|
|
(for ([i (in-range 1 10)])
|
|
(send drs-bindings-keymap map-function
|
|
(format "a:~a" i)
|
|
(format "show-tab-~a" i))
|
|
(send drs-bindings-keymap map-function
|
|
(format "m:~a" i)
|
|
(format "show-tab-~a" i)))
|
|
|
|
(define (get-drs-bindings-keymap) drs-bindings-keymap)
|
|
|
|
;; drs-bindings-keymap-mixin :
|
|
;; ((implements editor:keymap<%>) -> (implements editor:keymap<%>))
|
|
;; for any x that is an instance of the resulting class,
|
|
;; (is-a? (send (send x get-canvas) get-top-level-frame) drracket:unit:frame%)
|
|
(define drs-bindings-keymap-mixin
|
|
(mixin (editor:keymap<%>) (editor:keymap<%>)
|
|
(define/override (get-keymaps)
|
|
(editor:add-after-user-keymap drs-bindings-keymap (super get-keymaps)))
|
|
(super-new)))
|
|
|
|
;; Max length of output queue (user's thread blocks if the
|
|
;; queue is full):
|
|
(define output-limit-size 2000)
|
|
|
|
(define setup-scheme-interaction-mode-keymap
|
|
(λ (keymap)
|
|
(send keymap add-function "put-previous-sexp"
|
|
(λ (text event)
|
|
(send text copy-prev-previous-expr)))
|
|
(send keymap add-function "put-next-sexp"
|
|
(λ (text event)
|
|
(send text copy-next-previous-expr)))
|
|
|
|
(keymap:send-map-function-meta keymap "p" "put-previous-sexp")
|
|
(keymap:send-map-function-meta keymap "n" "put-next-sexp")
|
|
(send keymap map-function "c:up" "put-previous-sexp")
|
|
(send keymap map-function "c:down" "put-next-sexp")))
|
|
|
|
(define scheme-interaction-mode-keymap (make-object keymap:aug-keymap%))
|
|
(setup-scheme-interaction-mode-keymap scheme-interaction-mode-keymap)
|
|
|
|
(define drs-font-delta (make-object style-delta% 'change-family 'decorative))
|
|
|
|
(define output-delta (make-object style-delta%)) ; used to be 'change-weight 'bold
|
|
(define result-delta (make-object style-delta%)) ; used to be 'change-weight 'bold
|
|
(define error-delta (make-object style-delta%
|
|
'change-style
|
|
'italic))
|
|
(send error-delta set-delta-foreground (make-object color% 255 0 0))
|
|
(send result-delta set-delta-foreground (make-object color% 0 0 175))
|
|
(send output-delta set-delta-foreground (make-object color% 150 0 150))
|
|
|
|
(define error-text-style-delta (make-object style-delta%))
|
|
(send error-text-style-delta set-delta-foreground (make-object color% 200 0 0))
|
|
|
|
(define grey-delta (make-object style-delta%))
|
|
(send grey-delta set-delta-foreground "GREY")
|
|
|
|
(define welcome-delta (make-object style-delta% 'change-family 'decorative))
|
|
(define click-delta (gui-utils:get-clickback-delta))
|
|
(define red-delta (make-object style-delta%))
|
|
(define dark-green-delta (make-object style-delta%))
|
|
(send* red-delta
|
|
(copy welcome-delta)
|
|
(set-delta-foreground "RED"))
|
|
(send* dark-green-delta
|
|
(copy welcome-delta)
|
|
(set-delta-foreground "dark green"))
|
|
(define warning-style-delta (make-object style-delta% 'change-bold))
|
|
(send* warning-style-delta
|
|
(set-delta-foreground "BLACK")
|
|
(set-delta-background "YELLOW"))
|
|
(define (get-welcome-delta) welcome-delta)
|
|
(define (get-dark-green-delta) dark-green-delta)
|
|
|
|
;; is-default-settings? : language-settings -> boolean
|
|
;; determines if the settings in `language-settings'
|
|
;; correspond to the default settings of the language.
|
|
(define (is-default-settings? language-settings)
|
|
(send (drracket:language-configuration:language-settings-language language-settings)
|
|
default-settings?
|
|
(drracket:language-configuration:language-settings-settings language-settings)))
|
|
|
|
(define (extract-language-name language-settings defs-text)
|
|
(cond
|
|
[(is-a? (drracket:language-configuration:language-settings-language language-settings)
|
|
drracket:module-language:module-language<%>)
|
|
(send (drracket:language-configuration:language-settings-language language-settings)
|
|
get-users-language-name defs-text)]
|
|
[else
|
|
(send (drracket:language-configuration:language-settings-language language-settings)
|
|
get-language-name)]))
|
|
(define (extract-language-style-delta language-settings)
|
|
(send (drracket:language-configuration:language-settings-language language-settings)
|
|
get-style-delta))
|
|
(define (extract-language-url language-settings)
|
|
(send (drracket:language-configuration:language-settings-language language-settings)
|
|
get-language-url))
|
|
|
|
(define-struct sexp (left right prompt))
|
|
|
|
(let* ([list-of? (λ (p?)
|
|
(λ (l)
|
|
(and (list? l)
|
|
(andmap p? l))))]
|
|
[snip/string? (λ (s) (or (is-a? s snip%) (string? s)))]
|
|
[list-of-snip/strings? (list-of? snip/string?)]
|
|
[list-of-lists-of-snip/strings? (list-of? list-of-snip/strings?)])
|
|
(preferences:set-default
|
|
'drracket:console-previous-exprs
|
|
null
|
|
list-of-lists-of-snip/strings?))
|
|
(define (marshall-previous-exprs lls)
|
|
(map (λ (ls)
|
|
(list
|
|
(apply
|
|
string-append
|
|
(reverse
|
|
(map (λ (s)
|
|
(cond
|
|
[(is-a? s string-snip%)
|
|
(send s get-text 0 (send s get-count))]
|
|
[(string? s) s]
|
|
[else "'non-string-snip"]))
|
|
ls)))))
|
|
lls))
|
|
(let ([unmarshall (λ (x) x)])
|
|
(preferences:set-un/marshall
|
|
'drracket:console-previous-exprs
|
|
marshall-previous-exprs unmarshall))
|
|
|
|
(define color? ((get-display-depth) . > . 8))
|
|
|
|
;; instances of this interface provide a context for a rep:text%
|
|
;; its connection to its graphical environment (ie frame) for
|
|
;; error display and status infromation is all mediated
|
|
;; through an instance of this interface.
|
|
|
|
(define file-icon
|
|
(let ([bitmap
|
|
(make-object bitmap%
|
|
(collection-file-path "file.gif" "icons"))])
|
|
(if (send bitmap ok?)
|
|
(make-object image-snip% bitmap)
|
|
(make-object string-snip% "[open file]"))))
|
|
|
|
|
|
;; insert/delta : (instanceof text%) (union snip string) (listof style-delta%) *-> (values number number)
|
|
;; inserts the string/snip into the text at the end and changes the
|
|
;; style of the newly inserted text based on the style deltas.
|
|
(define (insert/delta text s . deltas)
|
|
(define before (send text last-position))
|
|
(send text insert s before before #f)
|
|
(define after (send text last-position))
|
|
(for ([delta (in-list deltas)])
|
|
(when (is-a? delta style-delta%)
|
|
(send text change-style delta before after)))
|
|
(values before after))
|
|
|
|
(define log-max-size 1000)
|
|
(define log-entry-max-size 1000)
|
|
|
|
(define after-expression (make-parameter #f))
|
|
|
|
(define text-mixin
|
|
(mixin ((class->interface text%)
|
|
text:ports<%>
|
|
editor:file<%>
|
|
racket:text<%>
|
|
color:text<%>
|
|
text:ports<%>)
|
|
(-text<%>)
|
|
(init-field context)
|
|
(inherit auto-wrap
|
|
begin-edit-sequence
|
|
change-style
|
|
clear-box-input-port
|
|
clear-undos
|
|
clear-input-port
|
|
clear-output-ports
|
|
delete
|
|
delete/io
|
|
end-edit-sequence
|
|
erase
|
|
find-snip
|
|
find-string
|
|
freeze-colorer
|
|
get-active-canvas
|
|
get-admin
|
|
get-can-close-parent
|
|
get-canvases
|
|
get-character
|
|
get-end-position
|
|
get-err-port
|
|
get-extent
|
|
get-focus-snip
|
|
get-in-port
|
|
get-in-box-port
|
|
get-insertion-point
|
|
get-out-port
|
|
get-regions
|
|
get-snip-position
|
|
get-start-position
|
|
get-styles-fixed
|
|
get-style-list
|
|
get-text
|
|
get-top-level-window
|
|
get-unread-start-point
|
|
get-value-port
|
|
in-edit-sequence?
|
|
insert
|
|
insert-before
|
|
insert-between
|
|
is-locked?
|
|
last-position
|
|
line-location
|
|
lock
|
|
paragraph-start-position
|
|
position-line
|
|
position-paragraph
|
|
port-name-matches?
|
|
release-snip
|
|
reset-input-box
|
|
reset-regions
|
|
run-after-edit-sequence
|
|
scroll-to-position
|
|
send-eof-to-in-port
|
|
set-allow-edits
|
|
set-caret-owner
|
|
set-clickback
|
|
set-insertion-point
|
|
set-position
|
|
set-styles-sticky
|
|
set-styles-fixed
|
|
set-unread-start-point
|
|
split-snip
|
|
thaw-colorer)
|
|
|
|
(define definitions-text 'not-yet-set-definitions-text)
|
|
(define/public (set-definitions-text dt) (set! definitions-text dt))
|
|
(define/public (get-definitions-text) definitions-text)
|
|
|
|
(unless (is-a? context drracket:rep:context<%>)
|
|
(error 'drracket:rep:text%
|
|
"expected an object that implements drracket:rep:context<%> as initialization argument, got: ~e"
|
|
context))
|
|
|
|
(define/public (get-context) context)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; ;;;
|
|
;;; User -> Kernel ;;;
|
|
;;; ;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; =User= (probably doesn't matter)
|
|
(define/private queue-system-callback
|
|
(λ (ut thunk [always? #f])
|
|
(parameterize ([current-eventspace drracket:init:system-eventspace])
|
|
(queue-callback
|
|
(λ ()
|
|
(when (or always? (eq? ut (get-user-thread)))
|
|
(thunk)))
|
|
#f))))
|
|
|
|
;; =User=
|
|
(define/private queue-system-callback/sync
|
|
(λ (ut thunk)
|
|
(let ([s (make-semaphore 0)])
|
|
(queue-system-callback
|
|
ut
|
|
(λ ()
|
|
(when (eq? ut (get-user-thread))
|
|
(thunk))
|
|
(semaphore-post s))
|
|
#t)
|
|
(semaphore-wait s))))
|
|
|
|
;; display-results : (listof TST) -> void
|
|
;; prints each element of anss that is not void as values in the REPL.
|
|
(define/public (display-results anss) ; =User=, =Handler=, =Breaks=
|
|
(display-results/void (filter (λ (x) (not (void? x))) anss)))
|
|
|
|
;; display-results : (listof TST) -> void
|
|
;; prints each element of anss in the REPL.
|
|
(define/public (display-results/void anss) ; =User=, =Handler=, =Breaks=
|
|
(for-each
|
|
(λ (v)
|
|
(let* ([ls (current-language-settings)]
|
|
[lang (drracket:language-configuration:language-settings-language ls)]
|
|
[settings (drracket:language-configuration:language-settings-settings ls)])
|
|
(send lang render-value/format
|
|
v
|
|
settings
|
|
(get-value-port)
|
|
(get-repl-char-width))))
|
|
anss))
|
|
|
|
;; get-repl-char-width : -> (and/c exact? integer?)
|
|
;; returns the width of the repl in characters, or 80 if the
|
|
;; answer cannot be found.
|
|
(define/private (get-repl-char-width)
|
|
(let ([admin (get-admin)]
|
|
[standard (send (get-style-list) find-named-style "Standard")])
|
|
(if (and admin standard)
|
|
(let ([bw (box 0)])
|
|
(send admin get-view #f #f bw #f)
|
|
(let* ([dc (send admin get-dc)]
|
|
[standard-font (send standard get-font)]
|
|
[old-font (send dc get-font)])
|
|
(send dc set-font standard-font)
|
|
(let* ([char-width (send dc get-char-width)]
|
|
[answer (inexact->exact (floor (/ (unbox bw) char-width)))])
|
|
(send dc set-font old-font)
|
|
answer)))
|
|
80)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; ;;;
|
|
;;; Error Highlighting ;;;
|
|
;;; ;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; error-ranges : (union false? (cons srcloc (listof srcloc)))
|
|
(define error-ranges #f)
|
|
(define/public (get-error-ranges) error-ranges)
|
|
(define/public (set-error-ranges ranges)
|
|
(set! error-ranges (and ranges
|
|
(not (null? ranges))
|
|
(cleanup-locs ranges))))
|
|
(define clear-error-highlighting void)
|
|
(define/public (reset-error-ranges)
|
|
(set-error-ranges #f)
|
|
(when definitions-text (send definitions-text set-error-arrows #f))
|
|
(clear-error-highlighting))
|
|
|
|
;; highlight-error : file number number -> void
|
|
(define/public (highlight-error file start end)
|
|
(highlight-errors (list (make-srcloc file #f #f start (- end start))) #f))
|
|
|
|
;; highlight-errors/exn : exn -> void
|
|
;; highlights all of the errors associated with the exn (incl. arrows)
|
|
(define/public (highlight-errors/exn exn)
|
|
(let ([locs (cond
|
|
[(exn:srclocs? exn)
|
|
((exn:srclocs-accessor exn) exn)]
|
|
[else '()])])
|
|
(highlight-errors locs #f)))
|
|
|
|
;; =Kernel= =handler=
|
|
;; highlight-errors : (listof srcloc)
|
|
;; (union #f (listof srcloc))
|
|
;; -> (void)
|
|
(define/public (highlight-errors raw-locs [raw-error-arrows #f])
|
|
(clear-error-highlighting)
|
|
(when definitions-text (send definitions-text set-error-arrows #f))
|
|
(set-error-ranges raw-locs)
|
|
(define locs (or (get-error-ranges) '())) ;; calling set-error-range cleans up the locs
|
|
(define error-arrows (and raw-error-arrows (cleanup-locs raw-error-arrows)))
|
|
|
|
(for-each (λ (loc) (send (srcloc-source loc) begin-edit-sequence)) locs)
|
|
|
|
(when color?
|
|
(let ([resets
|
|
(map (λ (loc)
|
|
(let* ([file (srcloc-source loc)]
|
|
[start (- (srcloc-position loc) 1)]
|
|
[span (srcloc-span loc)]
|
|
[finish (+ start span)])
|
|
(send file highlight-range start finish (drracket:debug:get-error-color) #f 'high)))
|
|
locs)])
|
|
(when (and definitions-text error-arrows)
|
|
(let ([filtered-arrows
|
|
(remove-duplicate-error-arrows
|
|
(filter
|
|
(λ (arr) (embedded-in? (srcloc-source arr) definitions-text))
|
|
error-arrows))])
|
|
(send definitions-text set-error-arrows filtered-arrows)))
|
|
|
|
(set! clear-error-highlighting
|
|
(λ ()
|
|
(set! clear-error-highlighting void)
|
|
(for-each (λ (x) (x)) resets)))))
|
|
|
|
(let* ([first-loc (and (pair? locs) (car locs))]
|
|
[first-file (and first-loc (srcloc-source first-loc))]
|
|
[first-start (and first-loc (- (srcloc-position first-loc) 1))]
|
|
[first-span (and first-loc (srcloc-span first-loc))])
|
|
|
|
(when (and first-loc first-start first-span)
|
|
(let ([first-finish (+ first-start first-span)])
|
|
(when (eq? first-file definitions-text) ;; only move set the cursor in the defs window
|
|
(send first-file set-position first-start first-start))
|
|
(send first-file scroll-to-position first-start #f first-finish)))
|
|
|
|
(for-each (λ (loc) (send (srcloc-source loc) end-edit-sequence)) locs)
|
|
|
|
(when first-loc
|
|
|
|
(when (eq? first-file definitions-text)
|
|
;; when we're highlighting something in the defs window,
|
|
;; make sure it is visible
|
|
(let ([tlw (send first-file get-top-level-window)])
|
|
(when (is-a? tlw drracket:unit:frame<%>)
|
|
(send tlw ensure-defs-shown))))
|
|
|
|
(send first-file set-caret-owner (get-focus-snip) 'global))))
|
|
|
|
;; unlike highlight-error just above, this function does not change
|
|
;; what the currently noted errors locations are, it just highlights
|
|
;; one of them.
|
|
(define/public (highlight-a-single-error raw-loc)
|
|
(define loc (car (cleanup-locs (list raw-loc))))
|
|
(define source (srcloc-source loc))
|
|
(when (and (is-a? source text%)
|
|
(srcloc-position loc)
|
|
(srcloc-span loc))
|
|
(send source begin-edit-sequence)
|
|
|
|
(clear-error-highlighting) ;; clear the 'highlight-range' from previous errors
|
|
|
|
(define start (- (srcloc-position loc) 1))
|
|
(define span (srcloc-span loc))
|
|
(define finish (+ start span))
|
|
|
|
(let ([reset (send source highlight-range start finish (drracket:debug:get-error-color) #f 'high)])
|
|
(set! clear-error-highlighting
|
|
(λ ()
|
|
(set! clear-error-highlighting void)
|
|
(reset))))
|
|
|
|
(when (and start span)
|
|
(let ([finish (+ start span)])
|
|
(when (eq? source definitions-text) ;; only move set the cursor in the defs window
|
|
(send source set-position start span))
|
|
(send source scroll-to-position start #f finish)))
|
|
|
|
(send source end-edit-sequence)
|
|
|
|
(when (eq? source definitions-text)
|
|
;; when we're highlighting something in the defs window,
|
|
;; make sure it is visible
|
|
(let ([tlw (send source get-top-level-window)])
|
|
(when (is-a? tlw drracket:unit:frame<%>)
|
|
(send tlw ensure-defs-shown))))
|
|
|
|
(send source set-caret-owner (get-focus-snip) 'global)))
|
|
|
|
(define/private (cleanup-locs locs)
|
|
(let ([ht (make-hasheq)])
|
|
(filter (λ (loc) (and (is-a? (srcloc-source loc) text:basic<%>)
|
|
(number? (srcloc-position loc))
|
|
(number? (srcloc-span loc))))
|
|
(map (λ (srcloc)
|
|
(cond
|
|
[(hash-ref ht (srcloc-source srcloc) #f)
|
|
=>
|
|
(λ (e)
|
|
(make-srcloc e
|
|
(srcloc-line srcloc)
|
|
(srcloc-column srcloc)
|
|
(srcloc-position srcloc)
|
|
(srcloc-span srcloc)))]
|
|
[(send definitions-text port-name-matches? (srcloc-source srcloc))
|
|
(hash-set! ht (srcloc-source srcloc) definitions-text)
|
|
(make-srcloc definitions-text
|
|
(srcloc-line srcloc)
|
|
(srcloc-column srcloc)
|
|
(srcloc-position srcloc)
|
|
(srcloc-span srcloc))]
|
|
[(port-name-matches? (srcloc-source srcloc))
|
|
(hash-set! ht (srcloc-source srcloc) this)
|
|
(make-srcloc this
|
|
(srcloc-line srcloc)
|
|
(srcloc-column srcloc)
|
|
(srcloc-position srcloc)
|
|
(srcloc-span srcloc))]
|
|
[(and (symbol? (srcloc-source srcloc))
|
|
(text:lookup-port-name (srcloc-source srcloc)))
|
|
=>
|
|
(lambda (editor)
|
|
(make-srcloc editor
|
|
(srcloc-line srcloc)
|
|
(srcloc-column srcloc)
|
|
(srcloc-position srcloc)
|
|
(srcloc-span srcloc)))]
|
|
[else srcloc]))
|
|
locs))))
|
|
|
|
(define highlights-can-be-reset (make-parameter #t))
|
|
(define/public (reset-highlighting)
|
|
(when (highlights-can-be-reset) (reset-error-ranges)))
|
|
(define/public (call-without-reset-highlighting thunk)
|
|
(parameterize ([highlights-can-be-reset #f])
|
|
(thunk)))
|
|
|
|
;; remove-duplicate-error-arrows : (listof X) -> (listof X)
|
|
;; duplicate arrows point from and to the same place -- only
|
|
;; need one arrow for each pair of locations they point to.
|
|
(define/private (remove-duplicate-error-arrows error-arrows)
|
|
(let ([ht (make-hash)])
|
|
(let loop ([arrs error-arrows]
|
|
[n 0])
|
|
(unless (null? arrs)
|
|
(hash-set! ht (car arrs) n)
|
|
(loop (cdr arrs) (+ n 1))))
|
|
(let* ([unsorted (hash-map ht list)]
|
|
[sorted (sort unsorted (λ (x y) (<= (cadr x) (cadr y))))]
|
|
[arrs (map car sorted)])
|
|
arrs)))
|
|
|
|
(define/private (embedded-in? txt-inner txt-outer)
|
|
(let loop ([txt-inner txt-inner])
|
|
(cond
|
|
[(eq? txt-inner txt-outer) #t]
|
|
[else (let ([admin (send txt-inner get-admin)])
|
|
(and (is-a? admin editor-snip-editor-admin<%>)
|
|
(loop (send (send (send admin get-snip) get-admin) get-editor))))])))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;
|
|
;; specialization
|
|
;;
|
|
|
|
(define/override (after-io-insertion)
|
|
(super after-io-insertion)
|
|
(let ([frame (get-frame)])
|
|
(when frame
|
|
(let ([tab (send definitions-text get-tab)])
|
|
(when (eq? (send frame get-current-tab) tab)
|
|
(send context ensure-rep-shown this))))))
|
|
|
|
(define/augment (after-insert start len)
|
|
(inner (void) after-insert start len)
|
|
(cond
|
|
[(in-edit-sequence?)
|
|
(set! had-an-insert (cons start had-an-insert))]
|
|
[else (update-after-inserts (list start))]))
|
|
|
|
;; private field
|
|
(define had-an-insert '())
|
|
|
|
(define/augment (after-edit-sequence)
|
|
(inner (void) after-edit-sequence)
|
|
(unless (null? had-an-insert)
|
|
(let ([to-clean had-an-insert])
|
|
(set! had-an-insert '())
|
|
(update-after-inserts to-clean))))
|
|
|
|
(define/private (update-after-inserts starts)
|
|
(unless inserting-prompt?
|
|
(reset-highlighting))
|
|
(when (and prompt-position
|
|
(ormap (λ (start) (< start prompt-position))
|
|
starts))
|
|
(set! prompt-position (get-unread-start-point))
|
|
(reset-regions (append (all-but-last (get-regions))
|
|
(list (list prompt-position 'end))))))
|
|
|
|
(define/augment (after-delete x y)
|
|
(unless inserting-prompt?
|
|
(reset-highlighting))
|
|
(inner (void) after-delete x y))
|
|
|
|
(define/override (get-keymaps)
|
|
(editor:add-after-user-keymap scheme-interaction-mode-keymap
|
|
(super get-keymaps)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; ;;;
|
|
;;; Evaluation ;;;
|
|
;;; ;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(define/public (eval-busy?)
|
|
(not (and (get-user-thread)
|
|
(thread-running? (get-user-thread)))))
|
|
|
|
(field (user-language-settings #f)
|
|
(user-custodian-parent #f)
|
|
(memory-killed-cust-box #f)
|
|
(user-custodian #f)
|
|
(custodian-limit (and (custodian-memory-accounting-available?)
|
|
(preferences:get 'drracket:child-only-memory-limit)))
|
|
(user-eventspace-box (make-weak-box #f))
|
|
(user-namespace-box (make-weak-box #f))
|
|
(user-eventspace-main-thread #f)
|
|
(user-break-parameterization #f)
|
|
(user-logger (make-logger))
|
|
|
|
;; user-exit-code (union #f byte?)
|
|
;; #f indicates that exit wasn't called. Integer indicates exit code
|
|
(user-exit-code #f))
|
|
|
|
(define/public (get-user-language-settings) user-language-settings)
|
|
(define/public (get-user-custodian) user-custodian)
|
|
(define/public (get-user-eventspace) (weak-box-value user-eventspace-box))
|
|
(define/public (get-user-thread) user-eventspace-main-thread)
|
|
(define/public (get-user-namespace) (weak-box-value user-namespace-box))
|
|
(define/pubment (get-user-break-parameterization) user-break-parameterization) ;; final method
|
|
(define/pubment (get-custodian-limit) custodian-limit)
|
|
(define/pubment (set-custodian-limit c) (set! custodian-limit c))
|
|
(field (in-evaluation? #f)
|
|
(ask-about-kill? #f))
|
|
(define/public (get-in-evaluation?) in-evaluation?)
|
|
|
|
(define/public-final (insert-warning message)
|
|
(let ([locked? (is-locked?)])
|
|
(when locked? (lock #f))
|
|
(begin-edit-sequence)
|
|
(let ([start (get-unread-start-point)])
|
|
(insert-before message)
|
|
(let ([end (get-unread-start-point)])
|
|
(change-style warning-style-delta start end)
|
|
(insert-before "\n")))
|
|
(end-edit-sequence)
|
|
(when locked? (lock #t))))
|
|
|
|
(field (show-no-user-evaluation-message? #t))
|
|
|
|
;; use this to be able to kill the evaluator without the popup dialog
|
|
(define/public (set-show-no-user-evaluation-message? b)
|
|
(set! show-no-user-evaluation-message? b))
|
|
|
|
(define/private (cleanup)
|
|
(set! in-evaluation? #f)
|
|
(update-running #f)
|
|
(unless (and (get-user-thread) (thread-running? (get-user-thread)))
|
|
(lock #t)
|
|
(when (and show-no-user-evaluation-message? (not shutting-down?))
|
|
(no-user-evaluation-message
|
|
(get-frame)
|
|
user-exit-code
|
|
(not (custodian-box-value memory-killed-cust-box))))
|
|
(set! show-no-user-evaluation-message? #t)))
|
|
|
|
(field (need-interaction-cleanup? #f))
|
|
|
|
(define/private (no-user-evaluation-message frame exit-code memory-killed?)
|
|
(define new-limit (and custodian-limit (+ custodian-limit custodian-limit)))
|
|
(define-values (ans checked?)
|
|
(if (preferences:get 'drracket:show-killed-dialog)
|
|
(message+check-box/custom
|
|
(string-constant evaluation-terminated)
|
|
(string-append
|
|
(string-constant evaluation-terminated-explanation)
|
|
(if exit-code
|
|
(string-append
|
|
"\n\n"
|
|
(if (zero? exit-code)
|
|
(string-constant exited-successfully)
|
|
(format (string-constant exited-with-error-code) exit-code)))
|
|
"")
|
|
(if memory-killed?
|
|
(string-append
|
|
"\n\n"
|
|
(string-constant program-ran-out-of-memory))
|
|
""))
|
|
(string-constant evaluation-terminated-ask)
|
|
(string-constant ok)
|
|
#f
|
|
(and memory-killed?
|
|
new-limit
|
|
(format "Increase memory limit to ~a megabytes"
|
|
(floor (/ new-limit 1024 1024))))
|
|
frame
|
|
'(default=1 stop checked)
|
|
#:dialog-mixin frame:focus-table-mixin)
|
|
(values 1 #t)))
|
|
(unless checked?
|
|
(preferences:set 'drracket:show-killed-dialog #f))
|
|
(when (equal? ans 3)
|
|
(set-custodian-limit new-limit)
|
|
(preferences:set 'drracket:child-only-memory-limit new-limit))
|
|
(set-insertion-point (last-position))
|
|
(insert-warning "\nInteractions disabled"))
|
|
|
|
(define/private (cleanup-interaction) ; =Kernel=, =Handler=
|
|
(set! need-interaction-cleanup? #f)
|
|
(begin-edit-sequence)
|
|
(set-caret-owner #f 'display)
|
|
(cleanup)
|
|
(end-edit-sequence)
|
|
(send context set-breakables #f #f)
|
|
(send context enable-evaluation))
|
|
|
|
(define/augment (submit-to-port? key)
|
|
(or (eq? (send key get-key-code) 'numpad-enter)
|
|
(send key get-control-down)
|
|
(send key get-alt-down)
|
|
(and prompt-position
|
|
(let ([lang (drracket:language-configuration:language-settings-language user-language-settings)])
|
|
(cond
|
|
[(is-a? lang drracket:module-language:module-language<%>)
|
|
(let ([pred
|
|
(send lang get-language-info
|
|
'drracket:submit-predicate
|
|
(λ (port only-whitespace-afterwards?)
|
|
(and only-whitespace-afterwards?
|
|
(submit-predicate this prompt-position))))])
|
|
(pred
|
|
;; no good! giving away the farm here. need to hand
|
|
;; over a proxy that is limited to just read access
|
|
(open-input-text-editor this prompt-position)
|
|
(only-whitespace-after-insertion-point)))]
|
|
[else
|
|
(and (only-whitespace-after-insertion-point)
|
|
(submit-predicate this prompt-position))])))))
|
|
|
|
(define/private (only-whitespace-after-insertion-point)
|
|
(let ([start (get-start-position)]
|
|
[end (get-end-position)])
|
|
(and (= start end)
|
|
(let loop ([pos start])
|
|
(cond
|
|
[(= pos (last-position)) #t]
|
|
[else (and (char-whitespace? (get-character pos))
|
|
(loop (+ pos 1)))])))))
|
|
|
|
(define/augment (on-submit)
|
|
(inner (void) on-submit)
|
|
(when (and (get-user-thread)
|
|
(thread-running? (get-user-thread)))
|
|
;; the -2 drops the last newline from history (why -2 and not -1?!)
|
|
(save-interaction-in-history prompt-position (- (last-position) 2))
|
|
|
|
(let* ([old-regions (get-regions)]
|
|
[abl (all-but-last old-regions)]
|
|
[lst (last old-regions)])
|
|
(reset-regions (append abl (list (list (list-ref lst 0) (last-position))))))
|
|
|
|
(send context repl-submit-happened)
|
|
|
|
;; lets us know we are done with this one interaction
|
|
;; (since there may be multiple expressions at the prompt)
|
|
(send-eof-to-in-port)
|
|
|
|
(set! prompt-position #f)
|
|
(evaluate-from-port
|
|
(get-in-port)
|
|
#f
|
|
(λ ()
|
|
;; clear out the eof object if it wasn't consumed
|
|
(clear-input-port)))))
|
|
|
|
(inherit get-backward-sexp)
|
|
(define/override (on-local-char key)
|
|
(let ([start (get-start-position)]
|
|
[end (get-end-position)]
|
|
[code (send key get-key-code)])
|
|
(cond
|
|
[(not (or (eq? code 'numpad-enter)
|
|
(equal? code #\return)
|
|
(equal? code #\newline)))
|
|
(super on-local-char key)]
|
|
[(not prompt-position)
|
|
;; evaluating? just drop the keypress
|
|
(void)]
|
|
[(and (< end prompt-position)
|
|
(= start end)
|
|
(get-backward-sexp end))
|
|
=>
|
|
(λ (sexp-start)
|
|
(copy-down sexp-start end))]
|
|
[(and (< end prompt-position)
|
|
(not (= start end)))
|
|
(copy-down start end)]
|
|
[else
|
|
(super on-local-char key)])))
|
|
|
|
(define/private (copy-down start end)
|
|
(begin-edit-sequence)
|
|
(split-snip start)
|
|
(split-snip end)
|
|
(let loop ([snip (find-snip start 'after-or-none)])
|
|
(when snip
|
|
(let ([pos (+ (get-snip-position snip)
|
|
(send snip get-count))])
|
|
(when (<= pos end)
|
|
(insert (send snip copy) (last-position) (last-position))
|
|
(loop (send snip next))))))
|
|
(set-position (last-position) (last-position))
|
|
(end-edit-sequence))
|
|
|
|
;; prompt-position : (union #f integer)
|
|
;; the position just after the last prompt
|
|
(field (prompt-position #f))
|
|
(define inserting-prompt? #f)
|
|
(define/public (get-prompt) "> ")
|
|
(define/public (insert-prompt)
|
|
(set! inserting-prompt? #t)
|
|
(begin-edit-sequence)
|
|
(reset-input-box)
|
|
(let* ([pmt (get-prompt)]
|
|
[prompt-space (string-length pmt)])
|
|
|
|
;; insert the prompt, possibly inserting a newline first
|
|
(let* ([usp (get-unread-start-point)]
|
|
[usp-para (position-paragraph usp)]
|
|
[usp-para-start (paragraph-start-position usp-para)])
|
|
(unless (equal? usp usp-para-start)
|
|
(insert-between "\n")
|
|
(set! prompt-space (+ prompt-space 1)))
|
|
(insert-between pmt))
|
|
|
|
(let ([sp (get-unread-start-point)])
|
|
(set! prompt-position sp)
|
|
(reset-regions (append (get-regions) (list (list sp 'end))))))
|
|
(end-edit-sequence)
|
|
(set! inserting-prompt? #f))
|
|
|
|
(field [submit-predicate (λ (text prompt-position) #t)])
|
|
(define/public (set-submit-predicate p)
|
|
(set! submit-predicate p))
|
|
|
|
(define/public (evaluate-from-port port complete-program? cleanup) ; =Kernel=, =Handler=
|
|
(send context disable-evaluation)
|
|
(send context reset-offer-kill)
|
|
(send context set-breakables (get-user-thread) (get-user-custodian))
|
|
(reset-pretty-print-width)
|
|
(set! in-evaluation? #t)
|
|
(update-running #t)
|
|
(set! need-interaction-cleanup? #t)
|
|
(define the-after-expression (after-expression))
|
|
|
|
(run-in-evaluation-thread
|
|
(λ () ; =User=, =Handler=, =No-Breaks=
|
|
(let* ([settings (current-language-settings)]
|
|
[lang (drracket:language-configuration:language-settings-language settings)]
|
|
[settings (drracket:language-configuration:language-settings-settings settings)]
|
|
[dummy-value (box #f)]
|
|
[get-sexp/syntax/eof
|
|
(if complete-program?
|
|
(send lang front-end/complete-program port settings)
|
|
(send lang front-end/interaction port settings))])
|
|
|
|
; Evaluate the user's expression. We're careful to turn on
|
|
; breaks as we go in and turn them off as we go out.
|
|
; (Actually, we adjust breaks however the user wanted it.)
|
|
|
|
|
|
;; this binding of last-results is to catch the results
|
|
;; that come from throwing to the prompt instead of
|
|
;; a normal exit
|
|
(define last-results
|
|
(call-with-values
|
|
(λ ()
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(call-with-break-parameterization
|
|
user-break-parameterization
|
|
(λ ()
|
|
(let loop ()
|
|
(define sexp/syntax/eof (with-stack-checkpoint (get-sexp/syntax/eof)))
|
|
(cond
|
|
[(eof-object? sexp/syntax/eof) (abort-current-continuation
|
|
(default-continuation-prompt-tag)
|
|
(λ () (values)))]
|
|
[else
|
|
(define results
|
|
(call-with-values
|
|
(λ ()
|
|
;; we duplicate the 'expand-syntax-to-top-form' dance that eval-syntax
|
|
;; does here, so that we can put 'with-stack-checkpoint's in to limit
|
|
;; the amount of DrRacket code we see in stacktraces
|
|
(let loop ([stx sexp/syntax/eof])
|
|
(define top-expanded (with-stack-checkpoint (expand-syntax-to-top-form stx)))
|
|
(syntax-case top-expanded (begin)
|
|
[(begin a1 . args)
|
|
(let lloop ([args (syntax->list #'(a1 . args))])
|
|
(cond
|
|
[(null? (cdr args))
|
|
(loop (car args))]
|
|
[else
|
|
(loop (car args))
|
|
(lloop (cdr args))]))]
|
|
[_
|
|
(let ([expanded (with-stack-checkpoint (expand-syntax top-expanded))])
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(with-stack-checkpoint (eval-syntax expanded)))
|
|
(default-continuation-prompt-tag)
|
|
(λ args
|
|
(apply
|
|
abort-current-continuation
|
|
(default-continuation-prompt-tag)
|
|
args))))])))
|
|
list))
|
|
(parameterize ([pretty-print-columns pretty-print-width])
|
|
(for ([x (in-list results)])
|
|
((current-print) x)))
|
|
(loop)])))))
|
|
(default-continuation-prompt-tag)
|
|
(letrec ([me
|
|
(λ args
|
|
(cond
|
|
[(and (pair? args)
|
|
(null? (cdr args))
|
|
(procedure? (car args))
|
|
(procedure-arity-includes? (car args) 0))
|
|
(call-with-continuation-prompt (car args)
|
|
(default-continuation-prompt-tag)
|
|
me)]
|
|
[else
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(apply
|
|
abort-current-continuation
|
|
(default-continuation-prompt-tag)
|
|
args)))))]))])
|
|
me)))
|
|
list))
|
|
(parameterize ([pretty-print-columns pretty-print-width])
|
|
(for ([x (in-list last-results)])
|
|
((current-print) x)))
|
|
|
|
(when complete-program?
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(call-with-break-parameterization
|
|
user-break-parameterization
|
|
(λ ()
|
|
(send lang front-end/finished-complete-program settings))))
|
|
(default-continuation-prompt-tag)
|
|
(λ args (void))))
|
|
|
|
(when the-after-expression
|
|
(call-with-continuation-prompt
|
|
(λ ()
|
|
(the-after-expression))))
|
|
|
|
(set! in-evaluation? #f)
|
|
(update-running #f)
|
|
(cleanup)
|
|
(flush-output (get-value-port))
|
|
(queue-system-callback/sync
|
|
(get-user-thread)
|
|
(λ () ; =Kernel=, =Handler=
|
|
(after-many-evals)
|
|
(cleanup-interaction)
|
|
(insert-prompt)))))))
|
|
|
|
;; =User=, =Handler=
|
|
(define/pubment (on-execute rout) (inner (void) on-execute rout))
|
|
|
|
;; =Kernel=, =Handler=
|
|
(define/pubment (after-many-evals) (inner (void) after-many-evals))
|
|
|
|
(define/private shutdown-user-custodian ; =Kernel=, =Handler=
|
|
; Use this procedure to shutdown when in the middle of other cleanup
|
|
; operations, such as when the user clicks "Run".
|
|
; Don't use it to kill a thread where other, external cleanup
|
|
; actions must occur (e.g., the exit handler for the user's
|
|
; thread). In that case, shut down user-custodian directly.
|
|
(λ ()
|
|
(when user-custodian
|
|
(custodian-shutdown-all user-custodian))
|
|
(set! user-custodian #f)
|
|
(set! user-eventspace-main-thread #f)))
|
|
|
|
(define/public (kill-evaluation) ; =Kernel=, =Handler=
|
|
(when user-custodian
|
|
(custodian-shutdown-all user-custodian))
|
|
(set! user-custodian #f))
|
|
|
|
(field (eval-thread-thunks null)
|
|
(eval-thread-state-sema 'not-yet-state-sema)
|
|
(eval-thread-queue-sema 'not-yet-thread-sema)
|
|
|
|
(cleanup-sucessful 'not-yet-cleanup-sucessful)
|
|
(cleanup-semaphore 'not-yet-cleanup-semaphore)
|
|
(thread-killed 'not-yet-thread-killed))
|
|
(define/private (initialize-killed-thread) ; =Kernel=
|
|
(when (thread? thread-killed)
|
|
(kill-thread thread-killed))
|
|
(set! thread-killed
|
|
(thread
|
|
(λ () ; =Kernel=
|
|
(let ([ut (get-user-thread)])
|
|
(thread-wait ut)
|
|
(queue-system-callback
|
|
ut
|
|
(λ () ; =Kernel=, =Handler=
|
|
(if need-interaction-cleanup?
|
|
(cleanup-interaction)
|
|
(cleanup))
|
|
;; HACK: lock the interactions now; the reason for this
|
|
;; is that `cleanup-interaction' invokes
|
|
;; `enable-evaluation', and in "unit.rkt" this is defined
|
|
;; to unlock the interactions which might make sense in
|
|
;; that context.
|
|
(lock #t))))))))
|
|
|
|
(define/public (run-in-evaluation-thread thunk) ; =Kernel=
|
|
(semaphore-wait eval-thread-state-sema)
|
|
(set! eval-thread-thunks (append eval-thread-thunks (list thunk)))
|
|
(semaphore-post eval-thread-state-sema)
|
|
(semaphore-post eval-thread-queue-sema))
|
|
|
|
(define/private (init-evaluation-thread) ; =Kernel=
|
|
(set! user-language-settings (send definitions-text get-next-settings))
|
|
|
|
(set! user-custodian-parent (make-custodian))
|
|
(set! user-custodian (parameterize ([current-custodian user-custodian-parent])
|
|
(make-custodian)))
|
|
(set! memory-killed-cust-box (make-custodian-box user-custodian-parent #t))
|
|
(when custodian-limit
|
|
(custodian-limit-memory user-custodian-parent
|
|
custodian-limit
|
|
user-custodian-parent))
|
|
(let ([user-eventspace (parameterize ([current-custodian user-custodian])
|
|
(make-eventspace))])
|
|
(set! user-eventspace-box (make-weak-box user-eventspace))
|
|
(set! user-break-parameterization (parameterize-break
|
|
#t
|
|
(current-break-parameterization)))
|
|
(set! eval-thread-thunks null)
|
|
(set! eval-thread-state-sema (make-semaphore 1))
|
|
(set! eval-thread-queue-sema (make-semaphore 0))
|
|
(set! user-exit-code #f)
|
|
|
|
(reset-logger-messages)
|
|
|
|
(let* ([init-thread-complete (make-semaphore 0)]
|
|
[goahead (make-semaphore)])
|
|
|
|
; setup standard parameters
|
|
(let ([snip-classes
|
|
; the snip-classes in the DrRacket eventspace's snip-class-list
|
|
(drracket:eval:get-snip-classes)]
|
|
[drs-eventspace (current-eventspace)])
|
|
(queue-user/wait
|
|
(λ () ; =User=, =No-Breaks=
|
|
; No user code has been evaluated yet, so we're in the clear...
|
|
(break-enabled #f)
|
|
(set! user-eventspace-main-thread (current-thread))
|
|
|
|
(current-logger user-logger)
|
|
|
|
(thread
|
|
(λ ()
|
|
(struct gui-event (start? msec name) #:prefab)
|
|
;; forward system events the user's logger, and record any
|
|
;; events that happen on the user's logger to show in the GUI
|
|
(let ([sys-evt (make-log-receiver drracket:init:system-logger 'debug)]
|
|
[user-evt (make-log-receiver user-logger 'debug)])
|
|
(let loop ()
|
|
(sync
|
|
(handle-evt
|
|
sys-evt
|
|
(λ (logged)
|
|
(unless (gui-event? (vector-ref logged 2))
|
|
(log-message user-logger
|
|
(vector-ref logged 0)
|
|
(vector-ref logged 1)
|
|
(vector-ref logged 2)))
|
|
(loop)))
|
|
(handle-evt
|
|
user-evt
|
|
(λ (vec)
|
|
(unless (gui-event? (vector-ref vec 2))
|
|
(parameterize ([current-eventspace drracket:init:system-eventspace])
|
|
(queue-callback (λ () (new-log-message vec)))))
|
|
(loop))))))))
|
|
|
|
(initialize-parameters snip-classes)
|
|
(let ([drracket-exit-handler
|
|
(λ (x)
|
|
(parameterize-break
|
|
#f
|
|
(let ([s (make-semaphore)])
|
|
(parameterize ([current-eventspace drs-eventspace])
|
|
(queue-callback
|
|
(λ ()
|
|
(set! user-exit-code
|
|
(if (exact-integer? x)
|
|
(modulo x 256)
|
|
0))
|
|
(semaphore-post s))))
|
|
(semaphore-wait s)
|
|
(custodian-shutdown-all user-custodian))))])
|
|
(exit-handler drracket-exit-handler)))))
|
|
|
|
|
|
(queue-user/wait
|
|
(λ ()
|
|
;; register drscheme with the planet-terse-register for the user's namespace
|
|
;; must be called after 'initialize-parameters' is called (since it initializes
|
|
;; the user's namespace)
|
|
(planet-terse-set-key (namespace-module-registry (current-namespace)))
|
|
(planet-terse-register
|
|
(lambda (tag package)
|
|
(parameterize ([current-eventspace drracket:init:system-eventspace])
|
|
(queue-callback (λ () (new-planet-info tag package))))))))
|
|
|
|
;; disable breaks until an evaluation actually occurs
|
|
(send context set-breakables #f #f)
|
|
|
|
;; initialize the language
|
|
(send (drracket:language-configuration:language-settings-language user-language-settings)
|
|
on-execute
|
|
(drracket:language-configuration:language-settings-settings user-language-settings)
|
|
(let ([run-on-user-thread (lambda (t)
|
|
(queue-user/wait
|
|
(λ ()
|
|
(with-handlers ((exn? (λ (x) (oprintf "~s\n" (exn-message x)))))
|
|
(t)))))])
|
|
run-on-user-thread))
|
|
|
|
;; setup the special repl values
|
|
(let ([raised-exn? #f]
|
|
[exn #f])
|
|
(queue-user/wait
|
|
(λ () ; =User=, =No-Breaks=
|
|
(with-handlers ((void (λ (x)
|
|
(set! exn x)
|
|
(set! raised-exn? #t))))
|
|
(drracket:language:setup-setup-values))))
|
|
(when raised-exn?
|
|
(eprintf
|
|
(string-append
|
|
"copied exn raised when setting up snip values"
|
|
" (thunk passed as third argume to drracket:language:add-snip-value)\n"))
|
|
(raise exn)))
|
|
|
|
;; allow extensions to this class to do some setup work
|
|
(on-execute
|
|
(let ([run-on-user-thread (lambda (t) (queue-user/wait t))])
|
|
run-on-user-thread))
|
|
|
|
(parameterize ([current-eventspace user-eventspace])
|
|
(queue-callback
|
|
(λ ()
|
|
(set! in-evaluation? #f)
|
|
(update-running #f)
|
|
(send context set-breakables #f #f)
|
|
|
|
;; after this returns, future event dispatches
|
|
;; will use the user's break parameterization
|
|
(initialize-dispatch-handler)
|
|
|
|
;; let init-thread procedure return,
|
|
;; now that parameters are set
|
|
(semaphore-post init-thread-complete)
|
|
|
|
; We're about to start running user code.
|
|
|
|
; Pause to let killed-thread get initialized
|
|
(semaphore-wait goahead)
|
|
|
|
(let loop () ; =User=, =Handler=, =No-Breaks=
|
|
; Wait for something to do
|
|
(unless (semaphore-try-wait? eval-thread-queue-sema)
|
|
; User event callbacks run here; we turn on
|
|
; breaks in the dispatch handler.
|
|
(yield eval-thread-queue-sema))
|
|
; About to eval something
|
|
(semaphore-wait eval-thread-state-sema)
|
|
(let ([thunk (car eval-thread-thunks)])
|
|
(set! eval-thread-thunks (cdr eval-thread-thunks))
|
|
(semaphore-post eval-thread-state-sema)
|
|
; This thunk evals the user's expressions with appropriate
|
|
; protections.
|
|
(thunk))
|
|
(loop)))))
|
|
(semaphore-wait init-thread-complete)
|
|
; Start killed-thread
|
|
(initialize-killed-thread)
|
|
; Let user expressions go...
|
|
(semaphore-post goahead))))
|
|
|
|
(define/private (queue-user/wait thnk)
|
|
(let ([wait (make-semaphore 0)])
|
|
(parameterize ([current-eventspace (get-user-eventspace)])
|
|
(queue-callback
|
|
(λ ()
|
|
(thnk)
|
|
(semaphore-post wait))))
|
|
(semaphore-wait wait)))
|
|
|
|
(field (shutting-down? #f))
|
|
|
|
(define/override (allow-close-with-no-filename?) #t)
|
|
(define/augment (can-close?)
|
|
(and (cond
|
|
[in-evaluation?
|
|
(equal? (message-box/custom
|
|
(string-constant drscheme)
|
|
(string-constant program-is-still-running)
|
|
(string-constant close-anyway)
|
|
(string-constant cancel)
|
|
#f
|
|
(or (get-top-level-window) (get-can-close-parent))
|
|
'(default=1 caution)
|
|
2
|
|
#:dialog-mixin frame:focus-table-mixin)
|
|
1)]
|
|
[(let ([user-eventspace (get-user-eventspace)])
|
|
(and user-eventspace
|
|
(parameterize ([current-eventspace user-eventspace])
|
|
(not (null? (get-top-level-windows))))))
|
|
(equal? (message-box/custom
|
|
(string-constant drscheme)
|
|
(string-constant program-has-open-windows)
|
|
(string-constant close-anyway)
|
|
(string-constant cancel)
|
|
#f
|
|
(or (get-top-level-window) (get-can-close-parent))
|
|
'(default=1 caution)
|
|
2
|
|
#:dialog-mixin frame:focus-table-mixin)
|
|
1)]
|
|
[else #t])
|
|
(inner #t can-close?)))
|
|
|
|
(define/augment (on-close)
|
|
(shutdown)
|
|
(preferences:set 'drracket:console-previous-exprs
|
|
(trim-previous-exprs
|
|
(append
|
|
(preferences:get 'drracket:console-previous-exprs)
|
|
local-previous-exprs)))
|
|
(inner (void) on-close))
|
|
|
|
(define/public (shutdown) ; =Kernel=, =Handler=
|
|
(set! shutting-down? #t)
|
|
(when (thread? thread-killed)
|
|
(kill-thread thread-killed)
|
|
(set! thread-killed #f))
|
|
(shutdown-user-custodian))
|
|
|
|
(define/private update-running ; =User=, =Handler=, =No-Breaks=
|
|
(λ (bool)
|
|
(queue-system-callback
|
|
(get-user-thread)
|
|
(λ ()
|
|
(send context update-running bool)))))
|
|
|
|
(define logger-editor #f)
|
|
(define logger-messages '())
|
|
(define/public (get-logger-messages) logger-messages)
|
|
(define/private (new-log-message vec)
|
|
(let* ([level (vector-ref vec 0)]
|
|
[str (cond
|
|
[(<= (string-length (vector-ref vec 1)) log-entry-max-size)
|
|
(vector-ref vec 1)]
|
|
[else
|
|
(substring (vector-ref vec 1) 0 log-entry-max-size)])]
|
|
[msg (vector level str)])
|
|
(cond
|
|
[(< (length logger-messages) log-max-size)
|
|
(set! logger-messages (cons msg logger-messages))
|
|
(update-logger-gui (cons 'add-line msg))]
|
|
[else
|
|
(set! logger-messages
|
|
(cons
|
|
msg
|
|
(let loop ([msgs logger-messages])
|
|
(cond
|
|
[(null? (cdr msgs)) null]
|
|
[else (cons (car msgs) (loop (cdr msgs)))]))))
|
|
(update-logger-gui (cons 'clear-last-line-and-add-line msg))])))
|
|
|
|
(define/private (reset-logger-messages)
|
|
(set! logger-messages '())
|
|
(update-logger-gui #f))
|
|
|
|
(define/private (update-logger-gui command)
|
|
(let ([tab (send definitions-text get-tab)])
|
|
(send tab update-logger-window command)))
|
|
|
|
(define/private (new-planet-info tag package)
|
|
(let ([frame (get-frame)])
|
|
(when frame
|
|
(send (send frame get-current-tab) new-planet-status tag package))))
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;;; ;;;
|
|
;;; Execution ;;;
|
|
;;; ;;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; initialize-paramters : (listof snip-class%) -> void
|
|
(define/private (initialize-parameters snip-classes) ; =User=
|
|
|
|
(current-language-settings user-language-settings)
|
|
(error-print-source-location #f)
|
|
(error-display-handler drracket-error-display-handler)
|
|
(current-load-relative-directory #f)
|
|
(current-custodian user-custodian)
|
|
(current-load text-editor-load-handler)
|
|
|
|
(drracket:eval:set-basic-parameters snip-classes)
|
|
(current-rep this)
|
|
(let ([dir (or (send context get-directory)
|
|
drracket:init:first-dir)])
|
|
(current-directory dir))
|
|
|
|
(set! user-namespace-box (make-weak-box (current-namespace)))
|
|
|
|
(current-output-port (get-out-port))
|
|
(current-error-port (get-err-port))
|
|
(current-value-port (get-value-port))
|
|
(current-input-port (get-in-box-port))
|
|
|
|
(current-print (lambda (v) (display-results (list v)))))
|
|
|
|
(define/private (initialize-dispatch-handler) ;;; =User=
|
|
(let* ([primitive-dispatch-handler (event-dispatch-handler)])
|
|
(event-dispatch-handler
|
|
(letrec ([drscheme-event-dispatch-handler ; <= a name for #<...> printout
|
|
(λ (eventspace) ; =User=, =Handler=
|
|
; Breaking is enabled if the user turned on breaks and
|
|
; is in a `yield'. If we get a break, that's ok, because
|
|
; the kernel never queues an event in the user's eventspace.
|
|
(cond
|
|
[(eq? eventspace (get-user-eventspace))
|
|
; =User=, =Handler=
|
|
|
|
; We must distinguish between "top-level" events and
|
|
; those within `yield' in the user's program.
|
|
(cond
|
|
[(not in-evaluation?)
|
|
;; at this point, we must not be in a nested dispatch, so we can
|
|
;; just disable breaks and rely on call-with-break-parameterization
|
|
;; to restore them to the user's setting.
|
|
(call-with-break-parameterization
|
|
no-breaks-break-parameterization
|
|
(λ ()
|
|
; =No-Breaks=
|
|
(send context reset-offer-kill)
|
|
(send context set-breakables (get-user-thread) (get-user-custodian))
|
|
(call-with-continuation-prompt
|
|
(λ () ; =User=, =Handler=, =No-Breaks=
|
|
(call-with-break-parameterization
|
|
user-break-parameterization
|
|
(λ () (primitive-dispatch-handler eventspace)))))
|
|
|
|
;; in principle, the line below might cause
|
|
;; "race conditions" in the GUI. That is, there might
|
|
;; be many little events that the user won't quite
|
|
;; be able to break.
|
|
(send context set-breakables #f #f)))]
|
|
[else
|
|
; Nested dispatch; don't adjust interface
|
|
(primitive-dispatch-handler eventspace)])]
|
|
[else
|
|
; =User=, =Non-Handler=, =No-Breaks=
|
|
(primitive-dispatch-handler eventspace)]))])
|
|
drscheme-event-dispatch-handler))))
|
|
|
|
(define/public (reset-console)
|
|
(when (thread? thread-killed)
|
|
(kill-thread thread-killed))
|
|
(send context clear-annotations)
|
|
(drracket:debug:hide-backtrace-window)
|
|
(shutdown-user-custodian)
|
|
(clear-input-port)
|
|
(clear-box-input-port)
|
|
(clear-output-ports)
|
|
(set-allow-edits #t)
|
|
|
|
;; in case the last evaluation thread was killed, clean up some state.
|
|
(lock #f)
|
|
(set! in-evaluation? #f)
|
|
(update-running #f)
|
|
|
|
;; clear out repl first before doing any work.
|
|
(begin-edit-sequence)
|
|
(set! prompt-position #f)
|
|
(reset-input-box)
|
|
(delete (paragraph-start-position 1) (last-position))
|
|
(end-edit-sequence)
|
|
|
|
;; must init-evaluation-thread before determining
|
|
;; the language's name, since this updates user-language-settings
|
|
(init-evaluation-thread)
|
|
|
|
(begin-edit-sequence)
|
|
(set-position (last-position) (last-position))
|
|
|
|
(set! setting-up-repl? #t)
|
|
(insert/delta this (string-append (string-constant language) ": ") welcome-delta)
|
|
(let-values (((before after)
|
|
(insert/delta
|
|
this
|
|
(extract-language-name user-language-settings definitions-text)
|
|
dark-green-delta
|
|
(extract-language-style-delta user-language-settings)))
|
|
((url) (extract-language-url user-language-settings)))
|
|
(when url
|
|
(set-clickback before after (λ args (send-url url))
|
|
click-delta)))
|
|
(unless (is-default-settings? user-language-settings)
|
|
(insert/delta this (string-append " [" (string-constant custom) "]") dark-green-delta))
|
|
(when custodian-limit
|
|
(insert/delta this
|
|
"; memory limit: "
|
|
welcome-delta)
|
|
(insert/delta this
|
|
(format "~a MB" (floor (/ custodian-limit 1024 1024)))
|
|
dark-green-delta))
|
|
(insert/delta this ".\n" welcome-delta)
|
|
|
|
(let ([osf (get-styles-fixed)])
|
|
(set-styles-fixed #f)
|
|
(send (drracket:language-configuration:language-settings-language user-language-settings)
|
|
extra-repl-information
|
|
(drracket:language-configuration:language-settings-settings user-language-settings)
|
|
(open-output-text-editor this 'end))
|
|
(set-styles-fixed osf))
|
|
|
|
(set! setting-up-repl? #f)
|
|
|
|
(reset-regions (list (list (last-position) (last-position))))
|
|
(set-unread-start-point (last-position))
|
|
(set-insertion-point (last-position))
|
|
(set! indenting-limit (last-position))
|
|
(set-allow-edits #f)
|
|
(set! repl-header-end #f)
|
|
(end-edit-sequence))
|
|
|
|
(define/public (initialize-console)
|
|
(begin-edit-sequence)
|
|
(freeze-colorer)
|
|
(set! setting-up-repl? #t)
|
|
(insert/delta this (string-append (string-constant welcome-to) " ") welcome-delta)
|
|
(let-values ([(before after)
|
|
(insert/delta this
|
|
(string-constant drscheme)
|
|
click-delta
|
|
drs-font-delta)])
|
|
(insert/delta this (format (string-append ", " (string-constant version) " ~a [~a].\n")
|
|
(version:version) (system-type 'gc))
|
|
welcome-delta)
|
|
(set-clickback before after
|
|
(λ args (drracket:app:about-drscheme))
|
|
click-delta))
|
|
(set! setting-up-repl? #f)
|
|
(send context disable-evaluation)
|
|
(reset-console)
|
|
(thaw-colorer)
|
|
(insert-prompt)
|
|
|
|
;; call the first-opened method on the user's thread, but wait here for that to terminate
|
|
(let ([lang (drracket:language-configuration:language-settings-language user-language-settings)]
|
|
[drr-evtspace (current-eventspace)]
|
|
[s (make-semaphore 0)])
|
|
|
|
(define-values (sp-err-other-end sp-err) (make-pipe-with-specials))
|
|
(define-values (sp-out-other-end sp-out) (make-pipe-with-specials))
|
|
(define io-chan (make-channel))
|
|
|
|
;; collect the IO to replay later
|
|
(thread
|
|
(λ ()
|
|
(let loop ([ports (list sp-err-other-end sp-out-other-end)]
|
|
[io '()])
|
|
(cond
|
|
[(null? ports) (channel-put io-chan io)]
|
|
[else
|
|
(apply sync
|
|
(map (λ (port) (handle-evt
|
|
port
|
|
(λ (_)
|
|
(define byte/special (read-byte-or-special port))
|
|
(if (eof-object? byte/special)
|
|
(loop (remq port ports) io)
|
|
(loop ports (cons (cons port byte/special)
|
|
io))))))
|
|
ports))]))))
|
|
|
|
(run-in-evaluation-thread
|
|
(λ ()
|
|
(let/ec k
|
|
;; we set the io ports here to ones that just collect the data
|
|
;; since we're blocking the eventspace handler thread (and thus IO to
|
|
;; the user's ports can deadlock)
|
|
(parameterize ([error-escape-handler (λ () (k (void)))]
|
|
[current-output-port sp-out]
|
|
[current-error-port sp-err])
|
|
(cond
|
|
;; this is for backwards compatibility; drracket used to
|
|
;; expect this method to be a thunk (but that was a bad decision)
|
|
[(object-method-arity-includes? lang 'first-opened 1)
|
|
(send lang first-opened
|
|
(drracket:language-configuration:language-settings-settings user-language-settings))]
|
|
[else
|
|
;; this is the backwards compatible case.
|
|
(send lang first-opened)])))
|
|
(semaphore-post s)))
|
|
|
|
;; wait for the first-opened method to finish up
|
|
(semaphore-wait s)
|
|
|
|
;; close the output ports to get the above thread to terminate
|
|
(close-output-port sp-err)
|
|
(close-output-port sp-out)
|
|
|
|
;; duplicate it over to the user's ports, now that there is
|
|
;; no danger of deadlock
|
|
(for ([i (in-list (reverse (channel-get io-chan)))])
|
|
(define obj (cdr i))
|
|
|
|
((if (byte? obj) write-byte write-special)
|
|
obj
|
|
(if (eq? (car i) sp-err-other-end)
|
|
(get-err-port)
|
|
(get-out-port))))
|
|
(flush-output (get-err-port))
|
|
(flush-output (get-out-port)))
|
|
|
|
(send context enable-evaluation)
|
|
(end-edit-sequence)
|
|
(clear-undos))
|
|
|
|
(define indenting-limit 0)
|
|
(define/override (get-limit n)
|
|
(cond
|
|
[(< n indenting-limit) 0]
|
|
[else indenting-limit]))
|
|
|
|
;; avoid calling paragraph-start-position very often.
|
|
(define repl-header-end #f)
|
|
(define/private (get-repl-header-end)
|
|
(if repl-header-end
|
|
repl-header-end
|
|
(begin (set! repl-header-end (paragraph-start-position 2))
|
|
repl-header-end)))
|
|
|
|
(define setting-up-repl? #f)
|
|
(define/augment (can-change-style? start len)
|
|
(and (inner #t can-change-style? start len)
|
|
(or setting-up-repl?
|
|
(start . >= . (get-repl-header-end)))))
|
|
|
|
(define/private (last-str l)
|
|
(if (null? (cdr l))
|
|
(car l)
|
|
(last-str (cdr l))))
|
|
|
|
(field (previous-expr-pos -1))
|
|
|
|
(define/public (copy-previous-expr)
|
|
(when prompt-position
|
|
(let ([snip/strings (list-ref (get-previous-exprs) previous-expr-pos)])
|
|
(begin-edit-sequence)
|
|
(delete prompt-position (last-position) #f)
|
|
(for-each (λ (snip/string)
|
|
(insert (if (is-a? snip/string snip%)
|
|
(send snip/string copy)
|
|
snip/string)
|
|
prompt-position))
|
|
snip/strings)
|
|
(set-position (last-position))
|
|
(end-edit-sequence))))
|
|
|
|
(define/public (copy-next-previous-expr)
|
|
(let ([previous-exprs (get-previous-exprs)])
|
|
(unless (null? previous-exprs)
|
|
(set! previous-expr-pos
|
|
(if (< (add1 previous-expr-pos) (length previous-exprs))
|
|
(add1 previous-expr-pos)
|
|
0))
|
|
(copy-previous-expr))))
|
|
(define/public (copy-prev-previous-expr)
|
|
(let ([previous-exprs (get-previous-exprs)])
|
|
(unless (null? previous-exprs)
|
|
(set! previous-expr-pos
|
|
(if (previous-expr-pos . <= . 0)
|
|
(sub1 (length previous-exprs))
|
|
(sub1 previous-expr-pos)))
|
|
(copy-previous-expr))))
|
|
|
|
;; private fields
|
|
(define global-previous-exprs (preferences:get 'drracket:console-previous-exprs))
|
|
(define local-previous-exprs null)
|
|
(define/private (get-previous-exprs)
|
|
(append global-previous-exprs local-previous-exprs))
|
|
(define/private (add-to-previous-exprs snips)
|
|
(set! local-previous-exprs (append local-previous-exprs (list snips))))
|
|
|
|
; list-of-lists-of-snip/strings? -> list-of-lists-of-snip/strings?
|
|
(define/private (trim-previous-exprs lst)
|
|
(define max-size 10000)
|
|
(define (expr-size expr)
|
|
(for/fold ([s 0]) ([e expr]) (+ s (string-length e))))
|
|
(define within-bound
|
|
(let loop ([marshalled (reverse (marshall-previous-exprs lst))]
|
|
[keep 0]
|
|
[sum 0])
|
|
(if (empty? marshalled)
|
|
keep
|
|
(let* ([size (expr-size (first marshalled))]
|
|
[w/another (+ size sum)])
|
|
(if (> w/another max-size)
|
|
keep
|
|
(loop (rest marshalled) (add1 keep) w/another))))))
|
|
(take-right lst within-bound))
|
|
|
|
(define/private (save-interaction-in-history start end)
|
|
(split-snip start)
|
|
(split-snip end)
|
|
(let ([snips
|
|
(let loop ([snip (find-snip start 'after-or-none)]
|
|
[snips null])
|
|
(cond
|
|
[(not snip) snips]
|
|
[((get-snip-position snip) . <= . end)
|
|
(loop (send snip next)
|
|
(cons (send snip copy) snips))]
|
|
[else snips]))])
|
|
(set! previous-expr-pos -1)
|
|
(add-to-previous-exprs snips)))
|
|
|
|
(define pretty-print-width (pretty-print-columns))
|
|
(define/private (reset-pretty-print-width)
|
|
(let* ([standard (send (get-style-list) find-named-style "Standard")])
|
|
(when standard
|
|
(let* ([admin (get-admin)]
|
|
[width
|
|
(let ([bw (box 0)]
|
|
[b2 (box 0)])
|
|
(send admin get-view b2 b2 bw b2)
|
|
(unbox bw))]
|
|
[dc (send admin get-dc)]
|
|
[new-font (send standard get-font)]
|
|
[old-font (send dc get-font)])
|
|
(send dc set-font new-font)
|
|
(let* ([char-width (send dc get-char-width)]
|
|
[min-columns 50]
|
|
[new-columns (max min-columns
|
|
(floor (/ width char-width)))])
|
|
(send dc set-font old-font)
|
|
(set! pretty-print-width new-columns))))))
|
|
|
|
;; get-frame : -> (or/c #f (is-a?/c frame))
|
|
(define/private (get-frame)
|
|
(let ([c (get-any-canvas)])
|
|
(and c (send c get-top-level-window))))
|
|
|
|
|
|
;; returns the most recently active canvas or, if no canvas
|
|
;; has ever been active, it returns just any canvas
|
|
(define/private (get-any-canvas)
|
|
(or (get-active-canvas)
|
|
(let ([canvases (get-canvases)])
|
|
(and (not (null? canvases))
|
|
(car canvases)))))
|
|
|
|
(inherit paragraph-end-position)
|
|
(define/override (get-start-of-line pos)
|
|
(define para (position-paragraph pos))
|
|
(define para-start (paragraph-start-position para))
|
|
(define para-end (paragraph-end-position para))
|
|
(define after-prompt-start
|
|
(let* ([prompt (get-prompt)]
|
|
[para-start-text (get-text para-start (+ para-start (string-length prompt)))])
|
|
(cond
|
|
[(equal? prompt para-start-text)
|
|
(+ para-start (string-length prompt))]
|
|
[else
|
|
para-start])))
|
|
(define first-non-whitespace
|
|
(let loop ([i after-prompt-start])
|
|
(cond
|
|
[(= i para-end) #f]
|
|
[(char-whitespace? (get-character i))
|
|
(loop (+ i 1))]
|
|
[else i])))
|
|
(define new-pos
|
|
(cond
|
|
[(not first-non-whitespace) after-prompt-start]
|
|
[(< pos after-prompt-start) after-prompt-start]
|
|
[(= pos after-prompt-start) first-non-whitespace]
|
|
[(<= pos first-non-whitespace) after-prompt-start]
|
|
[else first-non-whitespace]))
|
|
new-pos)
|
|
|
|
(super-new)
|
|
(auto-wrap #t)
|
|
(set-styles-sticky #f)
|
|
|
|
(inherit set-max-undo-history)
|
|
(set-max-undo-history 'forever)))
|
|
|
|
(define (all-but-last lst)
|
|
(let loop ([o lst])
|
|
(cond
|
|
[(null? o) null]
|
|
[(null? (cdr o)) null]
|
|
[else (cons (car o) (loop (cdr o)))])))
|
|
|
|
(define input-delta (make-object style-delta%))
|
|
(send input-delta set-delta-foreground (make-object color% 0 150 0))
|
|
|
|
;; insert-error-in-text : (is-a?/c text%)
|
|
;; (union #f (is-a?/c drracket:rep:text<%>))
|
|
;; string?
|
|
;; exn?
|
|
;; (union false? (and/c string? directory-exists?))
|
|
;; ->
|
|
;; void?
|
|
(define (insert-error-in-text text interactions-text msg exn user-dir)
|
|
(insert-error-in-text/highlight-errors
|
|
text
|
|
(λ (l) (send interactions-text highlight-errors l))
|
|
msg
|
|
exn
|
|
user-dir))
|
|
|
|
;; insert-error-in-text/highlight-errors : (is-a?/c text%)
|
|
;; ((listof (list text% number number)) -> void)
|
|
;; string?
|
|
;; exn?
|
|
;; (union false? (and/c string? directory-exists?))
|
|
;; ->
|
|
;; void?
|
|
(define (insert-error-in-text/highlight-errors text highlight-errors msg exn user-dir)
|
|
(let ([locked? (send text is-locked?)]
|
|
[insert-file-name/icon
|
|
;; insert-file-name/icon : string number number number number -> void
|
|
(λ (source-name start span row col)
|
|
(let ([range-spec
|
|
(cond
|
|
[(and row col)
|
|
(format ":~a:~a" row col)]
|
|
[start
|
|
(format "::~a" start)]
|
|
[else ""])])
|
|
(cond
|
|
[(file-exists? source-name)
|
|
(let* ([normalized-name (normalize-path source-name)]
|
|
[short-name (if user-dir
|
|
(find-relative-path user-dir normalized-name)
|
|
source-name)])
|
|
(let-values ([(icon-start icon-end) (insert/delta text (send file-icon copy))]
|
|
[(space-start space-end) (insert/delta text " ")]
|
|
[(name-start name-end) (insert/delta text short-name)]
|
|
[(range-start range-end) (insert/delta text range-spec)]
|
|
[(colon-start colon-ent) (insert/delta text ": ")])
|
|
(when (number? start)
|
|
(send text set-clickback icon-start range-end
|
|
(λ (_1 _2 _3)
|
|
(open-file-and-highlight normalized-name
|
|
(- start 1)
|
|
(if span
|
|
(+ start -1 span)
|
|
start)))))))]
|
|
[else
|
|
(insert/delta text source-name)
|
|
(insert/delta text range-spec)
|
|
(insert/delta text ": ")])))])
|
|
(send text begin-edit-sequence)
|
|
(send text lock #f)
|
|
(cond
|
|
[(exn:fail:syntax? exn)
|
|
(for-each
|
|
(λ (expr)
|
|
(let ([src (and (syntax? expr) (syntax-source expr))]
|
|
[pos (and (syntax? expr) (syntax-position expr))]
|
|
[span (and (syntax? expr) (syntax-span expr))]
|
|
[col (and (syntax? expr) (syntax-column expr))]
|
|
[line (and (syntax? expr) (syntax-line expr))])
|
|
(when (and (string? src)
|
|
(number? pos)
|
|
(number? span)
|
|
(number? line)
|
|
(number? col))
|
|
(insert-file-name/icon src pos span line col))
|
|
(insert/delta text (format "~a" (exn-message exn)) error-delta)
|
|
(when (and (error-print-source-location)
|
|
(syntax? expr))
|
|
(insert/delta text " in: ")
|
|
(insert/delta text (format "~s" (syntax->datum expr)) error-text-style-delta))
|
|
(insert/delta text "\n")
|
|
(when (and (is-a? src text:basic<%>)
|
|
(number? pos)
|
|
(number? span))
|
|
(highlight-errors (list (list src (- pos 1) (+ pos -1 span)))))))
|
|
(exn:fail:syntax-exprs exn))]
|
|
[(exn:fail:read? exn)
|
|
'(let ([src (exn:read-source exn)]
|
|
[pos (exn:read-position exn)]
|
|
[span (exn:read-span exn)]
|
|
[line (exn:read-line exn)]
|
|
[col (exn:read-column exn)])
|
|
(when (and (string? src)
|
|
(number? pos)
|
|
(number? span)
|
|
(number? line)
|
|
(number? col))
|
|
(insert-file-name/icon src pos span line col))
|
|
(insert/delta text (format "~a" (exn-message exn)) error-delta)
|
|
(insert/delta text "\n")
|
|
(when (and (is-a? src text:basic<%>)
|
|
(number? pos)
|
|
(number? span))
|
|
(highlight-errors (list (list src (- pos 1) (+ pos -1 span))))))]
|
|
[(exn? exn)
|
|
(insert/delta text (format "~a" (exn-message exn)) error-delta)
|
|
(insert/delta text "\n")]
|
|
[else
|
|
(insert/delta text "uncaught exception: " error-delta)
|
|
(insert/delta text (format "~s" exn) error-delta)
|
|
(insert/delta text "\n")])
|
|
(send text lock locked?)
|
|
(send text end-edit-sequence)))
|
|
|
|
|
|
;; open-file-and-highlight : string (union number #f) (union number #f)
|
|
;; =Kernel, =Handler=
|
|
;; opens the file named by filename. If position is #f,
|
|
;; doesn't highlight anything. If position is a number and other-position
|
|
;; is #f, highlights the range from position to the end of sexp.
|
|
;; if other-position is a number, highlights from position to
|
|
;; other position.
|
|
(define (open-file-and-highlight filename position other-position)
|
|
(let ([file (handler:edit-file filename)])
|
|
(when (and (is-a? file drracket:unit:frame%)
|
|
position)
|
|
(if other-position
|
|
(send (send file get-interactions-text)
|
|
highlight-error
|
|
(send file get-definitions-text)
|
|
position
|
|
other-position)
|
|
(send (send file get-interactions-text)
|
|
highlight-error/forward-sexp
|
|
(send file get-definitions-text)
|
|
position)))))
|
|
|
|
(define drs-autocomplete-mixin
|
|
(λ (get-defs x)
|
|
(class (text:autocomplete-mixin x)
|
|
(define/override (get-all-words)
|
|
(let* ([definitions-text (get-defs this)]
|
|
[settings (send definitions-text get-next-settings)]
|
|
[language (drracket:language-configuration:language-settings-language settings)])
|
|
(send language capability-value 'drscheme:autocomplete-words)))
|
|
(super-new))))
|
|
|
|
(define -text%
|
|
(text-mixin
|
|
;; drs-bindings-keymap-mixin has to come
|
|
;; before text-mixin so that the keymaps
|
|
;; get added in the right order (specifically
|
|
;; so that esc;n and esc;p work right in the
|
|
;; repl (prev and next interaction) and in the defs
|
|
;; (previous and next error))
|
|
(drs-bindings-keymap-mixin
|
|
(text:ports-mixin
|
|
(racket:text-mixin
|
|
(color:text-mixin
|
|
(text:info-mixin
|
|
(editor:info-mixin
|
|
(text:searching-mixin
|
|
(mode:host-text-mixin
|
|
(drs-autocomplete-mixin
|
|
(λ (txt) (send txt get-definitions-text))
|
|
(text:foreground-color-mixin
|
|
(text:normalize-paste-mixin
|
|
text:clever-file-format%))))))))))))))
|