diff --git a/Changes.md b/Changes.md index ea72da7e..27888678 100644 --- a/Changes.md +++ b/Changes.md @@ -1,4 +1,5 @@ ## master +- Add support for doc strings when hovering on modules. ## Release 1.0.3 of rescript-vscode This [commit](https://github.com/rescript-lang/rescript-editor-support/commit/214d220d8573f9f0c8d54e623c163e01617bf124) is vendored in [rescript-vscode 1.0.3](https://github.com/rescript-lang/rescript-vscode/releases/tag/1.0.3). diff --git a/examples/example-project/src/ModuleWithDocComment.res b/examples/example-project/src/ModuleWithDocComment.res new file mode 100644 index 00000000..a09e2692 --- /dev/null +++ b/examples/example-project/src/ModuleWithDocComment.res @@ -0,0 +1,13 @@ +@@ocaml.doc("This comment is for the **toplevel** module.") + +@ocaml.doc("This comment is for the first **nested** module.") +module Nested = { + let x = "123" + + @ocaml.doc("This comment is for the inner **nested-again** module.") + module NestedAgain = { + let y = 123 + } +} + +module M = Nested.NestedAgain diff --git a/examples/example-project/src/ZZ.res b/examples/example-project/src/ZZ.res index b56eb60c..f06643b7 100644 --- a/examples/example-project/src/ZZ.res +++ b/examples/example-project/src/ZZ.res @@ -81,4 +81,6 @@ let testRecordFields = (gr: gr) => { @ocaml.doc("vr docstring") type vr = | V1 | V2 -let v1 = V1 \ No newline at end of file +let v1 = V1 + +module DoubleNested = ModuleWithDocComment.Nested.NestedAgain \ No newline at end of file diff --git a/src/rescript-editor-support/Hover.re b/src/rescript-editor-support/Hover.re index b446cd6b..b3c6c5e9 100644 --- a/src/rescript-editor-support/Hover.re +++ b/src/rescript-editor-support/Hover.re @@ -17,7 +17,11 @@ let codeBlock = code => { }; let showModuleTopLevel = - (~name, topLevel: list(SharedTypes.declared(SharedTypes.moduleItem))) => { + ( + ~docstring, + ~name, + topLevel: list(SharedTypes.declared(SharedTypes.moduleItem)), + ) => { let contents = topLevel |> List.map(item => @@ -32,19 +36,25 @@ let showModuleTopLevel = ) |> String.concat("\n"); let full = "module " ++ name ++ " = {" ++ "\n" ++ contents ++ "\n}"; - Some(codeBlock(full)); + let doc = + switch (docstring) { + | None => "" + | Some(s) => "\n" ++ s ++ "\n" + }; + Some(doc ++ codeBlock(full)); }; let showModule = ( + ~docstring, ~file: SharedTypes.file, ~name, declared: option(SharedTypes.declared(SharedTypes.moduleKind)), ) => { switch (declared) { - | None => showModuleTopLevel(~name, file.contents.topLevel) + | None => showModuleTopLevel(~docstring, ~name, file.contents.topLevel) | Some({item: Structure({topLevel})}) => - showModuleTopLevel(~name, topLevel) + showModuleTopLevel(~docstring, ~name, topLevel) | Some({item: Ident(_)}) => Some("Unable to resolve module reference") }; }; @@ -60,12 +70,12 @@ let newHover = (~rootUri, ~file: SharedTypes.file, ~getModule, loc) => { let%opt md = Hashtbl.find_opt(file.stamps.modules, stamp); let%opt (file, declared) = References.resolveModuleReference(~file, ~getModule, md); - let name = + let (name, docstring) = switch (declared) { - | Some(d) => d.name.txt - | None => file.moduleName + | Some(d) => (d.name.txt, d.docstring) + | None => (file.moduleName, file.contents.docstring) }; - showModule(~name, ~file, declared); + showModule(~docstring, ~name, ~file, declared); | LModule(GlobalReference(moduleName, path, tip)) => let%opt file = getModule(moduleName); let env = {Query.file, exported: file.contents.exported}; @@ -74,16 +84,21 @@ let newHover = (~rootUri, ~file: SharedTypes.file, ~getModule, loc) => { let%opt md = Hashtbl.find_opt(file.stamps.modules, stamp); let%opt (file, declared) = References.resolveModuleReference(~file, ~getModule, md); - let name = + let (name, docstring) = switch (declared) { - | Some(d) => d.name.txt - | None => file.moduleName + | Some(d) => (d.name.txt, d.docstring) + | None => (file.moduleName, file.contents.docstring) }; - showModule(~name, ~file, declared); + showModule(~docstring, ~name, ~file, declared); | LModule(NotFound) => None | TopLevelModule(name) => let%opt file = getModule(name); - showModule(~name=file.moduleName, ~file, None); + showModule( + ~docstring=file.contents.docstring, + ~name=file.moduleName, + ~file, + None, + ); | Typed(_, Definition(_, Field(_) | Constructor(_))) => None | Constant(t) => Some( diff --git a/src/rescript-editor-support/ProcessCmt.re b/src/rescript-editor-support/ProcessCmt.re index d4e750b7..631f2a50 100644 --- a/src/rescript-editor-support/ProcessCmt.re +++ b/src/rescript-editor-support/ProcessCmt.re @@ -203,7 +203,7 @@ and forSignatureType = (env, signature) => { signature, [], ); - {exported, topLevel}; + {docstring: None, exported, topLevel}; } and forModuleType = (env, moduleType) => switch (moduleType) { @@ -370,7 +370,14 @@ let forSignature = (~env, items) => { let exported = initExported(); let topLevel = items |> List.map(forSignatureItem(~env, ~exported)) |> List.flatten; - {exported, topLevel}; + let attributes = + switch (items) { + | [{sig_desc: Tsig_attribute(attribute)}, ..._] => [attribute] + | _ => [] + }; + let docstring = + ProcessAttributes.findDocAttribute(attributes) |?>> env.processDoc; + {docstring, exported, topLevel}; }; let forTreeModuleType = (~env, {mty_desc}) => @@ -554,7 +561,14 @@ and forStructure = (~env, items) => { items, [], ); - {exported, topLevel}; + let attributes = + switch (items) { + | [{str_desc: Tstr_attribute(attribute)}, ..._] => [attribute] + | _ => [] + }; + let docstring = + ProcessAttributes.findDocAttribute(attributes) |?>> env.processDoc; + {docstring, exported, topLevel}; }; let forCmt = diff --git a/src/rescript-editor-support/SharedTypes.re b/src/rescript-editor-support/SharedTypes.re index d90f292f..be08cc19 100644 --- a/src/rescript-editor-support/SharedTypes.re +++ b/src/rescript-editor-support/SharedTypes.re @@ -141,8 +141,9 @@ type moduleItem = | MType(Type.t, Types.rec_status) | Module(moduleKind) and moduleContents = { + docstring: option(string), exported, - mutable topLevel: list(declared(moduleItem)), + topLevel: list(declared(moduleItem)), } and moduleKind = | Ident(Path.t) @@ -176,6 +177,7 @@ let emptyFile = (moduleName, uri) => { stamps: initStamps(), moduleName, contents: { + docstring: None, exported: initExported(), topLevel: [], },