diff --git a/check-syntax.rkt b/check-syntax.rkt index 651aa09..06ea89f 100644 --- a/check-syntax.rkt +++ b/check-syntax.rkt @@ -7,14 +7,16 @@ racket/logging racket/list racket/string + racket/port syntax/modread racket/sandbox setup/path-to-relative + json "editor.rkt" "responses.rkt" + "server.rkt" "interfaces.rkt" "doc-trace.rkt" - "msg-io.rkt" "responses.rkt" "scheduler.rkt" "path-util.rkt") @@ -132,16 +134,17 @@ ; check for a #reader directive at start of file, ignoring comments ; the ^ anchor here matches start-of-string, not start-of-line (if (regexp-match #rx"^(;[^\n]*\n)*#reader" text) - #f ; most likely a drracket file, use default indentation - ; (https://github.com/jeapostrophe/racket-langserver/issues/86) - 'missing)] + #f ; most likely a drracket file, use default indentation + ; (https://github.com/jeapostrophe/racket-langserver/issues/86) + 'missing)] [else #f])) (define-syntax-rule (timeout time-sec body) (with-limits time-sec #f body)) -(define (send-diagnostics uri diag-lst) - (display-message/flush (diagnostics-message uri diag-lst))) +(define (send-diagnostics-notification uri diag-lst) + (send current-server flush-message + (diagnostics-message uri diag-lst))) (define (check-syntax uri doc-text) (define src (uri->path uri)) @@ -170,9 +173,9 @@ [current-namespace ns] [current-load-relative-directory src-dir]) (with-intercepted-logging - (lambda (l) - (define result (check-typed-racket-log doc-text l)) - (when (list? result) (set! diags (append result diags)))) + (lambda (l) + (define result (check-typed-racket-log doc-text l)) + (when (list? result) (set! diags (append result diags)))) (lambda () (with-handlers ([(or/c exn:fail:read? exn:fail:syntax? @@ -196,21 +199,21 @@ (define warn-diags (set->list (send new-trace get-warn-diags))) (define other-diags (append err-diags lang-diag diags)) - (send-diagnostics uri (append warn-diags other-diags)) + (send-diagnostics-notification uri (append warn-diags other-diags)) (define (task) (send new-trace walk-text text) (define new-warn-diags (set->list (send new-trace get-warn-diags))) ;; send a diagnostics to force client send a new code action request - (when (not (equal? new-warn-diags warn-diags)) - (send-diagnostics uri (append new-warn-diags other-diags)))) + (unless (equal? new-warn-diags warn-diags) + (send-diagnostics-notification uri (append new-warn-diags other-diags)))) (when valid (scheduler-push-task! uri 'walk-text task)) (if valid new-trace #f)) (provide - (contract-out - [check-syntax (-> any/c (is-a?/c lsp-editor%) - (or/c #f (is-a?/c build-trace%)))])) + (contract-out + [check-syntax (-> any/c (is-a?/c lsp-editor%) + (or/c #f (is-a?/c build-trace%)))])) diff --git a/info.rkt b/info.rkt index e750ffc..a8e4c72 100644 --- a/info.rkt +++ b/info.rkt @@ -15,6 +15,6 @@ "racket-index" ;; for cross references (setup/xref) "html-parsing" ;; for parsing documentation text )) -(define build-deps '("chk-lib")) +(define build-deps '("chk-lib" "rackunit-lib")) (define pkg-desc "Language Server Protocol implementation for Racket.") (define version "1.0") diff --git a/main.rkt b/main.rkt index 7b7eed1..9f28d6d 100644 --- a/main.rkt +++ b/main.rkt @@ -5,10 +5,13 @@ racket/function racket/list racket/match + racket/class + racket/async-channel "debug.rkt" "error-codes.rkt" "methods.rkt" "msg-io.rkt" + "server.rkt" "responses.rkt") ;; https://www.cs.utah.edu/plt/publications/pldi04-ff.pdf @@ -56,6 +59,9 @@ ;; * out-t - defined in `msg-io.rkt`, put the response message ;; to a specified output-port or current-output-port. (define (main-loop) + (define resp-ch (make-async-channel)) + (set-current-server! (new server% [output-channel resp-ch])) + (define q (queue)) (define (consume) (define msg (sync (queue-recv-evt q))) @@ -66,10 +72,14 @@ [_ (maybe-debug-log msg) (with-handlers ([exn:fail? report-error]) - (process-message msg))]) + (send current-server process-message msg))]) (consume)) + (define (write-resp) + (display-message/flush (async-channel-get resp-ch)) + (write-resp)) (spawn consume) + (spawn write-resp) (for ([msg (in-port read-message)]) (sync (queue-send-evt q msg))) (eprintf "Unexpected EOF\n") diff --git a/methods.rkt b/methods.rkt index 0849ea0..f288eee 100644 --- a/methods.rkt +++ b/methods.rkt @@ -4,11 +4,11 @@ racket/contract/base racket/exn racket/match + racket/class + racket/async-channel "error-codes.rkt" - "msg-io.rkt" "responses.rkt" "struct.rkt" - "server-request.rkt" (prefix-in workspace/ "workspace.rkt") (prefix-in text-document/ "text-document.rkt")) @@ -55,43 +55,71 @@ ;; Dispatch ;;;;;;;;;;;;; -;; Processes a message. This displays any repsonse it generates -;; and should always return void. -(define (process-message msg) - (match msg - ;; Request - [(hash-table ['id (? (or/c number? string?) id)] - ['method (? string? method)]) - (define params (hash-ref msg 'params hasheq)) - (define response (process-request id method params)) - ;; the result can be a response or a procedure which returns - ;; a response. If it's a procedure, then it's expected to run - ;; concurrently. - (thread (λ () - (display-message/flush - (if (procedure? response) - (response) - response)))) - (void)] - ;; Notification - [(hash-table ['method (? string? method)]) - (define params (hash-ref msg 'params hasheq)) - (process-notification method params)] - [(hash-table ['jsonrpc "2.0"] - ['id id] - ['result result]) - (define handler (hash-ref response-handlers id)) - (handler result) - (hash-remove! response-handlers id)] - ;; Batch Request - [(? (non-empty-listof (and/c hash? jsexpr?))) - (for-each process-message msg)] - ;; Invalid Message - [_ - (define id-ref (hash-ref msg 'id void)) - (define id (if ((or/c number? string?) id-ref) id-ref (json-null))) - (define err "The JSON sent is not a valid request object.") - (display-message/flush (error-response id INVALID-REQUEST err))])) +(define server% + (class object% + (super-new) + + (init-field output-channel) + (field + ; Each request sent by server should register its response handler here + [response-handlers (make-hash)]) + + (define/public (flush-message msg) + (async-channel-put output-channel msg)) + + (define/public (send-request id method params handler) + ; register handler + (hash-set! response-handlers id handler) + ; send request to LSP client + (flush-message (hasheq 'id id + 'method method + 'params params))) + + ;; Processes a message (a JSON). This displays any repsonse it generates + ;; and should always return void. + (define/public (process-message msg) + (match msg + ;; Request + [(hash-table ['id (? (or/c number? string?) id)] + ['method (? string? method)]) + (define params (hash-ref msg 'params hasheq)) + (define response (handle-request id method params)) + ;; the result can be a response or a procedure which returns + ;; a response. If it's a procedure, then it's expected to run + ;; concurrently. + (thread (λ () + (flush-message + (if (procedure? response) + (response) + response)))) + (void)] + ;; Notification + [(hash-table ['method (? string? method)]) + (define params (hash-ref msg 'params hasheq)) + (handle-notification method params)] + ;; Response + [(hash-table ['jsonrpc "2.0"] + ['id id] + ['result result]) + (define handler (hash-ref response-handlers id)) + (handler result) + (hash-remove! response-handlers id)] + ;; Batch Request + [(? (non-empty-listof (and/c hash? jsexpr?))) + (for-each (lambda (msg) (process-message msg)) msg)] + ;; Invalid Message + [_ + (define id-ref (hash-ref msg 'id void)) + (define id (if ((or/c number? string?) id-ref) id-ref (json-null))) + (define err "The JSON sent is not a valid request object.") + (flush-message (error-response id INVALID-REQUEST err))])) + + (define/public (handle-request id method params) + (process-request id method params)) + + (define/public (handle-notification method params) + (process-notification method params)) + )) (define ((report-request-error id method) exn) (eprintf "Caught exn in request ~v\n~a\n" method (exn->string exn)) @@ -211,10 +239,10 @@ 'workspace (hasheq 'fileOperations (hasheq 'didRename ; workspace.fileOperations.didRename - (hasheq 'filters - (map (lambda (ext) - (hasheq 'scheme "file" 'pattern (hasheq 'glob (format "**/*.~a" ext)))) - (get-module-suffixes)))) + (hasheq 'filters + (map (lambda (ext) + (hasheq 'scheme "file" 'pattern (hasheq 'glob (format "**/*.~a" ext)))) + (get-module-suffixes)))) 'workspaceFolders (hasheq 'changeNotifications #t)))) (define resp (success-response id (hasheq 'capabilities server-capabilities))) @@ -229,8 +257,4 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(provide - (contract-out - [process-message - (jsexpr? . -> . void?)])) - +(provide server%) diff --git a/server-request.rkt b/server-request.rkt deleted file mode 100644 index d9ff92a..0000000 --- a/server-request.rkt +++ /dev/null @@ -1,13 +0,0 @@ -#lang racket/base -(provide response-handlers send-request) -(require "msg-io.rkt") - -(define response-handlers (make-hash)) ; Each request sent by server will record its response handler here - -;; Send a request from server to client and register handler of response. -(define (send-request id method params handler) - (hash-set! response-handlers id handler) - (display-message/flush - (hasheq 'id id - 'method method - 'params params))) diff --git a/server.rkt b/server.rkt new file mode 100644 index 0000000..76e0d8e --- /dev/null +++ b/server.rkt @@ -0,0 +1,7 @@ +#lang racket/base +(provide current-server + set-current-server!) + +(define current-server #f) +(define (set-current-server! s) + (set! current-server s)) diff --git a/tests/client.rkt b/tests/client.rkt index 994298c..4491de6 100644 --- a/tests/client.rkt +++ b/tests/client.rkt @@ -3,108 +3,109 @@ ;; This module provides a simple incomplete lsp client for test. (provide with-racket-lsp - Lsp? client-send client-wait-response + client-wait-notification make-request make-expected-response make-notification handle-server-request) (require racket/os + racket/async-channel + racket/match json - data/queue - "../msg-io.rkt") - -(struct Lsp - (stdout stdin stderr response-queue [id #:mutable #:auto]) - #:auto-value -1) + "../server.rkt" + "../methods.rkt") +(define id 0) (define (Lsp-genid lsp) - (set-Lsp-id! lsp (add1 (Lsp-id lsp))) - (Lsp-id lsp)) + (set! id (add1 id)) + id) + +(define server-output-ch (make-parameter #f)) +(define response-channel (make-parameter #f)) +(define notification-channel (make-parameter #f)) -(define/contract (process-incoming-message lsp msg) - (-> Lsp? jsexpr? void?) +(define/contract (process-message-from-server lsp msg) + (-> any/c jsexpr? void?) - (cond + (match msg ;; Server request - handle immediately and send response - [(and (hash-has-key? msg 'method) (hash-has-key? msg 'id)) + [(hash-table ['id (? (or/c number? string?) id)] + ['method (? string? method)]) (handle-server-request lsp msg)] ;; Server notification - queue diagnostic notifications, ignore others - [(hash-has-key? msg 'method) - (define method (hash-ref msg 'method)) - (when (equal? method "textDocument/publishDiagnostics") - (enqueue! (Lsp-response-queue lsp) msg))] - ;; Response - queue it for client-wait-response - [else - (enqueue! (Lsp-response-queue lsp) msg)])) - -(define ((forward-errors in)) - (for ([str (in-port read-line in)]) - (displayln (format "LSP ERROR: ~a" str) (current-error-port)))) - -(define/contract (with-racket-lsp path proc) - (-> string? (-> Lsp? any/c) void?) - - (define racket-path (find-executable-path "racket")) - (define-values (sp stdout stdin stderr) - (subprocess #f #f #f racket-path path)) - (define _err-thd (thread (forward-errors stderr))) - (define lsp (Lsp stdout stdin stderr (make-queue))) - - (define init-req - (make-request lsp "initialize" - (hasheq 'processId (getpid) - 'capabilities (hasheq)))) - (client-send lsp init-req) - (client-wait-response lsp) - - (proc lsp) - - (define shutdown-req - (make-request lsp "shutdown" #f)) - (client-send lsp shutdown-req) - (client-wait-response lsp) - - (define exit-notf (make-notification "exit" #f)) - (client-send lsp exit-notf) - (client-should-no-response lsp) - - (subprocess-wait sp)) + [(hash-table ['method (? string? method)]) + (match method + ["textDocument/publishDiagnostics" + (async-channel-put (notification-channel) msg)] + ; ignore unknown method + [_ (void)])] + ;; Response - put it to another channel for client-wait-response + [msg (async-channel-put (response-channel) msg)])) + +(define/contract (with-racket-lsp proc) + (-> (-> any/c any/c) void?) + (define ch (make-async-channel)) + + (parameterize ([server-output-ch ch] + [response-channel (make-async-channel)] + [notification-channel (make-async-channel)]) + (set-current-server! (new server% [output-channel ch])) + (define lsp current-server) + + (define (handle-output) + (define msg (async-channel-get (server-output-ch))) + (cond + [(jsexpr? msg) (process-message-from-server lsp msg)] + [(void? msg) (void)] + [else (printf "handle-output ~a~n" msg)]) + (handle-output)) + (thread handle-output) + + (define init-req + (make-request lsp "initialize" + (hasheq 'processId (getpid) + 'capabilities (hasheq)))) + (client-send lsp init-req) + (client-wait-response lsp) + + (proc lsp) + + (define shutdown-req + (make-request lsp "shutdown" #f)) + (client-send lsp shutdown-req) + (client-wait-response lsp) + + (define exit-notf (make-notification "exit" #f)) + (client-send lsp exit-notf) + (client-should-no-response lsp) + + (void))) (define/contract (client-send lsp req) - (-> Lsp? jsexpr? void?) + (-> any/c jsexpr? void?) - (display-message/flush req (Lsp-stdin lsp))) + (send lsp process-message req)) (define/contract (client-wait-response lsp) - (-> Lsp? jsexpr?) - - ;; First, check if there's already a response in the queue - (define queued-response - (if (queue-empty? (Lsp-response-queue lsp)) - #f - (dequeue! (Lsp-response-queue lsp)))) - - (if queued-response - queued-response - ;; No queued response, so read and process messages until we get one - (let loop () - (define msg (read-message (Lsp-stdout lsp))) - (process-incoming-message lsp msg) - ;; Check if processing the message added a response to the queue - (if (queue-empty? (Lsp-response-queue lsp)) - (loop) - (dequeue! (Lsp-response-queue lsp)))))) + (-> any/c jsexpr?) + + (define js (async-channel-get (response-channel))) + (make-immutable-hasheq (hash->list js))) + +(define (client-wait-notification lsp) + (async-channel-get (notification-channel))) (define/contract (client-should-no-response lsp) - (-> Lsp? eof-object?) - - (read-message (Lsp-stdout lsp))) + (-> any/c eof-object?) + + (async-channel-get (response-channel)) + ) (define/contract (make-request lsp method params) - (-> Lsp? string? jsexpr? jsexpr?) + (-> any/c string? jsexpr? jsexpr?) (define req (hasheq 'jsonrpc "2.0" @@ -133,7 +134,7 @@ ;; Currently this is just a stub procedure that always reply null (define/contract (handle-server-request lsp request) - (-> Lsp? jsexpr? void?) + (-> any/c jsexpr? void?) (define id (hash-ref request 'id)) (define method (hash-ref request 'method)) diff --git a/tests/sync/test.rkt b/tests/sync/test.rkt index d1b01f8..d51be26 100644 --- a/tests/sync/test.rkt +++ b/tests/sync/test.rkt @@ -1,51 +1,55 @@ #lang racket/base (require "../client.rkt" - "../../json-util.rkt" - chk - json) + "../../json-util.rkt") (module+ test - (with-racket-lsp "../../main.rkt" - (λ (lsp) - - ;; didopen - (define didopen-req - (make-notification "textDocument/didOpen" - (hasheq 'textDocument - (hasheq 'uri "file:///test.rkt" - 'languageId "racket" - 'version 0 - 'text "#lang racke")))) - ;; should report "collection not found" diagnostic error - (client-send lsp didopen-req) - (let ([resp (client-wait-response lsp)]) - (chk* - (chk (jsexpr-has-key? resp '(params diagnostics))) - (define diagnostics-msg (jsexpr-ref resp '(params diagnostics))) - (chk (not (null? diagnostics-msg))) - (define resp-no-message (hash-remove (car diagnostics-msg) 'message)) - (chk #:= resp-no-message (read-json (open-input-file "diagnostics.json"))))) - - - (define didchange-req - (make-notification "textDocument/didChange" - (hasheq 'textDocument - (hasheq 'uri "file:///test.rkt" - 'version 1) - 'contentChanges - (list (hasheq 'text "#lang racket"))))) - ;; should not report any error - (client-send lsp didchange-req) - (let ([resp (client-wait-response lsp)]) - (chk* - (chk (jsexpr-has-key? resp '(params diagnostics))) - (chk (null? (jsexpr-ref resp '(params diagnostics)))))) - - - ;; no response for didClose request - (define didclose-req - (make-notification "textDocument/didClose" - (hasheq 'textDocument - (hasheq 'uri "file:///test.rkt")))) - (client-send lsp didclose-req)))) + (require rackunit + json + racket/port) + + (with-racket-lsp + (λ (lsp) + + ;; didopen + (define didopen-req + (make-notification "textDocument/didOpen" + (hasheq 'textDocument + (hasheq 'uri "file:///test.rkt" + 'languageId "racket" + 'version 0 + 'text "#lang racke")))) + ;; should report "collection not found" diagnostic error + (client-send lsp didopen-req) + (let ([resp (client-wait-notification lsp)]) + (check-true (jsexpr-has-key? resp '(params diagnostics))) + (define diagnostics-msg (jsexpr-ref resp '(params diagnostics))) + (check-false (null? diagnostics-msg)) + (define dm (with-input-from-string + (jsexpr->string (car diagnostics-msg)) + (lambda () (read-json)))) + (define resp-no-message (hash-remove dm 'message)) + (check-equal? (jsexpr->string resp-no-message) + (jsexpr->string (read-json (open-input-file "diagnostics.json"))))) + + + (define didchange-req + (make-notification "textDocument/didChange" + (hasheq 'textDocument + (hasheq 'uri "file:///test.rkt" + 'version 1) + 'contentChanges + (list (hasheq 'text "#lang racket"))))) + ;; should not report any error + (client-send lsp didchange-req) + (let ([resp (client-wait-notification lsp)]) + (check-true (jsexpr-has-key? resp '(params diagnostics))) + (check-true (null? (jsexpr-ref resp '(params diagnostics))))) + + + ;; no response for didClose request + (define didclose-req + (make-notification "textDocument/didClose" + (hasheq 'textDocument + (hasheq 'uri "file:///test.rkt")))) + (client-send lsp didclose-req)))) diff --git a/tests/textDocument/code-action/code-action.rkt b/tests/textDocument/code-action/code-action.rkt index 2817b30..6f18368 100644 --- a/tests/textDocument/code-action/code-action.rkt +++ b/tests/textDocument/code-action/code-action.rkt @@ -1,8 +1,6 @@ #lang racket -(require "../with-document.rkt" - chk - json) +(require "../with-document.rkt") (define uri "file:///test.rkt") @@ -15,10 +13,14 @@ END ) (module+ test - (with-document "../../../main.rkt" uri code + (require rackunit + json) + + (with-document uri code (λ (lsp) (let ([req (read-json (open-input-file "req1.json"))] [resp (read-json (open-input-file "resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp))))) \ No newline at end of file + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp)))))) diff --git a/tests/textDocument/completion/completion.rkt b/tests/textDocument/completion/completion.rkt index 93504de..71733a3 100644 --- a/tests/textDocument/completion/completion.rkt +++ b/tests/textDocument/completion/completion.rkt @@ -14,14 +14,14 @@ END ) (module+ test - (with-document "../../../main.rkt" uri code + (with-document uri code (λ (lsp) ;; completion requires a document change. ;; only move cursor to that position is not enough. (define didchange-req (read-json (open-input-file "change-req.json"))) (client-send lsp didchange-req) - (client-wait-response lsp) + (client-wait-notification lsp) (define comp-req (read-json (open-input-file "comp-req.json"))) (client-send lsp comp-req) diff --git a/tests/textDocument/find-symbol/test.rkt b/tests/textDocument/find-symbol/test.rkt index 704b987..c28a8ef 100644 --- a/tests/textDocument/find-symbol/test.rkt +++ b/tests/textDocument/find-symbol/test.rkt @@ -1,8 +1,6 @@ #lang racket -(require "../with-document.rkt" - chk - json) +(require "../with-document.rkt") (define uri "file:///test.rkt") @@ -18,7 +16,10 @@ END ) (module+ test - (with-document "../../../main.rkt" uri code + (require rackunit + json) + + (with-document uri code (λ (lsp) ;; definition @@ -26,25 +27,29 @@ END [resp (read-json (open-input-file "definition-resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp)) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp))) ;; documentHighlight (let ([req (read-json (open-input-file "highlight-req1.json"))] [resp (read-json (open-input-file "highlight-resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp)) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp))) ;; symbol (let ([req (read-json (open-input-file "symbol-req1.json"))] [resp (read-json (open-input-file "symbol-resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp)) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp))) ;; references (let ([req (read-json (open-input-file "ref-req1.json"))] [resp (read-json (open-input-file "ref-resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp))))) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp)))))) diff --git a/tests/textDocument/formatting.rkt b/tests/textDocument/formatting.rkt index 1aa6d9c..34294b0 100644 --- a/tests/textDocument/formatting.rkt +++ b/tests/textDocument/formatting.rkt @@ -1,7 +1,6 @@ #lang racket -(require "with-document.rkt" - chk) +(require "with-document.rkt") (define uri "file:///test.rkt") @@ -15,7 +14,10 @@ END ) (module+ test - (with-document "../../main.rkt" uri code + (require rackunit + json) + + (with-document uri code (λ (lsp) ;; Insert a new line with indentation after line 2 (let ([notif (make-notification @@ -35,7 +37,7 @@ END 'rangeLength 0 'text "\n"))))]) (client-send lsp notif) - (client-wait-response lsp)) + (client-wait-notification lsp)) ;; Format on type for pre-indented new line 3 (let* ([req (make-request lsp @@ -49,22 +51,22 @@ END 'options (hasheq 'tabSize 4 'insertSpaces #t)))] [res (make-expected-response req - (list - (hasheq 'range - (hasheq 'start - (hasheq 'line 3 - 'character 0) - 'end - (hasheq 'line 3 - 'character 0)) - 'newText "") - (hasheq 'range - (hasheq 'start - (hasheq 'line 3 - 'character 0) - 'end - (hasheq 'line 3 - 'character 0)) - 'newText " ")))]) + (list + (hasheq 'range + (hasheq 'start + (hasheq 'line 3 + 'character 0) + 'end + (hasheq 'line 3 + 'character 0)) + 'newText "") + (hasheq 'range + (hasheq 'start + (hasheq 'line 3 + 'character 0) + 'end + (hasheq 'line 3 + 'character 0)) + 'newText " ")))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) res))))) + (check-equal? (jsexpr->string (client-wait-response lsp)) (jsexpr->string res)))))) diff --git a/tests/textDocument/hover.rkt b/tests/textDocument/hover.rkt index 089ab94..65afdfc 100644 --- a/tests/textDocument/hover.rkt +++ b/tests/textDocument/hover.rkt @@ -1,8 +1,7 @@ #lang racket (require "../../json-util.rkt" - "with-document.rkt" - chk) + "with-document.rkt") (define uri "file:///test.rkt") @@ -15,7 +14,9 @@ END ) (module+ test - (with-document "../../main.rkt" uri code + (require rackunit) + + (with-document uri code (λ (lsp) (define hover-req @@ -28,14 +29,14 @@ END (client-send lsp hover-req) (let ([resp (client-wait-response lsp)]) - (chk (jsexpr-has-key? resp '(result contents))) - (chk (not (string=? "" (jsexpr-ref resp '(result contents))))) - - (chk (jsexpr-has-key? resp '(result range start line))) - (chk (jsexpr-has-key? resp '(result range start character))) - (chk (jsexpr-has-key? resp '(result range end line))) - (chk (jsexpr-has-key? resp '(result range end character))) - (chk #:= (jsexpr-ref resp '(result range start line)) 2) - (chk #:= (jsexpr-ref resp '(result range start character)) 1) - (chk #:= (jsexpr-ref resp '(result range end line)) 2) - (chk #:= (jsexpr-ref resp '(result range end character)) 5))))) + (check-true (jsexpr-has-key? resp '(result contents))) + (check-false (string=? "" (jsexpr-ref resp '(result contents)))) + + (check-true (jsexpr-has-key? resp '(result range start line))) + (check-true (jsexpr-has-key? resp '(result range start character))) + (check-true (jsexpr-has-key? resp '(result range end line))) + (check-true (jsexpr-has-key? resp '(result range end character))) + (check-equal? (jsexpr-ref resp '(result range start line)) 2) + (check-equal? (jsexpr-ref resp '(result range start character)) 1) + (check-equal? (jsexpr-ref resp '(result range end line)) 2) + (check-equal? (jsexpr-ref resp '(result range end character)) 5))))) diff --git a/tests/textDocument/rename/rename.rkt b/tests/textDocument/rename/rename.rkt index 3a0d175..190ace6 100644 --- a/tests/textDocument/rename/rename.rkt +++ b/tests/textDocument/rename/rename.rkt @@ -18,14 +18,19 @@ END ) (module+ test - (with-document "../../../main.rkt" uri code + (require rackunit + json) + + (with-document uri code (λ (lsp) (let ([req (read-json (open-input-file "req1.json"))] [resp (read-json (open-input-file "resp1.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp)) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp))) (let ([req (read-json (open-input-file "req2.json"))] [resp (read-json (open-input-file "resp2.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp))))) \ No newline at end of file + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp)))))) diff --git a/tests/textDocument/resyntax/resyntax.rkt b/tests/textDocument/resyntax/resyntax.rkt index 36c50c8..9ec524f 100644 --- a/tests/textDocument/resyntax/resyntax.rkt +++ b/tests/textDocument/resyntax/resyntax.rkt @@ -3,8 +3,7 @@ (require "../with-document.rkt" "../../../service/dynamic-import.rkt" "../../../json-util.rkt" - chk - json) + chk) (define uri "file:///test.rkt") @@ -19,17 +18,21 @@ END ;; detect if resyntax is available (define has-resyntax? #t) (dynamic-imports ('resyntax - resyntax-analyze) + resyntax-analyze) (λ () (set! has-resyntax? #f))) (module+ test + (require rackunit + json) + (when has-resyntax? - (with-document "../../../main.rkt" uri code + (with-document uri code (λ (lsp) - (define diag (client-wait-response lsp)) - (chk #:= (jsexpr-ref diag '(method)) "textDocument/publishDiagnostics") + (define diag (client-wait-notification lsp)) + (check-equal? (jsexpr-ref diag '(method)) "textDocument/publishDiagnostics") (let ([req (read-json (open-input-file "req.json"))] [resp (read-json (open-input-file "resp.json"))]) (client-send lsp req) - (chk #:= (client-wait-response lsp) resp)))))) + (check-equal? (jsexpr->string (client-wait-response lsp)) + (jsexpr->string resp))))))) diff --git a/tests/textDocument/signature-help.rkt b/tests/textDocument/signature-help.rkt index a31144a..55fc765 100644 --- a/tests/textDocument/signature-help.rkt +++ b/tests/textDocument/signature-help.rkt @@ -15,7 +15,7 @@ END ) (module+ test - (with-document "../../main.rkt" uri code + (with-document uri code (λ (lsp) (define help-req diff --git a/tests/textDocument/with-document.rkt b/tests/textDocument/with-document.rkt index 6bfd492..b07f996 100644 --- a/tests/textDocument/with-document.rkt +++ b/tests/textDocument/with-document.rkt @@ -3,32 +3,33 @@ (provide with-document client-send client-wait-response + client-wait-notification make-request make-expected-response make-notification) (require "../client.rkt") -(define/contract (with-document path uri text proc) - (-> string? string? string? (-> Lsp? any/c) any/c) +(define/contract (with-document uri text proc) + (-> string? string? (-> any/c any/c) any/c) - (with-racket-lsp path - (λ (lsp) - (define didopen-req - (make-notification "textDocument/didOpen" - (hasheq 'textDocument - (hasheq 'uri uri - 'languageId "racket" - 'version 0 - 'text text)))) - (client-send lsp didopen-req) - (client-wait-response lsp) + (with-racket-lsp + (λ (lsp) + (define didopen-req + (make-notification "textDocument/didOpen" + (hasheq 'textDocument + (hasheq 'uri uri + 'languageId "racket" + 'version 0 + 'text text)))) + (client-send lsp didopen-req) + (client-wait-notification lsp) - (proc lsp) + (proc lsp) - ;; no response for didClose request - (define didclose-req - (make-notification "textDocument/didClose" - (hasheq 'textDocument - (hasheq 'uri uri)))) - (client-send lsp didclose-req)))) + ;; no response for didClose request + (define didclose-req + (make-notification "textDocument/didClose" + (hasheq 'textDocument + (hasheq 'uri uri)))) + (client-send lsp didclose-req)))) diff --git a/tests/workspace/did-change-watched-files.rkt b/tests/workspace/did-change-watched-files.rkt index c55d741..37a90e7 100644 --- a/tests/workspace/did-change-watched-files.rkt +++ b/tests/workspace/did-change-watched-files.rkt @@ -4,13 +4,13 @@ json) (module+ test - (with-racket-lsp "../../main.rkt" - (λ (lsp) - (define noti - (make-notification "workspace/didChangeWatchedFiles" - (hasheq 'changes - (list (hasheq 'uri "file:///tmp/test.rkt" 'type 1) ; created - (hasheq 'uri "file:///tmp/other.rkt" 'type 2) ; changed - (hasheq 'uri "file:///tmp/old.rkt" 'type 3))))) ; deleted - (client-send lsp noti) - ))) + (with-racket-lsp + (λ (lsp) + (define noti + (make-notification "workspace/didChangeWatchedFiles" + (hasheq 'changes + (list (hasheq 'uri "file:///tmp/test.rkt" 'type 1) ; created + (hasheq 'uri "file:///tmp/other.rkt" 'type 2) ; changed + (hasheq 'uri "file:///tmp/old.rkt" 'type 3))))) ; deleted + (client-send lsp noti) + ))) diff --git a/tests/workspace/did-change-workspace-folders.rkt b/tests/workspace/did-change-workspace-folders.rkt index 6820df6..341e4a4 100644 --- a/tests/workspace/did-change-workspace-folders.rkt +++ b/tests/workspace/did-change-workspace-folders.rkt @@ -4,12 +4,12 @@ json) (module+ test - (with-racket-lsp "../../main.rkt" - (λ (lsp) - (define noti - (make-notification "workspace/didChangeWorkspaceFolders" - (hasheq 'event - (hasheq 'added (list (hasheq 'uri "/tmp/project_a" 'name "projectA")) - 'removed (list))))) - (client-send lsp noti) - ))) + (with-racket-lsp + (λ (lsp) + (define noti + (make-notification "workspace/didChangeWorkspaceFolders" + (hasheq 'event + (hasheq 'added (list (hasheq 'uri "/tmp/project_a" 'name "projectA")) + 'removed (list))))) + (client-send lsp noti) + ))) diff --git a/tests/workspace/did-rename.rkt b/tests/workspace/did-rename.rkt index 3126d62..140c2c5 100644 --- a/tests/workspace/did-rename.rkt +++ b/tests/workspace/did-rename.rkt @@ -4,11 +4,11 @@ json) (module+ test - (with-racket-lsp "../../main.rkt" - (λ (lsp) - (define did-rename-notification - (make-notification "workspace/didRenameFiles" - (hasheq 'files - (list (hasheq 'oldUri "a.rkt" 'newUri "a1.rkt"))))) - (client-send lsp did-rename-notification) - ))) + (with-racket-lsp + (λ (lsp) + (define did-rename-notification + (make-notification "workspace/didRenameFiles" + (hasheq 'files + (list (hasheq 'oldUri "a.rkt" 'newUri "a1.rkt"))))) + (client-send lsp did-rename-notification) + ))) diff --git a/text-document.rkt b/text-document.rkt index 06ee2a0..f450fe2 100644 --- a/text-document.rkt +++ b/text-document.rkt @@ -20,8 +20,8 @@ "doc.rkt" "struct.rkt" "scheduler.rkt" - "server-request.rkt" - "workspace.rkt") + "workspace.rkt" + "server.rkt") (require "open-docs.rkt") ;; @@ -80,9 +80,10 @@ [section string?]) (define (fetch-configuration uri) - (send-request 0 "workspace/configuration" - (hasheq 'items (list (ConfigurationItem #:scopeUri uri #:section "racket-langserver"))) - update-configuration)) + (send current-server send-request + 0 "workspace/configuration" + (hasheq 'items (list (ConfigurationItem #:scopeUri uri #:section "racket-langserver"))) + update-configuration)) ;; ;; Methods @@ -142,28 +143,29 @@ (define-values (start end text) (interval-map-ref/bounds hovers pos #f)) (match-define (list link tag) - (interval-map-ref (send doc-trace get-docs) pos (list #f #f))) + (interval-map-ref (send doc-trace get-docs) pos (list #f #f))) (define result (cond [text ;; We want signatures from `scribble/blueboxes` as they have better indentation, ;; but in some super rare cases blueboxes aren't accessible, thus we try to use the ;; parsed signature instead (match-define (list sigs args-descr) - (if tag - (get-docs-for-tag tag) - (list #f #f))) + (if tag + (get-docs-for-tag tag) + (list #f #f))) (define maybe-signature - (if sigs - (~a "```\n" - (string-join sigs "\n") - (if args-descr (~a "\n" args-descr) "") - "\n```\n---\n") - #f)) + (and sigs + (~a "```\n" + (string-join sigs "\n") + (if args-descr + (~a "\n" args-descr) + "") + "\n```\n---\n"))) (define documentation-text (if link (~a (or maybe-signature "") (or (extract-documentation-for-selected-element - link #:include-signature? (not maybe-signature)) + link #:include-signature? (not maybe-signature)) "")) "")) (define contents (if link @@ -177,7 +179,7 @@ text)) (hasheq 'contents contents 'range (start/end->range doc start end))] - [else (hasheq 'contents empty)])) + [else (hasheq 'contents "")])) (success-response id result)))] [_ (error-response id INVALID-PARAMS "textDocument/hover failed")])) @@ -257,15 +259,15 @@ (λ (doc) (define doc-trace (Doc-trace doc)) (define pos (sub1 (doc-pos doc line ch))) - (define completions + (define completions (append (send doc-trace get-completions) (send doc-trace get-online-completions (doc-guess-token doc pos)))) (define result (for/list ([completion (in-list completions)]) (hasheq 'label (symbol->string completion)))) (success-response id - (hash 'isIncomplete #t - 'items result))))] + (hash 'isIncomplete #t + 'items result))))] [_ (error-response id INVALID-PARAMS "textDocument/completion failed")])) @@ -368,10 +370,10 @@ (define ranges (cons (start/end->range doc left right) (get-bindings uri decl))) (WorkspaceEdit - #:changes - (hasheq (string->symbol uri) - (for/list ([range (in-list ranges)]) - (TextEdit #:range range #:newText new-name))))])] + #:changes + (hasheq (string->symbol uri) + (for/list ([range (in-list ranges)]) + (TextEdit #:range range #:newText new-name))))])] [#f (json-null)])))) (success-response id result)] [_ @@ -543,10 +545,10 @@ #:end (Pos #:line ed-ln #:char ed-ch))]) (define safe-doc (hash-ref open-docs (string->symbol uri))) (match-define (list start-pos end-pos) - (with-read-doc safe-doc - (λ (doc) - (list (doc-pos doc st-ln st-ch) - (doc-pos doc ed-ln ed-ch))))) + (with-read-doc safe-doc + (λ (doc) + (list (doc-pos doc st-ln st-ch) + (doc-pos doc ed-ln ed-ch))))) (semantic-tokens uri id safe-doc start-pos end-pos)] [_ (error-response id INVALID-PARAMS "textDocument/semanticTokens/range failed")])) @@ -560,32 +562,32 @@ (if tokens (success-response id (hash 'data tokens)) (async-query-wait - uri - (λ (_signal) - (define tokens (with-read-doc safe-doc (λ (doc) (doc-range-tokens doc start-pos end-pos)))) - (success-response id (hash 'data tokens)))))) + uri + (λ (_signal) + (define tokens (with-read-doc safe-doc (λ (doc) (doc-range-tokens doc start-pos end-pos)))) + (success-response id (hash 'data tokens)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (provide - (contract-out - [did-open! (jsexpr? . -> . void?)] - [did-close! (jsexpr? . -> . void?)] - [did-change! (jsexpr? . -> . void?)] - [hover (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [code-action (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [completion (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [signatureHelp (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [definition (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [document-highlight (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [references (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [document-symbol (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [inlay-hint (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [rename _rename rename (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [prepareRename (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [range-formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [on-type-formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] - [full-semantic-tokens (exact-nonnegative-integer? jsexpr? . -> . (or/c jsexpr? (-> jsexpr?)))] - [range-semantic-tokens (exact-nonnegative-integer? jsexpr? . -> . (or/c jsexpr? (-> jsexpr?)))])) + (contract-out + [did-open! (jsexpr? . -> . void?)] + [did-close! (jsexpr? . -> . void?)] + [did-change! (jsexpr? . -> . void?)] + [hover (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [code-action (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [completion (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [signatureHelp (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [definition (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [document-highlight (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [references (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [document-symbol (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [inlay-hint (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [rename _rename rename (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [prepareRename (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [range-formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [on-type-formatting! (exact-nonnegative-integer? jsexpr? . -> . jsexpr?)] + [full-semantic-tokens (exact-nonnegative-integer? jsexpr? . -> . (or/c jsexpr? (-> jsexpr?)))] + [range-semantic-tokens (exact-nonnegative-integer? jsexpr? . -> . (or/c jsexpr? (-> jsexpr?)))]))