mirror of
https://github.com/c-cube/linol.git
synced 2025-12-09 20:55:43 -05:00
332 lines
11 KiB
OCaml
332 lines
11 KiB
OCaml
open Import
|
|
open Fiber.O
|
|
|
|
let core_type_to_string typ =
|
|
ignore (Format.flush_str_formatter ());
|
|
Pprintast.core_type Format.str_formatter typ;
|
|
Format.flush_str_formatter ()
|
|
|> String.map ~f:(function
|
|
| '\n' -> ' '
|
|
| c -> c)
|
|
;;
|
|
|
|
let pattern_to_string pat =
|
|
ignore (Format.flush_str_formatter ());
|
|
Pprintast.pattern Format.str_formatter pat;
|
|
Format.flush_str_formatter ()
|
|
;;
|
|
|
|
let type_document_symbol (decl : Parsetree.type_declaration) : DocumentSymbol.t =
|
|
let kind : SymbolKind.t =
|
|
match decl.ptype_kind with
|
|
| Ptype_variant _ -> Enum
|
|
| _ -> TypeParameter
|
|
in
|
|
let children =
|
|
match decl.ptype_kind with
|
|
| Ptype_variant decls ->
|
|
List.map decls ~f:(fun (decl : Parsetree.constructor_declaration) ->
|
|
DocumentSymbol.create
|
|
~kind:EnumMember
|
|
~name:decl.pcd_name.txt
|
|
~range:(Range.of_loc decl.pcd_loc)
|
|
~selectionRange:(Range.of_loc decl.pcd_name.loc)
|
|
())
|
|
| Ptype_record fields ->
|
|
List.map fields ~f:(fun (field : Parsetree.label_declaration) ->
|
|
DocumentSymbol.create
|
|
~kind:Field
|
|
~name:field.pld_name.txt
|
|
~detail:(core_type_to_string field.pld_type)
|
|
~range:(Range.of_loc field.pld_loc)
|
|
~selectionRange:(Range.of_loc field.pld_name.loc)
|
|
())
|
|
| _ -> []
|
|
in
|
|
DocumentSymbol.create
|
|
~name:decl.ptype_name.txt
|
|
~kind
|
|
~range:(Range.of_loc decl.ptype_loc)
|
|
~selectionRange:(Range.of_loc decl.ptype_loc)
|
|
~children
|
|
()
|
|
;;
|
|
|
|
let longident_to_string lident = String.concat ~sep:"." (Longident.flatten lident)
|
|
|
|
let type_ext_document_symbol (ext : Parsetree.type_extension) : DocumentSymbol.t =
|
|
let children =
|
|
List.map ext.ptyext_constructors ~f:(fun (ext : Parsetree.extension_constructor) ->
|
|
DocumentSymbol.create
|
|
~name:ext.pext_name.txt
|
|
~kind:EnumMember
|
|
~range:(Range.of_loc ext.pext_loc)
|
|
~selectionRange:(Range.of_loc ext.pext_loc)
|
|
())
|
|
in
|
|
let range =
|
|
List.fold_left
|
|
children
|
|
~init:(Range.of_loc ext.ptyext_path.loc)
|
|
~f:(fun (range : Range.t) (child : DocumentSymbol.t) ->
|
|
let start =
|
|
match Position.compare range.start child.range.start with
|
|
| Lt | Eq -> range.start
|
|
| Gt -> child.range.start
|
|
in
|
|
let end_ =
|
|
match Position.compare range.end_ child.range.end_ with
|
|
| Lt | Eq -> child.range.end_
|
|
| Gt -> range.end_
|
|
in
|
|
Range.create ~start ~end_)
|
|
in
|
|
DocumentSymbol.create
|
|
~name:(longident_to_string ext.ptyext_path.txt)
|
|
~kind:Enum
|
|
~range
|
|
~selectionRange:(Range.of_loc ext.ptyext_path.loc)
|
|
~children
|
|
()
|
|
;;
|
|
|
|
let value_document_symbol (value : Parsetree.value_description) =
|
|
let kind : SymbolKind.t =
|
|
match value.pval_type.ptyp_desc with
|
|
| Ptyp_arrow _ -> Function
|
|
| _ -> Variable
|
|
in
|
|
DocumentSymbol.create
|
|
~kind
|
|
~name:value.pval_name.txt
|
|
~detail:(core_type_to_string value.pval_type)
|
|
~range:(Range.of_loc value.pval_loc)
|
|
~selectionRange:(Range.of_loc value.pval_name.loc)
|
|
()
|
|
;;
|
|
|
|
let module_decl_document_symbol (pmod : Parsetree.module_declaration) ~children =
|
|
DocumentSymbol.create
|
|
~name:(Option.value pmod.pmd_name.txt ~default:"_")
|
|
~kind:Module
|
|
~range:(Range.of_loc pmod.pmd_loc)
|
|
~selectionRange:(Range.of_loc pmod.pmd_name.loc)
|
|
~children
|
|
()
|
|
;;
|
|
|
|
let module_type_decl_symbol (decl : Parsetree.module_type_declaration) ~children =
|
|
DocumentSymbol.create
|
|
~name:decl.pmtd_name.txt
|
|
~kind:Interface
|
|
~range:(Range.of_loc decl.pmtd_loc)
|
|
~selectionRange:(Range.of_loc decl.pmtd_name.loc)
|
|
~children
|
|
()
|
|
;;
|
|
|
|
let module_binding_document_symbol (pmod : Parsetree.module_binding) ~children =
|
|
DocumentSymbol.create
|
|
~name:(Option.value pmod.pmb_name.txt ~default:"_")
|
|
~kind:Module
|
|
~range:(Range.of_loc pmod.pmb_loc)
|
|
~selectionRange:(Range.of_loc pmod.pmb_name.loc)
|
|
~children
|
|
()
|
|
;;
|
|
|
|
let binding_document_symbol
|
|
(binding : Parsetree.value_binding)
|
|
~ppx
|
|
~is_top_level
|
|
~children
|
|
=
|
|
let variables_in_pattern (pattern : Parsetree.pattern) =
|
|
let symbols = ref [] in
|
|
let pat (iterator : Ast_iterator.iterator) (pattern : Parsetree.pattern) =
|
|
match pattern.ppat_desc with
|
|
| Ppat_var name ->
|
|
let symbol =
|
|
DocumentSymbol.create
|
|
~kind:Variable
|
|
~name:name.txt
|
|
~range:(Range.of_loc name.loc)
|
|
~selectionRange:(Range.of_loc name.loc)
|
|
()
|
|
in
|
|
symbols := symbol :: !symbols
|
|
| _ -> Ast_iterator.default_iterator.pat iterator pattern
|
|
in
|
|
let iterator = { Ast_iterator.default_iterator with pat } in
|
|
iterator.pat iterator pattern;
|
|
List.rev !symbols
|
|
in
|
|
let name =
|
|
match binding.pvb_pat.ppat_desc with
|
|
| Ppat_var name | Ppat_extension (_, PPat ({ ppat_desc = Ppat_var name; _ }, _)) ->
|
|
`Parent name.txt
|
|
| _ ->
|
|
(match is_top_level, children with
|
|
| true, [] | false, _ -> `Variables (variables_in_pattern binding.pvb_pat)
|
|
| true, _ :: _ ->
|
|
(match ppx with
|
|
| Some ppx -> `Parent (ppx ^ ": " ^ pattern_to_string binding.pvb_pat)
|
|
| None -> `Parent (pattern_to_string binding.pvb_pat)))
|
|
in
|
|
match name with
|
|
| `Parent name ->
|
|
let kind : SymbolKind.t =
|
|
match ppx, binding.pvb_expr.pexp_desc with
|
|
| None, (Pexp_function _ | Pexp_newtype _) -> Function
|
|
| Some _, _ -> Property
|
|
| _ -> Variable
|
|
in
|
|
let detail =
|
|
None
|
|
(* CR-rgrinberg: Re-enable in 5.0: {[
|
|
Option.map binding.pvb_constraint ~f:(function
|
|
| Pvc_constraint { typ; _ } -> core_type_to_string typ
|
|
| Pvc_coercion { coercion; _ } -> core_type_to_string coercion)
|
|
]}
|
|
*)
|
|
in
|
|
[ DocumentSymbol.create
|
|
~name
|
|
~kind
|
|
?detail
|
|
~range:(Range.of_loc binding.pvb_loc)
|
|
~selectionRange:(Range.of_loc binding.pvb_pat.ppat_loc)
|
|
~children
|
|
()
|
|
]
|
|
| `Variables symbols -> symbols @ children
|
|
;;
|
|
|
|
let symbols_from_parsetree parsetree =
|
|
let current = ref [] in
|
|
let descend
|
|
(iter : unit -> unit)
|
|
(get_current_symbol : children:DocumentSymbol.t list -> DocumentSymbol.t)
|
|
=
|
|
let outer = !current in
|
|
current := [];
|
|
iter ();
|
|
current := outer @ [ get_current_symbol ~children:!current ]
|
|
in
|
|
let signature_item (iterator : Ast_iterator.iterator) (item : Parsetree.signature_item) =
|
|
match item.psig_desc with
|
|
| Psig_type (_, decls) -> current := !current @ List.map decls ~f:type_document_symbol
|
|
| Psig_typext ext -> current := !current @ [ type_ext_document_symbol ext ]
|
|
| Psig_value value -> current := !current @ [ value_document_symbol value ]
|
|
| Psig_module pmd ->
|
|
descend
|
|
(fun () -> Ast_iterator.default_iterator.signature_item iterator item)
|
|
(module_decl_document_symbol pmd)
|
|
| Psig_recmodule modules ->
|
|
List.iter modules ~f:(iterator.module_declaration iterator)
|
|
| Psig_modtype decl ->
|
|
descend
|
|
(fun () -> Ast_iterator.default_iterator.module_type_declaration iterator decl)
|
|
(module_type_decl_symbol decl)
|
|
| _ -> Ast_iterator.default_iterator.signature_item iterator item
|
|
in
|
|
let rec structure_item
|
|
~ppx
|
|
(iterator : Ast_iterator.iterator)
|
|
(item : Parsetree.structure_item)
|
|
=
|
|
match item.pstr_desc with
|
|
| Pstr_type (_, decls) -> current := !current @ List.map decls ~f:type_document_symbol
|
|
| Pstr_typext ext -> current := !current @ [ type_ext_document_symbol ext ]
|
|
| Pstr_module pmod ->
|
|
descend
|
|
(fun () -> iterator.module_expr iterator pmod.pmb_expr)
|
|
(module_binding_document_symbol pmod)
|
|
| Pstr_recmodule modules -> List.iter modules ~f:(iterator.module_binding iterator)
|
|
| Pstr_modtype decl ->
|
|
descend
|
|
(fun () -> Ast_iterator.default_iterator.module_type_declaration iterator decl)
|
|
(module_type_decl_symbol decl)
|
|
| Pstr_value (_, bindings) ->
|
|
let outer = !current in
|
|
current
|
|
:= outer
|
|
@ List.concat_map bindings ~f:(fun (binding : Parsetree.value_binding) ->
|
|
current := [];
|
|
iterator.expr iterator binding.pvb_expr;
|
|
binding_document_symbol binding ~ppx ~is_top_level:true ~children:!current)
|
|
| Pstr_extension ((name, PStr items), _) ->
|
|
List.iter items ~f:(fun item -> structure_item ~ppx:(Some name.txt) iterator item)
|
|
| _ -> Ast_iterator.default_iterator.structure_item iterator item
|
|
in
|
|
let expr (iterator : Ast_iterator.iterator) (item : Parsetree.expression) =
|
|
match item.pexp_desc with
|
|
| Pexp_let (_, bindings, inner) ->
|
|
let outer = !current in
|
|
let bindings =
|
|
List.concat_map bindings ~f:(fun (binding : Parsetree.value_binding) ->
|
|
current := [];
|
|
iterator.expr iterator binding.pvb_expr;
|
|
binding_document_symbol binding ~ppx:None ~is_top_level:false ~children:!current)
|
|
in
|
|
current := outer @ bindings;
|
|
iterator.expr iterator inner
|
|
| _ -> Ast_iterator.default_iterator.expr iterator item
|
|
in
|
|
let iterator =
|
|
{ Ast_iterator.default_iterator with
|
|
signature_item
|
|
; structure_item = structure_item ~ppx:None
|
|
; expr
|
|
}
|
|
in
|
|
let () =
|
|
match parsetree with
|
|
| `Interface signature -> iterator.signature iterator signature
|
|
| `Implementation structure -> iterator.structure iterator structure
|
|
in
|
|
!current
|
|
;;
|
|
|
|
let rec flatten_document_symbols ~uri ~container_name (symbols : DocumentSymbol.t list) =
|
|
List.concat_map symbols ~f:(fun symbol ->
|
|
let symbol_information =
|
|
SymbolInformation.create
|
|
?containerName:container_name
|
|
~kind:symbol.kind
|
|
~location:{ range = symbol.range; uri }
|
|
~name:symbol.name
|
|
()
|
|
in
|
|
let children =
|
|
flatten_document_symbols
|
|
~uri
|
|
~container_name:(Some symbol.name)
|
|
(Option.value symbol.children ~default:[])
|
|
in
|
|
symbol_information :: children)
|
|
;;
|
|
|
|
let run (client_capabilities : ClientCapabilities.t) doc uri =
|
|
match Document.kind doc with
|
|
| `Other -> Fiber.return None
|
|
| `Merlin _ ->
|
|
let+ symbols =
|
|
Document.Merlin.with_pipeline_exn
|
|
~name:"document-symbols"
|
|
(Document.merlin_exn doc)
|
|
(fun pipeline -> Mpipeline.reader_parsetree pipeline |> symbols_from_parsetree)
|
|
in
|
|
(match
|
|
Option.value
|
|
~default:false
|
|
(let open Option.O in
|
|
let* textDocument = client_capabilities.textDocument in
|
|
let* ds = textDocument.documentSymbol in
|
|
ds.hierarchicalDocumentSymbolSupport)
|
|
with
|
|
| true -> Some (`DocumentSymbol symbols)
|
|
| false ->
|
|
let flattened = flatten_document_symbols ~uri ~container_name:None symbols in
|
|
Some (`SymbolInformation flattened))
|
|
;;
|