File: hash-lang-bridge.rkt

package info (click to toggle)
racket-mode 20250711~git.8a80578-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,024 kB
  • sloc: lisp: 17,215; makefile: 106
file content (192 lines) | stat: -rw-r--r-- 7,183 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
;; Copyright (c) 2020-2023 by Greg Hendershott.
;; SPDX-License-Identifier: GPL-3.0-or-later

#lang racket/base

(require racket/async-channel
         racket/class
         racket/match
         racket/runtime-path
         "elisp.rkt"
         "lang-info.rkt"
         "util.rkt")

(provide hash-lang
         hash-lang-notify-channel)

;; Bridge for Emacs front end to use hash-lang%
;;
;; - Reference hash-lang% objects by a serializable ID supplied by the
;;   front end.
;;
;; - Adjust Emacs 1-based positions to/from hash-lang% 0-based.
;;
;; - Handle notifications about changed languages and tokens, by
;;   putting values to an async channel that is handled in
;;   command-server.rkt, and then and up in Emacs, similar to
;;   notifications used for logging and debugging.

(define-runtime-path hash-lang.rkt "hash-lang.rkt")

(define hash-lang-class-or-error-message
  (with-handlers ([exn:fail? exn-message])
    (dynamic-require hash-lang.rkt 'hash-lang%)))

(define our-hash-lang%
  (when (class? hash-lang-class-or-error-message)
    (class hash-lang-class-or-error-message
      (super-new)
      (init-field id)
      (define/override (on-changed-lang-info _gen li)
        (async-channel-put
         hash-lang-notify-channel
         (list
          'hash-lang id
          'lang
          'module-language    (lang-info-module-language li)
          'racket-grouping    (lang-info-grouping-position-is-racket? li)
          'range-indenter     (and (lang-info-range-indenter li) #t)
          'submit-predicate   (and (lang-info-submit-predicate li) #t)
          ;; String-ize paren-matches and quotes-matches data to avoid
          ;; discrepancies with Emacs Lisp allowed symbols and char
          ;; reader syntax.
          'paren-matches      (for/list ([o/c (in-list (lang-info-paren-matches li))])
                                (match-define (list o c) o/c)
                                (cons (symbol->string o) (symbol->string c)))
          'quote-matches      (for/list ([c (in-list (lang-info-quote-matches li))])
                                (make-string 1 c))
          'comment-delimiters (lang-info-comment-delimiters li))))
      (define/override (on-changed-tokens gen beg end)
        (when (< beg end)
          (async-channel-put hash-lang-notify-channel
                             (list 'hash-lang id
                                   'update
                                   gen (add1 beg) (add1 end))))))))

(define (hash-lang . args)
  (cond
    [(class? hash-lang-class-or-error-message) (apply hash-lang* args)]
    [(eq? 'create (car args)) #f]
    [else (error 'hash-lang hash-lang-class-or-error-message)]))

(define (hash-lang* . args)
  (match args
    [`(create ,id ,ols ,str)                       (create id ols str)]
    [`(delete ,id)                                 (delete id)]
    [`(update ,id ,gen ,pos ,old-len ,str)         (update id gen pos old-len str)]
    [`(indent-amount ,id ,gen ,pos)                (indent-amount id gen pos)]
    [`(indent-region-amounts ,id ,gen ,from ,upto) (indent-region-amounts id gen from upto)]
    [`(classify ,id ,gen ,pos)                     (classify id gen pos)]
    [`(grouping ,id ,gen ,pos ,dir ,limit ,count)  (grouping id gen pos dir limit count)]
    [`(get-tokens ,id ,gen ,from ,upto)            (get-tokens id gen from upto)]
    [`(submit-predicate ,id ,str ,eos?)            (submit-predicate id str eos?)]))

(define hash-lang-notify-channel (make-async-channel))

(define ht (make-hash)) ;id => hash-lang%
(define (get-object id)
  (hash-ref ht id
            (λ () (error 'hash-lang-bridge
                         "No hash-lang exists with ID ~v" id))))

(define (create id ols str) ;any/c (or/c #f string?) string? -> void
  (define obj (new our-hash-lang%
                   [id id]
                   [other-lang-source (and ols (not (null? ols)) ols)]))
  (hash-set! ht id obj)
  (send obj update! 1 0 0 str)
  id)

(define (delete id)
  (hash-remove! ht id))

(define (update id gen pos old-len str)
  (send (get-object id) update! gen (sub1 pos) old-len str))

(define (indent-amount id gen pos)
  (with-time/log "hash-lang indent-amount"
    (send (get-object id) indent-line-amount gen (sub1 pos))))

(define (indent-region-amounts id gen from upto)
  (with-time/log "hash-lang indent-region-amounts"
    (match (send (get-object id) indent-range-amounts gen (sub1 from) (sub1 upto))
      [#f 'false] ;avoid Elisp nil/`() punning problem
      [v v])))

(define (classify id gen pos)
  (match-define (list beg end attribs) (send (get-object id) classify gen (sub1 pos)))
  (list (add1 beg) (add1 end) (attribs->types attribs)))

(define (grouping id gen pos dir limit count)
  (match (send (get-object id) grouping gen (sub1 pos) dir limit count)
    [(? number? n) (add1 n)]
    [v v]))

(define (get-tokens id gen from upto)
  (for/list ([tok (in-list (send (get-object id) get-tokens gen (sub1 from) (sub1 upto)))])
    (match-define (list (app add1 beg) (app add1 end) (app attribs->types types)) tok)
    (list beg end types)))

(define (attribs->types attribs)
  (match attribs
    [(? symbol? s) (list s)]
    [(? hash? ht)  (cons (or (hash-ref ht 'semantic-type-guess #f)
                             (hash-ref ht 'type 'unknown))
                         (if (hash-ref ht 'comment? #f)
                             '(sexp-comment-body)
                             null))]))

(define (submit-predicate id str -eos?)
  (define in (open-input-string str))
  (define eos (as-racket-bool -eos?))
  (send (get-object id) submit-predicate in eos))

(module+ example-0
  (define id 0)
  (define str "#lang racket\n42 (print \"hello\") @print{Hello} 'foo #:bar")
  (hash-lang 'create id str)
  (hash-lang 'update id 2 14 2 "9999")
  (hash-lang 'classify id 2 14)
  (hash-lang 'update id 3 14 4 "")
  (hash-lang 'classify id 3 14)
  (hash-lang 'classify id 3 15)
  (hash-lang 'grouping id 3 15 'forward 0 1))

(module+ example-1
  (define id 0)
  (define str "#lang at-exp racket\n42 (print \"hello\") @print{Hello (there)} 'foo #:bar")
  (hash-lang 'create id str)
  (hash-lang 'classify id 1 (sub1 (string-length str))))

(module+ example-1.5
  (define id 0)
  (define str "#lang scribble/manual\n(print \"hello\")\n@print[#:kw 12]{Hello (there) #:not-a-keyword}\n")
  (hash-lang 'create id str))

(module+ example-2
  (define id 0)
  (define str "#lang scribble/text\nHello @(print \"hello\") @print{Hello (there)} #:not-a-keyword")
  (hash-lang 'create id str)
  (hash-lang 'classify id (sub1 (string-length str))))

(module+ example-3
  (define id 0)
  (define str "#lang racket\n(λ () #t)")
  (hash-lang 'create id str)
  (hash-lang 'classify id 1 14)
  (hash-lang 'classify id 1 (sub1 (string-length str))))

(module+ example-4
  (define id 0)
  (define str "#lang racket\n#rx\"1234\"\n#(1 2 3)\n#'(1 2 3)")
  (hash-lang 'create id str))

(module+ example-5
  (define id 0)
  (define str "#lang racket\n123\n(print 123)\n")
  ;;           1234567890123 4567 890123456789 0
  ;;                    1           2          3
  (hash-lang 'create id str)
  (indent-amount id 1 18)
  (update id 2 28 0 "\n")
  (indent-amount id 2 29))