[tint][wgsl] Stub 'ls' the language server package for WGSL.

Doesn't do anything (yet), except for handling the shutdown request.

Bug: tint:2127
Change-Id: If946e99801db40220ec36a1123702bca89341c07
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/174881
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/tintd/BUILD.bazel b/src/tint/cmd/tintd/BUILD.bazel
index 0416234..3139cdf 100644
--- a/src/tint/cmd/tintd/BUILD.bazel
+++ b/src/tint/cmd/tintd/BUILD.bazel
@@ -42,7 +42,27 @@
     "main.cc",
   ],
   deps = [
-  ],
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+  ] + select({
+    ":tint_build_tintd": [
+      
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_tintd_and_tint_build_wgsl_reader": [
+      "//src/tint/lang/wgsl/ls",
+    ],
+    "//conditions:default": [],
+  }),
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
diff --git a/src/tint/cmd/tintd/BUILD.cmake b/src/tint/cmd/tintd/BUILD.cmake
index 3b02917..f950a1f 100644
--- a/src/tint/cmd/tintd/BUILD.cmake
+++ b/src/tint/cmd/tintd/BUILD.cmake
@@ -44,6 +44,31 @@
   cmd/tintd/main.cc
 )
 
+tint_target_add_dependencies(tint_cmd_tintd_cmd cmd
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+if(TINT_BUILD_TINTD)
+  tint_target_add_external_dependencies(tint_cmd_tintd_cmd cmd
+    "langsvr"
+  )
+endif(TINT_BUILD_TINTD)
+
+if(TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER)
+  tint_target_add_dependencies(tint_cmd_tintd_cmd cmd
+    tint_lang_wgsl_ls
+  )
+endif(TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER)
+
 tint_target_set_output_name(tint_cmd_tintd_cmd cmd "tintd")
 
 endif(TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER)
\ No newline at end of file
diff --git a/src/tint/cmd/tintd/BUILD.gn b/src/tint/cmd/tintd/BUILD.gn
index f656d63..af1da9e 100644
--- a/src/tint/cmd/tintd/BUILD.gn
+++ b/src/tint/cmd/tintd/BUILD.gn
@@ -41,6 +41,25 @@
   tint_executable("tintd") {
     output_name = "tintd"
     sources = [ "main.cc" ]
-    deps = []
+    deps = [
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+
+    if (tint_build_tintd) {
+      deps += [ "${tint_src_dir}:langsvr" ]
+    }
+
+    if (tint_build_tintd && tint_build_wgsl_reader) {
+      deps += [ "${tint_src_dir}/lang/wgsl/ls" ]
+    }
   }
 }
diff --git a/src/tint/cmd/tintd/main.cc b/src/tint/cmd/tintd/main.cc
index b8e03bf..cf5ea15 100644
--- a/src/tint/cmd/tintd/main.cc
+++ b/src/tint/cmd/tintd/main.cc
@@ -25,7 +25,51 @@
 // 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.
 
+#if TINT_BUILD_IS_WIN
+#include <fcntl.h>  // _O_BINARY
+#include <io.h>     // _setmode
+#endif              // TINT_BUILD_IS_WIN
+
+#include <fstream>
+#include <iostream>
+
+#include "src/tint/lang/wgsl/ls/serve.h"
+
+namespace {
+
+class StdinStream : public langsvr::Reader {
+  public:
+    /// @copydoc langsvr::Reader
+    size_t Read(std::byte* out, size_t count) override { return fread(out, 1, count, stdin); }
+};
+
+class StdoutStream : public langsvr::Writer {
+  public:
+    /// @copydoc langsvr::Reader
+    langsvr::Result<langsvr::SuccessType> Write(const std::byte* in, size_t count) override {
+        fwrite(in, 1, count, stdout);
+        fflush(stdout);
+        return langsvr::Success;
+    }
+};
+
+}  // namespace
+
 int main() {
-    // TODO(crbug.com/tint/2127): Stub.
+#if TINT_BUILD_IS_WIN
+    // Change stdin & stdout from text mode to binary mode.
+    // This ensures sequences of \r\n are not changed to \n.
+    _setmode(_fileno(stdin), _O_BINARY);
+    _setmode(_fileno(stdout), _O_BINARY);
+#endif  // TINT_BUILD_IS_WIN
+
+    StdoutStream stdout_stream;
+    StdinStream stdin_stream;
+
+    if (auto res = tint::wgsl::ls::Serve(stdin_stream, stdout_stream); res != tint::Success) {
+        std::cerr << res.Failure();
+        return 1;
+    }
+
     return 0;
 }
diff --git a/src/tint/cmd/tintd/vscode/extension.js b/src/tint/cmd/tintd/vscode/extension.js
new file mode 100644
index 0000000..c98c364
--- /dev/null
+++ b/src/tint/cmd/tintd/vscode/extension.js
@@ -0,0 +1,75 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// 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.
+
+var path = require('path');
+var vscode = require('vscode');
+var langClient = require('vscode-languageclient');
+
+var LanguageClient = langClient.LanguageClient;
+
+// activate() is called when the extension is activated
+function activate(context) {
+    let serverModule = path.join(context.extensionPath, 'tintd');
+    let debugOptions = {};
+
+    // If the extension is launched in debug mode then the debug server options are used
+    // Otherwise the run options are used
+    let serverOptions = {
+        run: { command: serverModule, transport: langClient.stdio },
+        debug: { command: serverModule, transport: langClient.stdio, options: debugOptions }
+    }
+
+    // Options to control the language client
+    let clientOptions = {
+        documentSelector: ['wgsl'],
+        synchronize: {
+            // Synchronize the setting section 'wgsl' to the server
+            configurationSection: 'wgsl',
+            // Notify the server about file changes to .wgsl files contained in the workspace
+            fileEvents: vscode.workspace.createFileSystemWatcher('**/*.wgsl')
+        }
+    }
+
+    // Create the language client and start the client.
+    let disposable = new LanguageClient('wgsl', serverOptions, clientOptions).start();
+
+    // Push the disposable to the context's subscriptions so that the
+    // client can be deactivated on extension deactivation
+    context.subscriptions.push(disposable);
+
+    // Set the language configuration here instead of a language configuration
+    // file to work around https://github.com/microsoft/vscode/issues/42649.
+    vscode.languages.setLanguageConfiguration("wgsl", {
+        comments: { "lineComment": "//" },
+    });
+}
+exports.activate = activate;
+
+// this method is called when your extension is deactivated
+function deactivate() {
+}
+exports.deactivate = deactivate;
diff --git a/src/tint/cmd/tintd/vscode/package.json b/src/tint/cmd/tintd/vscode/package.json
new file mode 100644
index 0000000..4f5e66a
--- /dev/null
+++ b/src/tint/cmd/tintd/vscode/package.json
@@ -0,0 +1,39 @@
+{
+    "name": "tintd",
+    "description": "Language support for WGSL",
+    "author": "Google",
+    "license": "BSD-3-Clause",
+    "version": "0.0.1",
+    "private": true,
+    "publisher": "Google",
+    "engines": {
+        "vscode": "^1.67.0"
+    },
+    "categories": [
+        "Programming Languages"
+    ],
+    "contributes": {
+        "languages": [
+            {
+                "id": "wgsl",
+                "extensions": [
+                    "wgsl"
+                ]
+            }
+        ],
+        "grammars": [
+            {
+                "language": "wgsl",
+                "scopeName": "source.wgsl",
+                "path": "wgsl.tmLanguage.json"
+            }
+        ]
+    },
+    "dependencies": {
+        "vscode-languageclient": "^8.1.0-next.4"
+    },
+    "activationEvents": [
+        "*"
+    ],
+    "main": "./extension.js"
+}
diff --git a/src/tint/cmd/tintd/vscode/wgsl.tmLanguage.json b/src/tint/cmd/tintd/vscode/wgsl.tmLanguage.json
new file mode 100644
index 0000000..5072a29
--- /dev/null
+++ b/src/tint/cmd/tintd/vscode/wgsl.tmLanguage.json
@@ -0,0 +1,241 @@
+{
+    "name": "WGSL",
+    "scopeName": "source.wgsl",
+    "patterns": [
+        {
+            "include": "#comments"
+        },
+        {
+            "include": "#keywords"
+        },
+        {
+            "include": "#attributes"
+        },
+        {
+            "include": "#functions"
+        },
+        {
+            "include": "#function_calls"
+        },
+        {
+            "include": "#literals"
+        },
+        {
+            "include": "#punctuation"
+        }
+    ],
+    "repository": {
+        "comments": {
+            "comment": "single line comment",
+            "name": "comment.line.double-slash.wgsl",
+            "match": "\\s*//.*"
+        },
+        "functions": {
+            "patterns": [
+                {
+                    "comment": "function definition",
+                    "name": "meta.function.definition.wgsl",
+                    "begin": "\\b(fn)\\s+([A-Za-z0-9_]+)(\\([^)]*\\))\\s+(->)\\s+([^{]+)",
+                    "beginCaptures": {
+                        "1": {
+                            "name": "keyword.other.fn.wgsl"
+                        },
+                        "2": {
+                            "name": "entity.name.function.wgsl"
+                        },
+                        "3": {
+                            "name": "punctuation.brackets.round.wgsl"
+                        },
+                        "4": {
+                            "name": "punctuation.fn.arrow.wgsl"
+                        }
+                    },
+                    "end": "\\{",
+                    "endCaptures": {
+                        "0": {
+                            "name": "punctuation.brackets.curly.wgsl"
+                        }
+                    },
+                    "patterns": [
+                        {
+                            "include": "#comments"
+                        },
+                        {
+                            "include": "#keywords"
+                        },
+                        {
+                            "include": "#function_calls"
+                        },
+                        {
+                            "include": "#literals"
+                        },
+                        {
+                            "include": "#punctuation"
+                        }
+                    ]
+                }
+            ]
+        },
+        "function_calls": {
+            "patterns": [
+                {
+                    "comment": "function call",
+                    "name": "meta.function.call.wgsl",
+                    "begin": "([A-Za-z0-9_]+)(\\()",
+                    "beginCaptures": {
+                        "1": {
+                            "name": "entity.name.function.wgsl"
+                        },
+                        "2": {
+                            "name": "punctuation.brackets.round.wgsl"
+                        }
+                    },
+                    "end": "\\)",
+                    "endCaptures": {
+                        "0": {
+                            "name": "punctuation.brackets.round.wgsl"
+                        }
+                    },
+                    "patterns": [
+                        {
+                            "include": "#comments"
+                        },
+                        {
+                            "include": "#keywords"
+                        },
+                        {
+                            "include": "#function_calls"
+                        },
+                        {
+                            "include": "#literals"
+                        },
+                        {
+                            "include": "#punctuation"
+                        }
+                    ]
+                }
+            ]
+        },
+        "attributes": {
+            "patterns": [
+                {
+                    "comment": "attribute",
+                    "name": "keyword.attribute.wgsl",
+                    "match": "(@[A-Za-z0-9_]+[^(])"
+                }
+            ]
+        },
+        "literals": {
+            "patterns": [
+                {
+                    "comment": "floating point literal",
+                    "name": "constant.numeric.float.wgsl",
+                    "match": "(-?\\b[0-9][0-9]*\\.[0-9][0-9]*)([eE][+-]?[0-9]+)?[fh]?\\b"
+                },
+                {
+                    "comment": "integer literal",
+                    "name": "constant.numeric.integer.wgsl",
+                    "match": "-?\\b0x[0-9a-fA-F]+[iufh]?\\b|\\b0[iufh]?\\b|-?\\b[1-9][0-9]*[iufh]?\\b"
+                },
+                {
+                    "comment": "boolean constant",
+                    "name": "constant.language.boolean.wgsl",
+                    "match": "\\b(true|false)\\b"
+                }
+            ]
+        },
+        "punctuation": {
+            "patterns": [
+                {
+                    "comment": "comma",
+                    "name": "punctuation.comma.wgsl",
+                    "match": ","
+                },
+                {
+                    "comment": "braces",
+                    "name": "punctuation.brackets.curly.wgsl",
+                    "match": "[{}]"
+                },
+                {
+                    "comment": "parentheses",
+                    "name": "punctuation.brackets.round.wgsl",
+                    "match": "[()]"
+                },
+                {
+                    "comment": "semicolon",
+                    "name": "punctuation.semi.wgsl",
+                    "match": ";"
+                },
+                {
+                    "comment": "square brackets",
+                    "name": "punctuation.brackets.square.wgsl",
+                    "match": "[\\[\\]]"
+                },
+                {
+                    "comment": "angle brackets",
+                    "name": "punctuation.brackets.angle.wgsl",
+                    "match": "(?<!=)[<>]"
+                },
+                {
+                    "comment": "function ret",
+                    "name": "punctuation.fn.ret.wgsl",
+                    "match": "(->)"
+                }
+            ]
+        },
+        "keywords": {
+            "patterns": [
+                {
+                    "comment": "other keywords",
+                    "name": "keyword.control.wgsl",
+                    "match": "\\b(alias|break|case|const_assert|continue|continuing|default|diagnostic|discard|else|enable|false|fn|for|if|loop|override|requires|return|struct|switch|true|while)\\b"
+                },
+                {
+                    "comment": "reserved keywords",
+                    "name": "keyword.control.wgsl.reserved",
+                    "match": "\\b(NULL|Self|abstract|active|alignas|alignof|as|asm|asm_fragment|async|attribute|auto|await|become|binding_array|cast|catch|class|co_await|co_return|co_yield|coherent|column_major|common|compile|compile_fragment|concept|const_cast|consteval|constexpr|constinit|crate|debugger|decltype|delete|demote|demote_to_helper|do|dynamic_cast|enum|explicit|export|extends|extern|external|fallthrough|filter|final|finally|friend|from|fxgroup|get|goto|groupshared|highp|impl|implements|import|inline|instanceof|interface|layout|lowp|macro|macro_rules|match|mediump|meta|mod|module|move|mut|mutable|namespace|new|nil|noexcept|noinline|nointerpolation|noperspective|null|nullptr|of|operator|package|packoffset|partition|pass|patch|pixelfragment|precise|precision|premerge|priv|protected|pub|public|readonly|ref|regardless|register|reinterpret_cast|require|resource|restrict|self|set|shared|sizeof|smooth|snorm|static|static_assert|static_cast|std|subroutine|super|target|template|this|thread_local|throw|trait|try|type|typedef|typeid|typename|typeof|union|unless|unorm|unsafe|unsized|use|using|varying|virtual|volatile|wgsl|where|with|writeonly|yield)\\b"
+                },
+                {
+                    "comment": "variable keywords",
+                    "name": "keyword.other.wgsl storage.type.wgsl",
+                    "match": "\\b(const|let|var|override)\\b"
+                },
+                {
+                    "comment": "struct keyword",
+                    "name": "keyword.declaration.struct.wgsl storage.type.wgsl",
+                    "match": "\\b(struct)\\b"
+                },
+                {
+                    "comment": "fn",
+                    "name": "keyword.other.fn.wgsl",
+                    "match": "\\bfn\\b"
+                },
+                {
+                    "comment": "logical operators",
+                    "name": "keyword.operator.logical.wgsl",
+                    "match": "(\\^|\\||\\|\\||&&|<<|>>|!)(?!=)"
+                },
+                {
+                    "comment": "assignment operators",
+                    "name": "keyword.operator.assignment.wgsl",
+                    "match": "(\\+=|-=|\\*=|/=|%=|\\^=|&=|\\|=|<<=|>>=)"
+                },
+                {
+                    "comment": "comparison operators",
+                    "name": "keyword.operator.comparison.wgsl",
+                    "match": "(==|!=|<|<=|>|>=)"
+                },
+                {
+                    "comment": "math operators",
+                    "name": "keyword.operator.math.wgsl",
+                    "match": "(([+%]|(\\*(?!\\w)))(?!=))|(-(?!>))|(/(?!/))"
+                },
+                {
+                    "comment": "member access",
+                    "name": "keyword.operator.access.dot.wgsl",
+                    "match": "\\.(?!\\.)"
+                }
+            ]
+        }
+    }
+}
diff --git a/src/tint/lang/wgsl/BUILD.cmake b/src/tint/lang/wgsl/BUILD.cmake
index 63b67a8..2b40c72 100644
--- a/src/tint/lang/wgsl/BUILD.cmake
+++ b/src/tint/lang/wgsl/BUILD.cmake
@@ -41,6 +41,7 @@
 include(lang/wgsl/inspector/BUILD.cmake)
 include(lang/wgsl/intrinsic/BUILD.cmake)
 include(lang/wgsl/ir/BUILD.cmake)
+include(lang/wgsl/ls/BUILD.cmake)
 include(lang/wgsl/program/BUILD.cmake)
 include(lang/wgsl/reader/BUILD.cmake)
 include(lang/wgsl/resolver/BUILD.cmake)
diff --git a/src/tint/lang/wgsl/ls/BUILD.bazel b/src/tint/lang/wgsl/ls/BUILD.bazel
new file mode 100644
index 0000000..4081683
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/BUILD.bazel
@@ -0,0 +1,88 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.bazel.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+load("//src/tint:flags.bzl", "COPTS")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "ls",
+  srcs = [
+    "serve.cc",
+    "server.cc",
+  ],
+  hdrs = [
+    "serve.h",
+    "server.h",
+  ],
+  deps = [
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    
+  ] + select({
+    ":tint_build_tintd": [
+      
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
+alias(
+  name = "tint_build_tintd",
+  actual = "//src/tint:tint_build_tintd_true",
+)
+
+alias(
+  name = "tint_build_wgsl_reader",
+  actual = "//src/tint:tint_build_wgsl_reader_true",
+)
+
+selects.config_setting_group(
+    name = "tint_build_tintd_and_tint_build_wgsl_reader",
+    match_all = [
+        ":tint_build_tintd",
+        ":tint_build_wgsl_reader",
+    ],
+)
+
diff --git a/src/tint/lang/wgsl/ls/BUILD.cfg b/src/tint/lang/wgsl/ls/BUILD.cfg
new file mode 100644
index 0000000..d838768
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/BUILD.cfg
@@ -0,0 +1,3 @@
+{
+    "condition": "tint_build_tintd && tint_build_wgsl_reader"
+}
diff --git a/src/tint/lang/wgsl/ls/BUILD.cmake b/src/tint/lang/wgsl/ls/BUILD.cmake
new file mode 100644
index 0000000..c0a29ea
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/BUILD.cmake
@@ -0,0 +1,73 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.cmake.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+if(TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER)
+################################################################################
+# Target:    tint_lang_wgsl_ls
+# Kind:      lib
+# Condition: TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER
+################################################################################
+tint_add_target(tint_lang_wgsl_ls lib
+  lang/wgsl/ls/serve.cc
+  lang/wgsl/ls/serve.h
+  lang/wgsl/ls/server.cc
+  lang/wgsl/ls/server.h
+)
+
+tint_target_add_dependencies(tint_lang_wgsl_ls lib
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_wgsl_ls lib
+  "thread"
+)
+
+if(TINT_BUILD_TINTD)
+  tint_target_add_external_dependencies(tint_lang_wgsl_ls lib
+    "langsvr"
+  )
+endif(TINT_BUILD_TINTD)
+
+endif(TINT_BUILD_TINTD AND TINT_BUILD_WGSL_READER)
\ No newline at end of file
diff --git a/src/tint/lang/wgsl/ls/BUILD.gn b/src/tint/lang/wgsl/ls/BUILD.gn
new file mode 100644
index 0000000..c08f57b
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/BUILD.gn
@@ -0,0 +1,66 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.gn.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+import("../../../../../scripts/tint_overrides_with_defaults.gni")
+
+import("${tint_src_dir}/tint.gni")
+if (tint_build_tintd && tint_build_wgsl_reader) {
+  libtint_source_set("ls") {
+    sources = [
+      "serve.cc",
+      "serve.h",
+      "server.cc",
+      "server.h",
+    ]
+    deps = [
+      "${tint_src_dir}:thread",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+
+    if (tint_build_tintd) {
+      deps += [ "${tint_src_dir}:langsvr" ]
+    }
+  }
+}
diff --git a/src/tint/lang/wgsl/ls/serve.cc b/src/tint/lang/wgsl/ls/serve.cc
new file mode 100644
index 0000000..241eee1
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/serve.cc
@@ -0,0 +1,123 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// 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 "src/tint/lang/wgsl/ls/serve.h"
+
+#include <stdio.h>
+#include <string>
+
+#include "langsvr/content_stream.h"
+#include "langsvr/lsp/lsp.h"
+#include "langsvr/session.h"
+
+#include "src/tint/lang/wgsl/ls/server.h"
+#include "src/tint/utils/macros/compiler.h"
+#include "src/tint/utils/macros/defer.h"
+
+////////////////////////////////////////////////////////////////////////////////
+// Debug switches
+////////////////////////////////////////////////////////////////////////////////
+#define LOG_TO_FILE 0        // Log all raw protocol messages to "log.txt"
+#define WAIT_FOR_DEBUGGER 0  // Wait for a debugger to attach on startup
+
+#if WAIT_FOR_DEBUGGER
+#include <unistd.h>
+#include <thread>
+#endif
+
+#if LOG_TO_FILE
+#define LOG(msg, ...)                          \
+    {                                          \
+        fprintf(log, msg "\n", ##__VA_ARGS__); \
+        fflush(log);                           \
+    }                                          \
+    TINT_REQUIRE_SEMICOLON
+#else
+#define LOG(...) TINT_REQUIRE_SEMICOLON
+#endif
+
+namespace tint::wgsl::ls {
+
+namespace {
+
+#if LOG_TO_FILE
+FILE* log = nullptr;
+void TintInternalCompilerErrorReporter(const tint::InternalCompilerError& err) {
+    if (log) {
+        LOG("\n--------------------------------------------------------------");
+        LOG("%s:%d %s", err.File(), static_cast<int>(err.Line()), err.Message().c_str());
+        LOG("--------------------------------------------------------------\n");
+    }
+}
+#endif
+
+}  // namespace
+
+Result<SuccessType> Serve(langsvr::Reader& reader, langsvr::Writer& writer) {
+#if LOG_TO_FILE
+    log = fopen("log.txt", "wb");
+    TINT_DEFER(fclose(log));
+    tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
+#endif
+
+#if WAIT_FOR_DEBUGGER
+    LOG("waiting for debugger. pid: %s", std::to_string(getpid()).c_str());
+    std::this_thread::sleep_for(std::chrono::seconds(10));
+#endif
+
+    langsvr::Session session;
+    session.SetSender([&](std::string_view response) {  //
+        LOG("<< %s", std::string(response).c_str());
+        return langsvr::WriteContent(writer, response);
+    });
+
+    Server server(session);
+
+    LOG("Running...");
+
+    while (!server.ShuttingDown()) {
+        auto msg = langsvr::ReadContent(reader);
+        if (msg != langsvr::Success) {
+            LOG("ERROR: %s", msg.Failure().reason.c_str());
+            break;
+        }
+        LOG(">> %s", msg.Get().c_str());
+
+        auto res = session.Receive(msg.Get());
+        if (res != langsvr::Success) {
+            LOG("ERROR: %s", res.Failure().reason.c_str());
+            break;
+        }
+
+        LOG("----------------");
+    }
+
+    LOG("Shutting down");
+    return Success;
+}
+
+}  // namespace tint::wgsl::ls
diff --git a/src/tint/lang/wgsl/ls/serve.h b/src/tint/lang/wgsl/ls/serve.h
new file mode 100644
index 0000000..774d119
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/serve.h
@@ -0,0 +1,43 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// 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.
+
+#ifndef SRC_TINT_LANG_WGSL_LS_SERVE_H_
+#define SRC_TINT_LANG_WGSL_LS_SERVE_H_
+
+#include "langsvr/reader.h"
+#include "langsvr/writer.h"
+#include "src/tint/utils/result/result.h"
+
+namespace tint::wgsl::ls {
+
+/// Serve creates a WGSL language server that reads from @p reader and writes to @p writer.
+/// Blocks until the server is shutdown by the client.
+Result<SuccessType> Serve(langsvr::Reader& reader, langsvr::Writer& writer);
+
+}  // namespace tint::wgsl::ls
+
+#endif  // SRC_TINT_LANG_WGSL_LS_SERVE_H_
diff --git a/src/tint/lang/wgsl/ls/server.cc b/src/tint/lang/wgsl/ls/server.cc
new file mode 100644
index 0000000..cc9bbe2
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/server.cc
@@ -0,0 +1,58 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// 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 "src/tint/lang/wgsl/ls/server.h"
+
+#include "langsvr/session.h"
+
+namespace lsp = langsvr::lsp;
+
+namespace tint::wgsl::ls {
+
+Server::Server(langsvr::Session& session) : session_(session) {
+    session.Register([&](const lsp::InitializeRequest&)
+                         -> langsvr::Result<typename lsp::InitializeRequest::Result> {
+        lsp::InitializeResult result;
+        return result;
+    });
+
+    session.Register([&](const lsp::ShutdownRequest&) {
+        shutting_down_ = true;
+        return lsp::Null{};
+    });
+}
+
+Server::~Server() = default;
+
+Server::Logger::~Logger() {
+    lsp::WindowLogMessageNotification n;
+    n.type = lsp::MessageType::kLog;
+    n.message = msg.str();
+    (void)session.Send(n);
+}
+
+}  // namespace tint::wgsl::ls
diff --git a/src/tint/lang/wgsl/ls/server.h b/src/tint/lang/wgsl/ls/server.h
new file mode 100644
index 0000000..333f00e
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/server.h
@@ -0,0 +1,81 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// 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.
+
+#ifndef SRC_TINT_LANG_WGSL_LS_SERVER_H_
+#define SRC_TINT_LANG_WGSL_LS_SERVER_H_
+
+#include <memory>
+
+#include "langsvr/lsp/lsp.h"
+#include "langsvr/session.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::wgsl::ls {
+
+/// The language server state object.
+class Server {
+  public:
+    /// Constructor
+    /// @param session the LSP session.
+    explicit Server(langsvr::Session& session);
+
+    /// Destructor
+    ~Server();
+
+    /// @returns true if the server has been requested to shut down.
+    bool ShuttingDown() const { return shutting_down_; }
+
+  private:
+    /// Logger is a string-stream like utility for logging to the client.
+    /// Append message content with '<<'. The message is sent when the logger is destructed.
+    struct Logger {
+        ~Logger();
+
+        /// @brief Appends @p value to the log message
+        /// @return this logger
+        template <typename T>
+        Logger& operator<<(T&& value) {
+            msg << value;
+            return *this;
+        }
+
+        langsvr::Session& session;
+        StringStream msg{};
+    };
+
+    /// Log constructs a new Logger to send a log message to the client.
+    Logger Log() { return Logger{session_}; }
+
+    /// The LSP session.
+    langsvr::Session& session_;
+    /// True if the server has been asked to shutdown.
+    bool shutting_down_ = false;
+};
+
+}  // namespace tint::wgsl::ls
+
+#endif  // SRC_TINT_LANG_WGSL_LS_SERVER_H_