├── .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 [![Build Status](https://travis-ci.org/theia-ide/racket-language-server.svg?branch=master)](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. --------------------------------------------------------------------------------