diff --git a/collects/drracket/check-syntax.rkt b/collects/drracket/check-syntax.rkt index 90bd6bffc7..a864230980 100644 --- a/collects/drracket/check-syntax.rkt +++ b/collects/drracket/check-syntax.rkt @@ -11,6 +11,9 @@ (or/c path-string? #f) (values (->* (syntax?) ((-> syntax? void?)) void?) (-> void?)))] + + [current-max-to-send-at-once + (parameter/c (or/c +inf.0 (and/c exact-integer? (>=/c 2))))] [syncheck-annotations<%> interface?] [current-annotations @@ -25,7 +28,7 @@ syncheck:add-background-color syncheck:add-require-open-menu syncheck:add-docs-menu - syncheck:add-rename-menu + syncheck:add-id-set syncheck:add-arrow syncheck:add-tail-arrow syncheck:add-mouse-over-status diff --git a/collects/drracket/private/syncheck/gui.rkt b/collects/drracket/private/syncheck/gui.rkt index ce100b0a04..e983425f07 100644 --- a/collects/drracket/private/syncheck/gui.rkt +++ b/collects/drracket/private/syncheck/gui.rkt @@ -38,6 +38,7 @@ If the namespace does not, they are colored the unbound color. framework net/url browser/external + data/union-find (for-syntax racket/base) (only-in ffi/unsafe register-finalizer) "../../syncheck-drracket-button.rkt" @@ -219,11 +220,15 @@ If the namespace does not, they are colored the unbound color. (define-struct tooltip-info (text pos-left pos-right msg) #:transparent) + ;; set : (uf-set (list/c source position span)) + ;; name-dup? : symbol? -> boolean? + (define-struct identifier-location-set (set name-dup?) #:transparent) + ;; color : string ;; text: text:basic<%> ;; start, fin: number ;; used to represent regions to highlight when passing the mouse over the syncheck window - (define-struct colored-region (color text start fin)) + (define-struct colored-region (color text start fin) #:transparent) ;; id : symbol -- the nominal-source-id from identifier-binding ;; filename : path @@ -355,11 +360,30 @@ If the namespace does not, they are colored the unbound color. ;; - tail-link ;; - arrow ;; - string + ;; - colored-region + ;; - identifier-location-set (define/private (get-arrow-record table text) + (unless (object? text) + (error 'get-arrow-record "expected a text as the second argument, got ~e" text)) (hash-ref! table text (lambda () (make-interval-map)))) (define arrow-records #f) + (define/public (dump-arrow-records) + (cond + [arrow-records + (for ([(k v) (in-hash arrow-records)]) + (printf "\n\n~s:\n" k) + (let loop ([it (interval-map-iterate-first v)]) + (when it + (printf "~s =>\n" (interval-map-iterate-key v it)) + (for ([v (in-list (interval-map-iterate-value v it))]) + (printf " ~s\n" v)) + (printf "\n") + (loop (interval-map-iterate-next v it)))))] + [else + (printf "arrow-records empty\n")])) + ;; cleanup-texts : (or/c #f (listof text)) (define cleanup-texts #f) @@ -612,17 +636,23 @@ If the namespace does not, they are colored the unbound color. (λ (x y) (visit-docs-url))))))) - (define/public (syncheck:add-rename-menu id-as-sym to-be-renamed/poss name-dup?) - (define (make-menu menu) - (let ([name-to-offer (format "~a" id-as-sym)]) - (new menu-item% - [parent menu] - [label (fw:gui-utils:format-literal-label (string-constant cs-rename-var) name-to-offer)] - [callback - (λ (x y) - (let ([frame-parent (find-menu-parent menu)]) - (rename-callback name-to-offer - frame-parent)))]))) + (define/public (syncheck:add-id-set to-be-renamed/poss name-dup?) + (add-identifier-to-range to-be-renamed/poss name-dup?)) + + (define/private (make-rename-menu menu an-identifier-location-set) + (define example-lst (set-first (uf-find (identifier-location-set-set an-identifier-location-set)))) + (define name-to-offer (send (list-ref example-lst 0) get-text + (list-ref example-lst 1) + (list-ref example-lst 2))) + (new menu-item% + [parent menu] + [label (fw:gui-utils:format-literal-label (string-constant cs-rename-var) name-to-offer)] + [callback + (λ (x y) + (let ([frame-parent (find-menu-parent menu)]) + (rename-menu-callback frame-parent + name-to-offer + an-identifier-location-set)))])) ;; rename-callback : string ;; (and/c syncheck-text<%> definitions-text<%>) @@ -631,61 +661,63 @@ If the namespace does not, they are colored the unbound color. ;; (union #f (is-a?/c top-level-window<%>)) ;; -> void ;; callback for the rename popup menu item - (define (rename-callback name-to-offer parent) - (let ([new-str - (fw:keymap:call/text-keymap-initializer - (λ () - (get-text-from-user - (string-constant cs-rename-id) - (fw:gui-utils:format-literal-label (string-constant cs-rename-var-to) name-to-offer) - parent - name-to-offer - #:dialog-mixin frame:focus-table-mixin)))]) - (when new-str - (define new-sym (format "~s" (string->symbol new-str))) - (define dup-name? (name-dup? new-sym)) - - (define do-renaming? - (or (not dup-name?) - (equal? - (message-box/custom - (string-constant check-syntax) - (fw:gui-utils:format-literal-label (string-constant cs-name-duplication-error) - new-sym) - (string-constant cs-rename-anyway) - (string-constant cancel) - #f - parent - '(stop default=2) - #:dialog-mixin frame:focus-table-mixin) - 1))) - - (when do-renaming? - (unless (null? to-be-renamed/poss) - (let ([txts (list this)]) - (define positions-to-rename - (remove-duplicates - (sort to-be-renamed/poss - > - #:key cadr))) - (begin-edit-sequence) - (for ([info (in-list positions-to-rename)]) - (define source-editor (list-ref info 0)) - (define start (list-ref info 1)) - (define end (list-ref info 2)) - (when (is-a? source-editor text%) - (unless (memq source-editor txts) - (send source-editor begin-edit-sequence) - (set! txts (cons source-editor txts))) - (send source-editor delete start end #f) - (send source-editor insert new-sym start start #f))) - (invalidate-bitmap-cache) - (for ([txt (in-list txts)]) - (send txt end-edit-sequence)))))))) + (define/private (rename-menu-callback parent name-to-offer an-identifier-location-set) + (define name-dup? (identifier-location-set-name-dup? an-identifier-location-set)) + (let ([new-str + (fw:keymap:call/text-keymap-initializer + (λ () + (get-text-from-user + (string-constant cs-rename-id) + (fw:gui-utils:format-literal-label (string-constant cs-rename-var-to) name-to-offer) + parent + name-to-offer + #:dialog-mixin frame:focus-table-mixin)))]) + (when new-str + (define new-sym (format "~s" (string->symbol new-str))) + (define dup-name? (name-dup? new-sym)) + + (define do-renaming? + (or (not dup-name?) + (equal? + (message-box/custom + (string-constant check-syntax) + (fw:gui-utils:format-literal-label (string-constant cs-name-duplication-error) + new-sym) + (string-constant cs-rename-anyway) + (string-constant cancel) + #f + parent + '(stop default=2) + #:dialog-mixin frame:focus-table-mixin) + 1))) + + (when do-renaming? + (let ([txts (list this)]) + (define positions-to-rename + (remove-duplicates + (sort (set->list (uf-find + (identifier-location-set-set + an-identifier-location-set))) + > + #:key cadr))) + (begin-edit-sequence) + (for ([info (in-list positions-to-rename)]) + (define source-editor (list-ref info 0)) + (define start (list-ref info 1)) + (define end (list-ref info 2)) + (when (is-a? source-editor text%) + (unless (memq source-editor txts) + (send source-editor begin-edit-sequence) + (set! txts (cons source-editor txts))) + (send source-editor delete start end #f) + (send source-editor insert new-sym start start #f))) + (invalidate-bitmap-cache) + (for ([txt (in-list txts)]) + (send txt end-edit-sequence))))))) ;; find-parent : menu-item-container<%> -> (union #f (is-a?/c top-level-window<%>) - (define (find-menu-parent menu) + (define/private (find-menu-parent menu) (let loop ([menu menu]) (cond [(is-a? menu menu-bar%) (send menu get-frame)] @@ -701,13 +733,7 @@ If the namespace does not, they are colored the unbound color. [else #f]))] [(is-a? menu menu-item<%>) (loop (send menu get-parent))] [else #f]))) - - (for ([loc (in-list to-be-renamed/poss)]) - (define source (list-ref loc 0)) - (define start (list-ref loc 1)) - (define fin (list-ref loc 2)) - (syncheck:add-menu source start fin id-as-sym make-menu))) - + (define/private (syncheck:add-menu text start-pos end-pos key make-menu) (when arrow-records (when (<= 0 start-pos end-pos (last-position)) @@ -792,6 +818,31 @@ If the namespace does not, they are colored the unbound color. start (add1 end) to-add null)]))) + (define/private (add-identifier-to-range text/start/ends name-dup?) + (define id-set (apply set text/start/ends)) + (define fresh-uf (uf-new id-set)) + (define new-il-set (identifier-location-set fresh-uf name-dup?)) + (for ([text/start/span (in-list text/start/ends)]) + (define arrow-record (get-arrow-record arrow-records (list-ref text/start/span 0))) + (define start (list-ref text/start/span 1)) + (define end (list-ref text/start/span 2)) + (interval-map-update*! arrow-record + start (add1 end) + (lambda (curr-val) + (define this-uf-set + (for/or ([thing (in-list curr-val)]) + (and (identifier-location-set? thing) + (identifier-location-set-set thing)))) + (cond + [this-uf-set + (set! id-set (set-union (uf-find this-uf-set) id-set)) + (uf-union! fresh-uf this-uf-set) + (uf-set-canonical! this-uf-set id-set) + curr-val] + [else + (cons new-il-set curr-val)])) + '()))) + (inherit get-top-level-window) (define/augment (on-change) @@ -1092,6 +1143,10 @@ If the namespace does not, they are colored the unbound color. (define var-arrows (filter var-arrow? arrows)) (define add-menus (append (map cdr (filter pair? vec-ents)) (filter procedure? vec-ents))) + (define identifier-location-set/f + (for/or ([x (in-list vec-ents)]) + (and (identifier-location-set? x) + x))) (unless (null? arrows) (add-sep) (make-object menu-item% @@ -1149,7 +1204,10 @@ If the namespace does not, they are colored the unbound color. arrow-record start-selection end-selection)))) - (for-each (λ (f) (f menu)) add-menus)))) + (for-each (λ (f) (f menu)) add-menus) + (when identifier-location-set/f + (make-rename-menu menu identifier-location-set/f)) + (void)))) (define tooltip-frame #f) (define/private (update-tooltip-frame) @@ -1635,7 +1693,7 @@ If the namespace does not, they are colored the unbound color. (send defs-text syncheck:add-require-open-menu defs-text start-pos end-pos file)] [`#(syncheck:add-docs-menu,start-pos ,end-pos ,key ,the-label ,path ,definition-tag ,tag) (send defs-text syncheck:add-docs-menu defs-text start-pos end-pos key the-label path definition-tag tag)] - [`#(syncheck:add-rename-menu ,id-as-sym ,to-be-renamed/poss ,name-dup-pc ,name-dup-id) + [`#(syncheck:add-id-set ,to-be-renamed/poss ,name-dup-pc ,name-dup-id) (define other-side-dead? #f) (define (name-dup? name) (cond @@ -1654,8 +1712,7 @@ If the namespace does not, they are colored the unbound color. (define to-be-renamed/poss/fixed (for/list ([lst (in-list to-be-renamed/poss)]) (list defs-text (list-ref lst 0) (list-ref lst 1)))) - (send defs-text syncheck:add-rename-menu id-as-sym to-be-renamed/poss/fixed - name-dup?)])) + (send defs-text syncheck:add-id-set to-be-renamed/poss/fixed name-dup?)])) (define/augment (enable-evaluation) (send check-syntax-button enable #t) diff --git a/collects/drracket/private/syncheck/intf.rkt b/collects/drracket/private/syncheck/intf.rkt index 5da73110ad..110c9094cb 100644 --- a/collects/drracket/private/syncheck/intf.rkt +++ b/collects/drracket/private/syncheck/intf.rkt @@ -8,7 +8,7 @@ syncheck:add-background-color syncheck:add-require-open-menu syncheck:add-docs-menu - syncheck:add-rename-menu + syncheck:add-id-set syncheck:add-arrow syncheck:add-tail-arrow syncheck:add-mouse-over-status @@ -36,7 +36,7 @@ (define/public (syncheck:find-source-object stx) #f) (define/public (syncheck:add-background-color source start end color) (void)) (define/public (syncheck:add-require-open-menu source start end key) (void)) - (define/public (syncheck:add-rename-menu id all-ids new-name-intereferes?) (void)) + (define/public (syncheck:add-id-set id all-ids new-name-intereferes?) (void)) (define/public (syncheck:add-docs-menu text start-pos end-pos key the-label path definition-tag tag) (void)) (define/public (syncheck:add-arrow start-text start-pos-left start-pos-right end-text end-pos-left end-pos-right diff --git a/collects/drracket/private/syncheck/local-member-names.rkt b/collects/drracket/private/syncheck/local-member-names.rkt index bc039a47d9..a7120e8fb1 100644 --- a/collects/drracket/private/syncheck/local-member-names.rkt +++ b/collects/drracket/private/syncheck/local-member-names.rkt @@ -13,7 +13,7 @@ syncheck:add-docs-menu syncheck:color-range syncheck:add-require-open-menu - syncheck:add-rename-menu + syncheck:add-id-set syncheck:add-arrow syncheck:add-tail-arrow syncheck:add-mouse-over-status diff --git a/collects/drracket/private/syncheck/online-comp.rkt b/collects/drracket/private/syncheck/online-comp.rkt index 4101e6e1fd..70af76fb28 100644 --- a/collects/drracket/private/syncheck/online-comp.rkt +++ b/collects/drracket/private/syncheck/online-comp.rkt @@ -56,10 +56,10 @@ (log syncheck:add-jump-to-definition _text start end id filename) (log syncheck:add-require-open-menu _text start-pos end-pos file) (log syncheck:add-docs-menu _text start-pos end-pos key the-label path definition-tag tag) - (define/override (syncheck:add-rename-menu id-as-sym to-be-renamed/poss dup-name?) + (define/override (syncheck:add-id-set to-be-renamed/poss dup-name?) (define id (hash-count table)) (hash-set! table id dup-name?) - (add-to-trace (vector 'syncheck:add-rename-menu id-as-sym (map cdr to-be-renamed/poss) remote id))) + (add-to-trace (vector 'syncheck:add-id-set (map cdr to-be-renamed/poss) remote id))) (define/public (get-trace) (reverse trace)) (define/private (add-to-trace thing) @@ -67,23 +67,24 @@ (super-new))) (define (go expanded path the-source orig-cust) - (with-handlers ((exn:fail? (λ (x) - (printf "~a\n" (exn-message x)) - (printf "---\n") - (for ([x (in-list - (continuation-mark-set->context - (exn-continuation-marks - x)))]) - (printf " ~s\n" x)) - (printf "===\n") - (raise x)))) - (define obj (new obj% - [src the-source] - [orig-cust orig-cust])) - (define-values (expanded-expression expansion-completed) - (make-traversal (current-namespace) - (get-init-dir path))) - (parameterize ([current-annotations obj]) - (expanded-expression expanded) - (expansion-completed)) - (send obj get-trace))) + (parameterize ([current-max-to-send-at-once 50]) + (with-handlers ((exn:fail? (λ (x) + (printf "~a\n" (exn-message x)) + (printf "---\n") + (for ([x (in-list + (continuation-mark-set->context + (exn-continuation-marks + x)))]) + (printf " ~s\n" x)) + (printf "===\n") + (raise x)))) + (define obj (new obj% + [src the-source] + [orig-cust orig-cust])) + (define-values (expanded-expression expansion-completed) + (make-traversal (current-namespace) + (get-init-dir path))) + (parameterize ([current-annotations obj]) + (expanded-expression expanded) + (expansion-completed)) + (send obj get-trace)))) diff --git a/collects/drracket/private/syncheck/traversals.rkt b/collects/drracket/private/syncheck/traversals.rkt index a5cbe6d537..a405d4356b 100644 --- a/collects/drracket/private/syncheck/traversals.rkt +++ b/collects/drracket/private/syncheck/traversals.rkt @@ -12,11 +12,15 @@ racket/set racket/class racket/list + racket/contract syntax/boundmap framework/preferences scribble/manual-struct) -(provide make-traversal) +(provide make-traversal + current-max-to-send-at-once) + +(define current-max-to-send-at-once (make-parameter +inf.0)) ; @@ -1134,10 +1138,23 @@ (for/or ([(level id-set) (in-hash phase-to-map)]) (get-ids id-set new-id)))))))) #t)) - (send defs-text syncheck:add-rename-menu - id-as-sym - loc-lst - name-dup?))))))) + + (define max-to-send-at-once (current-max-to-send-at-once)) + (let loop ([loc-lst loc-lst] + [len (length loc-lst)]) + (cond + [(<= len max-to-send-at-once) + (send defs-text syncheck:add-id-set + loc-lst + name-dup?)] + [else + (send defs-text syncheck:add-id-set + (take loc-lst max-to-send-at-once) + name-dup?) + ;; drop one fewer so that we're sure that the + ;; sets get unioned properly + (loop (drop loc-lst (- max-to-send-at-once 1)) + (- len (- max-to-send-at-once 1)))])))))))) ;; remove-duplicates-stx : (listof syntax[original]) -> (listof syntax[original]) ;; removes duplicates, based on the source locations of the identifiers diff --git a/collects/scribblings/tools/tools.scrbl b/collects/scribblings/tools/tools.scrbl index bdaa49e966..84e285560c 100644 --- a/collects/scribblings/tools/tools.scrbl +++ b/collects/scribblings/tools/tools.scrbl @@ -650,6 +650,10 @@ Check Syntax is a part of the DrRacket collection, but is implemented via the to from @racket[make-traversal]. } +@defparam[current-max-to-send-at-once m (or/c +inf.0 (and/c exact-integer? (>=/c 2)))]{ + See @xmethod[syncheck-annotations<%> syncheck:add-id-set]. +} + @definterface[syncheck-annotations<%> ()]{ Classes implementing this interface are @@ -708,14 +712,26 @@ Check Syntax is a part of the DrRacket collection, but is implemented via the to be longer than 200 characters). } - @defmethod[(syncheck:add-rename-menu [id symbol?] - [all-ids (listof (list/c (not/c #f) exact-nonnegative-integer? exact-nonnegative-integer?))] - [new-name-interferes? (-> symbol boolean?)]) + @defmethod[(syncheck:add-id-set [all-ids (listof (list/c (not/c #f) + exact-nonnegative-integer? + exact-nonnegative-integer?))] + [new-name-interferes? (-> symbol boolean?)]) void?]{ - Called to indicate that there is a variable that can be renamed. The - identifier's name is @racket[id] and all of the occurrences of the identifier are given in the - list @racket[all-ids]. The @racket[new-name-interferes?] procedure determines if a potential name would - interfere with the existing bindings. + Called to indicate that all of the locations in the @racket[all-ids] list + refer to the same identifier. + + The @racket[new-name-interferes?] procedure determines if a potential new name + at one of the corresponding places would interfere with the existing bindings + in the program. + + Usually, this method is called with maximal sets in @racket[all-ids], in the + sense that, for a given call, either a source location is in the list, or + the location it does not contain a identifier that refers to one of the ones + in @racket[all-ids]. If, however, @racket[current-max-to-send-at-once] is not + @racket[+inf.0], then this set might not contain all of the source locations + for a given identifier and multiple calls are made. In the case that multiple + calls are made, the intersection of the @racket[all-ids] lists (on those + multiple calls) is non-empty. } @defmethod[(syncheck:add-arrow [start-source-obj (not/c #f)]