├── .gitignore
├── .travis.yml
├── README.md
├── info.rkt
├── lang
├── check-syntax.rkt
├── document.rkt
├── indent.rkt
├── lexer.rkt
├── worker.rkt
└── workspace.rkt
├── main.rkt
├── protocol
├── conversion.rkt
├── dispatch.rkt
├── json-util.rkt
├── jsonrpc.rkt
├── lsp.rkt
├── methods.rkt
└── notifications.rkt
└── scribblings
└── racket-language-server.scrbl
/.gitignore:
--------------------------------------------------------------------------------
1 | compiled/
2 | doc/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: c
2 | sudo: false
3 |
4 | env:
5 | global:
6 | - RACKET_DIR=~/racket
7 | - PACKAGE_NAME=racket-language-server
8 | matrix:
9 | - RACKET_VERSION=6.12
10 |
11 | before_install:
12 | - git clone https://github.com/greghendershott/travis-racket.git
13 | - cat travis-racket/install-racket.sh | bash
14 | - export PATH="${RACKET_DIR}/bin:${PATH}"
15 | - mkdir $(pwd)/xdg-runtime-dir
16 | - export XDG_RUNTIME_DIR="$(pwd)/xdg-runtime-dir"
17 |
18 | script:
19 | - raco pkg install --deps search-auto
20 | - raco setup --check-pkg-deps $PACKAGE_NAME
21 | - raco make main.rkt
22 | - xvfb-run raco test -p $PACKAGE_NAME
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Racket Language Server [](https://travis-ci.org/theia-ide/racket-language-server)
2 |
3 | Implementation of the
4 | [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specification)
5 | for [Racket](https://racket-lang.org/).
6 |
7 | ## Custom extensions
8 | #### Colorize Notification (:arrow_left:)
9 |
10 | Colorize notifications are sent from the server to the client to enable semantic
11 | syntax highlighting.
12 |
13 | When a file changes it is the server's responsibility to re-compute syntax
14 | highlighting and push them to the client. Newly pushed tokens always replace
15 | previously pushed tokens. There is no merging that happens on the client side.
16 |
17 | _Notification_:
18 | * method: 'racket/colorize'
19 | * params: `RacketColorizeParams` defined as follows:
20 |
21 | ```typescript
22 | interface RacketColorizeParams {
23 | /**
24 | * The URI for which colorization is reported.
25 | */
26 | uri: DocumentUri;
27 |
28 | /**
29 | * An array of diagnostic information items.
30 | */
31 | tokens: RacketToken[];
32 | }
33 |
34 | interface RacketToken {
35 | /**
36 | * The kind of token.
37 | */
38 | kind: RacketTokenKind;
39 |
40 | /**
41 | * The lexer mode used.
42 | */
43 | mode: RacketLexerMode;
44 |
45 | /**
46 | * Range of the token.
47 | */
48 | range: Range;
49 | }
50 |
51 | enum RacketLexerMode {
52 | Racket = "racket",
53 | Scribble = "scribble",
54 | Other = "other"
55 | }
56 |
57 | enum RacketTokenKind {
58 | // Generic token kinds
59 | Symbol = "symbol",
60 | Keyword = "keyword",
61 | Comment = "comment",
62 | String = "string",
63 | Constant = "constant",
64 | Parenthesis = "parenthesis",
65 | Error = "error",
66 | NoColor = "no-color",
67 | Other = "other",
68 | // Racket token kinds
69 | HashColonKeyword = "hash-colon-keyword",
70 | SexpComment = "sexp-comment",
71 | // Racket semantic token kinds
72 | Imported = "imported",
73 | LexicallyBound = "lexically-bound",
74 | Free = "free",
75 | Set!d = "set!d",
76 | // Scribble token kinds
77 | Text = "text"
78 | }
79 | ```
80 |
81 | #### Indent Request (:arrow_left:)
82 |
83 | Indent requests are sent from the client to the server to compute the
84 | indentation for a line number.
85 |
86 | _Request_:
87 | * method: 'racket/indent'
88 | * params: `RacketIndentParams` defined as follows:
89 |
90 | ```typescript
91 | interface RacketIndentParams {
92 | /**
93 | * The TextDocument for which indentation is requested.
94 | */
95 | textDocument: TextDocumentIdentifier;
96 |
97 | /**
98 | * The line to indent.
99 | */
100 | line: number;
101 | }
102 | ```
103 |
104 | _Response_:
105 | * result: `number`
106 | * error: code and message set in case an exception happens during the definition request.
107 |
108 | ## References
109 | - [0] https://microsoft.github.io/language-server-protocol/specification
110 | - [1] https://docs.racket-lang.org/tools/lang-languages-customization.html
111 | - [2] https://github.com/jeapostrophe/racket-langserver
112 |
113 | ## License
114 | Copyright 2018 David Craven and others
115 |
116 | Permission to use, copy, modify, and/or distribute this software for any purpose
117 | with or without fee is hereby granted, provided that the above copyright notice
118 | and this permission notice appear in all copies.
119 |
120 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
121 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
122 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
123 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
124 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
125 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
126 | THIS SOFTWARE.
127 |
--------------------------------------------------------------------------------
/info.rkt:
--------------------------------------------------------------------------------
1 | #lang info
2 | (define collection "racket-language-server")
3 | (define version "0.0.1")
4 | (define scribblings '(("scribblings/racket-language-server.scrbl")))
5 | (define deps '("base"
6 | "data-lib"
7 | "drracket-tool-lib"
8 | "gui-lib"
9 | "scribble-lib"
10 | "syntax-color-lib"))
11 | (define build-deps '("at-exp-lib"
12 | "data-doc"
13 | "racket-doc"
14 | "rackunit-lib"
15 | "scribble-lib"))
16 | (define racket-launcher-libraries '("main.rkt"))
17 | (define racket-launcher-names '("racket-language-server"))
18 |
--------------------------------------------------------------------------------
/lang/check-syntax.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/list
4 | racket/match
5 | data/interval-map
6 | drracket/check-syntax
7 | syntax/modread)
8 |
9 | (struct exception (code msg srclocs) #:transparent)
10 | (struct warning (code msg srclocs) #:transparent)
11 | (struct binding (start end require?) #:transparent)
12 | (struct reference (filename id) #:transparent)
13 | (struct link (start end file))
14 |
15 | (define (exn->exception e)
16 | (match-define-values (struct-type _) (struct-info e))
17 | (match-define-values (code _ _ _ _ _ _ _) (struct-type-info struct-type))
18 | (define msg (exn-message e))
19 | (define srclocs (if (exn:srclocs? e) ((exn:srclocs-accessor e) e) '()))
20 | (exception (symbol->string code) msg srclocs))
21 |
22 | (define build-trace%
23 | (class (annotations-mixin object%)
24 | (init-field src)
25 |
26 | (define errors empty)
27 | (define warnings empty)
28 | (define semantic-coloring (make-interval-map))
29 | (define hovers (make-interval-map))
30 | (define bindings (make-interval-map))
31 | (define definitions (make-hasheq))
32 | (define references (make-interval-map))
33 | (define require-locations empty)
34 | (define documentation empty)
35 | (define tails (make-hasheq))
36 |
37 | ;; Getters
38 | (define/public (get-errors) errors)
39 | (define/public (get-warnings) warnings)
40 | (define/public (get-diagnostics) (append errors warnings))
41 | (define/public (get-semantic-coloring) semantic-coloring)
42 | (define/public (get-hovers) hovers)
43 | ;; Bindings to locations in current file.
44 | (define/public (get-bindings) bindings)
45 | ;; Definitions are things that can be referenced.
46 | (define/public (get-definitions) definitions)
47 | ;; References locations in other files.
48 | (define/public (get-references) references)
49 | ;; References a file.
50 | (define/public (get-require-locations) require-locations)
51 | (define/public (get-documentation) documentation)
52 | ;; Tail recursion
53 | (define/public (get-tails) tails)
54 |
55 | (define/public (add-error err)
56 | (set! errors (cons err errors))
57 | void)
58 |
59 | (define/public (add-warning warn)
60 | (set! warnings (cons warn warnings))
61 | void)
62 |
63 | (define/override (syncheck:find-source-object stx)
64 | ;; Skip annotations if source-object's source location is
65 | ;; from a different file.
66 | (and (equal? src (syntax-source stx))
67 | stx))
68 |
69 | (define/override (syncheck:add-tail-arrow from-text from-pos to-text to-pos)
70 | (hash-set! tails from-pos to-pos)
71 | void)
72 |
73 | (define/override (syncheck:add-arrow/name-dup/pxpy
74 | _start-text start-pos-left start-pos-right start-px start-py
75 | _end-text end-pos-left end-pos-right end-px end-py
76 | actual? level require-arrow? name-dup?)
77 | (interval-map-set! bindings end-pos-left end-pos-right
78 | (binding start-pos-left start-pos-right require-arrow?))
79 | void)
80 |
81 | (define/override (syncheck:add-mouse-over-status
82 | _text pos-left pos-right hover-content)
83 | (interval-map-set! hovers pos-left pos-right hover-content)
84 | void)
85 |
86 | (define/override (syncheck:add-text-type _text pos-left pos-right text-type)
87 | ;; TODO document-identifier matching-identifiers
88 | ;; 'unused-identifier is only applied when there is an unused-require.
89 | #;(when (eq? text-type 'unused-identifier)
90 | (add-warning
91 | (warning "warn:unused-identifier" "Unused identifier."
92 | ;; line and column unknown
93 | (list
94 | (srcloc src #f #f pos-left
95 | (- pos-right pos-left))))))
96 | void)
97 |
98 | (define/override (syncheck:add-jump-to-definition
99 | _text pos-left pos-right id filename submods)
100 | (interval-map-set! references pos-left pos-right
101 | (reference filename id))
102 | void)
103 |
104 | (define/override (syncheck:add-definition-target
105 | _text pos-left pos-right id mods)
106 | (hash-set! definitions id '(pos-left . pos-right))
107 | void)
108 |
109 | (define/override (syncheck:add-require-open-menu
110 | _text start-pos end-pos file)
111 | (set! require-locations
112 | (cons (link start-pos end-pos
113 | (string-append "file://" (path->string file)))
114 | require-locations))
115 | void)
116 |
117 | (define/override (syncheck:add-docs-menu
118 | _text start-pos end-pos key the-label path
119 | definition-tag tag)
120 | (define doc-uri (format "file://~a#~a" path tag))
121 | (set! documentation (cons (link start-pos end-pos doc-uri) documentation))
122 | void)
123 |
124 | (define/override (syncheck:add-prefixed-require-reference
125 | _req-src req-pos-left req-pos-right)
126 | ;; Required to avoid arity error.
127 | ;; TODO: Any useful information here?
128 | void)
129 |
130 | (define/override (syncheck:add-unused-require
131 | _req-src req-pos-left req-pos-right)
132 | (add-warning
133 | (warning "warn:unused-require" "Unused require."
134 | ;; line and column unknown
135 | (list
136 | (srcloc src #f #f req-pos-left
137 | (- req-pos-right req-pos-left)))))
138 | void)
139 |
140 | (define/override (syncheck:color-range _text start end style-name)
141 | (define type (substring style-name 22))
142 | (when (not (equal? type "unused-require"))
143 | (interval-map-set! semantic-coloring (add1 start) (add1 end)
144 | (string->symbol type)))
145 | void)
146 |
147 | (super-new)))
148 |
149 | (define ((report-error trace) exn)
150 | (send trace add-error (exn->exception exn)))
151 |
152 | (define (check-syntax path text)
153 | (define ns (make-base-namespace))
154 | (define trace (new build-trace% [src path]))
155 | (match-define-values (load-dir _ #f)
156 | (split-path path))
157 |
158 | (parameterize ([current-annotations trace])
159 | (define-values (expanded-expression expansion-completed)
160 | (make-traversal ns path))
161 | (define port (open-input-string text))
162 | (port-count-lines! port)
163 | (parameterize ([current-namespace ns]
164 | [current-load-relative-directory load-dir])
165 | (with-handlers ([exn? (report-error trace)])
166 | (expanded-expression
167 | (expand
168 | (with-module-reading-parameterization
169 | (lambda ()
170 | (read-syntax path port)))))))
171 | (expansion-completed))
172 | trace)
173 |
174 | (provide (all-defined-out))
175 |
176 | (module+ test
177 | (require rackunit)
178 |
179 | (define code-no-errors #<path uri)
24 | (substring uri 7))
25 |
26 | (define (make-traced-document doc)
27 | (match-define (tokenized-document uri version text tokens) doc)
28 |
29 | (define trace
30 | (check-syntax (uri->path uri) text))
31 |
32 | (traced-document uri version text tokens trace))
33 |
34 | (define (document:uri doc)
35 | (match doc
36 | [(document uri _ _) uri]
37 | [(tokenized-document uri _ _ _) uri]
38 | [(traced-document uri _ _ _ _) uri]))
39 |
40 | (define (document:version doc)
41 | (match doc
42 | [(document _ version _) version]
43 | [(tokenized-document _ version _ _) version]
44 | [(traced-document _ version _ _ _) version]))
45 |
46 | (define (document:text doc)
47 | (match doc
48 | [(document _ _ text) text]
49 | [(tokenized-document _ _ text _) text]
50 | [(traced-document _ _ text _ _) text]))
51 |
52 | (define (document:tokens doc)
53 | (match doc
54 | [(document _ _ _) #f]
55 | [(tokenized-document _ _ _ tokens) tokens]
56 | [(traced-document _ _ _ tokens _) tokens]))
57 |
58 | (define (document:trace doc)
59 | (match doc
60 | [(document _ _ _) #f]
61 | [(tokenized-document _ _ _ _) #f]
62 | [(traced-document _ _ _ _ trace) trace]))
63 |
64 | (define (has-syntax-error doc)
65 | (match-define (traced-document _ _ _ _ trace) doc)
66 | (not (empty? (send trace get-errors))))
67 |
68 | (define (document->text% doc)
69 | (define doc-text (new text%))
70 | (send doc-text insert (document:text doc) 0)
71 | doc-text)
72 |
73 | (provide (all-defined-out))
74 |
--------------------------------------------------------------------------------
/lang/indent.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/list
4 | racket/string
5 | framework
6 | "document.rkt")
7 |
8 | (module+ test
9 | (require rackunit)
10 |
11 | (define (space-char? x) (char=? x #\space))
12 |
13 | (define (string-indents str)
14 | (for/list ([line (in-list (string-split str "\n"))])
15 | (define len (length (takef (string->list line) space-char?)))
16 | (and (exact-positive-integer? len) len)))
17 |
18 | (define racket-str #<indent:text% text)
62 | (define tbox (new indent:text% [indenter (get-indenter text)]))
63 | (send tbox insert text 0)
64 | tbox)
65 |
66 | (define (indent text [start 0] [end (string-length text)])
67 | (define tbox (string->indent:text% text))
68 | (send tbox tabify-selection start end)
69 | (send tbox get-text))
70 |
71 | (module+ test
72 | (check-equal?
73 | (string-indents (indent racket-str))
74 | '(#f #f 1 2 3 4))
75 |
76 | ;; Disable electron dsl tests in CI
77 | (when (not (getenv "CI"))
78 | (check-equal?
79 | (string-indents (indent electron-str))
80 | '(#f #f #f #f 2 2 2 #f))))
81 |
82 | (define (compute-amount-to-indent text line)
83 | (define tbox (string->indent:text% text))
84 | (define pos (send tbox line-start-position line))
85 | (send tbox compute-amount-to-indent pos))
86 |
87 | (module+ test
88 | (check-equal?
89 | (compute-amount-to-indent racket-str 2) 1)
90 |
91 | ;; Disable electron dsl tests in CI
92 | (when (not (getenv "CI"))
93 | (check-equal?
94 | (compute-amount-to-indent electron-str 4) 2)))
95 |
96 | (provide indent compute-amount-to-indent)
97 |
--------------------------------------------------------------------------------
/lang/lexer.rkt:
--------------------------------------------------------------------------------
1 | #lang at-exp racket/base
2 |
3 | (require racket/contract/base
4 | racket/function
5 | racket/generator
6 | racket/match
7 | racket/list
8 | data/interval-map
9 | scribble/srcdoc
10 | syntax-color/module-lexer
11 | syntax-color/racket-lexer
12 | syntax-color/scribble-lexer
13 | "check-syntax.rkt") ; for exception
14 |
15 | (require (for-doc racket/base scribble/manual))
16 |
17 | (module+ test
18 | (require rackunit))
19 |
20 | ;; Definitions
21 | (provide
22 | (struct-doc
23 | token
24 | ([lexeme any/c]
25 | [type (or/c symbol? false/c)]
26 | [data any/c]
27 | [start (or/c exact-nonnegative-integer? false/c)]
28 | [end (or/c exact-nonnegative-integer? false/c)]
29 | [mode (or/c (one-of/c 'racket 'scribble 'lang 'other) false/c)]
30 | [offset (or/c exact-integer? false/c)])
31 | @{This @racket[struct] represents a @racket[token].}))
32 | (struct token (lexeme type data start end mode offset) #:transparent)
33 |
34 | (provide
35 | (thing-doc
36 | eof-token
37 | (struct/c token eof-object? false/c false/c false/c false/c false/c false/c)
38 | @{An alias for a @racket[token] with the lexeme set to @racket[eof].}))
39 | (define eof-token (token eof #f #f #f #f #f #f))
40 |
41 | (provide
42 | (proc-doc/names
43 | eof-token?
44 | (-> any/c boolean?)
45 | (tok)
46 | @{Determines whether a @racket[token] is an @racket[eof-token].}))
47 | (define (eof-token? tok)
48 | (match tok
49 | [(token (? eof-object?) _ _ _ _ _ _) #t]
50 | [_ #f]))
51 |
52 |
53 | ;; Lexer
54 | (provide
55 | (proc-doc/names
56 | get-lexer
57 | (-> input-port? (-> struct?))
58 | (in)
59 | @{Returns a @racket[token] @racket[producer] for an @racket[input-port].}))
60 | (define (get-lexer in)
61 | (define offset 0)
62 | (define mode #f)
63 | (define (next-token)
64 | (match-define-values
65 | (lexeme type data start end newOffset newMode)
66 | (module-lexer in offset mode))
67 | (set! offset newOffset)
68 | (set! mode newMode)
69 | (token lexeme type data start end (mode->symbol mode) #f))
70 | next-token)
71 |
72 | (provide
73 | (proc-doc/names
74 | make-tokenizer
75 | (-> string? (-> struct?))
76 | (str)
77 | @{Returns a @racket[token] @racket[producer] for an input @racket[string].}))
78 | (define (make-tokenizer str)
79 | (define input-port (open-input-string str))
80 | (port-count-lines! input-port)
81 | (get-lexer input-port))
82 |
83 | (provide
84 | (proc-doc/names
85 | apply-tokenizer-maker
86 | (-> (-> string? (-> struct?)) string? (listof struct?))
87 | (tokenizer val)
88 | @{Applies a tokenizer to a @racket[string] and returns a @racket[list] of
89 | @racket[token]s.}))
90 | (define (apply-tokenizer-maker tokenizer val)
91 | (define next-token (tokenizer val))
92 | (for/list ([tok (in-producer next-token eof-token?)])
93 | tok))
94 |
95 | (module+ test
96 | (require rackunit)
97 |
98 | (define racket-str #<producer
166 | (-> (listof struct?) generator?)
167 | (tokens)
168 | @{Returns a @racket[producer] for a @racket[list] of @racket[token]s.}))
169 | (define (list->producer tokens)
170 | (infinite-generator
171 | (for-each yield tokens)
172 | (yield eof-token)))
173 |
174 | (module+ test
175 | (check-equal?
176 | (for/fold ([sum 0])
177 | ([elem (in-producer (list->producer '(1 2 3 #f)) not)])
178 | (+ sum elem))
179 | 6))
180 |
181 | (define (mode->symbol mode)
182 | (define mode-proc
183 | (match mode
184 | [(? procedure?) mode]
185 | [(? pair?) (car mode)]
186 | ['no-lang-line 'no-lang-line]
187 | ['before-lang-line 'before-lang-line]))
188 | (cond
189 | [(equal? mode-proc racket-lexer) 'racket]
190 | [(equal? mode-proc scribble-inside-lexer) 'scribble]
191 | [else 'other]))
192 |
193 | (module+ test
194 | (check-equal? (mode->symbol racket-lexer) 'racket)
195 | (check-equal? (mode->symbol scribble-inside-lexer) 'scribble)
196 | (check-equal? (mode->symbol identity) 'other)
197 | (check-equal? (mode->symbol (cons racket-lexer #f)) 'racket)
198 | (check-equal? (mode->symbol (cons identity #f)) 'other))
199 |
200 |
201 | ;; Token stream transformers
202 | (module+ test
203 | (define (check-token-transform transform tokens-in tokens-out)
204 | (define next-token (transform (list->producer tokens-in)))
205 | (check-equal?
206 | (append
207 | (for/list ([tok (in-producer next-token eof-token?)])
208 | tok)
209 | (list eof-token))
210 | tokens-out)))
211 |
212 | ;; Lang tokenizer
213 | ;;
214 | ;; Splits 'other tokens starting with #lang into a 'lang-keyword
215 | ;; and 'lang-symbol token.
216 | (provide
217 | (proc-doc/names
218 | lang-tokenizer
219 | (-> (-> struct?) (-> struct?))
220 | (next-token)
221 | @{Transforms tokens with type @racket['other] that contain a @bold{#lang}
222 | declaration into a @racket['keyword], @racket['white-space] and
223 | @racket['symbol] @racket[token].}))
224 | (define (lang-tokenizer next-token)
225 | (infinite-generator
226 | (define tok (next-token))
227 | (match tok
228 | [(token lexeme (? (curry eq? 'other)) data start end _ diff)
229 | (match (regexp-match #px"^(#lang)(\\s+)(.+)$" lexeme)
230 | [(list all lang white symbol)
231 | (define end-lang (+ start 5))
232 | (define end-white (+ end-lang (string-length white)))
233 | (yield (token lang 'keyword data start end-lang 'lang diff))
234 | (yield (token white 'white-space data end-lang end-white 'lang diff))
235 | (yield (token symbol 'symbol data end-white end 'lang diff))]
236 | [_ (yield tok)])]
237 | [_ (yield tok)])))
238 |
239 | (module+ test
240 | (check-token-transform
241 | lang-tokenizer
242 | (list
243 | (token "#lang racket" 'other #f 1 13 'racket #f)
244 | eof-token)
245 | (list
246 | (token "#lang" 'keyword #f 1 6 'lang #f)
247 | (token " " 'white-space #f 6 7 'lang #f)
248 | (token "racket" 'symbol #f 7 13 'lang #f)
249 | eof-token)))
250 |
251 | ;; Skip whitespace
252 | ;;
253 | ;; Skips white space tokens in token stream.
254 | (provide
255 | (proc-doc/names
256 | skip-white
257 | (-> (-> struct?) (-> struct?))
258 | (next-token)
259 | @{Takes a @racket[token] producer and returns a new @racket[token] producer
260 | that ignores @racket[token]s with type @racket['white-space] and
261 | @racket['whitespace].}))
262 | (define (skip-white next-token)
263 | (define (new-next-token)
264 | (match (next-token)
265 | [(token _ 'white-space _ _ _ _ _)
266 | (new-next-token)]
267 | [(token _ 'whitespace _ _ _ _ _)
268 | (new-next-token)]
269 | [tok tok]))
270 | new-next-token)
271 |
272 | (module+ test
273 | (check-token-transform
274 | skip-white
275 | (list
276 | (token #f 'a #f #f #f #f #f)
277 | (token #f 'white-space #f #f #f #f #f)
278 | (token #f 'b #f #f #f #f #f)
279 | (token #f 'whitespace #f #f #f #f #f)
280 | eof-token)
281 | (list
282 | (token #f 'a #f #f #f #f #f)
283 | (token #f 'b #f #f #f #f #f)
284 | eof-token)))
285 |
286 | ;; Sexp comment reclassifier
287 | ;;
288 | ;; Counts parenthesis and reclassifies the tokens of the sexp after
289 | ;; 'sexp-comment to 'comment.
290 | (provide
291 | (proc-doc/names
292 | sexp-comment-reclassifier
293 | (-> (-> struct?) (-> struct?))
294 | (next-token)
295 | @{Counts parenthesis and reclassifies @racket[token]s after a
296 | @racket['sexp-comment] @racket[token]s into @racket['comment]
297 | @racket[token]s.}))
298 | (define (sexp-comment-reclassifier next-token)
299 | (define (is-open paren)
300 | (or (eq? paren '|(|) (eq? paren '|{|) (eq? paren '|[|)))
301 | (define state 0)
302 | (define (reclassify tok)
303 | (match tok
304 | [(token lexeme type paren start end mode diff)
305 | (cond
306 | [(eq? type 'sexp-comment)
307 | (set! state 1)]
308 |
309 | [(and (eq? state 1) (eq? type 'symbol))
310 | (set! state -1)]
311 |
312 | [(and (> state 0) (eq? type 'parenthesis))
313 | (set! state (if (is-open paren)
314 | (add1 state)
315 | (sub1 state)))
316 | (when (eq? state 1)
317 | (set! state -1))]
318 |
319 | [(eq? state -1)
320 | (set! state 0)])
321 |
322 | (if (eq? state 0)
323 | tok
324 | (token lexeme 'comment paren start end mode diff))]
325 | [tok tok]))
326 |
327 | (define (new-next-token)
328 | (reclassify (next-token)))
329 | new-next-token)
330 |
331 | (module+ test
332 | (check-token-transform
333 | sexp-comment-reclassifier
334 | (list
335 | (token #f 'sexp-comment #f #f #f #f #f)
336 | (token #f 'symbol #f #f #f #f #f)
337 | (token #f 'symbol #f #f #f #f #f)
338 | eof-token)
339 | (list
340 | (token #f 'comment #f #f #f #f #f)
341 | (token #f 'comment #f #f #f #f #f)
342 | (token #f 'symbol #f #f #f #f #f)
343 | eof-token))
344 |
345 | (check-token-transform
346 | sexp-comment-reclassifier
347 | (list
348 | (token #f 'sexp-comment #f #f #f #f #f)
349 | (token #f 'parenthesis '|(| #f #f #f #f)
350 | (token #f 'symbol #f #f #f #f #f)
351 | (token #f 'parenthesis '|)| #f #f #f #f)
352 | (token #f 'symbol #f #f #f #f #f)
353 | eof-token)
354 | (list
355 | (token #f 'comment #f #f #f #f #f)
356 | (token #f 'comment '|(| #f #f #f #f)
357 | (token #f 'comment #f #f #f #f #f)
358 | (token #f 'comment '|)| #f #f #f #f)
359 | (token #f 'symbol #f #f #f #f #f)
360 | eof-token)))
361 |
362 | ;; Token stream matcher
363 | ;;
364 | ;; Matches a token stream to an old list of tokens and
365 | ;; sets the offset into the old token stream.
366 | (provide
367 | (proc-doc/names
368 | token-stream-matcher
369 | (-> (listof struct?) (-> (-> struct?) (-> struct?)))
370 | (old-tokens)
371 | @{Matches a @racket[token] producer to an old @racket[list] of @racket[token]s
372 | and sets the offset field.}))
373 | (define ((token-stream-matcher old-tokens) next-token)
374 | (define old-next-token (list->producer old-tokens))
375 | (define offset 0)
376 |
377 | (define (offset-delta tok old-tok)
378 | (match-define (token lexeme type _ start end _ _) tok)
379 | (match old-tok
380 | [(token olexeme otype _ ostart oend _ _)
381 | (cond
382 | ;; old token
383 | [(eq? type otype) (- oend end)]
384 | ;; new token
385 | [else #f])]
386 | [_ #f]))
387 |
388 | (define new-next-token
389 | (infinite-generator
390 | (define old-tok (old-next-token))
391 | (define (loop)
392 | (define tok (next-token))
393 | (when (eof-token? tok) (yield eof-token))
394 | (match-define (token lexeme type data start end mode _) tok)
395 |
396 | (define offset (offset-delta tok old-tok))
397 | (yield (token lexeme type data start end mode offset))
398 | (when (not offset) (loop)))
399 | (loop)))
400 |
401 | new-next-token)
402 |
403 | (module+ test
404 | (check-token-transform
405 | (token-stream-matcher '())
406 | (list
407 | (token #f 'a #f 1 2 #f #f)
408 | (token #f 'b #f 2 3 #f #f)
409 | eof-token)
410 | (list
411 | (token #f 'a #f 1 2 #f #f) ; new
412 | (token #f 'b #f 2 3 #f #f) ; new
413 | eof-token))
414 |
415 | (check-token-transform
416 | (token-stream-matcher (list (token "a" 'a #f 1 2 #f #f)
417 | (token "b" 'b #f 2 3 #f #f)
418 | (token "c" 'c #f 3 4 #f #f)))
419 | (list
420 | (token "a" 'a #f 1 2 #f #f) ; no-change
421 | (token "a" 'a #f 2 3 #f #f) ; new
422 | (token "B" 'b #f 3 4 #f #f) ; change
423 | (token "c" 'c #f 4 5 #f #f) ; no-change
424 | eof-token)
425 | (list
426 | (token "a" 'a #f 1 2 #f 0)
427 | (token "a" 'a #f 2 3 #f #f)
428 | (token "B" 'b #f 3 4 #f -1)
429 | (token "c" 'c #f 4 5 #f -1)
430 | eof-token))
431 |
432 | ;; to simplify implementation removal of old tokens is handled
433 | ;; in the workspace
434 | #;(check-token-transform
435 | (token-stream-matcher (list (token "a" 'a #f 1 2 #f #f)
436 | (token "b" 'b #f 2 3 #f #f)
437 | (token "c" 'c #f 3 4 #f #f)))
438 | (list
439 | (token "a" 'a #f 1 2 #f #f) ; no-change ; deleted
440 | (token "c" 'c #f 2 3 #f #f) ; no-change
441 | eof-token)
442 | (list
443 | (token "a" 'a #f 1 2 #f 0)
444 | (token "c" 'c #f 2 3 #f -1)
445 | eof-token)))
446 |
447 | ;; Semantic token reclassifier
448 | ;;
449 | ;; Reclassifies tokens based on semantic information.
450 | (provide
451 | (proc-doc/names
452 | semantic-reclassifier
453 | (->* (interval-map?) (boolean?) (-> (-> struct?) (-> struct?)))
454 | ((intervals) ((old-intervals #f)))
455 | @{Set's data field in a @racket[token] using an @racket[interval-map]. If
456 | old-intervals is @racket[#t] it will use the @racket[token]'s offset
457 | for lookup.}))
458 | (define ((semantic-reclassifier intervals [old-intervals #f]) next-token)
459 | (define (new-next-token)
460 | (match (next-token)
461 | [(token lexeme (? (curry eq? 'symbol) type) data start end mode offset)
462 | (define pos (if old-intervals
463 | (+ start (or offset 0))
464 | start))
465 | (match-define-values
466 | (sem-start sem-end new-data)
467 | (interval-map-ref/bounds intervals pos #f))
468 | (token lexeme type (if new-data new-data data) start end mode offset)]
469 | [token token]))
470 | new-next-token)
471 |
--------------------------------------------------------------------------------
/lang/worker.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/async-channel
3 | racket/match)
4 |
5 | (struct worker (thrd ch))
6 |
7 | (define (make-worker work)
8 | (define return-to (make-async-channel))
9 | (define (do-work)
10 | (define msg (empty-queue thread-try-receive (thread-receive)))
11 | (define res (work msg))
12 | (async-channel-put return-to res)
13 | (do-work))
14 | (worker (thread do-work) return-to))
15 |
16 | (define (worker-receive w last-msg)
17 | (define ((try-receive ch))
18 | (async-channel-try-get ch))
19 |
20 | (match-define (worker _ ch) w)
21 | (define msg (if last-msg last-msg (async-channel-get ch)))
22 | (empty-queue (try-receive ch) msg))
23 |
24 | (define (worker-send w msg)
25 | (match-define (worker thrd _) w)
26 | (thread-send thrd msg))
27 |
28 | (define (kill-worker w)
29 | (match-define (worker thrd _) w)
30 | (kill-thread thrd))
31 |
32 | (define (empty-queue try-receive last-msg)
33 | (define msg (try-receive))
34 | (if msg (empty-queue try-receive msg) last-msg))
35 |
36 | (provide make-worker worker-receive worker-send kill-worker)
37 |
38 | (module+ test
39 | (require rackunit)
40 |
41 | (define (work msg)
42 | (add1 msg))
43 |
44 | (define wrk (make-worker work))
45 | (worker-send wrk 2)
46 | (check-equal? (worker-receive wrk #f) 3)
47 |
48 | (worker-send wrk 8)
49 | (worker-send wrk 4)
50 | (worker-send wrk 10)
51 | (check-equal? (worker-receive wrk #f) 11))
52 |
--------------------------------------------------------------------------------
/lang/workspace.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/function
4 | racket/match
5 | racket/string
6 | racket/gui/base
7 | "document.rkt"
8 | "worker.rkt")
9 |
10 | (define current-doc-semaphore (make-semaphore 1))
11 | (struct doc-entry (version doc-text worker))
12 | (struct current-document (changed tokenized traced))
13 |
14 | (define (uri-is-path? str)
15 | (string-prefix? str "file://"))
16 |
17 | (define workspace%
18 | (class object%
19 | (super-new)
20 | (init-field
21 | [on-change #f]
22 | [on-tokenize #f]
23 | [on-trace #f]
24 | [doc-entries (make-hasheq)]
25 | [current-docs (make-hasheq)])
26 |
27 | (define (get-doc-entry uri)
28 | (hash-ref doc-entries (string->symbol uri)))
29 |
30 | (define (get-current-doc uri)
31 | (hash-ref current-docs (string->symbol uri)))
32 |
33 | (define (update-doc-entry uri update-procedure)
34 | (match-define (doc-entry version doc-text worker) (get-doc-entry uri))
35 |
36 | (update-procedure doc-text)
37 |
38 | (define new-version (add1 version))
39 | (define new-text (send doc-text get-text))
40 | (define entry (doc-entry new-version doc-text worker))
41 | (hash-set! doc-entries (string->symbol uri) entry)
42 |
43 | (define doc (document uri new-version new-text))
44 | (emit-on-change doc)
45 |
46 | (define doc-tokenized (make-tokenized-document doc))
47 | (emit-on-tokenize doc-tokenized)
48 |
49 | (worker-send worker doc-tokenized))
50 |
51 | (define (update-current-docs doc)
52 | (define uri (string->symbol (document:uri doc)))
53 | (call-with-semaphore
54 | current-doc-semaphore
55 | (lambda ()
56 | (match-define (current-document changed tokenized traced)
57 | (hash-ref current-docs uri))
58 | (define new-current-document
59 | (match doc
60 | [(document _ _ _)
61 | (current-document doc tokenized traced)]
62 | [(tokenized-document _ _ _ _)
63 | (current-document changed doc traced)]
64 | [(traced-document _ _ _ _ trace)
65 | (current-document changed tokenized doc)]))
66 | (hash-set! current-docs uri new-current-document))))
67 |
68 | ;; Events
69 | (define (emit doc cb)
70 | (update-current-docs doc)
71 | (when cb (cb doc)))
72 |
73 | (define/public (set-on-change cb) (set! on-change cb))
74 | (define/public (set-on-tokenize cb) (set! on-tokenize cb))
75 | (define/public (set-on-trace cb) (set! on-trace cb))
76 | (define (emit-on-change doc) (emit doc on-change))
77 | (define (emit-on-tokenize doc) (emit doc on-tokenize))
78 | (define (emit-on-trace doc) (emit doc on-trace))
79 |
80 | ;; Background thread
81 | (define (start-worker)
82 | (define (work doc-tokenized)
83 | (define doc-traced (make-traced-document doc-tokenized))
84 | (emit-on-trace doc-traced)
85 | doc-traced)
86 | (make-worker work))
87 |
88 | (define (receive-document worker version)
89 | (define doc (worker-receive worker #f))
90 | (if (>= (document:version doc) version)
91 | doc
92 | (receive-document worker version)))
93 |
94 |
95 | ;; Notification interface
96 | (define/public (notify-open uri text)
97 | (unless (uri-is-path? uri)
98 | (error 'document-symbol "uri is not a path"))
99 | (define entry (doc-entry 0 (new text%) (start-worker)))
100 | (define docs (current-document #f #f #f))
101 | (hash-set! doc-entries (string->symbol uri) entry)
102 | (hash-set! current-docs (string->symbol uri) docs)
103 | (update-doc-entry uri
104 | (lambda (doc-text)
105 | (send doc-text insert text 0))))
106 |
107 | (define/public (notify-close uri)
108 | (match (get-doc-entry uri)
109 | [(doc-entry _ _ worker)
110 | (kill-worker worker)
111 | (hash-remove! doc-entries (string->symbol uri))
112 | (hash-remove! current-docs (string->symbol uri))]))
113 |
114 | (define/public (notify-update uri text start end)
115 | (update-doc-entry uri
116 | (lambda (doc-text)
117 | (send doc-text insert text start end))))
118 |
119 | (define/public (notify-replace uri text)
120 | (update-doc-entry uri
121 | (lambda (doc-text)
122 | (send doc-text erase)
123 | (send doc-text insert text 0))))
124 |
125 | ;; Request interface
126 | (define/public (request uri)
127 | (match-define (current-document changed _ _)
128 | (get-current-doc uri))
129 | changed)
130 |
131 | (define/public (request-tokenized uri)
132 | (match-define (current-document _ tokenized _)
133 | (get-current-doc uri))
134 | tokenized)
135 |
136 | (define/public (request-traced uri)
137 | (match-define (doc-entry version _ worker)
138 | (get-doc-entry uri))
139 | (match-define (current-document _ _ traced)
140 | (get-current-doc uri))
141 | (if (and traced (eq? (document:version traced) version))
142 | traced
143 | (receive-document worker version)))
144 |
145 | ))
146 |
147 | (provide workspace%)
148 |
149 | (module+ test
150 | (require rackunit)
151 |
152 | (define ws (new workspace%))
153 |
154 | (define uris '("file:///home/user/file.txt"
155 | "file:///home/user/other.txt"))
156 |
157 | (define syntaxes
158 | (list #'(displayln "hello world!")
159 | #'(displayln "bye world!")))
160 |
161 | (define (syntax->text syn)
162 | (format "#lang racket\n\n~a" (syntax->datum syn)))
163 |
164 | (define texts (map syntax->text syntaxes))
165 |
166 | (define (check-document doc curi cversion ctext)
167 | (match-define (document uri version text) doc)
168 | (check-equal? uri curi)
169 | (check-equal? version cversion)
170 | (check-equal? text ctext))
171 |
172 | (define (check-tokenized-document doc curi cversion ctext)
173 | (match-define (tokenized-document uri version text _) doc)
174 | (check-equal? uri curi)
175 | (check-equal? version cversion)
176 | (check-equal? text ctext))
177 |
178 | (define (check-traced-document doc curi cversion ctext)
179 | (match-define (traced-document uri version text _ _) doc)
180 | (check-equal? uri curi)
181 | (check-equal? version cversion)
182 | (check-equal? text ctext))
183 |
184 | (define (register-checks ws curi cversion ctext)
185 | (send ws set-on-change
186 | (lambda (doc) (check-document doc curi cversion ctext)))
187 | (send ws set-on-tokenize
188 | (lambda (doc) (check-tokenized-document doc curi cversion ctext)))
189 | (send ws set-on-trace
190 | (lambda (doc) (check-traced-document doc curi cversion ctext))))
191 |
192 | (define (check-notify ws method uri version text)
193 | (register-checks ws uri version text)
194 | (dynamic-send ws method uri text)
195 | (check-document (send ws request uri) uri version text)
196 | (check-tokenized-document (send ws request-tokenized uri) uri version text)
197 | (check-traced-document (send ws request-traced uri) uri version text))
198 |
199 | (check-notify ws 'notify-open (car uris) 1 (car texts))
200 | (check-notify ws 'notify-open (car (cdr uris)) 1 (car (cdr texts)))
201 | (check-notify ws 'notify-replace (car uris) 2 (car (cdr texts)))
202 |
203 | (send ws notify-close (car uris))
204 | (send ws notify-close (car (cdr uris)))
205 | )
206 |
207 | (provide workspace%)
208 |
--------------------------------------------------------------------------------
/main.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | "lang/workspace.rkt"
4 | "protocol/jsonrpc.rkt"
5 | "protocol/dispatch.rkt"
6 | "protocol/notifications.rkt")
7 |
8 | (define (main-loop [ws (new workspace%
9 | [on-tokenize on-tokenize]
10 | [on-trace on-trace])])
11 | (define message (read-message))
12 | (process-message ws message)
13 | (main-loop ws))
14 |
15 | (module+ main
16 | (main-loop))
17 |
--------------------------------------------------------------------------------
/protocol/conversion.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/list
4 | racket/match
5 | "lsp.rkt"
6 | "../lang/check-syntax.rkt") ; For exception and warning structs
7 |
8 | (define (pos->line/char t pos)
9 | (define line (send t position-paragraph pos))
10 | (define line-begin (send t paragraph-start-position line))
11 | (define char (- pos line-begin))
12 | (values line char))
13 |
14 | (define (line/char->pos t line char)
15 | (+ char (send t paragraph-start-position line)))
16 |
17 | (define (pos->Position t pos)
18 | (define-values (line char) (pos->line/char t pos))
19 | (Position #:line line #:character char))
20 |
21 | (define (pos/pos->Range t start end)
22 | (Range #:start (pos->Position t start)
23 | #:end (pos->Position t end)))
24 |
25 | (define (srcloc->Range t sl)
26 | (match-define (srcloc src line col pos span) sl)
27 | (Range #:start (pos->Position t pos)
28 | #:end (pos->Position t (+ pos span))))
29 |
30 | (define ((exception->Diagnostics t) e)
31 | (define-values (code msg srclocs severity)
32 | (match e
33 | [(exception code msg srclocs)
34 | (values code msg srclocs DiagnosticSeverityError)]
35 | [(warning code msg srclocs)
36 | (values code msg srclocs DiagnosticSeverityWarning)]))
37 | (map (lambda (sl)
38 | (Diagnostic #:range (srcloc->Range t sl)
39 | #:message msg
40 | #:severity severity
41 | #:code code
42 | #:source "Racket"
43 | #:relatedInformation empty))
44 | srclocs))
45 |
46 | (provide (all-defined-out))
47 |
--------------------------------------------------------------------------------
/protocol/dispatch.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/exn
3 | racket/match
4 | "jsonrpc.rkt"
5 | "methods.rkt")
6 |
7 | (define (process-message state msg)
8 | (match msg
9 | ;; Request
10 | [(JsonRpcMessage #:id id #:method method)
11 | (define params (hash-ref msg 'params hasheq))
12 | (with-handlers ([exn:fail? (send-internal-server-error id method)]
13 | [exn:misc:match? (send-invalid-params-error id method)])
14 | (process-request state id method params))]
15 | ;; Notification
16 | [(JsonRpcMessage #:method method)
17 | (define params (hash-ref msg 'params hasheq))
18 | (with-handlers ([exn:fail? (send-internal-server-error 'null method)]
19 | [exn:misc:match? (send-invalid-params-error 'null method)])
20 | (process-notification state method params))]
21 | ;; Invalid message with id
22 | [(JsonRpcMessage #:id id)
23 | (send-invalid-request-error id)]
24 | ;; Invalid message without id
25 | [_ (send-invalid-request-error 'null)]))
26 |
27 | (define (process-request state id method params)
28 | (define (execute request)
29 | (write-message
30 | (success-response id (request state params))))
31 | (match method
32 | ["initialize" (execute lsp/initialize)]
33 | ["shutdown" (execute lsp/shutdown)]
34 | ["textDocument/hover" (execute text-document/hover)]
35 | ["textDocument/definition" (execute text-document/definition)]
36 | ["textDocument/documentSymbol" (execute text-document/document-symbol)]
37 | ["textDocument/documentLink" (execute text-document/document-link)]
38 | ["textDocument/formatting" (execute text-document/formatting)]
39 | ["textDocument/rangeFormatting" (execute text-document/range-formatting)]
40 | ["racket/indent" (execute racket/indent)]
41 | [_ (send-method-not-found-error id method)]))
42 |
43 | (define (process-notification state method params)
44 | (match method
45 | ["$/cancelRequest" (lsp/cancel-request state params)]
46 | ["initialized" (lsp/initialized state params)]
47 | ["exit" (lsp/exit state params)]
48 | ["textDocument/didOpen" (text-document/did-open state params)]
49 | ["textDocument/didClose" (text-document/did-close state params)]
50 | ["textDocument/didChange" (text-document/did-change state params)]
51 | [_ (send-method-not-found-error 'null method)]))
52 |
53 | (define (send-invalid-request-error id)
54 | (eprintf "Invalid request\n")
55 | (write-message
56 | (error-response id INVALID-REQUEST
57 | "The JSON sent is not a valid request object.")))
58 |
59 | (define (send-method-not-found-error id method)
60 | (eprintf "Method not found ~v\n" method)
61 | (define err (format "The method ~v was not found." method))
62 | (write-message
63 | (error-response id METHOD-NOT-FOUND err)))
64 |
65 | (define ((send-invalid-params-error id method) exn)
66 | (eprintf "Caught exn:misc:match? in request ~v\n~a\n" method (exn->string exn))
67 | (define err (format "invalid params for method ~v" method))
68 | (write-message
69 | (error-response id INVALID-PARAMS err)))
70 |
71 | (define ((send-internal-server-error id method) exn)
72 | (eprintf "Caught exn in request ~v\n~a\n" method (exn->string exn))
73 | (define err (format "internal error in method ~v" method))
74 | (write-message
75 | (error-response id INTERNAL-ERROR err)))
76 |
77 | (provide process-message)
78 |
--------------------------------------------------------------------------------
/protocol/json-util.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require (for-syntax racket/base
3 | syntax/parse
4 | syntax/parse/experimental/template)
5 | racket/match
6 | syntax/parse)
7 |
8 | (define-syntax (define-json-expander stx)
9 | (syntax-parse stx
10 | [(_ name:id [key:id ctc:expr] ...+)
11 | (with-syntax ([(key_ ...) (generate-temporaries #'(key ...))]
12 | [(keyword ...)
13 | (for/list ([k (syntax->datum #'(key ...))])
14 | (string->keyword (symbol->string k)))]
15 | [??-id (quote-syntax ??)])
16 | (syntax/loc stx
17 | (define-match-expander name
18 | (λ (stx)
19 | (syntax-parse stx
20 | [(_ (~optional (~seq keyword key_)) ...)
21 | (quasitemplate/loc stx (hash-table (??-id ['key (? ctc key_)]) ...))]))
22 | (λ (stx)
23 | (syntax-parse stx
24 | [(_ (~optional (~seq keyword key_)) ...)
25 | (syntax/loc stx
26 | (make-hasheq (list (cons 'key key_) ...)))])))))]))
27 |
28 | (module+ test
29 | (require rackunit)
30 |
31 | (define-json-expander greeting
32 | [hello string?]
33 | [who string?])
34 |
35 | (define hello_world (greeting #:hello "hello" #:who "world"))
36 | (check-equal? (match hello_world
37 | [(greeting #:hello hello) hello])
38 | "hello")
39 | (check-equal? (match hello_world
40 | [(greeting #:who who) who])
41 | "world")
42 |
43 | ;; No support for constructing partial elements
44 | #;(define hello (greeting #:hello "hello"))
45 | #;(check-equal? (match hello
46 | [(greeting #:hello hello) hello])
47 | "hello")
48 | )
49 |
50 | (provide define-json-expander)
51 |
--------------------------------------------------------------------------------
/protocol/jsonrpc.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/contract/base
3 | racket/match
4 | racket/port
5 | json
6 | "json-util.rkt")
7 |
8 | ;; Defined by JSON RPC
9 | (define PARSE-ERROR -32700)
10 | (define INVALID-REQUEST -32600)
11 | (define METHOD-NOT-FOUND -32601)
12 | (define INVALID-PARAMS -32602)
13 | (define INTERNAL-ERROR -32603)
14 | (define SERVER-ERROR-START -32099)
15 | (define SERVER-ERROR-END -32000)
16 | (define SERVER-NOT-INITIALIZED -32002)
17 | (define UNKNOWN-ERROR-CODE -32001)
18 |
19 | ;; Defined by LSP protocol
20 | (define REQUEST-CANCELLED -32800)
21 |
22 | (define output-semaphore (make-semaphore 1))
23 |
24 | ;; Read a message from in
25 | (define (read-message [in (current-input-port)])
26 | (match (read-line in 'return-linefeed)
27 | ["" (with-handlers ([exn:fail:read? (λ (exn) 'parse-json-error)])
28 | (read-json in))]
29 | [(? eof-object?) eof]
30 | [_ (read-message in)]))
31 |
32 | ;; Write a message to out
33 | (define (write-message msg [out (current-output-port)])
34 | (call-with-semaphore
35 | output-semaphore
36 | (lambda ()
37 | (define null-port (open-output-nowhere))
38 | (write-json msg null-port)
39 | (define content-length (file-position null-port))
40 | (fprintf out "Content-Length: ~a\r\n\r\n" content-length)
41 | (write-json msg out)
42 | (flush-output out))))
43 |
44 | ;; Send a notification
45 | (define (send-notification method params)
46 | (define notification
47 | (hasheq 'jsonrpc "2.0"
48 | 'method method
49 | 'params params))
50 | (write-message notification))
51 |
52 | (define-json-expander JsonRpcMessage
53 | [jsonrpc string?]
54 | [id (or/c number? string?)]
55 | [method string?]
56 | [params any/c])
57 |
58 | ;; Constructor for a response object representing success.
59 | (define (success-response id result)
60 | (hasheq 'jsonrpc "2.0"
61 | 'id id
62 | 'result result))
63 |
64 | ;; Constructor for a response object representing failure.
65 | (define (error-response id code message [data #f])
66 | (define err
67 | (hasheq 'code code
68 | 'message message))
69 | (define err*
70 | (if data err (hash-set err 'data data)))
71 | (hasheq 'jsonrpc "2.0"
72 | 'id id
73 | 'error err*))
74 |
75 | (provide (all-defined-out))
76 |
--------------------------------------------------------------------------------
/protocol/lsp.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/contract/base
3 | racket/function
4 | racket/list
5 | "json-util.rkt")
6 |
7 | ;; Position
8 | (define-json-expander Position
9 | [line exact-nonnegative-integer?]
10 | [character exact-nonnegative-integer?])
11 |
12 | ;; Range
13 | (define-json-expander Range
14 | [start any/c]
15 | [end any/c])
16 |
17 | ;; Location
18 | (define-json-expander Location
19 | [uri string?]
20 | [range any/c])
21 |
22 | ;; Diagnostic
23 | (define DiagnosticSeverityError 1)
24 | (define DiagnosticSeverityWarning 2)
25 | (define DiagnosticSeverityInformation 3)
26 | (define DiagnosticSeverityHint 4)
27 |
28 | (define-json-expander DiagnosticRelatedInformation
29 | [location any/c]
30 | [message string?])
31 |
32 | (define-json-expander Diagnostic
33 | [range any/c]
34 | [message string?]
35 | [severity exact-nonnegative-integer?]
36 | [code string?]
37 | [source string?]
38 | [relatedInformation list?])
39 |
40 | ;; Command
41 | (define-json-expander Command
42 | [title string?]
43 | [command string?]
44 | [arguments list?])
45 |
46 | ;; TextEdit
47 | (define-json-expander TextEdit
48 | [range any/c]
49 | [newText string?])
50 |
51 | ;; TextDocumentEdit
52 | (define-json-expander TextDocumentEdit
53 | [textDocument any/c]
54 | [edits list?])
55 |
56 | ;; WorkspaceEdit
57 | (define-json-expander WorkspaceEdit
58 | [documentChanges list?])
59 |
60 | ;; TextDocumentIdentifier
61 | (define-json-expander TextDocumentIdentifier
62 | [uri string?])
63 |
64 | ;; TextDocumentItem
65 | (define-json-expander TextDocumentItem
66 | [uri string?]
67 | [languageId string?]
68 | [version exact-nonnegative-integer?]
69 | [text string?])
70 |
71 | ;; VersionedTextDocumentIdentifier
72 | (define-json-expander VersionedTextDocumentIdentifier
73 | [uri string?]
74 | [version exact-nonnegative-integer?])
75 |
76 | ;; TextDocumentPositionParams
77 | (define-json-expander TextDocumentPositionParams
78 | [textDocument any/c]
79 | [position any/c])
80 |
81 | ;; DocumentFilter
82 | (define-json-expander DocumentFilter
83 | [language string?]
84 | [scheme string?]
85 | [pattern string?])
86 |
87 | ;; MarkupContent
88 | (define MarkupKindPlaintext "plaintext")
89 | (define MarkupKindMarkdown "markdown")
90 |
91 | (define-json-expander MarkupContent
92 | [kind string?]
93 | [value string?])
94 |
95 | ;; InitializeParams
96 | (define-json-expander InitializeParams
97 | [processId (or/c exact-nonnegative-integer? ((curry eq?) 'null))]
98 | [rootUri string?]
99 | [initializationOptions any/c]
100 | [capabilities any/c]
101 | [trace string?]
102 | [workspaceFolders (or/c list? ((curry eq?) 'null))])
103 |
104 | (define-json-expander ClientCapabilities
105 | [workspace any/c]
106 | [textDocument any/c]
107 | [experimental any/c])
108 |
109 | ;; InitializeResult
110 | (define-json-expander InitializeResult
111 | [capabilities any/c])
112 |
113 | (define-json-expander ServerCapabilities
114 | [textDocumentSync any/c]
115 | [hoverProvider boolean?]
116 | [completionProvider any/c]
117 | [signatureHelpProvider any/c]
118 | [definitionProvider boolean?]
119 | [typeDefinitionProvider boolean?]
120 | [implementationProvider boolean?]
121 | [referencesProvider boolean?]
122 | [documentHighlightProvider boolean?]
123 | [documentSymbolProvider boolean?]
124 | [workspaceSymbolProvider boolean?]
125 | [codeActionProvider boolean?]
126 | [codeLensProvider any/c]
127 | [documentFormattingProvider boolean?]
128 | [documentRangeFormattingProvider boolean?]
129 | [documentOnTypeFormattingProvider any/c]
130 | [renameProvider boolean?]
131 | [documentLinkProvider any/c]
132 | [colorProvider boolean?]
133 | [executeCommandProvider any/c]
134 | [workspace any/c]
135 | [experimental any/c])
136 |
137 | (define-json-expander TextDocumentSyncOptions
138 | [openClose boolean?]
139 | [change exact-nonnegative-integer?]
140 | [willSave boolean?]
141 | [willSaveWaitUntil boolean?]
142 | [save any/c])
143 |
144 | (define TextDocumentSyncKindNone 0)
145 | (define TextDocumentSyncKindFull 1)
146 | (define TextDocumentSyncKindIncremental 2)
147 |
148 | ;; DidOpenTextDocumentParams
149 | (define-json-expander DidOpenTextDocumentParams
150 | [textDocument any/c])
151 |
152 | ;; DidCloseTextDocumentParams
153 | (define-json-expander DidCloseTextDocumentParams
154 | [textDocument any/c])
155 |
156 | ;; DidChangeTextDocumentParams
157 | (define-json-expander DidChangeTextDocumentParams
158 | [textDocument any/c]
159 | [contentChanges list?])
160 |
161 | ;; TextDocumentContentChangeEvent
162 | (define-json-expander TextDocumentContentChangeEvent
163 | [range any/c]
164 | [rangeLength exact-nonnegative-integer?]
165 | [text string?])
166 |
167 | ;; PublishDiagnosticsParams
168 | (define-json-expander PublishDiagnosticsParams
169 | [uri string?]
170 | [diagnostics list?])
171 |
172 | ;; Hover
173 | (define-json-expander Hover
174 | [contents string?]
175 | [range any/c])
176 |
177 | ;; TextDocumentSymbolParams
178 | (define-json-expander TextDocumentSymbolParams
179 | [textDocument any/c])
180 |
181 | ;; SymbolInformation
182 | (define-json-expander SymbolInformation
183 | [name string?]
184 | [kind exact-nonnegative-integer?]
185 | [location any/c])
186 |
187 | ;; SymbolKind
188 | (define SymbolKindFile 1)
189 | (define SymbolKindModule 2)
190 | (define SymbolKindNamespace 3)
191 | (define SymbolKindPackage 4)
192 | (define SymbolKindClass 5)
193 | (define SymbolKindMethod 6)
194 | (define SymbolKindProperty 7)
195 | (define SymbolKindField 8)
196 | (define SymbolKindConstructor 9)
197 | (define SymbolKindEnum 10)
198 | (define SymbolKindInterface 11)
199 | (define SymbolKindFunction 12)
200 | (define SymbolKindVariable 13)
201 | (define SymbolKindConstant 14)
202 | (define SymbolKindString 15)
203 | (define SymbolKindNumber 16)
204 | (define SymbolKindBoolean 17)
205 | (define SymbolKindArray 18)
206 | (define SymbolKindObject 19)
207 | (define SymbolKindKey 20)
208 | (define SymbolKindNull 21)
209 | (define SymbolKindEnumMember 22)
210 | (define SymbolKindStruct 23)
211 | (define SymbolKindEvent 24)
212 | (define SymbolKindOperator 25)
213 | (define SymbolKindTypeParameter 26)
214 |
215 | ;; DocumentLinkParams
216 | (define-json-expander DocumentLinkParams
217 | [textDocument any/c])
218 |
219 | ;; DocumentLink
220 | (define-json-expander DocumentLink
221 | [range any/c]
222 | [target string?])
223 |
224 | ;; DocumentFormattingParams
225 | (define-json-expander DocumentFormattingParams
226 | [textDocument any/c]
227 | [options any/c])
228 |
229 | ;; DocumentRangeFormattingParams
230 | (define-json-expander DocumentRangeFormattingParams
231 | [textDocument any/c]
232 | [range any/c]
233 | [options any/c])
234 |
235 | ;; FormattingOptions
236 | (define-json-expander FormattingOptions
237 | [tabSize exact-nonnegative-integer?]
238 | [insertSpaces boolean?])
239 |
240 | (provide (all-defined-out))
241 |
--------------------------------------------------------------------------------
/protocol/methods.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/match
4 | racket/list
5 | racket/set
6 | data/interval-map
7 | "conversion.rkt"
8 | "lsp.rkt"
9 | "../lang/document.rkt"
10 | "../lang/check-syntax.rkt"
11 | "../lang/indent.rkt"
12 | "../lang/lexer.rkt") ; for token
13 |
14 | ;; Mutable variables
15 | (define already-initialized? #f)
16 | (define already-shutdown? #f)
17 |
18 | ;; Lifecycle methods
19 | (define (lsp/initialize ws params)
20 | (match-define
21 | (InitializeParams #:processId processId
22 | #:capabilities capabilities)
23 | params)
24 | (set! already-initialized? #t)
25 | (InitializeResult
26 | #:capabilities
27 | (hasheq 'textDocumentSync
28 | (TextDocumentSyncOptions
29 | #:openClose #t
30 | #:change TextDocumentSyncKindIncremental
31 | #:willSave #f
32 | #:willSaveWaitUntil #f
33 | #:save #f)
34 | 'hoverProvider #t
35 | 'definitionProvider #t
36 | 'documentSymbolProvider #t
37 | 'documentLinkProvider #t
38 | 'documentFormattingProvider #t
39 | 'documentRangeFormattingProvider #t)))
40 |
41 | (define (lsp/initialized ws params) #f)
42 |
43 | (define (lsp/shutdown ws params)
44 | (set! already-shutdown? #t)
45 | 'null)
46 |
47 | (define (lsp/exit ws params)
48 | (exit (if already-shutdown? 0 1)))
49 |
50 | (define (lsp/cancel-request ws params) #f)
51 |
52 | ;; Synchronization methods
53 | (define (text-document/did-open ws params)
54 | (match-define
55 | (DidOpenTextDocumentParams
56 | #:textDocument (TextDocumentItem #:uri uri
57 | #:text text))
58 | params)
59 | (send ws notify-open uri text))
60 |
61 | (define (text-document/did-close ws params)
62 | (match-define
63 | (DidCloseTextDocumentParams
64 | #:textDocument (TextDocumentIdentifier #:uri uri))
65 | params)
66 | (send ws notify-close uri))
67 |
68 | (define (text-document/did-change ws params)
69 | (match-define
70 | (DidChangeTextDocumentParams
71 | #:textDocument (VersionedTextDocumentIdentifier #:uri uri)
72 | #:contentChanges content-changes)
73 | params)
74 |
75 | (define doc (send ws request uri))
76 | (define doc-text (document->text% doc))
77 | (for ([change (in-list content-changes)])
78 | (match change
79 | [(TextDocumentContentChangeEvent
80 | #:range (Range #:start (Position #:line st-ln #:character st-ch))
81 | #:rangeLength range-ln
82 | #:text text)
83 | (define st-pos (line/char->pos doc-text st-ln st-ch))
84 | (define end-pos (+ st-pos range-ln))
85 | (send ws notify-update uri text st-pos end-pos)]
86 | [(TextDocumentContentChangeEvent #:text text)
87 | (send ws notify-replace uri text)])))
88 |
89 | ;; Text document methods
90 | (define (text-document/hover ws params)
91 | (match-define
92 | (TextDocumentPositionParams
93 | #:textDocument (TextDocumentIdentifier #:uri uri)
94 | #:position (Position #:line line #:character char))
95 | params)
96 | (define doc (send ws request-traced uri))
97 | (define doc-text (document->text% doc))
98 | (define hovers (send (document:trace doc) get-hovers))
99 | (define pos (line/char->pos doc-text line char))
100 | (define-values (start end text)
101 | (interval-map-ref/bounds hovers pos #f))
102 |
103 | (if text
104 | (Hover #:contents text
105 | #:range (pos/pos->Range doc-text start end))
106 | 'null))
107 |
108 | (define (text-document/definition ws params)
109 | (match-define
110 | (TextDocumentPositionParams
111 | #:textDocument (TextDocumentIdentifier #:uri uri)
112 | #:position (Position #:line line #:character char))
113 | params)
114 | (define doc (send ws request-traced uri))
115 | (define doc-text (document->text% doc))
116 | (define bindings (send (document:trace doc) get-bindings))
117 | (define pos (line/char->pos doc-text line char))
118 | (define declaration (interval-map-ref bindings pos #f))
119 | (match declaration
120 | [#f 'null]
121 | [(binding start end type)
122 | (Location
123 | #:uri uri
124 | #:range
125 | (Range
126 | #:start (pos->Position doc-text start)
127 | #:end (pos->Position doc-text end)))]))
128 |
129 | (define (text-document/document-symbol ws params)
130 | (match-define
131 | (TextDocumentSymbolParams
132 | #:textDocument (TextDocumentIdentifier #:uri uri))
133 | params)
134 | (define doc (send ws request-tokenized uri))
135 | (define doc-text (document->text% doc))
136 | (define after-define? (box #f))
137 | (define (definition? str)
138 | (cond [(unbox after-define?)
139 | (set-box! after-define? #f)
140 | #t]
141 | [(set-member? '("define" "define-type") str)
142 | (set-box! after-define? #t)
143 | #f]
144 | [else #f]))
145 | (define filtered-tokens
146 | (filter
147 | (lambda (tok)
148 | (match-define (token lexeme type data start end mode diff) tok)
149 | (and (eq? 'symbol type) (definition? lexeme)))
150 | (document:tokens doc)))
151 | (map
152 | (lambda (tok)
153 | (match-define (token lexeme type data start end mode diff) tok)
154 | (SymbolInformation
155 | #:name lexeme
156 | #:kind SymbolKindFunction
157 | #:location
158 | (Location
159 | #:uri uri
160 | #:range (pos/pos->Range doc-text (sub1 start) (sub1 end)))))
161 | filtered-tokens))
162 |
163 | (define (text-document/document-link ws params)
164 | (match-define
165 | (DocumentLinkParams
166 | #:textDocument (TextDocumentIdentifier #:uri uri))
167 | params)
168 | (define doc (send ws request-traced uri))
169 | (define doc-text (document->text% doc))
170 | (define document-links
171 | (append
172 | (send (document:trace doc) get-require-locations)
173 | (send (document:trace doc) get-documentation)))
174 |
175 | (map
176 | (lambda (ln)
177 | (match-define (link start end target) ln)
178 | (DocumentLink
179 | #:range (pos/pos->Range doc-text start end)
180 | #:target target))
181 | document-links))
182 |
183 | (define (text-document/formatting ws params)
184 | (match-define
185 | (DocumentFormattingParams
186 | #:textDocument (TextDocumentIdentifier #:uri uri))
187 | params)
188 | (define doc (send ws request uri))
189 | (define doc-text (document->text% doc))
190 | (list
191 | (TextEdit
192 | #:range (pos/pos->Range doc-text 0 (string-length (document:text doc)))
193 | #:newText (indent (document:text doc)))))
194 |
195 | (define (text-document/range-formatting ws params)
196 | (match-define
197 | (DocumentRangeFormattingParams
198 | #:textDocument (TextDocumentIdentifier #:uri uri)
199 | #:range (Range #:start (Position #:line start-ln #:character start-ch)
200 | #:end (Position #:line end-ln #:character end-ch)))
201 | params)
202 | (define doc (send ws request uri))
203 | (define doc-text (document->text% doc))
204 | (define start (line/char->pos doc-text start-ln start-ch))
205 | (define end (line/char->pos doc-text end-ln end-ch))
206 | (list
207 | (TextEdit
208 | #:range (pos/pos->Range doc-text 0 (string-length (document:text doc)))
209 | #:newText (indent (document:text doc) start end))))
210 |
211 | (define (racket/indent ws params)
212 | (match-define
213 | (hash-table ['textDocument (TextDocumentIdentifier #:uri uri)]
214 | ['line line])
215 | params)
216 | (define doc (send ws request uri))
217 | (compute-amount-to-indent (document:text doc) line))
218 |
219 | (provide (all-defined-out))
220 |
--------------------------------------------------------------------------------
/protocol/notifications.rkt:
--------------------------------------------------------------------------------
1 | #lang racket/base
2 | (require racket/class
3 | racket/list
4 | racket/match
5 | data/interval-map
6 | "conversion.rkt"
7 | "lsp.rkt"
8 | "jsonrpc.rkt"
9 | "../lang/document.rkt"
10 | "../lang/lexer.rkt")
11 |
12 | (define (on-trace doc)
13 | (define doc-text (document->text% doc))
14 | (racket/colorize doc doc-text)
15 | (text-document/publish-diagnostics doc doc-text))
16 |
17 | (define (on-tokenize doc)
18 | (define doc-text (document->text% doc))
19 | (racket/colorize doc doc-text))
20 |
21 | ;; Publish diagnostics notification
22 | (define (text-document/publish-diagnostics doc doc-text)
23 | (match-define (traced-document uri _ text _ trace) doc)
24 |
25 | (define diagnostics (flatten (map (exception->Diagnostics doc-text)
26 | (send trace get-diagnostics))))
27 | (send-notification
28 | "textDocument/publishDiagnostics"
29 | (PublishDiagnosticsParams #:uri uri
30 | #:diagnostics diagnostics)))
31 |
32 | ;; Racket colorize notification
33 | (define last-traced-document #f)
34 | (define (racket/colorize doc doc-text)
35 | (define new-trace (document:trace doc))
36 | (when (and new-trace (not (has-syntax-error doc)))
37 | (set! last-traced-document doc))
38 |
39 | (define next-token
40 | (if last-traced-document
41 | ((compose
42 | sexp-comment-reclassifier
43 | skip-white
44 | (semantic-reclassifier
45 | (send (document:trace last-traced-document) get-semantic-coloring)
46 | #t)
47 | (token-stream-matcher (document:tokens last-traced-document)))
48 | (list->producer (document:tokens doc)))
49 | ((compose
50 | sexp-comment-reclassifier
51 | skip-white)
52 | (list->producer (document:tokens doc)))))
53 |
54 | (define tokens
55 | (for/list ([tok (in-producer next-token eof-token?)])
56 | (match-define (token text type data start end mode diff) tok)
57 | (hasheq 'kind (symbol->string (if (and (eq? type 'symbol) data) data type))
58 | 'mode (symbol->string mode)
59 | 'range (pos/pos->Range doc-text (sub1 start) (sub1 end)))))
60 |
61 | (send-notification "racket/colorize"
62 | (hasheq 'uri (document:uri doc)
63 | 'tokens tokens)))
64 |
65 | (provide on-tokenize on-trace)
66 |
--------------------------------------------------------------------------------
/scribblings/racket-language-server.scrbl:
--------------------------------------------------------------------------------
1 | #lang scribble/manual
2 | @(require scribble/extract
3 | (for-label racket))
4 |
5 | @title{Racket Language Server}
6 | @author{David Craven}
7 |
8 | @defmodule[racket-language-server]
9 |
10 | Implementation of the
11 | @hyperlink["https://microsoft.github.io/language-server-protocol/specification"]{Language Server Protocol}
12 | for @hyperlink["https://racket-lang.org/"]{Racket}.
13 |
14 | @table-of-contents[]
15 |
16 | @section[#:tag "custom-extensions"]{Custom Extensions}
17 |
18 | @subsection[#:tag "racket_colorize"]{Colorize Notification}
19 |
20 | Colorize notifications are sent from the server to the client to enable semantic
21 | syntax highlighting.
22 |
23 | When a file changes it is the server's responsibility to re-compute syntax
24 | highlighting and push them to the client. Newly pushed tokens always replace
25 | previously pushed tokens. There is no merging that happens on the client side.
26 |
27 | @italic{Notification}:
28 | @itemize{
29 | @item{method: 'racket/colorize'}
30 | @item{params: `RacketColorizeParams` defined as follows:}
31 | }
32 |
33 | @verbatim|{
34 | interface RacketColorizeParams {
35 | /**
36 | * The URI for which colorization is reported.
37 | */
38 | uri: DocumentUri;
39 |
40 | /**
41 | * An array of diagnostic information items.
42 | */
43 | tokens: RacketToken[];
44 | }
45 |
46 | interface RacketToken {
47 | /**
48 | * The kind of token.
49 | */
50 | kind: RacketTokenKind;
51 |
52 | /**
53 | * The lexer mode used.
54 | */
55 | mode: RacketLexerMode;
56 |
57 | /**
58 | * Range of the token.
59 | */
60 | range: Range;
61 | }
62 |
63 | enum RacketLexerMode {
64 | Racket = "racket",
65 | Scribble = "scribble",
66 | Other = "other"
67 | }
68 |
69 | enum RacketTokenKind {
70 | // Generic token kinds
71 | Symbol = "symbol",
72 | Keyword = "keyword",
73 | Comment = "comment",
74 | String = "string",
75 | Constant = "constant",
76 | Parenthesis = "parenthesis",
77 | Error = "error",
78 | NoColor = "no-color",
79 | Other = "other",
80 | // Racket token kinds
81 | HashColonKeyword = "hash-colon-keyword",
82 | SexpComment = "sexp-comment",
83 | // Racket semantic token kinds
84 | Imported = "imported",
85 | LexicallyBound = "lexically-bound",
86 | Free = "free",
87 | Set!d = "set!d",
88 | // Scribble token kinds
89 | Text = "text"
90 | }
91 | }|
92 |
93 | @subsection[#:tag "racket_indent"]{Indent Request}
94 |
95 | Indent requests are sent from the client to the server to compute the
96 | indentation for a line number.
97 |
98 | @italic{Request}:
99 | @itemize{
100 | @item{method: 'racket/indent'}
101 | @item{params: `RacketIndentParams` defined as follows:}
102 | }
103 |
104 | @verbatim|{
105 | interface RacketIndentParams {
106 | /**
107 | * The TextDocument for which indentation is requested.
108 | */
109 | textDocument: TextDocumentIdentifier;
110 |
111 | /**
112 | * The line to indent.
113 | */
114 | line: number;
115 | }
116 | }|
117 |
118 | @italic{Response}:
119 | @itemize{
120 | @item{result: `number`}
121 | @item{error: code and message set in case an exception happens during the
122 | definition request.}
123 | }
124 |
125 | @section[#:tag "api"]{Application Programming Interface}
126 | The codebase is split in two folders, `lang` which provides Racket language
127 | support procedures that extract and combine information from drracket and other
128 | sources and `protocol` which converts the information to the Language Server
129 | Protocol.
130 |
131 | @subsection[#:tag "lang"]{Language support}
132 | @declare-exporting[racket-language-server/lang/lexer racket-language-server]
133 | @include-extracted["../lang/lexer.rkt"]
134 |
135 | @subsection[#:tag "protocol"]{Protocol}
136 |
137 | @section[#:tag "references"]{References}
138 |
139 | @itemize{
140 | @item{[0] @url{https://microsoft.github.io/language-server-protocol/specification}}
141 | @item{[1] @url{https://docs.racket-lang.org/tools/lang-languages-customization.html}}
142 | @item{[2] @url{https://github.com/jeapostrophe/racket-langserver}}
143 | }
144 |
145 | @section[#:tag "license"]{License}
146 | Copyright 2018 David Craven and others
147 |
148 | Permission to use, copy, modify, and/or distribute this software for any purpose
149 | with or without fee is hereby granted, provided that the above copyright notice
150 | and this permission notice appear in all copies.
151 |
152 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
153 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
154 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
155 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
156 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
157 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
158 | THIS SOFTWARE.
--------------------------------------------------------------------------------