open Test.Import let semantic_tokens_full_debug = "ocamllsp/textDocument/semanticTokens/full" let client_capabilities = let textDocument = let semanticTokens = (* copied from vscode v1.69.2 client capabilities for semantic tokens; it's easier to read in this form *) SemanticTokensClientCapabilities.t_of_yojson @@ Yojson.Safe.from_string {| { "dynamicRegistration": true, "tokenTypes": [ "namespace", "type", "class", "enum", "interface", "struct", "typeParameter", "parameter", "variable", "property", "enumMember", "event", "function", "method", "macro", "keyword", "modifier", "comment", "string", "number", "regexp", "operator", "decorator" ], "tokenModifiers": [ "declaration", "definition", "readonly", "static", "deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary" ], "formats": [ "relative" ], "requests": { "range": true, "full": { "delta": true } }, "multilineTokenSupport": false, "overlappingTokenSupport": false, "serverCancelSupport": true, "augmentsSyntaxTokens": true } |} in TextDocumentClientCapabilities.create ~semanticTokens () in ClientCapabilities.create ~textDocument () ;; type 'resp req_ctx = { initializeResult : InitializeResult.t ; resp : 'resp } let test : type resp. src:string -> (SemanticTokensParams.t -> resp Client.out_request) -> (resp req_ctx -> unit Fiber.t) -> unit = fun ~src req consume_resp -> let wait_for_diagnostics = Fiber.Ivar.create () in let handler = Client.Handler.make ~on_notification:(fun client -> function | Lsp.Server_notification.PublishDiagnostics _ -> (* we don't want to close the connection from client-side before we process diagnostics arrived on the channel. TODO: would a better solution be to simply flush on closing the connection because now semantic tokens tests is coupled to diagnostics *) let+ () = Fiber.Ivar.fill wait_for_diagnostics () in Client.state client | _ -> Fiber.return ()) () in Test.run ~handler (fun client -> let run_client () = Client.start client (InitializeParams.create ~capabilities:client_capabilities ()) in let run () = let* (initializeResult : InitializeResult.t) = Client.initialized client in let uri = DocumentUri.of_path "test.ml" in let textDocument = TextDocumentItem.create ~uri ~languageId:"ocaml" ~version:0 ~text:src in let* () = Client.notification client (TextDocumentDidOpen (DidOpenTextDocumentParams.create ~textDocument)) in let* resp = let textDocument = TextDocumentIdentifier.create ~uri in let params = SemanticTokensParams.create ~textDocument () in Client.request client (req params) in let* () = consume_resp { initializeResult; resp } in let* () = Fiber.fork_and_join_unit (fun () -> Fiber.Ivar.read wait_for_diagnostics) (fun () -> Client.request client Shutdown) in Client.stop client in Fiber.fork_and_join_unit run_client run) ;; let test_semantic_tokens_full src = let print_resp { initializeResult; resp } = Fiber.return @@ match resp with | None -> print_endline "empty response" | Some { SemanticTokens.data; _ } -> let legend = match initializeResult.InitializeResult.capabilities .ServerCapabilities.semanticTokensProvider with | None -> failwith "no server capabilities for semantic tokens" | Some (`SemanticTokensOptions { legend; _ }) -> legend | Some (`SemanticTokensRegistrationOptions { legend; _ }) -> legend in print_endline @@ Semantic_hl_helpers.annotate_src_with_tokens ~legend ~encoded_tokens:data ~annot_mods:true src in test ~src (fun p -> SemanticTokensFull p) print_resp ;; let%expect_test "tokens for ocaml_lsp_server.ml" = test_semantic_tokens_full Semantic_hl_data.src0; [%expect {| module Moo : sig type t type koo = | Foo of string | Bar of [ `Int of int | `String of string ] val u : unit val f : unit -> t end = struct type t = int type koo = | Foo of string | Bar of [ `Int of int | `String of string ] let u = () let f () = 0 end module type Bar = sig type t = { foo : Moo.t ; bar : int } end type t = Moo.koo = | Foo of string | Bar of [ `BarInt of int | `BarString of string ] let f (foo : t) = match foo with | Moo.Foo s -> s ^ string_of_int 0 | Moo.Bar (`BarInt i) -> string_of_int i | Moo.Bar (`BarString s) -> s module Foo (Arg : Bar) = struct module Inner_foo = struct type t = string end end module Foo_inst = Foo (struct type t = { foo : Moo.t ; bar : int } end) |}] ;; let test_semantic_tokens_full_debug src = test ~src (fun p -> UnknownRequest { meth = semantic_tokens_full_debug ; params = Some (SemanticTokensParams.yojson_of_t p |> Jsonrpc.Structured.t_of_yojson) }) (fun { resp; _ } -> resp |> Yojson.Safe.pretty_to_string ~std:false |> print_endline |> Fiber.return) ;; let%expect_test "tokens for ocaml_lsp_server.ml" = test_semantic_tokens_full_debug Semantic_hl_data.src0; [%expect {| [ { "start_pos": { "character": 7, "line": 1 }, "length": 3, "type": "namespace", "modifiers": [ "definition" ] }, { "start_pos": { "character": 7, "line": 2 }, "length": 1, "type": "type", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 7, "line": 4 }, "length": 3, "type": "enum", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 6, "line": 5 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 13, "line": 5 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 6 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 23, "line": 6 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 40, "line": 6 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 8 }, "length": 1, "type": "variable", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 10, "line": 8 }, "length": 4, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 10 }, "length": 1, "type": "function", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 10, "line": 10 }, "length": 4, "type": "type", "modifiers": [] }, { "start_pos": { "character": 18, "line": 10 }, "length": 1, "type": "type", "modifiers": [] }, { "start_pos": { "character": 7, "line": 12 }, "length": 1, "type": "type", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 11, "line": 12 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 7, "line": 14 }, "length": 3, "type": "enum", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 6, "line": 15 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 13, "line": 15 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 16 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 23, "line": 16 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 40, "line": 16 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 18 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 6, "line": 20 }, "length": 1, "type": "function", "modifiers": [ "definition" ] }, { "start_pos": { "character": 13, "line": 20 }, "length": 1, "type": "number", "modifiers": [] }, { "start_pos": { "character": 12, "line": 23 }, "length": 3, "type": "interface", "modifiers": [] }, { "start_pos": { "character": 7, "line": 24 }, "length": 1, "type": "struct", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 6, "line": 25 }, "length": 3, "type": "property", "modifiers": [] }, { "start_pos": { "character": 12, "line": 25 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 16, "line": 25 }, "length": 1, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 26 }, "length": 3, "type": "property", "modifiers": [] }, { "start_pos": { "character": 12, "line": 26 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 5, "line": 30 }, "length": 1, "type": "enum", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 9, "line": 30 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 13, "line": 30 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 4, "line": 31 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 11, "line": 31 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 4, "line": 32 }, "length": 3, "type": "enumMember", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 24, "line": 32 }, "length": 3, "type": "type", "modifiers": [] }, { "start_pos": { "character": 44, "line": 32 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 4, "line": 34 }, "length": 1, "type": "function", "modifiers": [ "definition" ] }, { "start_pos": { "character": 7, "line": 34 }, "length": 3, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 13, "line": 34 }, "length": 1, "type": "type", "modifiers": [] }, { "start_pos": { "character": 8, "line": 35 }, "length": 3, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 4, "line": 36 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 8, "line": 36 }, "length": 3, "type": "enumMember", "modifiers": [] }, { "start_pos": { "character": 12, "line": 36 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 17, "line": 36 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 19, "line": 36 }, "length": 1, "type": "function", "modifiers": [] }, { "start_pos": { "character": 21, "line": 36 }, "length": 13, "type": "function", "modifiers": [] }, { "start_pos": { "character": 35, "line": 36 }, "length": 1, "type": "number", "modifiers": [] }, { "start_pos": { "character": 4, "line": 37 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 8, "line": 37 }, "length": 3, "type": "enumMember", "modifiers": [] }, { "start_pos": { "character": 21, "line": 37 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 27, "line": 37 }, "length": 13, "type": "function", "modifiers": [] }, { "start_pos": { "character": 41, "line": 37 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 4, "line": 38 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 8, "line": 38 }, "length": 3, "type": "enumMember", "modifiers": [] }, { "start_pos": { "character": 24, "line": 38 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 30, "line": 38 }, "length": 1, "type": "variable", "modifiers": [] }, { "start_pos": { "character": 7, "line": 40 }, "length": 3, "type": "namespace", "modifiers": [ "definition" ] }, { "start_pos": { "character": 12, "line": 40 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 18, "line": 40 }, "length": 3, "type": "interface", "modifiers": [] }, { "start_pos": { "character": 9, "line": 41 }, "length": 9, "type": "namespace", "modifiers": [ "definition" ] }, { "start_pos": { "character": 9, "line": 42 }, "length": 1, "type": "type", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 13, "line": 42 }, "length": 6, "type": "type", "modifiers": [] }, { "start_pos": { "character": 7, "line": 46 }, "length": 8, "type": "namespace", "modifiers": [ "definition" ] }, { "start_pos": { "character": 18, "line": 46 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 7, "line": 47 }, "length": 1, "type": "struct", "modifiers": [ "declaration" ] }, { "start_pos": { "character": 6, "line": 48 }, "length": 3, "type": "property", "modifiers": [] }, { "start_pos": { "character": 12, "line": 48 }, "length": 3, "type": "namespace", "modifiers": [] }, { "start_pos": { "character": 16, "line": 48 }, "length": 1, "type": "type", "modifiers": [] }, { "start_pos": { "character": 6, "line": 49 }, "length": 3, "type": "property", "modifiers": [] }, { "start_pos": { "character": 12, "line": 49 }, "length": 3, "type": "type", "modifiers": [] } ] |}] ;; let%expect_test "highlighting longidents with space between identifiers" = test_semantic_tokens_full @@ String.trim {| let foo = Bar.jar let joo = Bar. jar |}; [%expect {| let foo = Bar.jar let joo = Bar. jar |}] ;; let%expect_test "highlighting longidents with space between identifiers and infix fns" = test_semantic_tokens_full @@ String.trim {| Bar.(+) ;; Bar.( + ) ;; Bar. (+) ;; Bar. ( + ) ;; |}; [%expect {| Bar.(+) ;; Bar.( + ) ;; Bar. (+) ;; Bar. ( + ) ;; |}] ;; let%expect_test "longidents in records" = test_semantic_tokens_full @@ String.trim {| module M = struct type r = { foo : int ; bar : string } end let x = { M . foo = 0 ; bar = "bar"} |}; [%expect {| module M = struct type r = { foo : int ; bar : string } end let x = { M . foo = 0 ; bar = "bar"} |}] ;; let%expect_test "operators" = test_semantic_tokens_full @@ String.trim {| let x = 1.0 *. 2.0 let y = 1 * 2 let z = 0 >>= 1 |}; [%expect {| let x = 1.0 *. 2.0 let y = 1 * 2 let z = 0 >>= 1 |}] ;; let%expect_test "comment in unit" = test_semantic_tokens_full @@ String.trim {| let y = (* comment *) 0 let x = ((* comment *)) let ((*comment*)) = () |}; [%expect {| let y = (* comment *) 0 let x = ((* comment *)) let ((*comment*)) = () |}] ;;