linol/thirdparty/lsp/ocaml-lsp-server/src/semantic_highlighting.ml

1021 lines
32 KiB
OCaml

open Import
open Fiber.O
module Array_view = Lsp.Private.Array_view
module Parsetree_utils = Merlin_analysis.Parsetree_utils
(* TODO:
- [x] support textDocument/semanticTokens/full
- [x] support textDocument/semanticTokens/full/delta
- [ ] support textDocument/semanticTokens/range *)
module Token_type : sig
type t
val of_builtin : SemanticTokenTypes.t -> t
val module_ : t
val module_type : t
val to_int : t -> int
val to_legend : t -> string
val tokenTypes : string list
end = struct
type t = SemanticTokenTypes.t
let of_builtin t = t
let module_ = SemanticTokenTypes.Namespace
let module_type = SemanticTokenTypes.Interface
let legend : SemanticTokenTypes.t list =
[ Namespace
; Type
; Class
; Enum
; Interface
; Struct
; TypeParameter
; Parameter
; Variable
; Property
; EnumMember
; Event
; Function
; Method
; Macro
; Keyword
; Modifier
; Comment
; String
; Number
; Regexp
; Operator
; Decorator
]
;;
let tokenTypes : string list =
List.map legend ~f:(fun s ->
match SemanticTokenTypes.yojson_of_t s with
| `String s -> s
| _ -> assert false)
;;
let to_int =
let module Table = MoreLabels.Hashtbl in
let table =
lazy
(let t = Table.create (List.length legend) in
List.iteri legend ~f:(fun data key -> Table.add t ~key ~data);
t)
in
fun t -> Table.find (Lazy.force table) t
;;
let to_legend t =
match SemanticTokenTypes.yojson_of_t t with
| `String s -> s
| _ -> assert false
;;
end
module Token_modifiers_set : sig
type t
val to_int : t -> int
val singleton : SemanticTokenModifiers.t -> t
val empty : t
val list : string list
val to_legend : t -> string list
end = struct
type t = int
let to_int x = x
let empty = 0
let singleton : SemanticTokenModifiers.t -> t = function
| Declaration -> 1 lsl 0
| Definition -> 1 lsl 1
| Readonly -> 1 lsl 2
| Static -> 1 lsl 3
| Deprecated -> 1 lsl 4
| Abstract -> 1 lsl 5
| Async -> 1 lsl 6
| Modification -> 1 lsl 7
| Documentation -> 1 lsl 8
| DefaultLibrary -> 1 lsl 9
;;
let list =
[ "declaration"
; "definition"
; "readonly"
; "static"
; "deprecated"
; "abstract"
; "async"
; "modification"
; "documentation"
; "defaultLibrary"
]
;;
let array = lazy (Array.of_list list)
let to_legend =
let cache = lazy (Hashtbl.create 3) in
fun t ->
let cache = Lazy.force cache in
match Hashtbl.find_opt cache t with
| Some x -> x
| None ->
let rec translate t i acc : string list =
let is_set = Int.equal (t land 1) 1 in
let t' = t lsr 1 in
let acc' = if is_set then (Lazy.force array).(i) :: acc else acc in
if Int.equal t' 0 then List.rev acc' else translate (i + 1) t' acc'
in
let res = translate t 0 [] in
Hashtbl.add cache t res;
res
;;
end
let legend =
SemanticTokensLegend.create
~tokenTypes:Token_type.tokenTypes
~tokenModifiers:Token_modifiers_set.list
;;
(** Represents a collection of semantic tokens. *)
module Tokens : sig
type t
val create : unit -> t
val append_token : t -> Loc.t -> Token_type.t -> Token_modifiers_set.t -> unit
val append_token'
: t
-> Position.t
-> length:int
-> Token_type.t
-> Token_modifiers_set.t
-> unit
val yojson_of_t : t -> Yojson.Safe.t
val encode : t -> int array
end = struct
type token =
{ start : Position.t
; length : int
; token_type : Token_type.t
; token_modifiers : Token_modifiers_set.t
}
type t =
{ mutable tokens : token list (* the last appended token is the head of this list *)
; mutable count : int
}
let create () : t = { tokens = []; count = 0 }
let append_token : t -> Loc.t -> Token_type.t -> Token_modifiers_set.t -> unit =
fun t loc token_type token_modifiers ->
if loc.loc_ghost
then ()
else (
let range = Range.of_loc_opt loc in
Option.iter range ~f:(fun ({ start; end_ } : Range.t) ->
(* TODO: we currently don't handle multi-line range; could handle if
client supports it - see client's capabilities on initialization *)
if Int.equal start.line end_.line
then (
let new_token : token =
let length = end_.character - start.character in
{ start; length; token_type; token_modifiers }
in
t.tokens <- new_token :: t.tokens;
t.count <- t.count + 1)))
;;
let append_token'
: t -> Position.t -> length:int -> Token_type.t -> Token_modifiers_set.t -> unit
=
fun t start ~length token_type token_modifiers ->
let new_token : token = { start; length; token_type; token_modifiers } in
t.tokens <- new_token :: t.tokens;
t.count <- t.count + 1
;;
let set_token
arr
~delta_line_index
~delta_line
~delta_start
~length
~token_type
~token_modifiers
=
arr.(delta_line_index) <- delta_line;
arr.(delta_line_index + 1) <- delta_start;
arr.(delta_line_index + 2) <- length;
arr.(delta_line_index + 3) <- token_type;
arr.(delta_line_index + 4) <- token_modifiers
;;
let yojson_of_token { start; length; token_type; token_modifiers } =
`Assoc
[ "start_pos", Position.yojson_of_t start
; "length", `Int length
; "type", `String (Token_type.to_legend token_type)
; ( "modifiers"
, Json.Conv.yojson_of_list
Json.Conv.yojson_of_string
(Token_modifiers_set.to_legend token_modifiers) )
]
;;
let yojson_of_t t = Json.Conv.yojson_of_list yojson_of_token (List.rev t.tokens)
let encode (t : t) : int array =
let data = Array.init (t.count * 5) ~f:(fun (_ : int) -> 0) in
let rec aux ix = function
| [] -> ()
| [ { start; length; token_type; token_modifiers } ] ->
set_token
data
~delta_line_index:0
~delta_line:start.line
~delta_start:start.character
~length
~token_type:(Token_type.to_int token_type)
~token_modifiers:(Token_modifiers_set.to_int token_modifiers)
| current :: previous :: rest ->
let delta_line = current.start.line - previous.start.line in
let delta_start =
if Int.equal delta_line 0
then current.start.character - previous.start.character
else current.start.character
in
let { length; token_type; token_modifiers; _ } = current in
let delta_line_index = (ix - 1) * 5 in
set_token
data
~delta_line_index
~delta_line
~delta_start
~length
~token_type:(Token_type.to_int token_type)
~token_modifiers:(Token_modifiers_set.to_int token_modifiers);
aux (ix - 1) (previous :: rest)
in
aux t.count t.tokens;
data
;;
end
(** To traverse OCaml parsetree and produce semantic tokens. *)
module Parsetree_fold (M : sig
val source : string
end) : sig
val apply : Mreader.parsetree -> Tokens.t
end = struct
(* mutable state *)
let tokens = Tokens.create ()
let source_excerpt ({ loc_start; loc_end; _ } : Loc.t) =
let start_offset = loc_start.pos_cnum in
let end_offset = loc_end.pos_cnum in
String.sub M.source ~pos:start_offset ~len:(end_offset - start_offset)
;;
let add_token loc token_type token_modifiers =
Tokens.append_token tokens loc token_type token_modifiers
;;
let add_token' pos ~length token_type token_modifiers =
Tokens.append_token' tokens pos ~length token_type token_modifiers
;;
(* TODO: make sure we follow specs when parsing -
https://v2.ocaml.org/manual/names.html#sss:refer-named *)
let lident
({ loc; _ } : Longident.t Loc.loc)
rightmost_name
?(modifiers = Token_modifiers_set.empty)
()
=
if loc.loc_ghost
then ()
else (
let start = Position.of_lexical_position loc.loc_start in
match start with
| None -> ()
| Some start ->
let lid = source_excerpt loc in
let i = ref 0 in
let line = ref start.line in
let character = ref start.character in
let parse_word () : Position.t * [ `Length of int ] =
let left_pos = { Position.line = !line; character = !character } in
while
!i < String.length lid
&&
match lid.[!i] with
| '\n' | ' ' | '.' -> false
| _ -> true
do
incr character;
incr i
done;
left_pos, `Length (!character - left_pos.character)
in
while !i < String.length lid do
match lid.[!i] with
| '\n' ->
incr line;
character := 0;
incr i
| ' ' | '.' ->
incr character;
incr i
| _ ->
let pos, `Length length = parse_word () in
let token_type, mods =
if !i = String.length lid
then rightmost_name, modifiers
else Token_type.module_, Token_modifiers_set.empty
in
add_token' pos ~length token_type mods
done)
;;
let constructor_arguments
(self : Ast_iterator.iterator)
(ca : Parsetree.constructor_arguments)
=
match ca with
| Pcstr_tuple l -> List.iter l ~f:(fun ct -> self.typ self ct)
| Pcstr_record l -> List.iter l ~f:(fun r -> self.label_declaration self r)
;;
let module_binding
(self : Ast_iterator.iterator)
({ pmb_name; pmb_expr; pmb_attributes; pmb_loc = _ } : Parsetree.module_binding)
=
add_token pmb_name.loc Token_type.module_ (Token_modifiers_set.singleton Definition);
self.module_expr self pmb_expr;
self.attributes self pmb_attributes
;;
let typ
(self : Ast_iterator.iterator)
({ ptyp_desc; ptyp_attributes; ptyp_loc; ptyp_loc_stack = _ } as ct :
Parsetree.core_type)
=
let iter =
match ptyp_desc with
| Ptyp_var _ ->
add_token ptyp_loc (Token_type.of_builtin TypeParameter) Token_modifiers_set.empty;
`Custom_iterator
| Ptyp_constr (name, cts) | Ptyp_class (name, cts) ->
List.iter cts ~f:(fun ct -> self.typ self ct);
lident name (Token_type.of_builtin Type) ();
`Custom_iterator
| Ptyp_poly (tps, ct) ->
List.iter tps ~f:(fun (tp : _ Asttypes.loc) ->
add_token tp.loc (Token_type.of_builtin TypeParameter) Token_modifiers_set.empty);
self.typ self ct;
`Custom_iterator
| Ptyp_any -> `Custom_iterator
| Ptyp_variant (_, _, _)
| Ptyp_alias (_, _)
| Ptyp_arrow _
| Ptyp_extension _
| Ptyp_package _
| Ptyp_object _
| Ptyp_tuple _
| Ptyp_open _ -> `Default_iterator
in
match iter with
| `Default_iterator -> Ast_iterator.default_iterator.typ self ct
| `Custom_iterator -> self.attributes self ptyp_attributes
;;
let constructor_declaration
(self : Ast_iterator.iterator)
({ pcd_name; pcd_vars; pcd_args; pcd_res; pcd_loc = _; pcd_attributes } :
Parsetree.constructor_declaration)
=
add_token
pcd_name.loc
(Token_type.of_builtin EnumMember)
(Token_modifiers_set.singleton Declaration);
List.iter pcd_vars ~f:(fun (var : _ Asttypes.loc) ->
add_token var.loc (Token_type.of_builtin TypeParameter) Token_modifiers_set.empty);
constructor_arguments self pcd_args;
Option.iter pcd_res ~f:(fun ct -> self.typ self ct);
self.attributes self pcd_attributes
;;
let label_declaration
(self : Ast_iterator.iterator)
({ pld_name; pld_mutable = _; pld_type; pld_loc = _; pld_attributes } :
Parsetree.label_declaration)
=
add_token pld_name.loc (Token_type.of_builtin Property) Token_modifiers_set.empty;
self.typ self pld_type;
self.attributes self pld_attributes
;;
let value_binding
(self : Ast_iterator.iterator)
({ pvb_pat; pvb_expr; pvb_attributes; _ } as vb : Parsetree.value_binding)
=
match
match pvb_pat.ppat_desc, pvb_expr.pexp_desc with
| Parsetree.Ppat_var fn_name, _ ->
(match pvb_expr.pexp_desc with
| Pexp_function _ ->
add_token
fn_name.loc
(Token_type.of_builtin Function)
(Token_modifiers_set.singleton Definition);
self.expr self pvb_expr;
`Custom_iterator
| _ -> `Default_iterator)
| ( Ppat_constraint ({ ppat_desc = Ppat_var n; _ }, pat_ct)
, Pexp_constraint (e, exp_ct) )
when Loc.compare pat_ct.ptyp_loc exp_ct.ptyp_loc = 0 ->
(* handles [let f : t -> unit = fun t -> ()] *)
add_token
n.loc
(match pat_ct.ptyp_desc with
| Ptyp_poly (_, { ptyp_desc = Ptyp_arrow _; _ }) | Ptyp_arrow _ ->
Token_type.of_builtin Function
| _ -> Token_type.of_builtin Variable)
Token_modifiers_set.empty;
self.typ self pat_ct;
self.expr self e;
`Custom_iterator
| _ -> `Default_iterator
with
| `Default_iterator -> Ast_iterator.default_iterator.value_binding self vb
| `Custom_iterator -> self.attributes self pvb_attributes
;;
let type_declaration
(self : Ast_iterator.iterator)
({ ptype_name
; ptype_params
; ptype_cstrs
; ptype_kind
; ptype_private = _
; ptype_manifest
; ptype_attributes
; ptype_loc = _
} :
Parsetree.type_declaration)
=
List.iter
ptype_params
~f:
(fun
((core_type, _) :
Parsetree.core_type * (Asttypes.variance * Asttypes.injectivity))
->
add_token
core_type.ptyp_loc
(Token_type.of_builtin TypeParameter)
Token_modifiers_set.empty);
add_token
ptype_name.loc
(match ptype_kind with
| Parsetree.Ptype_abstract | Ptype_open -> Token_type.of_builtin Type
| Ptype_variant _ -> Token_type.of_builtin Enum
| Ptype_record _ -> Token_type.of_builtin Struct)
(Token_modifiers_set.singleton Declaration);
List.iter ptype_cstrs ~f:(fun (ct0, ct1, (_ : Loc.t)) ->
self.typ self ct0;
self.typ self ct1);
Option.iter ptype_manifest ~f:(fun ct -> self.typ self ct);
(match ptype_kind with
| Parsetree.Ptype_abstract | Parsetree.Ptype_open -> ()
| Ptype_variant cds ->
List.iter cds ~f:(fun cd -> self.constructor_declaration self cd)
| Ptype_record lds -> List.iter lds ~f:(fun ld -> self.label_declaration self ld));
self.attributes self ptype_attributes
;;
let const loc (constant : Parsetree.constant) =
let token_type =
match Parsetree_utils.constant_desc constant with
| Parsetree.Pconst_integer _ | Pconst_float _ -> Token_type.of_builtin Number
| Pconst_char _ | Pconst_string _ -> Token_type.of_builtin String
in
add_token loc token_type Token_modifiers_set.empty
;;
let pexp_apply (self : Ast_iterator.iterator) (expr : Parsetree.expression) args =
match expr.pexp_desc with
| Pexp_ident { txt = Ldot (Lident "Array", "set"); _ }
| Pexp_ident { txt = Ldot (Lident "Array", "get"); _ }
| Pexp_ident { txt = Ldot (Lident "String", "set"); _ }
| Pexp_ident { txt = Ldot (Lident "String", "get"); _ } ->
List.iter args ~f:(fun ((_ : Asttypes.arg_label), e) -> self.expr self e);
`Custom_iterator
| Pexp_ident lid ->
(match args with
| (_, fst_arg) :: rest
when (* true if applied function is infix, i.e., function name occurs
after the first argument *)
Loc.compare lid.loc fst_arg.pexp_loc > 0 ->
self.expr self fst_arg;
(* [lident] parses the identifier to find module names, which we don't
need to do for infix operators. *)
add_token lid.loc (Token_type.of_builtin Function) Token_modifiers_set.empty;
List.iter rest ~f:(fun (_, e) -> self.expr self e)
| _ ->
lident lid (Token_type.of_builtin Function) ();
List.iter args ~f:(fun (_, e) -> self.expr self e));
`Custom_iterator
| Pexp_field (e, l) ->
self.expr self e;
lident l (Token_type.of_builtin Function) ();
`Custom_iterator
| _ -> `Default_iterator
;;
let expr
(self : Ast_iterator.iterator)
({ pexp_desc; pexp_loc; pexp_loc_stack = _; pexp_attributes } as exp :
Parsetree.expression)
=
match
match pexp_desc with
| Parsetree.Pexp_ident l ->
lident l (Token_type.of_builtin Variable) ();
`Custom_iterator
| Pexp_construct (c, vo) ->
(match c.txt with
| Lident "::" ->
(* because [a; b] is desugared to [Pexp_construct (Lident "::",
Pexp_tuple(...))] *)
Option.iter vo ~f:(fun v -> self.expr self v)
| Lident "[]" -> () (* TDOO: is this correct? *)
| Lident "()" -> ()
| _ ->
lident c (Token_type.of_builtin EnumMember) ();
Option.iter vo ~f:(fun v -> self.expr self v));
`Custom_iterator
| Pexp_apply (expr, args) -> pexp_apply self expr args
| Pexp_function _ | Pexp_let (_, _, _) -> `Default_iterator
| Pexp_try (_, _)
| Pexp_tuple _
| Pexp_variant (_, _)
(* ^ label for a poly variant is missing location info -- we could have a
workaround by "parsing" this part of code ourselves*)
| Pexp_match (_, _) -> `Default_iterator
| Pexp_record (props, exp) ->
Option.iter exp ~f:(fun e -> self.expr self e);
List.iter props ~f:(fun (lid, (exp : Parsetree.expression)) ->
lident lid (Token_type.of_builtin Property) ();
if Loc.compare lid.loc exp.pexp_loc <> 0 (* handles field punning *)
then self.expr self exp);
`Custom_iterator
| Pexp_field (e, l) ->
self.expr self e;
lident l (Token_type.of_builtin Property) ();
`Custom_iterator
| Pexp_send (e, m) ->
self.expr self e;
add_token m.loc (Token_type.of_builtin Method) Token_modifiers_set.empty;
`Custom_iterator
| Pexp_setfield (e0, l, e1) ->
self.expr self e0;
lident l (Token_type.of_builtin Variable) ();
self.expr self e1;
`Custom_iterator
| Pexp_new l ->
lident l (Token_type.of_builtin Class) ();
`Custom_iterator
| Pexp_newtype (t, e) ->
add_token t.loc (Token_type.of_builtin TypeParameter) Token_modifiers_set.empty;
self.expr self e;
`Custom_iterator
| Pexp_letmodule (name, me, e) ->
add_token name.loc Token_type.module_ Token_modifiers_set.empty;
self.module_expr self me;
(* ^ handle function applications like this *)
self.expr self e;
`Custom_iterator
| Pexp_constant c ->
const pexp_loc c;
`Custom_iterator
| Pexp_sequence (e0, e1) ->
self.expr self e0;
self.expr self e1;
`Custom_iterator
| Pexp_constraint (e, ct) ->
(* handles [let f () : int = 1] and [let f () = (1 : int)] *)
if Loc.compare e.pexp_loc ct.ptyp_loc > 0
then (
self.typ self ct;
self.expr self e)
else (
self.expr self e;
self.typ self ct);
`Custom_iterator
| Pexp_letop { let_; ands; body } ->
List.iter
(let_ :: ands)
~f:(fun { Parsetree.pbop_op = _; pbop_pat; pbop_exp; pbop_loc = _ } ->
self.pat self pbop_pat;
if
Loc.compare pbop_pat.ppat_loc pbop_exp.pexp_loc
<> 0 (* handles punning as in e.g. [let* foo in <expr>]*)
then self.expr self pbop_exp);
self.expr self body;
`Custom_iterator
| Pexp_unreachable -> `Custom_iterator
| Pexp_array _
| Pexp_ifthenelse (_, _, _)
| Pexp_while (_, _)
| Pexp_for (_, _, _, _, _)
| Pexp_coerce (_, _, _)
| Pexp_setinstvar (_, _)
| Pexp_override _
| Pexp_letexception (_, _)
| Pexp_assert _ | Pexp_lazy _
| Pexp_poly (_, _)
| Pexp_object _ | Pexp_pack _
| Pexp_open (_, _)
| Pexp_extension _ -> `Default_iterator
with
| `Default_iterator -> Ast_iterator.default_iterator.expr self exp
| `Custom_iterator -> self.attributes self pexp_attributes
;;
let pat
(self : Ast_iterator.iterator)
({ ppat_desc; ppat_loc; ppat_loc_stack = _; ppat_attributes } as pat :
Parsetree.pattern)
=
match
match ppat_desc with
| Parsetree.Ppat_var v ->
add_token v.loc (Token_type.of_builtin Variable) Token_modifiers_set.empty;
`Custom_iterator
| Ppat_alias (p, a) ->
self.pat self p;
add_token a.loc (Token_type.of_builtin Variable) Token_modifiers_set.empty;
`Custom_iterator
| Ppat_construct (c, args) ->
let process_args () =
Option.iter args ~f:(fun (tvs, pat) ->
List.iter tvs ~f:(fun (tv : _ Asttypes.loc) ->
add_token
tv.loc
(Token_type.of_builtin TypeParameter)
Token_modifiers_set.empty);
self.pat self pat)
in
(match c.txt with
| Lident "::" -> process_args ()
| Lident "[]" -> ()
| Lident "()" -> ()
| _ ->
lident c (Token_type.of_builtin EnumMember) ();
process_args ());
`Custom_iterator
| Ppat_constant c ->
const ppat_loc c;
`Custom_iterator
| Ppat_open (lid, p) ->
lident lid Token_type.module_ ();
self.pat self p;
`Custom_iterator
| Ppat_unpack m ->
Option.iter m.txt ~f:(fun _ ->
add_token m.loc Token_type.module_ Token_modifiers_set.empty);
`Custom_iterator
| Ppat_type t ->
lident t (Token_type.of_builtin Type) ();
`Custom_iterator
| Ppat_record (flds, _) ->
List.iter flds ~f:(fun (fld, (pat : Parsetree.pattern)) ->
lident fld (Token_type.of_builtin Property) ();
if Loc.compare fld.loc pat.ppat_loc <> 0 (* handles field punning *)
then self.pat self pat);
`Custom_iterator
| Ppat_constraint (p, ct) ->
self.pat self p;
self.typ self ct;
`Custom_iterator
| Ppat_or _
| Ppat_exception _
| Ppat_variant _
| Ppat_array _
| Ppat_extension _
| Ppat_tuple _
| Ppat_lazy _
| Ppat_any
| Ppat_interval _
| _ -> `Default_iterator
with
| `Default_iterator -> Ast_iterator.default_iterator.pat self pat
| `Custom_iterator -> self.attributes self ppat_attributes
;;
let module_expr
(self : Ast_iterator.iterator)
({ pmod_desc; pmod_loc = _; pmod_attributes } as me : Parsetree.module_expr)
=
match
match pmod_desc with
| Pmod_ident s ->
lident s Token_type.module_ ();
`Custom_iterator
| Pmod_functor (fp, me) ->
(match fp with
| Unit -> ()
| Named (n, mt) ->
add_token n.loc Token_type.module_ Token_modifiers_set.empty;
self.module_type self mt);
self.module_expr self me;
`Custom_iterator
| Pmod_constraint (me, mt) ->
if Loc.compare me.pmod_loc mt.pmty_loc > 0
then (
self.module_type self mt;
self.module_expr self me)
else (
self.module_expr self me;
self.module_type self mt);
`Custom_iterator
| Pmod_extension _ -> `Custom_iterator
| _ ->
(* We rely on the wildcard pattern to improve compatibility with
multiple OCaml's parsetree versions *)
`Default_iterator
with
| `Custom_iterator -> self.attributes self pmod_attributes
| `Default_iterator -> Ast_iterator.default_iterator.module_expr self me
;;
let module_type_declaration
(self : Ast_iterator.iterator)
({ pmtd_name; pmtd_type; pmtd_attributes; pmtd_loc = _ } :
Parsetree.module_type_declaration)
=
add_token pmtd_name.loc Token_type.module_type Token_modifiers_set.empty;
Option.iter pmtd_type ~f:(fun mdtt -> self.module_type self mdtt);
self.attributes self pmtd_attributes
;;
let value_description
(self : Ast_iterator.iterator)
({ pval_name; pval_type; pval_prim = _; pval_attributes; pval_loc = _ } :
Parsetree.value_description)
=
add_token
pval_name.loc
(match pval_type.ptyp_desc with
| Ptyp_arrow (_, _, _) -> Token_type.of_builtin Function
| Ptyp_class (_, _) -> Token_type.of_builtin Class
| Ptyp_package _ -> Token_type.module_
| Ptyp_extension _
| Ptyp_constr (_, _)
| Ptyp_object (_, _)
| Ptyp_alias (_, _)
| Ptyp_variant (_, _, _)
| Ptyp_poly (_, _)
| Ptyp_tuple _ | Ptyp_any | Ptyp_var _ | Ptyp_open _ ->
Token_type.of_builtin Variable)
(Token_modifiers_set.singleton Declaration);
self.typ self pval_type;
(* TODO: handle pval_prim ? *)
self.attributes self pval_attributes
;;
let module_declaration
(self : Ast_iterator.iterator)
({ pmd_name; pmd_type; pmd_attributes; pmd_loc = _ } :
Parsetree.module_declaration)
=
add_token pmd_name.loc Token_type.module_ (Token_modifiers_set.singleton Declaration);
self.module_type self pmd_type;
self.attributes self pmd_attributes
;;
let module_type (self : Ast_iterator.iterator) (mt : Parsetree.module_type) =
match
match mt.pmty_desc with
| Pmty_ident l ->
lident l Token_type.module_type ();
`Custom_iterator
| Pmty_functor (fp, mt) ->
(match fp with
| Unit -> ()
| Named (n, mt) ->
add_token n.loc Token_type.module_ Token_modifiers_set.empty;
self.module_type self mt);
self.module_type self mt;
`Custom_iterator
| Pmty_alias m ->
lident m Token_type.module_ ();
`Custom_iterator
| Pmty_signature sis ->
List.iter sis ~f:(fun si -> self.signature_item self si);
`Custom_iterator
| Pmty_with (_, _) | Pmty_typeof _ | Pmty_extension _ -> `Default_iterator
with
| `Custom_iterator -> ()
| `Default_iterator -> Ast_iterator.default_iterator.module_type self mt
;;
(* TODO: *)
let attribute _self _attr = ()
(* TODO: *)
let attributes _self _attrs = ()
let iterator =
{ Ast_iterator.default_iterator with
module_binding
; type_declaration
; expr
; pat
; constructor_declaration
; label_declaration
; typ
; value_binding
; module_type_declaration
; attribute
; attributes
; module_expr
; value_description
; module_type
; module_declaration
}
;;
let apply parsetree =
(match parsetree with
| `Interface signature -> iterator.signature iterator signature
| `Implementation structure -> iterator.structure iterator structure);
tokens
;;
end
(** File-wide mutable state that allows to generate unique IDs for semantic
tokens requests (both [full] and [full/delta]) *)
let gen_new_id =
let i = ref 0 in
fun () ->
let x = !i in
incr i;
string_of_int x
;;
let compute_tokens doc =
let+ parsetree, source =
Document.Merlin.with_pipeline_exn ~name:"semantic highlighting" doc (fun p ->
Mpipeline.reader_parsetree p, Mpipeline.input_source p)
in
let module Fold =
Parsetree_fold (struct
let source = Msource.text source
end)
in
Fold.apply parsetree
;;
let compute_encoded_tokens doc =
let+ tokens = compute_tokens doc in
Tokens.encode tokens
;;
(** Contains implementation of a custom request that provides human-readable
tokens representation *)
module Debug = struct
let meth_request_full = "ocamllsp/textDocument/semanticTokens/full"
let on_request_full : params:Jsonrpc.Structured.t option -> State.t -> Json.t Fiber.t =
fun ~params state ->
Fiber.of_thunk (fun () ->
match params with
| None ->
Jsonrpc.Response.Error.raise
@@ Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.InvalidParams
~message:(meth_request_full ^ " expects an argument but didn't receive any")
()
| Some (`Assoc _ as json) | Some (`List _ as json) ->
let params = SemanticTokensParams.t_of_yojson json in
let store = state.store in
let uri = params.textDocument.uri in
let doc = Document_store.get store uri in
(match Document.kind doc with
| `Other ->
Jsonrpc.Response.Error.raise
@@ Jsonrpc.Response.Error.make
~code:Jsonrpc.Response.Error.Code.InvalidParams
~message:"expected a merlin document"
()
| `Merlin merlin ->
let+ tokens = compute_tokens merlin in
Tokens.yojson_of_t tokens))
;;
end
let on_request_full : State.t -> SemanticTokensParams.t -> SemanticTokens.t option Fiber.t
=
fun state params ->
Fiber.of_thunk (fun () ->
let store = state.store in
let uri = params.textDocument.uri in
let doc = Document_store.get store uri in
match Document.kind doc with
| `Other -> Fiber.return None
| `Merlin doc ->
let+ tokens = compute_encoded_tokens doc in
let resultId = gen_new_id () in
Document_store.update_semantic_tokens_cache store uri ~resultId ~tokens;
Some { SemanticTokens.resultId = Some resultId; data = tokens })
;;
(* TODO: refactor [find_diff] and write (inline?) tests *)
(* [find_diff] finds common prefix and common suffix and reports the rest as
array difference. This is not ideal but good enough. The idea comes from the
Rust Analyzer implementation of this function. *)
let find_diff ~(old : int array) ~(new_ : int array) : SemanticTokensEdit.t list =
let old_len = Array.length old in
let new_len = Array.length new_ in
let left_offset = Array.common_prefix_len ~equal:Int.equal old new_ in
if left_offset = old_len
then
if left_offset = new_len
then (* [old] and [new_] are simply equal *) []
else
(* [old] is prefix of [new_] *)
[ SemanticTokensEdit.create
~start:left_offset
~deleteCount:0
~data:(Array.sub new_ ~pos:left_offset ~len:(new_len - left_offset))
()
]
else if left_offset = new_len
then
(* [new_] is prefix of [old] *)
[ SemanticTokensEdit.create ~start:left_offset ~deleteCount:(old_len - left_offset) ()
]
else (
let common_suffix_len =
let old_noncommon = Array_view.make old ~pos:left_offset in
let new_noncommon = Array_view.make new_ ~pos:left_offset in
Array_view.common_suffix_len old_noncommon new_noncommon
in
let deleteCount =
let right_offset_old = old_len - common_suffix_len in
right_offset_old - left_offset
in
let data =
let right_offset_new = new_len - common_suffix_len in
Array.sub new_ ~pos:left_offset ~len:(right_offset_new - left_offset)
in
[ SemanticTokensEdit.create ~start:left_offset ~deleteCount ~data () ])
;;
let on_request_full_delta
: State.t
-> SemanticTokensDeltaParams.t
-> [ `SemanticTokens of SemanticTokens.t
| `SemanticTokensDelta of SemanticTokensDelta.t
]
option
Fiber.t
=
fun state params ->
Fiber.of_thunk (fun () ->
let store = state.store in
let uri = params.textDocument.uri in
let doc = Document_store.get store uri in
match Document.kind doc with
| `Other -> Fiber.return None
| `Merlin doc ->
let+ tokens = compute_encoded_tokens doc in
let resultId = gen_new_id () in
let cached_token_info =
Document_store.get_semantic_tokens_cache state.store params.textDocument.uri
in
Document_store.update_semantic_tokens_cache store uri ~resultId ~tokens;
(match cached_token_info with
| Some cached_v when String.equal cached_v.resultId params.previousResultId ->
let edits = find_diff ~old:cached_v.tokens ~new_:tokens in
Some
(`SemanticTokensDelta { SemanticTokensDelta.resultId = Some resultId; edits })
| Some _ | None ->
Some (`SemanticTokens { SemanticTokens.resultId = Some resultId; data = tokens })))
;;