[tintd] Improve signature help

Use the 'documentation' field of SignatureInformation to hold the
template constraints. Improves the visual quality of this popup in
VSCode.

Bug: tint:2127
Change-Id: I6b523e9ef92809306eb09278ce64ef018cca29f9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/183001
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/wgsl/ls/signature_help.cc b/src/tint/lang/wgsl/ls/signature_help.cc
index 6e4f422..d0f1850 100644
--- a/src/tint/lang/wgsl/ls/signature_help.cc
+++ b/src/tint/lang/wgsl/ls/signature_help.cc
@@ -25,6 +25,8 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+#include <sstream>
+#include "src/tint/lang/wgsl/diagnostic_severity.h"
 #include "src/tint/lang/wgsl/ls/server.h"
 
 #include "langsvr/lsp/comparators.h"
@@ -34,6 +36,7 @@
 #include "src/tint/lang/wgsl/sem/call.h"
 #include "src/tint/utils/rtti/switch.h"
 #include "src/tint/utils/text/string_stream.h"
+#include "src/tint/utils/text/text_style.h"
 
 namespace lsp = langsvr::lsp;
 
@@ -98,72 +101,67 @@
 }
 
 /// PrintOverload() emits a description of the intrinsic overload @p overload of the function with
-/// name @p intrinsic_name to @p ss.
-void PrintOverload(StyledText& ss,
+/// name @p intrinsic_name to @p name and @p description.
+void PrintOverload(std::string& name,
+                   StyledText& description,
                    core::intrinsic::Context& context,
                    const core::intrinsic::OverloadInfo& overload,
                    std::string_view intrinsic_name) {
-    // Restore old style before returning.
-    auto prev_style = ss.Style();
-    TINT_DEFER(ss << prev_style);
-
     core::intrinsic::TemplateState templates;
 
     auto earliest_eval_stage = core::EvaluationStage::kConstant;
 
-    ss << style::Code << style::Function(intrinsic_name);
-
+    StyledText name_st;
+    name_st << style::Code << intrinsic_name;
     if (overload.num_explicit_templates > 0) {
-        ss << "<";
+        name_st << "<";
         for (size_t i = 0; i < overload.num_explicit_templates; i++) {
             const auto& tmpl = context.data[overload.templates + i];
             if (i > 0) {
-                ss << ", ";
+                name_st << ", ";
             }
-            ss << style::Type(tmpl.name) << " ";
+            name_st << style::Type(tmpl.name) << " ";
         }
-        ss << ">";
+        name_st << ">";
     }
 
-    ss << "(";
+    name_st << "(";
     for (size_t i = 0; i < overload.num_parameters; i++) {
         const auto& parameter = context.data[overload.parameters + i];
         auto* matcher_indices = context.data[parameter.matcher_indices];
 
         if (i > 0) {
-            ss << ", ";
+            name_st << ", ";
         }
 
         if (parameter.usage != core::ParameterUsage::kNone) {
-            ss << style::Variable(parameter.usage, ": ");
+            name_st << style::Variable(parameter.usage, ": ");
         }
-        context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(ss);
+        context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(name_st);
     }
-    ss << ")";
+    name_st << ")";
     if (overload.return_matcher_indices.IsValid()) {
-        ss << " -> ";
+        name_st << " -> ";
         auto* matcher_indices = context.data[overload.return_matcher_indices];
-        context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(ss);
+        context.Match(templates, overload, matcher_indices, earliest_eval_stage).PrintType(name_st);
     }
 
-    bool first = true;
-    auto separator = [&] {
-        ss << style::Plain(first ? " where:\n     " : "\n     ");
-        first = false;
-    };
+    {  // Like name_st.Plain(), but no code quotes.
+        StringStream ss;
+        name_st.Walk([&](std::string_view text, TextStyle) { ss << text; });
+        name = ss.str();
+    }
 
     for (size_t i = 0; i < overload.num_templates; i++) {
         auto& tmpl = context.data[overload.templates + i];
         if (auto* matcher_indices = context.data[tmpl.matcher_indices]) {
-            separator();
-
-            ss << style::Type(tmpl.name) << style::Plain(" is ");
+            description << "\n" << style::Type(tmpl.name) << style::Plain(" is ");
             if (tmpl.kind == core::intrinsic::TemplateInfo::Kind::kType) {
                 context.Match(templates, overload, matcher_indices, earliest_eval_stage)
-                    .PrintType(ss);
+                    .PrintType(description);
             } else {
                 context.Match(templates, overload, matcher_indices, earliest_eval_stage)
-                    .PrintNum(ss);
+                    .PrintNum(description);
             }
         }
     }
@@ -193,7 +191,6 @@
                auto& data = wgsl::intrinsic::Dialect::kData;
                auto& builtins = data.builtins;
                auto& intrinsic_info = builtins[static_cast<size_t>(target->Fn())];
-               std::string name{wgsl::str(target->Fn())};
 
                for (size_t i = 0; i < intrinsic_info.num_overloads; i++) {
                    auto& overload = data[intrinsic_info.overloads + i];
@@ -203,13 +200,15 @@
                    auto type_mgr = core::type::Manager::Wrap(program.Types());
                    auto symbols = SymbolTable::Wrap(program.Symbols());
 
-                   StyledText ss;
+                   StyledText description;
                    core::intrinsic::Context ctx{data, type_mgr, symbols};
-                   PrintOverload(ss, ctx, overload, name);
+                   std::string name;
+                   PrintOverload(name, description, ctx, overload, wgsl::str(target->Fn()));
 
                    lsp::SignatureInformation sig;
                    sig.parameters = params;
-                   sig.label = ss.Plain();
+                   sig.label = name;
+                   sig.documentation = Conv(description);
                    help.signatures.push_back(sig);
 
                    if (&overload == &target->Overload()) {
diff --git a/src/tint/lang/wgsl/ls/signature_help_test.cc b/src/tint/lang/wgsl/ls/signature_help_test.cc
index 31fcaba..f1f67b6 100644
--- a/src/tint/lang/wgsl/ls/signature_help_test.cc
+++ b/src/tint/lang/wgsl/ls/signature_help_test.cc
@@ -55,9 +55,15 @@
             /* documentation */ {},
         });
 
+        lsp::MarkupContent documentation;
+        documentation.kind = lsp::MarkupKind::kMarkdown;
+        documentation.value =
+            R"(
+`T` is `abstract-float`, `abstract-int`, `f32`, `i32`, `u32` or `f16`)";
+
         lsp::SignatureInformation sig{};
-        sig.label = R"('max(T, T) -> T' where:
-     'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16')";
+        sig.label = "max(T, T) -> T";
+        sig.documentation = documentation;
         sig.parameters = std::move(parameters);
 
         out.push_back(std::move(sig));
@@ -74,9 +80,15 @@
             /* documentation */ {},
         });
 
+        lsp::MarkupContent documentation;
+        documentation.kind = lsp::MarkupKind::kMarkdown;
+        documentation.value =
+            R"(
+`T` is `abstract-float`, `abstract-int`, `f32`, `i32`, `u32` or `f16`)";
+
         lsp::SignatureInformation sig{};
-        sig.label = R"('max(vecN<T>, vecN<T>) -> vecN<T>' where:
-     'T' is 'abstract-float', 'abstract-int', 'f32', 'i32', 'u32' or 'f16')";
+        sig.label = R"(max(vecN<T>, vecN<T>) -> vecN<T>)";
+        sig.documentation = documentation;
         sig.parameters = std::move(parameters);
 
         out.push_back(std::move(sig));