mirror of
https://github.com/c-cube/linol.git
synced 2025-12-10 05:04:00 -05:00
1319 lines
29 KiB
OCaml
1319 lines
29 KiB
OCaml
open Test.Import
|
|
|
|
let iter_completions
|
|
?prep
|
|
?path
|
|
?(triggerCharacter = "")
|
|
?(triggerKind = CompletionTriggerKind.Invoked)
|
|
~position
|
|
=
|
|
let makeRequest textDocument =
|
|
let context = CompletionContext.create ~triggerCharacter ~triggerKind () in
|
|
Lsp.Client_request.TextDocumentCompletion
|
|
(CompletionParams.create ~textDocument ~position ~context ())
|
|
in
|
|
Lsp_helpers.iter_lsp_response ?prep ?path ~makeRequest
|
|
;;
|
|
|
|
let print_completions
|
|
?(prep = fun _ -> Fiber.return ())
|
|
?(path = "foo.ml")
|
|
?(limit = 10)
|
|
?(pre_print = fun x -> x)
|
|
source
|
|
position
|
|
=
|
|
iter_completions ~prep ~path ~source ~position (function
|
|
| None -> print_endline "No completion Items"
|
|
| Some completions ->
|
|
let items =
|
|
match completions with
|
|
| `CompletionList comp -> comp.items
|
|
| `List comp -> comp
|
|
in
|
|
items
|
|
|> pre_print
|
|
|> (function
|
|
| [] -> print_endline "No completions"
|
|
| items ->
|
|
print_endline "Completions:";
|
|
let originalLength = List.length items in
|
|
items
|
|
|> List.take (min limit originalLength)
|
|
|> List.iter ~f:(fun item ->
|
|
item
|
|
|> CompletionItem.yojson_of_t
|
|
|> Yojson.Safe.pretty_to_string ~std:false
|
|
|> print_endline);
|
|
if originalLength > limit then print_endline "............."))
|
|
;;
|
|
|
|
let%expect_test "can start completion at arbitrary position (before the dot)" =
|
|
let source = {ocaml|Strin.func|ocaml} in
|
|
let position = Position.create ~line:0 ~character:5 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "String",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "String",
|
|
"range": {
|
|
"end": { "character": 5, "line": 0 },
|
|
"start": { "character": 0, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "StringLabels",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "StringLabels",
|
|
"range": {
|
|
"end": { "character": 5, "line": 0 },
|
|
"start": { "character": 0, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|
|
|
|
let%expect_test "can start completion at arbitrary position" =
|
|
let source = {ocaml|StringLabels|ocaml} in
|
|
let position = Position.create ~line:0 ~character:6 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "String",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "String",
|
|
"range": {
|
|
"end": { "character": 6, "line": 0 },
|
|
"start": { "character": 0, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "StringLabels",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "StringLabels",
|
|
"range": {
|
|
"end": { "character": 6, "line": 0 },
|
|
"start": { "character": 0, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|
|
|
|
let%expect_test "can start completion at arbitrary position 2" =
|
|
let source = {ocaml|StringLabels|ocaml} in
|
|
let position = Position.create ~line:0 ~character:7 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "StringLabels",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "StringLabels",
|
|
"range": {
|
|
"end": { "character": 7, "line": 0 },
|
|
"start": { "character": 0, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|
|
|
|
let%expect_test "can start completion after operator without space" =
|
|
let source = {ocaml|[1;2]|>List.ma|ocaml} in
|
|
let position = Position.create ~line:0 ~character:14 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 12, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 12, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 12, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|
|
|
|
let%expect_test "can start completion after operator with space" =
|
|
let source = {ocaml|[1;2] |> List.ma|ocaml} in
|
|
let position = Position.create ~line:0 ~character:16 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 16, "line": 0 },
|
|
"start": { "character": 14, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 16, "line": 0 },
|
|
"start": { "character": 14, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 16, "line": 0 },
|
|
"start": { "character": 14, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can start completion in dot chain with tab" =
|
|
let source = {ocaml|[1;2] |> List. ma|ocaml} in
|
|
let position = Position.create ~line:0 ~character:17 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can start completion in dot chain with newline" =
|
|
let source =
|
|
{ocaml|[1;2] |> List.
|
|
ma|ocaml}
|
|
in
|
|
let position = Position.create ~line:1 ~character:2 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 2, "line": 1 },
|
|
"start": { "character": 0, "line": 1 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 2, "line": 1 },
|
|
"start": { "character": 0, "line": 1 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 2, "line": 1 },
|
|
"start": { "character": 0, "line": 1 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can start completion in dot chain with space" =
|
|
let source = {ocaml|[1;2] |> List. ma|ocaml} in
|
|
let position = Position.create ~line:0 ~character:17 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 17, "line": 0 },
|
|
"start": { "character": 15, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can start completion after dereference" =
|
|
let source =
|
|
{ocaml|let apple=ref 10 in
|
|
!ap|ocaml}
|
|
in
|
|
let position = Position.create ~line:1 ~character:3 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int ref",
|
|
"kind": 12,
|
|
"label": "apple",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "apple",
|
|
"range": {
|
|
"end": { "character": 3, "line": 1 },
|
|
"start": { "character": 1, "line": 1 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can complete symbol passed as a named argument" =
|
|
let source =
|
|
{ocaml|let g ~f = f 0 in
|
|
g ~f:ig|ocaml}
|
|
in
|
|
let position = Position.create ~line:1 ~character:7 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "'a -> unit",
|
|
"kind": 12,
|
|
"label": "ignore",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "ignore",
|
|
"range": {
|
|
"end": { "character": 7, "line": 1 },
|
|
"start": { "character": 5, "line": 1 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can complete symbol passed as a named argument - 2" =
|
|
let source =
|
|
{ocaml|module M = struct let igfoo _x = () end
|
|
let g ~f = f 0 in
|
|
g ~f:M.ig|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:9 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "'a -> unit",
|
|
"kind": 12,
|
|
"label": "igfoo",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "igfoo",
|
|
"range": {
|
|
"end": { "character": 9, "line": 2 },
|
|
"start": { "character": 7, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can complete symbol passed as an optional argument" =
|
|
let source =
|
|
{ocaml|
|
|
let g ?f = f in
|
|
g ?f:ig
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:7 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "'a -> unit",
|
|
"kind": 12,
|
|
"label": "ignore",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "ignore",
|
|
"range": {
|
|
"end": { "character": 7, "line": 2 },
|
|
"start": { "character": 5, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "can complete symbol passed as an optional argument - 2" =
|
|
let source =
|
|
{ocaml|module M = struct let igfoo _x = () end
|
|
let g ?f = f in
|
|
g ?f:M.ig|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:9 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "'a -> unit",
|
|
"kind": 12,
|
|
"label": "igfoo",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "igfoo",
|
|
"range": {
|
|
"end": { "character": 9, "line": 2 },
|
|
"start": { "character": 7, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes identifier after completion-triggering character" =
|
|
let source =
|
|
{ocaml|
|
|
module Test = struct
|
|
let somenum = 42
|
|
let somestring = "hello"
|
|
end
|
|
|
|
let x = Test.
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:6 ~character:13 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int",
|
|
"kind": 12,
|
|
"label": "somenum",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "somenum",
|
|
"range": {
|
|
"end": { "character": 13, "line": 6 },
|
|
"start": { "character": 13, "line": 6 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "string",
|
|
"kind": 12,
|
|
"label": "somestring",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "somestring",
|
|
"range": {
|
|
"end": { "character": 13, "line": 6 },
|
|
"start": { "character": 13, "line": 6 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes infix operators" =
|
|
let source =
|
|
{ocaml|
|
|
let (>>|) = (+)
|
|
let y = 1 >
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:11 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int -> int -> int",
|
|
"kind": 12,
|
|
"label": ">>|",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": ">>|",
|
|
"range": {
|
|
"end": { "character": 11, "line": 2 },
|
|
"start": { "character": 10, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'a -> bool",
|
|
"kind": 12,
|
|
"label": ">",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": ">",
|
|
"range": {
|
|
"end": { "character": 11, "line": 2 },
|
|
"start": { "character": 10, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'a -> bool",
|
|
"kind": 12,
|
|
"label": ">=",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": ">=",
|
|
"range": {
|
|
"end": { "character": 11, "line": 2 },
|
|
"start": { "character": 10, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes without prefix" =
|
|
let source =
|
|
{ocaml|
|
|
let somenum = 42
|
|
let somestring = "hello"
|
|
|
|
let plus_42 (x:int) (y:int) =
|
|
somenum +
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:5 ~character:12 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int -> int -> int",
|
|
"kind": 12,
|
|
"label": "+",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "+",
|
|
"range": {
|
|
"end": { "character": 12, "line": 5 },
|
|
"start": { "character": 11, "line": 5 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "float -> float -> float",
|
|
"kind": 12,
|
|
"label": "+.",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "+.",
|
|
"range": {
|
|
"end": { "character": 12, "line": 5 },
|
|
"start": { "character": 11, "line": 5 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes labels" =
|
|
let source = {ocaml|let f = ListLabels.map ~|ocaml} in
|
|
let position = Position.create ~line:0 ~character:24 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int -> int",
|
|
"kind": 12,
|
|
"label": "~+",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "~+",
|
|
"range": {
|
|
"end": { "character": 24, "line": 0 },
|
|
"start": { "character": 23, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "float -> float",
|
|
"kind": 12,
|
|
"label": "~+.",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "~+.",
|
|
"range": {
|
|
"end": { "character": 24, "line": 0 },
|
|
"start": { "character": 23, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "int -> int",
|
|
"kind": 12,
|
|
"label": "~-",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "~-",
|
|
"range": {
|
|
"end": { "character": 24, "line": 0 },
|
|
"start": { "character": 23, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "float -> float",
|
|
"kind": 12,
|
|
"label": "~-.",
|
|
"sortText": "0003",
|
|
"textEdit": {
|
|
"newText": "~-.",
|
|
"range": {
|
|
"end": { "character": 24, "line": 0 },
|
|
"start": { "character": 23, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'b",
|
|
"kind": 5,
|
|
"label": "~f",
|
|
"sortText": "0004",
|
|
"textEdit": {
|
|
"newText": "~f",
|
|
"range": {
|
|
"end": { "character": 24, "line": 0 },
|
|
"start": { "character": 23, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "works for polymorphic variants - function application context - 1" =
|
|
let source =
|
|
{ocaml|
|
|
let f (_a: [`String | `Int of int]) = ()
|
|
|
|
let u = f `Str
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:3 ~character:14 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "`String",
|
|
"kind": 20,
|
|
"label": "`String",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "`String",
|
|
"range": {
|
|
"end": { "character": 14, "line": 3 },
|
|
"start": { "character": 10, "line": 3 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "works for polymorphic variants - function application context - 2" =
|
|
let source =
|
|
{ocaml|
|
|
let f (_a: [`String | `Int of int]) = ()
|
|
|
|
let u = f `In
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:3 ~character:13 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "`Int of int",
|
|
"kind": 20,
|
|
"label": "`Int",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "`Int",
|
|
"range": {
|
|
"end": { "character": 13, "line": 3 },
|
|
"start": { "character": 10, "line": 3 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "works for polymorphic variants" =
|
|
let source =
|
|
{ocaml|
|
|
type t = [ `Int | `String ]
|
|
|
|
let x : t = `I
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:3 ~character:15 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "`Int",
|
|
"kind": 20,
|
|
"label": "`Int",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "`Int",
|
|
"range": {
|
|
"end": { "character": 15, "line": 3 },
|
|
"start": { "character": 13, "line": 3 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completion for holes" =
|
|
let source = {ocaml|let u : int = _|ocaml} in
|
|
let position = Position.create ~line:0 ~character:15 in
|
|
let filter =
|
|
List.filter ~f:(fun (item : CompletionItem.t) ->
|
|
not (String.starts_with ~prefix:"__" item.label))
|
|
in
|
|
print_completions ~pre_print:filter source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"filterText": "_0",
|
|
"kind": 1,
|
|
"label": "0",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "0",
|
|
"range": {
|
|
"end": { "character": 15, "line": 0 },
|
|
"start": { "character": 14, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes identifier at top level" =
|
|
let source =
|
|
{ocaml|
|
|
let somenum = 42
|
|
let somestring = "hello"
|
|
|
|
let () =
|
|
some
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:5 ~character:6 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "int",
|
|
"kind": 12,
|
|
"label": "somenum",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "somenum",
|
|
"range": {
|
|
"end": { "character": 6, "line": 5 },
|
|
"start": { "character": 2, "line": 5 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "string",
|
|
"kind": 12,
|
|
"label": "somestring",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "somestring",
|
|
"range": {
|
|
"end": { "character": 6, "line": 5 },
|
|
"start": { "character": 2, "line": 5 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completes from a module" =
|
|
let source = {ocaml|let f = List.m|ocaml} in
|
|
let position = Position.create ~line:0 ~character:14 in
|
|
print_completions source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "('a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "map",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "map",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list",
|
|
"kind": 12,
|
|
"label": "map2",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "map2",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "(int -> 'a -> 'b) -> 'a list -> 'b list",
|
|
"kind": 12,
|
|
"label": "mapi",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "mapi",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'a list -> bool",
|
|
"kind": 12,
|
|
"label": "mem",
|
|
"sortText": "0003",
|
|
"textEdit": {
|
|
"newText": "mem",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> ('a * 'b) list -> bool",
|
|
"kind": 12,
|
|
"label": "mem_assoc",
|
|
"sortText": "0004",
|
|
"textEdit": {
|
|
"newText": "mem_assoc",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> ('a * 'b) list -> bool",
|
|
"kind": 12,
|
|
"label": "mem_assq",
|
|
"sortText": "0005",
|
|
"textEdit": {
|
|
"newText": "mem_assq",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'a list -> bool",
|
|
"kind": 12,
|
|
"label": "memq",
|
|
"sortText": "0006",
|
|
"textEdit": {
|
|
"newText": "memq",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "('a -> 'a -> int) -> 'a list -> 'a list -> 'a list",
|
|
"kind": 12,
|
|
"label": "merge",
|
|
"sortText": "0007",
|
|
"textEdit": {
|
|
"newText": "merge",
|
|
"range": {
|
|
"end": { "character": 14, "line": 0 },
|
|
"start": { "character": 13, "line": 0 }
|
|
}
|
|
}
|
|
}|}]
|
|
;;
|
|
|
|
let%expect_test "completes a module name" =
|
|
let source = {ocaml|let f = L|ocaml} in
|
|
let position = Position.create ~line:0 ~character:9 in
|
|
print_completions ~pre_print:(List.take 5) source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "LargeFile",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "LargeFile",
|
|
"range": {
|
|
"end": { "character": 9, "line": 0 },
|
|
"start": { "character": 8, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "Lazy",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "Lazy",
|
|
"range": {
|
|
"end": { "character": 9, "line": 0 },
|
|
"start": { "character": 8, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "Lexing",
|
|
"sortText": "0002",
|
|
"textEdit": {
|
|
"newText": "Lexing",
|
|
"range": {
|
|
"end": { "character": 9, "line": 0 },
|
|
"start": { "character": 8, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "List",
|
|
"sortText": "0003",
|
|
"textEdit": {
|
|
"newText": "List",
|
|
"range": {
|
|
"end": { "character": 9, "line": 0 },
|
|
"start": { "character": 8, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "",
|
|
"kind": 9,
|
|
"label": "ListLabels",
|
|
"sortText": "0004",
|
|
"textEdit": {
|
|
"newText": "ListLabels",
|
|
"range": {
|
|
"end": { "character": 9, "line": 0 },
|
|
"start": { "character": 8, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
|}]
|
|
;;
|
|
|
|
let%expect_test "completion doesn't autocomplete record fields" =
|
|
let source =
|
|
{ocaml|
|
|
type r = {
|
|
x: int;
|
|
y: string
|
|
}
|
|
|
|
let _ =
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:5 ~character:8 in
|
|
print_completions
|
|
~pre_print:
|
|
(List.filter ~f:(fun (compl : CompletionItem.t) ->
|
|
compl.label = "x" || compl.label = "y"))
|
|
source
|
|
position;
|
|
(* We expect 0 completions*)
|
|
[%expect {| No completions |}]
|
|
;;
|
|
|
|
let%expect_test "completion for `in` keyword - no prefix" =
|
|
let source =
|
|
{ocaml|
|
|
let foo param1 =
|
|
let bar = param1 |ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:19 in
|
|
print_completions ~limit:3 source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"kind": 14,
|
|
"label": "in",
|
|
"textEdit": {
|
|
"newText": "in",
|
|
"range": {
|
|
"end": { "character": 19, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> 'b",
|
|
"kind": 12,
|
|
"label": "param1",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "param1",
|
|
"range": {
|
|
"end": { "character": 19, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a ref -> 'a",
|
|
"kind": 12,
|
|
"label": "!",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "!",
|
|
"range": {
|
|
"end": { "character": 19, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
............. |}]
|
|
;;
|
|
|
|
let%expect_test "completion for `in` keyword - prefix i" =
|
|
let source =
|
|
{ocaml|
|
|
let foo param1 =
|
|
let bar = param1 i
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:20 in
|
|
print_completions ~limit:3 source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"kind": 14,
|
|
"label": "in",
|
|
"textEdit": {
|
|
"newText": "in",
|
|
"range": {
|
|
"end": { "character": 20, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a -> unit",
|
|
"kind": 12,
|
|
"label": "ignore",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "ignore",
|
|
"range": {
|
|
"end": { "character": 20, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "in_channel -> int",
|
|
"kind": 12,
|
|
"label": "in_channel_length",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "in_channel_length",
|
|
"range": {
|
|
"end": { "character": 20, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
............. |}]
|
|
;;
|
|
|
|
let%expect_test "completion for `in` keyword - prefix in" =
|
|
let source =
|
|
{ocaml|
|
|
let foo param1 =
|
|
let bar = param1 in
|
|
|ocaml}
|
|
in
|
|
let position = Position.create ~line:2 ~character:21 in
|
|
print_completions ~limit:3 source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"kind": 14,
|
|
"label": "in",
|
|
"textEdit": {
|
|
"newText": "in",
|
|
"range": {
|
|
"end": { "character": 21, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "in_channel -> int",
|
|
"kind": 12,
|
|
"label": "in_channel_length",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "in_channel_length",
|
|
"range": {
|
|
"end": { "character": 21, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "int ref -> unit",
|
|
"kind": 12,
|
|
"label": "incr",
|
|
"sortText": "0001",
|
|
"textEdit": {
|
|
"newText": "incr",
|
|
"range": {
|
|
"end": { "character": 21, "line": 2 },
|
|
"start": { "character": 19, "line": 2 }
|
|
}
|
|
}
|
|
}
|
|
............. |}]
|
|
;;
|
|
|
|
(* Test case was taken from issue #1358 *)
|
|
let%expect_test "completion for object methods" =
|
|
let source = {ocaml|let f (x : < a_method : 'a >) = x#|ocaml} in
|
|
let position = Position.create ~line:0 ~character:34 in
|
|
print_completions ~limit:3 source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"kind": 14,
|
|
"label": "in",
|
|
"textEdit": {
|
|
"newText": "in",
|
|
"range": {
|
|
"end": { "character": 34, "line": 0 },
|
|
"start": { "character": 34, "line": 0 }
|
|
}
|
|
}
|
|
}
|
|
{
|
|
"detail": "'a",
|
|
"kind": 2,
|
|
"label": "a_method",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "a_method",
|
|
"range": {
|
|
"end": { "character": 34, "line": 0 },
|
|
"start": { "character": 34, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|
|
|
|
let%expect_test "completion for object methods" =
|
|
let source = {ocaml|let f (x : < a_method : 'a; ab_m : 'b >) = x#ab|ocaml} in
|
|
let position = Position.create ~line:0 ~character:49 in
|
|
print_completions ~limit:3 source position;
|
|
[%expect
|
|
{|
|
|
Completions:
|
|
{
|
|
"detail": "'b",
|
|
"kind": 2,
|
|
"label": "ab_m",
|
|
"sortText": "0000",
|
|
"textEdit": {
|
|
"newText": "ab_m",
|
|
"range": {
|
|
"end": { "character": 49, "line": 0 },
|
|
"start": { "character": 47, "line": 0 }
|
|
}
|
|
}
|
|
} |}]
|
|
;;
|