419 lines
15 KiB
Racket
419 lines
15 KiB
Racket
#lang racket/base
|
|
(require racket/class
|
|
racket/gui/base
|
|
racket/promise
|
|
data/interval-map
|
|
framework
|
|
unstable/class-iop
|
|
"pretty-printer.rkt"
|
|
"interfaces.rkt"
|
|
"prefs.rkt"
|
|
"util.rkt"
|
|
"../util/logger.rkt")
|
|
(provide print-syntax-to-editor
|
|
code-style)
|
|
|
|
(define-syntax-rule (uninterruptible e ...)
|
|
;; (coarsely) prevent breaks within editor operations
|
|
(parameterize-break #f (begin e ...))
|
|
#|
|
|
(parameterize-break #f
|
|
(let ([ta (now)])
|
|
(begin0 (begin e ...)
|
|
(let ([tb (now)])
|
|
(eprintf "****\n")
|
|
(pretty-write '(begin e ...) (current-error-port))
|
|
(eprintf " -- ~s ms\n\n" (- tb ta))))))
|
|
|#)
|
|
|
|
(define (now) (current-inexact-milliseconds))
|
|
|
|
;; FIXME: assumes text never moves
|
|
|
|
;; print-syntax-to-editor : syntax text controller<%> config number number
|
|
;; -> display<%>
|
|
;; Note: must call display<%>::refresh to finish styling.
|
|
(define (print-syntax-to-editor stx text controller config columns
|
|
[insertion-point (send text last-position)])
|
|
(define output-port (open-output-string/count-lines))
|
|
(define range
|
|
(with-log-time "** pretty-print-syntax"
|
|
(pretty-print-syntax stx output-port
|
|
(send/i controller controller<%> get-primary-partition)
|
|
(length (send/i config config<%> get-colors))
|
|
(send/i config config<%> get-suffix-option)
|
|
(send config get-pretty-styles)
|
|
columns
|
|
(send config get-pretty-abbrev?))))
|
|
(define output-string (get-output-string output-port))
|
|
(define output-length (sub1 (string-length output-string))) ;; skip final newline
|
|
(log-macro-stepper-debug "size of pretty-printed text: ~s" output-length)
|
|
(with-log-time "fixup-parentheses"
|
|
(fixup-parentheses output-string range))
|
|
(with-unlock text
|
|
(with-log-time "inserting pretty-printed text"
|
|
(uninterruptible
|
|
(send text insert output-length output-string insertion-point)))
|
|
(new display%
|
|
(text text)
|
|
(controller controller)
|
|
(config config)
|
|
(range range)
|
|
(start-position insertion-point)
|
|
(end-position (+ insertion-point output-length)))))
|
|
|
|
;; display%
|
|
;; Note: must call refresh method to finish styling.
|
|
(define display%
|
|
(class* object% (display<%>)
|
|
(init-field/i [controller controller<%>]
|
|
[config config<%>]
|
|
[range range<%>])
|
|
(init-field text
|
|
start-position
|
|
end-position)
|
|
|
|
(define base-style
|
|
(code-style text (send/i config config<%> get-syntax-font-size)))
|
|
|
|
;; on-next-refresh : (listof (cons stx style-delta))
|
|
;; Styles to be applied on next refresh only. (eg, underline)
|
|
(define on-next-refresh null)
|
|
|
|
;; extra-styles : hash[stx => (listof style-delta)]
|
|
;; Styles to be re-applied on every refresh.
|
|
(define extra-styles (make-hasheq))
|
|
|
|
;; to-undo-styles : (listof (cons nat nat))
|
|
;; Ranges to unbold or unhighlight when selection changes.
|
|
;; FIXME: ought to be managed by text:region-data (to auto-update ranges)
|
|
;; until then, positions are relative
|
|
(define to-undo-styles null)
|
|
|
|
;; initialize : -> void
|
|
(define/private (initialize)
|
|
(with-log-time "changing base style"
|
|
(uninterruptible
|
|
(send text change-style base-style start-position end-position #f)))
|
|
(with-log-time "applying primary styles"
|
|
(uninterruptible (apply-primary-partition-styles)))
|
|
(with-log-time "adding clickbacks"
|
|
(uninterruptible (add-clickbacks))))
|
|
|
|
;; add-clickbacks : -> void
|
|
(define/private (add-clickbacks)
|
|
(define mapping (send text get-region-mapping 'syntax))
|
|
(define lazy-interval-map-init
|
|
(delay
|
|
(with-log-time "forcing clickback mapping"
|
|
(uninterruptible
|
|
(for ([range (send/i range range<%> all-ranges)])
|
|
(let ([stx (range-obj range)]
|
|
[start (range-start range)]
|
|
[end (range-end range)])
|
|
(interval-map-set! mapping (+ start-position start) (+ start-position end) stx)))))))
|
|
(define (the-callback position)
|
|
(force lazy-interval-map-init)
|
|
(send/i controller selection-manager<%> set-selected-syntax
|
|
(interval-map-ref mapping position #f)))
|
|
(send text set-clickregion start-position end-position the-callback)
|
|
(send text set-clickregion start-position end-position the-callback 'right-down))
|
|
|
|
;; refresh : -> void
|
|
;; Clears all highlighting and reapplies all non-foreground styles.
|
|
(define/public (refresh)
|
|
(with-log-time "refresh"
|
|
(with-unlock text
|
|
(uninterruptible
|
|
(let ([undo-select/highlight-d (get-undo-select/highlight-d)])
|
|
(for ([r (in-list to-undo-styles)])
|
|
(send text change-style undo-select/highlight-d
|
|
(relative->text-position (car r))
|
|
(relative->text-position (cdr r)))))
|
|
(set! to-undo-styles null))
|
|
(uninterruptible
|
|
(for ([stx+delta (in-list on-next-refresh)])
|
|
(for ([r (in-list (send/i range range<%> get-ranges (car stx+delta)))])
|
|
(restyle-range r (cdr stx+delta) #f)))
|
|
(set! on-next-refresh null))
|
|
(uninterruptible
|
|
(apply-extra-styles))
|
|
(let ([selected-syntax
|
|
(send/i controller selection-manager<%>
|
|
get-selected-syntax)])
|
|
(uninterruptible
|
|
(apply-secondary-relation-styles selected-syntax))
|
|
(uninterruptible
|
|
(apply-selection-styles selected-syntax))))))
|
|
|
|
;; get-range : -> range<%>
|
|
(define/public (get-range) range)
|
|
|
|
;; get-start-position : -> number
|
|
(define/public (get-start-position) start-position)
|
|
|
|
;; get-end-position : -> number
|
|
(define/public (get-end-position) end-position)
|
|
|
|
;; highlight-syntaxes : (list-of syntax) string -> void
|
|
(define/public (highlight-syntaxes stxs hi-color)
|
|
(let ([delta (highlight-style-delta hi-color)])
|
|
(for ([stx (in-list stxs)])
|
|
(hash-set! extra-styles stx
|
|
(cons delta (hash-ref extra-styles stx null))))))
|
|
|
|
;; underline-syntaxes : (listof syntax) -> void
|
|
(define/public (underline-syntaxes stxs)
|
|
(for ([stx (in-list stxs)])
|
|
(set! on-next-refresh
|
|
(cons (cons stx underline-d) on-next-refresh))))
|
|
|
|
;; Primary styles
|
|
;; (Done once on initialization, never repeated)
|
|
|
|
;; apply-primary-partition-styles : -> void
|
|
;; Changes the foreground color according to the primary partition.
|
|
;; Only called once, when the syntax is first drawn.
|
|
(define/private (apply-primary-partition-styles)
|
|
(define style-list (send text get-style-list))
|
|
(define (color-style color)
|
|
(let ([delta (new style-delta%)])
|
|
(send delta set-delta-foreground color)
|
|
(send style-list find-or-create-style base-style delta)))
|
|
(define color-styles
|
|
(list->vector
|
|
(map color-style
|
|
(map translate-color
|
|
(send/i config config<%> get-colors)))))
|
|
(define overflow-style (color-style (translate-color "darkgray")))
|
|
(define color-partition
|
|
(send/i controller mark-manager<%> get-primary-partition))
|
|
(define offset start-position)
|
|
;; Optimization: don't call change-style when new style = old style
|
|
(let tr*loop ([trs (send/i range range<%> get-treeranges)] [old-style #f])
|
|
(for ([tr trs])
|
|
(define stx (treerange-obj tr))
|
|
(define start (treerange-start tr))
|
|
(define end (treerange-end tr))
|
|
(define subs (treerange-subs tr))
|
|
(define new-style
|
|
(primary-style stx color-partition color-styles overflow-style))
|
|
(unless (eq? old-style new-style)
|
|
(send text change-style new-style (+ offset start) (+ offset end) #f))
|
|
(tr*loop subs new-style)))
|
|
(void))
|
|
|
|
;; primary-style : syntax partition (vector-of style-delta%) style-delta%
|
|
;; -> style-delta%
|
|
(define/private (primary-style stx partition color-vector overflow)
|
|
(let ([n (send/i partition partition<%> get-partition stx)])
|
|
(cond [(< n (vector-length color-vector))
|
|
(vector-ref color-vector n)]
|
|
[else
|
|
overflow])))
|
|
|
|
;; Secondary Styling
|
|
;; May change in response to user actions
|
|
|
|
;; apply-extra-styles : -> void
|
|
;; Applies externally-added styles (such as highlighting)
|
|
(define/private (apply-extra-styles)
|
|
(for ([(stx deltas) (in-hash extra-styles)])
|
|
(for ([r (in-list (send/i range range<%> get-ranges stx))])
|
|
(for ([delta (in-list deltas)])
|
|
(restyle-range r delta #t)))))
|
|
|
|
;; apply-selection-styles : syntax -> void
|
|
;; Styles subterms eq to the selected syntax
|
|
(define/private (apply-selection-styles selected-syntax)
|
|
(for ([r (in-list (send/i range range<%> get-ranges selected-syntax))])
|
|
(restyle-range r select-d #t)))
|
|
|
|
;; apply-secondary-relation-styles : selected-syntax -> void
|
|
;; If the selected syntax is an identifier, then styles all identifiers
|
|
;; in the relation with it.
|
|
(define/private (apply-secondary-relation-styles selected-syntax)
|
|
(when (identifier? selected-syntax)
|
|
(let* ([name+relation
|
|
(send/i controller secondary-relation<%>
|
|
get-identifier=?)]
|
|
[relation (and name+relation (cdr name+relation))]
|
|
[secondary-highlight-d (get-secondary-highlight-d)])
|
|
(when relation
|
|
(for ([id (in-list (send/i range range<%> get-identifier-list))])
|
|
(when (relation selected-syntax id)
|
|
(for ([r (in-list (send/i range range<%> get-ranges id))])
|
|
(restyle-range r secondary-highlight-d #t))))))))
|
|
|
|
;; restyle-range : (cons num num) style-delta% boolean -> void
|
|
(define/private (restyle-range r style need-undo?)
|
|
(when need-undo? (set! to-undo-styles (cons r to-undo-styles)))
|
|
(send text change-style style
|
|
(relative->text-position (car r))
|
|
(relative->text-position (cdr r))))
|
|
|
|
;; relative->text-position : number -> number
|
|
(define/private (relative->text-position pos)
|
|
(+ pos start-position))
|
|
|
|
;; Initialize
|
|
(super-new)
|
|
(send/i controller controller<%> add-syntax-display this)
|
|
(initialize)))
|
|
|
|
;; fixup-parentheses : string range -> void
|
|
(define (fixup-parentheses string range)
|
|
(for ([r (send/i range range<%> all-ranges)])
|
|
(let ([stx (range-obj r)]
|
|
[start (range-start r)]
|
|
[end (range-end r)])
|
|
(when (and (syntax? stx) (pair? (syntax-e stx)))
|
|
(case (syntax-property stx 'paren-shape)
|
|
((#\[)
|
|
(string-set! string start #\[)
|
|
(string-set! string (sub1 end) #\]))
|
|
((#\{)
|
|
(string-set! string start #\{)
|
|
(string-set! string (sub1 end) #\})))))))
|
|
|
|
(define (open-output-string/count-lines)
|
|
(let ([os (open-output-string)])
|
|
(port-count-lines! os)
|
|
os))
|
|
|
|
;; code-style : text<%> number/#f -> style<%>
|
|
(define (code-style text font-size)
|
|
(let* ([style-list (send text get-style-list)]
|
|
[style (send style-list find-named-style (editor:get-default-color-style-name))])
|
|
(if font-size
|
|
(send style-list find-or-create-style
|
|
style
|
|
(make-object style-delta% 'change-size font-size))
|
|
style)))
|
|
|
|
;; anchor-snip%
|
|
(define anchor-snip%
|
|
(class snip%
|
|
(define/override (copy)
|
|
(make-object string-snip% ""))
|
|
(super-instantiate ())))
|
|
|
|
;; Color translation
|
|
|
|
;; translate-color : color-string -> color%
|
|
(define (translate-color color-string)
|
|
(let ([c (make-object color% color-string)])
|
|
(if (pref:invert-colors?)
|
|
(let-values ([(r* g* b*)
|
|
(lightness-invert (send c red) (send c green) (send c blue))])
|
|
#|
|
|
(printf "translate: ~s -> ~s\n"
|
|
(list (send c red) (send c green) (send c blue))
|
|
(list r* g* b*))
|
|
|#
|
|
(make-object color% r* g* b*))
|
|
c)))
|
|
|
|
;; lightness-invert : uint8 uint8 uint8 -> (values uint8 uint8 uint8)
|
|
(define (lightness-invert r g b)
|
|
(define (c x)
|
|
(/ (exact->inexact x) 255.0))
|
|
(define (d x)
|
|
(inexact->exact (round (* x 255))))
|
|
(let-values ([(r g b) (lightness-invert* (c r) (c g) (c b))])
|
|
(values (d r) (d g) (d b))))
|
|
|
|
(define (lightness-invert* R G B)
|
|
(let-values ([(Hp Sl L) (rgb->hsl* R G B)])
|
|
(hsl*->rgb Hp Sl (- 1.0 L))))
|
|
|
|
(define (rgb->hsl* R G B)
|
|
(define M (max R G B))
|
|
(define m (min R G B))
|
|
(define C (- M m))
|
|
(define Hp
|
|
(cond [(zero? C)
|
|
;; Undefined, but use 0
|
|
0.0]
|
|
[(= M R)
|
|
(realmod* (/ (- G B) C) 6)]
|
|
[(= M G)
|
|
(+ (/ (- B R) C) 2)]
|
|
[(= M B)
|
|
(+ (/ (- R G) C) 4)]))
|
|
(define L (* 0.5 (+ M m)))
|
|
(define Sl
|
|
(cond [(zero? C) 0.0]
|
|
[(>= L 0.5) (/ C (* 2 L))]
|
|
[else (/ C (- 2 (* 2 L)))]))
|
|
|
|
(values Hp Sl L))
|
|
|
|
(define (hsl*->rgb Hp Sl L)
|
|
(define C
|
|
(cond [(>= L 0.5) (* 2 L Sl)]
|
|
[else (* (- 2 (* 2 L)) Sl)]))
|
|
(define X (* C (- 1 (abs (- (realmod Hp 2) 1)))))
|
|
(define-values (R1 G1 B1)
|
|
(cond [(< Hp 1) (values C X 0)]
|
|
[(< Hp 2) (values X C 0)]
|
|
[(< Hp 3) (values 0 C X)]
|
|
[(< Hp 4) (values 0 X C)]
|
|
[(< Hp 5) (values X 0 C)]
|
|
[(< Hp 6) (values C 0 X)]))
|
|
(define m (- L (* 0.5 C)))
|
|
(values (+ R1 m) (+ G1 m) (+ B1 m)))
|
|
|
|
;; realmod : real integer -> real
|
|
;; Adjusts a real number to [0, base]
|
|
(define (realmod x base)
|
|
(define xint (ceiling x))
|
|
(define m (modulo xint base))
|
|
(realmod* (- m (- xint x)) base))
|
|
|
|
;; realmod* : real real -> real
|
|
;; Adjusts a number in [-base, base] to [0,base]
|
|
;; Not a real mod, but faintly reminiscent.
|
|
(define (realmod* x base)
|
|
(if (negative? x)
|
|
(+ x base)
|
|
x))
|
|
|
|
;; Styles
|
|
|
|
(define select-d
|
|
(make-object style-delta% 'change-weight 'bold))
|
|
|
|
(define underline-d
|
|
(make-object style-delta% 'change-underline #t))
|
|
|
|
(define (highlight-style-delta raw-color #:translate-color? [translate-color? #t])
|
|
(let ([sd (new style-delta%)]
|
|
[color (if translate-color? (translate-color raw-color) raw-color)])
|
|
(send sd set-delta-background color)
|
|
sd))
|
|
|
|
(define (mk-2-constant-style bow-color [wob-color (translate-color bow-color)])
|
|
(let ([wob-version (highlight-style-delta wob-color #:translate-color? #f)]
|
|
[bow-version (highlight-style-delta bow-color #:translate-color? #f)])
|
|
(λ ()
|
|
(if (pref:invert-colors?)
|
|
wob-version
|
|
bow-version))))
|
|
|
|
(define get-secondary-highlight-d
|
|
(mk-2-constant-style "yellow" "darkgoldenrod"))
|
|
|
|
#|
|
|
(define undo-select-d
|
|
(make-object style-delta% 'change-weight 'normal))
|
|
(define get-undo-highlight-d
|
|
(mk-2-constant-style "white" "black"))
|
|
|#
|
|
|
|
(define (get-undo-select/highlight-d)
|
|
(let ([sd (make-object style-delta% 'change-weight 'normal)]
|
|
[bg (if (pref:invert-colors?) "black" "white")])
|
|
(send sd set-delta-background bg)
|
|
sd))
|