diff --git a/analysis/src/Cli.ml b/analysis/src/Cli.ml index aba5b09ec..807650c36 100644 --- a/analysis/src/Cli.ml +++ b/analysis/src/Cli.ml @@ -30,7 +30,12 @@ Options: references: get references to item in Foo.res at line 10 column 2: - ./run.exe references src/Foo.res 10 2|} + ./run.exe references src/Foo.res 10 2 + + documentSymbol: get all symbols in Foo.res: + + ./run.exe documentSymbol src/Foo.res + |} let main () = match Array.to_list Sys.argv with @@ -45,6 +50,7 @@ let main () = | [_; "references"; path; line; col] -> Commands.references ~path ~line:(int_of_string line) ~col:(int_of_string col) + | [_; "documentSymbol"; path] -> Commands.documentSymbol ~path | _ :: "dump" :: files -> Commands.dump files | [_; "test"; path] -> Commands.test ~path | args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index df796254a..6cb667756 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -170,6 +170,40 @@ let references ~path ~line ~col = in print_endline result +let documentSymbol ~path = + let uri = Uri2.fromLocalPath path in + match ProcessCmt.fileForUri uri with + | Error _ -> print_endline Protocol.null + | Ok (file, _extra) -> + let open SharedTypes in + let rec getItems {topLevel} = + let fn {name = {txt}; extentLoc; item} = + let item, siblings = + match item with + | MValue v -> (v |> SharedTypes.variableKind, []) + | MType (t, _) -> (t.decl |> SharedTypes.declarationKind, []) + | Module (Structure contents) -> (Module, getItems contents) + | Module (Ident _) -> (Module, []) + in + if extentLoc.loc_ghost then siblings + else (txt, extentLoc, item) :: siblings + in + let x = topLevel |> List.map fn |> List.concat in + x + in + let allSymbols = + getItems file.contents + |> List.map (fun (name, loc, kind) -> + Protocol.stringifyDocumentSymbolItem + { + name; + location = + {uri = Uri2.toString uri; range = Utils.cmtLocToRange loc}; + kind = SharedTypes.symbolKind kind; + }) + in + print_endline ("[\n" ^ (allSymbols |> String.concat ",\n") ^ "\n]") + let test ~path = Uri2.stripPath := true; match Files.readFile path with @@ -202,6 +236,9 @@ let test ~path = ("References " ^ path ^ " " ^ string_of_int line ^ ":" ^ string_of_int col); references ~path ~line ~col + | "doc" -> + print_endline ("DocumentSymbol " ^ path); + documentSymbol ~path | "com" -> print_endline ("Complete " ^ path ^ " " ^ string_of_int line ^ ":" diff --git a/analysis/src/Protocol.ml b/analysis/src/Protocol.ml index 8cd8e2bc1..96338a726 100644 --- a/analysis/src/Protocol.ml +++ b/analysis/src/Protocol.ml @@ -1,72 +1,67 @@ -type position = { - line: int; - character: int; -} +type position = {line : int; character : int} -type range = { - start: position; - end_: position; -} +type range = {start : position; end_ : position} -type markupContent = { - kind: string; - value: string; -} +type markupContent = {kind : string; value : string} type completionItem = { - label: string; - kind: int; - tags: int list; - detail: string; - documentation: markupContent option; + label : string; + kind : int; + tags : int list; + detail : string; + documentation : markupContent option; } -type hover = { - contents: string; -} +type hover = {contents : string} -type location = { - uri: string; - range: range; -} +type location = {uri : string; range : range} + +type documentSymbolItem = {name : string; kind : int; location : location} let null = "null" -let array l = "[" ^ (String.concat ", " l) ^ "]" +let array l = "[" ^ String.concat ", " l ^ "]" let stringifyPosition p = - Printf.sprintf {|{"line": %i, "character": %i}|} p.line p.character + Printf.sprintf {|{"line": %i, "character": %i}|} p.line p.character let stringifyRange r = - Printf.sprintf {|{"start": %s, "end": %s}|} + Printf.sprintf {|{"start": %s, "end": %s}|} (stringifyPosition r.start) (stringifyPosition r.end_) -let stringifyMarkupContent (m: markupContent) = - Printf.sprintf {|{"kind": "%s", "value": "%s"}|} - m.kind (Json.escape m.value) +let stringifyMarkupContent (m : markupContent) = + Printf.sprintf {|{"kind": "%s", "value": "%s"}|} m.kind (Json.escape m.value) let stringifyCompletionItem c = - Printf.sprintf {|{ + Printf.sprintf + {|{ "label": "%s", "kind": %i, "tags": %s, "detail": "%s", "documentation": %s }|} - (Json.escape c.label) - c.kind - (c.tags |> List.map string_of_int |> array) - (Json.escape c.detail) - (match c.documentation with - | None -> null - | Some doc -> stringifyMarkupContent doc) + (Json.escape c.label) c.kind + (c.tags |> List.map string_of_int |> array) + (Json.escape c.detail) + (match c.documentation with + | None -> null + | Some doc -> stringifyMarkupContent doc) let stringifyHover h = - Printf.sprintf {|{"contents": "%s"}|} - (Json.escape h.contents) + Printf.sprintf {|{"contents": "%s"}|} (Json.escape h.contents) let stringifyLocation h = - Printf.sprintf {|{"uri": "%s", "range": %s}|} - (Json.escape h.uri) - (stringifyRange h.range) + Printf.sprintf {|{"uri": "%s", "range": %s}|} (Json.escape h.uri) + (stringifyRange h.range) + +let stringifyDocumentSymbolItem i = + Printf.sprintf + {|{ + "name": "%s", + "kind": %i, + "location": %s, + }|} + (Json.escape i.name) i.kind + (stringifyLocation i.location) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 9218d959b..860889284 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -233,5 +233,49 @@ let locationToString ({Location.loc_start; loc_end}, loc) = Printf.sprintf "%d:%d-%d:%d %s" pos1.line pos1.character pos2.line pos2.character (locToString loc) +type kinds = + | Module + | Enum + | Interface + | Function + | Variable + | Array + | Object + | Null + | EnumMember + | TypeParameter + +let rec variableKind t = + match t.Types.desc with + | Tlink t -> variableKind t + | Tsubst t -> variableKind t + | Tarrow _ -> Function + | Ttuple _ -> Array + | Tconstr _ -> Variable + | Tobject _ -> Object + | Tnil -> Null + | Tvariant _ -> EnumMember + | Tpoly _ -> EnumMember + | Tpackage _ -> Module + | _ -> Variable + +let symbolKind = function + | Module -> 2 + | Enum -> 10 + | Interface -> 11 + | Function -> 12 + | Variable -> 13 + | Array -> 18 + | Object -> 19 + | Null -> 21 + | EnumMember -> 22 + | TypeParameter -> 26 + +let declarationKind t = + match t.Types.type_kind with + | Type_open | Type_abstract -> TypeParameter + | Type_record _ -> Interface + | Type_variant _ -> Enum + (* for debugging *) let _ = locationToString diff --git a/analysis/tests/src/Complete.res b/analysis/tests/src/Complete.res index 8b3bd744f..7b03e4705 100644 --- a/analysis/tests/src/Complete.res +++ b/analysis/tests/src/Complete.res @@ -54,4 +54,4 @@ let zzz = 11 //^com let comp = ", "@", "~"] }, }, }; @@ -394,6 +396,20 @@ function onMessage(msg: m.Message) { // error: code and message set in case an exception happens during the definition request. }; send(definitionResponse); + } else if (msg.method === p.DocumentSymbolRequest.method) { + // https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol + let result: + | SymbolInformation[] + | null = utils.runAnalysisAfterSanityCheck(msg, (filePath) => [ + "documentSymbol", + filePath, + ]); + let definitionResponse: m.ResponseMessage = { + jsonrpc: c.jsonrpcVersion, + id: msg.id, + result, + }; + send(definitionResponse); } else if (msg.method === p.CompletionRequest.method) { let code = getOpenedFileContent(msg.params.textDocument.uri); let tmpname = utils.createFileInTempDir();