Skip to content

Commit

Permalink
Merge f7398a4 into b8e0c0e
Browse files Browse the repository at this point in the history
  • Loading branch information
awilliambauer committed May 14, 2024
2 parents b8e0c0e + f7398a4 commit 017cc02
Show file tree
Hide file tree
Showing 3 changed files with 347 additions and 52 deletions.
362 changes: 314 additions & 48 deletions ocaml-lsp-server/src/document_symbol.ml
Original file line number Diff line number Diff line change
@@ -1,65 +1,331 @@
open Import
open Fiber.O

let outline_kind kind : SymbolKind.t =
match kind with
| `Value -> Function
| `Constructor -> Constructor
| `Label -> Property
| `Module -> Module
| `Modtype -> Module
| `Type -> String
| `Exn -> Constructor
| `Class -> Class
| `Method -> Method
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 rec symbol (item : Query_protocol.item) =
let children = List.map item.children ~f:symbol in
let range = Range.of_loc item.location in
let kind = outline_kind item.outline_kind in
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:item.outline_name
~name:decl.ptype_name.txt
~kind
?detail:item.outline_type
~deprecated:item.deprecated
~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
~selectionRange:(Range.of_loc ext.ptyext_path.loc)
~children
()

let rec symbol_info ?containerName uri (item : Query_protocol.item) =
let info =
let kind = outline_kind item.outline_kind in
let location = { Location.uri; range = Range.of_loc item.location } in
SymbolInformation.create
~name:item.outline_name
~kind
~deprecated:false
~location
?containerName
()
let value_document_symbol (value : Parsetree.value_description) =
let kind : SymbolKind.t =
match value.pval_type.ptyp_desc with
| Ptyp_arrow _ -> Function
| _ -> Variable
in
let children =
List.concat_map item.children ~f:(symbol_info uri ~containerName:info.name)
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_fun _ | Pexp_newtype _) -> Function
| Some _, _ -> Property
| _ -> Variable
in
[ DocumentSymbol.create
~name
~kind
~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
info :: children
!current

let symbols_of_outline uri outline =
List.concat_map ~f:(symbol_info uri) outline
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 doc ->
let+ outline = Document.Merlin.dispatch_exn doc Outline in
Some
(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 -> `DocumentSymbol (List.map outline ~f:symbol)
| false -> `SymbolInformation (symbols_of_outline uri outline))
| `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))
3 changes: 0 additions & 3 deletions ocaml-lsp-server/src/document_symbol.mli
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
open Import

val symbols_of_outline :
Uri.t -> Query_protocol.item list -> SymbolInformation.t list

val run :
ClientCapabilities.t
-> Document.t
Expand Down

0 comments on commit 017cc02

Please sign in to comment.