Forráskód Böngészése

Language Server (#3112)

Add a language server for carbon as part of GSoC.

This currently does code outline using toolchain parser.

See development steps in utils/vscode/README.md for running and using
language server.
maan2003 2 éve
szülő
commit
7c891fdacd

+ 1 - 0
.pre-commit-config.yaml

@@ -215,4 +215,5 @@ exclude: |
       bazel/patches/.*\.patch|
       third_party/examples/.*/carbon/.*|
       third_party/llvm-project/.*|
+      third_party/clangd/.*|
   )$

+ 1 - 0
WORKSPACE

@@ -123,6 +123,7 @@ http_archive(
         "@carbon//bazel/patches/llvm:0001_Patch_for_mallinfo2_when_using_Bazel_build_system.patch",
         "@carbon//bazel/patches/llvm:0002_Added_Bazel_build_for_compiler_rt_fuzzer.patch",
         "@carbon//bazel/patches/llvm:0003_Modernize_py_binary_rule_for_lit.patch",
+        "@carbon//bazel/patches/llvm:0004_Add_library_for_clangd.patch",
     ],
     sha256 = "03a8eb4b243846ee037d700b048ec48a87eeef480cb129ab56aa7e0537172b98",
     strip_prefix = "llvm-project-{0}".format(llvm_version),

+ 77 - 0
bazel/patches/llvm/0004_Add_library_for_clangd.patch

@@ -0,0 +1,77 @@
+From 2625e497fef1429f2041922a739d841050eda909 Mon Sep 17 00:00:00 2001
+From: maan2003 <manmeetmann2003@gmail.com>
+Date: Sat, 19 Aug 2023 02:32:03 +0530
+Subject: [PATCH] Add library for clangd
+
+This exports clangd language server protocol helpers publically.
+Feature.h needs Feature.inc which is generated during build process, so we
+disable all features for now.
+---
+ clang-tools-extra/clangd/BUILD.bazel  | 36 +++++++++++++++++++++++++++
+ clang-tools-extra/clangd/Protocol.cpp | 15 +++++++----
+ clang-tools-extra/clangd/Protocol.h   | 12 +++++++--
+ clang-tools-extra/clangd/Transport.h  |  1 -
+ 4 files changed, 56 insertions(+), 8 deletions(-)
+ create mode 100644 clang-tools-extra/clangd/BUILD.bazel
+
+diff --git a/clang-tools-extra/clangd/BUILD.bazel b/clang-tools-extra/clangd/BUILD.bazel
+new file mode 100644
+index 000000000..9f3f93f24
+--- /dev/null
++++ b/clang-tools-extra/clangd/BUILD.bazel
+@@ -0,0 +1,39 @@
++package(default_visibility = ["//visibility:public"])
++
++cc_library(
++    name = "clangd_library",
++    srcs = [
++        "JSONTransport.cpp",
++        "Protocol.cpp",
++        "URI.cpp",
++        "index/SymbolID.cpp",
++        "support/Logger.cpp",
++        "support/Trace.cpp",
++        "support/MemoryTree.cpp",
++        "support/Context.cpp",
++        "support/Cancellation.cpp",
++        "support/ThreadCrashReporter.cpp",
++        "support/Shutdown.cpp",
++    ],
++    hdrs = [
++        "Transport.h",
++        "Protocol.h",
++        "URI.h",
++        "LSPBinder.h",
++        "index/SymbolID.h",
++        "support/Function.h",
++        "support/Cancellation.h",
++        "support/ThreadCrashReporter.h",
++        "support/Logger.h",
++        "support/Trace.h",
++        "support/MemoryTree.h",
++        "support/Context.h",
++        "support/Shutdown.h",
++    ],
++    includes = ["."],
++    deps = [
++        "//llvm:Support",
++        "//clang:basic",
++        "//clang:index",
++    ],
++)
+diff --git a/clang-tools-extra/clangd/Transport.h b/clang-tools-extra/clangd/Transport.h
+index 4e80ea95b..f17441cfc 100644
+--- a/clang-tools-extra/clangd/Transport.h
++++ b/clang-tools-extra/clangd/Transport.h
+@@ -17,9 +17,8 @@
+
+ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H
+ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_TRANSPORT_H
+
+-#include "Feature.h"
+ #include "llvm/ADT/StringRef.h"
+ #include "llvm/Support/JSON.h"
+ #include "llvm/Support/raw_ostream.h"
+
+--
+2.41.0

+ 28 - 0
language_server/BUILD

@@ -0,0 +1,28 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+package(default_visibility = [
+    "//language_server:__subpackages__",
+])
+
+cc_binary(
+    name = "language_server",
+    srcs = [
+        "language_server.cpp",
+        "language_server.h",
+        "main.cpp",
+    ],
+    # Some parameters are unused in clangd headers.
+    copts = ["-Wno-unused-parameter"],
+    deps = [
+        "//common:error",
+        "//toolchain/diagnostics:null_diagnostics",
+        "//toolchain/lexer:tokenized_buffer",
+        "//toolchain/parser:parse_node_kind",
+        "//toolchain/parser:parse_tree",
+        "//toolchain/source:source_buffer",
+        "@llvm-project//clang-tools-extra/clangd:clangd_library",
+        "@llvm-project//llvm:Support",
+    ],
+)

+ 155 - 0
language_server/language_server.cpp

@@ -0,0 +1,155 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "language_server/language_server.h"
+
+#include "clang-tools-extra/clangd/Protocol.h"
+#include "toolchain/diagnostics/null_diagnostics.h"
+#include "toolchain/lexer/tokenized_buffer.h"
+#include "toolchain/parser/parse_node_kind.h"
+#include "toolchain/parser/parse_tree.h"
+#include "toolchain/source/source_buffer.h"
+
+namespace Carbon::LS {
+
+void LanguageServer::OnDidOpenTextDocument(
+    clang::clangd::DidOpenTextDocumentParams const& params) {
+  files_.emplace(params.textDocument.uri.file(), params.textDocument.text);
+}
+
+void LanguageServer::OnDidChangeTextDocument(
+    clang::clangd::DidChangeTextDocumentParams const& params) {
+  // full text is sent if full sync is specified in capabilities.
+  assert(params.contentChanges.size() == 1);
+  std::string file = params.textDocument.uri.file().str();
+  files_[file] = params.contentChanges[0].text;
+}
+
+void LanguageServer::OnInitialize(
+    clang::clangd::NoParams const& client_capabilities,
+    clang::clangd::Callback<llvm::json::Object> cb) {
+  llvm::json::Object capabilities{{"documentSymbolProvider", true},
+                                  {"textDocumentSync", /*Full=*/1}};
+
+  llvm::json::Object reply{{"capabilities", std::move(capabilities)}};
+  cb(reply);
+};
+
+auto LanguageServer::onNotify(llvm::StringRef method, llvm::json::Value value)
+    -> bool {
+  if (method == "exit") {
+    return false;
+  }
+  if (auto handler = handlers_.NotificationHandlers.find(method);
+      handler != handlers_.NotificationHandlers.end()) {
+    handler->second(std::move(value));
+  } else {
+    clang::clangd::log("unhandled notification {0}", method);
+  }
+
+  return true;
+}
+
+auto LanguageServer::onCall(llvm::StringRef method, llvm::json::Value params,
+                            llvm::json::Value id) -> bool {
+  if (auto handler = handlers_.MethodHandlers.find(method);
+      handler != handlers_.MethodHandlers.end()) {
+    // TODO: improve this if add threads
+    handler->second(std::move(params),
+                    [&](llvm::Expected<llvm::json::Value> reply) {
+                      transport_->reply(id, std::move(reply));
+                    });
+  } else {
+    transport_->reply(
+        id, llvm::make_error<clang::clangd::LSPError>(
+                "method not found", clang::clangd::ErrorCode::MethodNotFound));
+  }
+
+  return true;
+}
+
+auto LanguageServer::onReply(llvm::json::Value /*id*/,
+                             llvm::Expected<llvm::json::Value> /*result*/)
+    -> bool {
+  return true;
+}
+
+// Returns the text of first child of kind ParseNodeKind::Name.
+static auto getName(ParseTree& p, ParseTree::Node node)
+    -> std::optional<llvm::StringRef> {
+  for (auto ch : p.children(node)) {
+    if (p.node_kind(ch) == ParseNodeKind::Name) {
+      return p.GetNodeText(ch);
+    }
+  }
+  return std::nullopt;
+}
+
+void LanguageServer::OnDocumentSymbol(
+    clang::clangd::DocumentSymbolParams const& params,
+    clang::clangd::Callback<std::vector<clang::clangd::DocumentSymbol>> cb) {
+  llvm::vfs::InMemoryFileSystem vfs;
+  auto file = params.textDocument.uri.file().str();
+  vfs.addFile(file, /*mtime=*/0,
+              llvm::MemoryBuffer::getMemBufferCopy(files_.at(file)));
+
+  auto buf = SourceBuffer::CreateFromFile(vfs, file);
+  auto lexed = TokenizedBuffer::Lex(*buf, NullDiagnosticConsumer());
+  auto parsed = ParseTree::Parse(lexed, NullDiagnosticConsumer(), nullptr);
+  std::vector<clang::clangd::DocumentSymbol> result;
+  for (const auto& node : parsed.postorder()) {
+    clang::clangd::SymbolKind symbol_kind;
+    switch (parsed.node_kind(node)) {
+      case ParseNodeKind::FunctionDeclaration:
+      case ParseNodeKind::FunctionDefinitionStart:
+        symbol_kind = clang::clangd::SymbolKind::Function;
+        break;
+      case ParseNodeKind::Namespace:
+        symbol_kind = clang::clangd::SymbolKind::Namespace;
+        break;
+      case ParseNodeKind::InterfaceDefinitionStart:
+      case ParseNodeKind::NamedConstraintDefinitionStart:
+        symbol_kind = clang::clangd::SymbolKind::Interface;
+        break;
+      case ParseNodeKind::ClassDefinitionStart:
+        symbol_kind = clang::clangd::SymbolKind::Class;
+        break;
+      default:
+        continue;
+    }
+
+    if (auto name = getName(parsed, node)) {
+      auto tok = parsed.node_token(node);
+      clang::clangd::Position pos{lexed.GetLineNumber(tok) - 1,
+                                  lexed.GetColumnNumber(tok) - 1};
+
+      clang::clangd::DocumentSymbol symbol{
+          .name = std::string(*name),
+          .kind = symbol_kind,
+          .range = {.start = pos, .end = pos},
+          .selectionRange = {.start = pos, .end = pos},
+      };
+
+      result.push_back(symbol);
+    }
+  }
+  cb(result);
+}
+
+void LanguageServer::Start() {
+  auto transport =
+      clang::clangd::newJSONTransport(stdin, llvm::outs(), nullptr, true);
+  LanguageServer ls(std::move(transport));
+  clang::clangd::LSPBinder binder(ls.handlers_, ls);
+  binder.notification("textDocument/didOpen", &ls,
+                      &LanguageServer::OnDidOpenTextDocument);
+  binder.notification("textDocument/didChange", &ls,
+                      &LanguageServer::OnDidChangeTextDocument);
+  binder.method("initialize", &ls, &LanguageServer::OnInitialize);
+  binder.method("textDocument/documentSymbol", &ls,
+                &LanguageServer::OnDocumentSymbol);
+  auto error = ls.transport_->loop(ls);
+  llvm::errs() << "Error: " << error << "\n";
+}
+}  // namespace Carbon::LS

+ 83 - 0
language_server/language_server.h

@@ -0,0 +1,83 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_LANGUAGE_SERVER_LANGUAGE_SERVER_H_
+#define CARBON_LANGUAGE_SERVER_LANGUAGE_SERVER_H_
+#include <unordered_map>
+#include <vector>
+
+#include "clang-tools-extra/clangd/LSPBinder.h"
+#include "clang-tools-extra/clangd/Protocol.h"
+#include "clang-tools-extra/clangd/Transport.h"
+#include "clang-tools-extra/clangd/support/Function.h"
+#include "toolchain/lexer/tokenized_buffer.h"
+#include "toolchain/parser/parse_tree.h"
+#include "toolchain/source/source_buffer.h"
+
+namespace Carbon::LS {
+class LanguageServer : public clang::clangd::Transport::MessageHandler,
+                       public clang::clangd::LSPBinder::RawOutgoing {
+ public:
+  // Start the language server.
+  static void Start();
+
+  // Transport::MessageHandler
+  // Handlers returns true to keep processing messages, or false to shut down.
+
+  // Handler called on notification by client.
+  auto onNotify(llvm::StringRef method, llvm::json::Value value)
+      -> bool override;
+  // Handler called on method call by client.
+  auto onCall(llvm::StringRef method, llvm::json::Value params,
+              llvm::json::Value id) -> bool override;
+  // Handler called on response of Transport::call.
+  auto onReply(llvm::json::Value id, llvm::Expected<llvm::json::Value> result)
+      -> bool override;
+
+  // LSPBinder::RawOutgoing
+
+  // Send method call to client
+  void callMethod(llvm::StringRef Method, llvm::json::Value Params,
+                  clang::clangd::Callback<llvm::json::Value> Reply) override {
+    // TODO: implement when needed
+  }
+
+  // Send notification to client
+  void notify(llvm::StringRef method, llvm::json::Value params) override {
+    transport_->notify(method, params);
+  }
+
+ private:
+  const std::unique_ptr<clang::clangd::Transport> transport_;
+  // content of files managed by the language client.
+  std::unordered_map<std::string, std::string> files_;
+  // handlers for client methods and notifications
+  clang::clangd::LSPBinder::RawHandlers handlers_;
+
+  explicit LanguageServer(std::unique_ptr<clang::clangd::Transport> transport)
+      : transport_(std::move(transport)) {}
+
+  // Typed handlers for notifications and method calls by client.
+
+  // Client opened a document.
+  void OnDidOpenTextDocument(
+      clang::clangd::DidOpenTextDocumentParams const& params);
+
+  // Client updated content of a document.
+  void OnDidChangeTextDocument(
+      clang::clangd::DidChangeTextDocumentParams const& params);
+
+  // Capabilities negotiation
+  void OnInitialize(clang::clangd::NoParams const& client_capabilities,
+                    clang::clangd::Callback<llvm::json::Object> cb);
+
+  // Code outline
+  void OnDocumentSymbol(
+      clang::clangd::DocumentSymbolParams const& params,
+      clang::clangd::Callback<std::vector<clang::clangd::DocumentSymbol>> cb);
+};
+
+}  // namespace Carbon::LS
+
+#endif  // CARBON_LANGUAGE_SERVER_LANGUAGE_SERVER_H_

+ 10 - 0
language_server/main.cpp

@@ -0,0 +1,10 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "language_server/language_server.h"
+
+auto main(int /*argc*/, char** /*argv*/) -> int {
+  Carbon::LS::LanguageServer::Start();
+  return 0;
+}

+ 8 - 1
scripts/fix_cc_deps.py

@@ -35,8 +35,11 @@ class ExternalRepo(NamedTuple):
 # paths for that repository.
 EXTERNAL_REPOS: Dict[str, ExternalRepo] = {
     # llvm:include/llvm/Support/Error.h ->llvm/Support/Error.h
+    # clang-tools-extra/clangd:URI.h -> clang-tools-extra/clangd/URI.h
     "@llvm-project": ExternalRepo(
-        lambda x: re.sub("^(.*:(lib|include))/", "", x), "...", None
+        lambda x: re.sub(":", "/", re.sub("^(.*:(lib|include))/", "", x)),
+        "...",
+        None,
     ),
     # :src/google/protobuf/descriptor.h -> google/protobuf/descriptor.h
     # - protobuf_headers is specified because there are multiple overlapping
@@ -69,6 +72,7 @@ EXTERNAL_REPOS: Dict[str, ExternalRepo] = {
 # Try using `bazel cquery --output=starlark` to print `target.files`.
 # For protobuf, need to add support for `alias` rule kind.
 IGNORE_HEADER_REGEX = re.compile("^(.*\\.pb\\.h)$")
+IGNORE_SOURCE_FILE_REGEX = re.compile("^third_party/clangd")
 
 
 class Rule(NamedTuple):
@@ -198,6 +202,9 @@ def get_missing_deps(
     for source_file in rule_files:
         if source_file in generated_files:
             continue
+        if IGNORE_SOURCE_FILE_REGEX.match(source_file):
+            continue
+
         with open(source_file, "r") as f:
             for header_groups in re.findall(
                 r'^(#include (?:"([^"]+)"|'

+ 6 - 2
utils/vscode/README.md

@@ -25,8 +25,12 @@ code --install-extension out/carbon.vsix
 
 ## Development
 
-1. Open utils/vscode folder in VS Code.
-2. Launch the extension using Run command (F5).
+1. `bazel build language_server` in project root.
+2. Open utils/vscode folder in VS Code.
+3. Launch the extension using Run command (F5).
+4. In the opened window, open the carbon-lang repository as folder.
+5. Open a carbon file.
+6. Open code outline (Ctrl+Shift+O).
 
 To update dependencies:
 

+ 74 - 6
utils/vscode/package-lock.json

@@ -7,11 +7,14 @@
     "": {
       "name": "carbon-lang",
       "version": "0.0.1",
+      "dependencies": {
+        "vscode-languageclient": "^8.1.0"
+      },
       "devDependencies": {
         "@vscode/vsce": "^2.19.0"
       },
       "engines": {
-        "vscode": "^1.78.0"
+        "vscode": "^1.70.1"
       }
     },
     "node_modules/@vscode/vsce": {
@@ -82,8 +85,7 @@
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
-      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
-      "dev": true
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
     "node_modules/base64-js": {
       "version": "1.5.1",
@@ -660,7 +662,6 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
       "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
       "dependencies": {
         "yallist": "^4.0.0"
       },
@@ -1221,6 +1222,74 @@
       "dev": true,
       "optional": true
     },
+    "node_modules/vscode-jsonrpc": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz",
+      "integrity": "sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==",
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/vscode-languageclient": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-8.1.0.tgz",
+      "integrity": "sha512-GL4QdbYUF/XxQlAsvYWZRV3V34kOkpRlvV60/72ghHfsYFnS/v2MANZ9P6sHmxFcZKOse8O+L9G7Czg0NUWing==",
+      "dependencies": {
+        "minimatch": "^5.1.0",
+        "semver": "^7.3.7",
+        "vscode-languageserver-protocol": "3.17.3"
+      },
+      "engines": {
+        "vscode": "^1.67.0"
+      }
+    },
+    "node_modules/vscode-languageclient/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/vscode-languageclient/node_modules/minimatch": {
+      "version": "5.1.6",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+      "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vscode-languageclient/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vscode-languageserver-protocol": {
+      "version": "3.17.3",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz",
+      "integrity": "sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==",
+      "dependencies": {
+        "vscode-jsonrpc": "8.1.0",
+        "vscode-languageserver-types": "3.17.3"
+      }
+    },
+    "node_modules/vscode-languageserver-types": {
+      "version": "3.17.3",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz",
+      "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA=="
+    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -1252,8 +1321,7 @@
     "node_modules/yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
     },
     "node_modules/yauzl": {
       "version": "2.10.0",

+ 3 - 0
utils/vscode/package.json

@@ -38,5 +38,8 @@
   },
   "devDependencies": {
     "@vscode/vsce": "^2.19.0"
+  },
+  "dependencies": {
+    "vscode-languageclient": "^8.1.0"
   }
 }

+ 28 - 2
utils/vscode/src/extension.js

@@ -4,7 +4,33 @@
  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  */
 
+const { LanguageClient } = require('vscode-languageclient/node');
+
+function activate(context) {
+  const command = './bazel-bin/language_server/language_server';
+  const serverOptions = {
+    run: { command },
+    debug: { command },
+  };
+
+  const clientOptions = {
+    documentSelector: [{ scheme: 'file', language: 'carbon' }],
+  };
+
+  const client = new LanguageClient(
+    'languageServer',
+    'Language Server for Carbon',
+    serverOptions,
+    clientOptions
+  );
+
+  // stop client on shutdown
+  context.subscriptions.push(client.start());
+}
+
+function deactivate() {}
+
 module.exports = {
-  activate() {},
-  deactivate() {},
+  activate,
+  deactivate,
 };