From 7c891fdacdecce631b715740f0183b3e075a5cbe Mon Sep 17 00:00:00 2001 From: maan2003 <49202620+Maan2003@users.noreply.github.com> Date: Mon, 21 Aug 2023 18:39:31 +0000 Subject: [PATCH] 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. --- .pre-commit-config.yaml | 1 + WORKSPACE | 1 + .../llvm/0004_Add_library_for_clangd.patch | 77 +++++++++ language_server/BUILD | 28 ++++ language_server/language_server.cpp | 155 ++++++++++++++++++ language_server/language_server.h | 83 ++++++++++ language_server/main.cpp | 10 ++ scripts/fix_cc_deps.py | 9 +- utils/vscode/README.md | 8 +- utils/vscode/package-lock.json | 80 ++++++++- utils/vscode/package.json | 3 + utils/vscode/src/extension.js | 30 +++- 12 files changed, 474 insertions(+), 11 deletions(-) create mode 100644 bazel/patches/llvm/0004_Add_library_for_clangd.patch create mode 100644 language_server/BUILD create mode 100644 language_server/language_server.cpp create mode 100644 language_server/language_server.h create mode 100644 language_server/main.cpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4493c46b557..daa7b8e474ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -215,4 +215,5 @@ exclude: | bazel/patches/.*\.patch| third_party/examples/.*/carbon/.*| third_party/llvm-project/.*| + third_party/clangd/.*| )$ diff --git a/WORKSPACE b/WORKSPACE index f350c02da335..524b42341eba 100644 --- a/WORKSPACE +++ b/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), diff --git a/bazel/patches/llvm/0004_Add_library_for_clangd.patch b/bazel/patches/llvm/0004_Add_library_for_clangd.patch new file mode 100644 index 000000000000..7c53b531916d --- /dev/null +++ b/bazel/patches/llvm/0004_Add_library_for_clangd.patch @@ -0,0 +1,77 @@ +From 2625e497fef1429f2041922a739d841050eda909 Mon Sep 17 00:00:00 2001 +From: maan2003 +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 diff --git a/language_server/BUILD b/language_server/BUILD new file mode 100644 index 000000000000..f552b9abcc0c --- /dev/null +++ b/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", + ], +) diff --git a/language_server/language_server.cpp b/language_server/language_server.cpp new file mode 100644 index 000000000000..d544a4b8ea25 --- /dev/null +++ b/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 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 reply) { + transport_->reply(id, std::move(reply)); + }); + } else { + transport_->reply( + id, llvm::make_error( + "method not found", clang::clangd::ErrorCode::MethodNotFound)); + } + + return true; +} + +auto LanguageServer::onReply(llvm::json::Value /*id*/, + llvm::Expected /*result*/) + -> bool { + return true; +} + +// Returns the text of first child of kind ParseNodeKind::Name. +static auto getName(ParseTree& p, ParseTree::Node node) + -> std::optional { + 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> 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 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 diff --git a/language_server/language_server.h b/language_server/language_server.h new file mode 100644 index 000000000000..a377add3f52b --- /dev/null +++ b/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 +#include + +#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 result) + -> bool override; + + // LSPBinder::RawOutgoing + + // Send method call to client + void callMethod(llvm::StringRef Method, llvm::json::Value Params, + clang::clangd::Callback 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 transport_; + // content of files managed by the language client. + std::unordered_map files_; + // handlers for client methods and notifications + clang::clangd::LSPBinder::RawHandlers handlers_; + + explicit LanguageServer(std::unique_ptr 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 cb); + + // Code outline + void OnDocumentSymbol( + clang::clangd::DocumentSymbolParams const& params, + clang::clangd::Callback> cb); +}; + +} // namespace Carbon::LS + +#endif // CARBON_LANGUAGE_SERVER_LANGUAGE_SERVER_H_ diff --git a/language_server/main.cpp b/language_server/main.cpp new file mode 100644 index 000000000000..1dfdf6470a05 --- /dev/null +++ b/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; +} diff --git a/scripts/fix_cc_deps.py b/scripts/fix_cc_deps.py index e03c9bad957b..99f925470e56 100755 --- a/scripts/fix_cc_deps.py +++ b/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 (?:"([^"]+)"|' diff --git a/utils/vscode/README.md b/utils/vscode/README.md index eb033b0b2585..4d5ce9ad74a1 100644 --- a/utils/vscode/README.md +++ b/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: diff --git a/utils/vscode/package-lock.json b/utils/vscode/package-lock.json index 9d2aa2d9c5cb..fc8fe0798832 100644 --- a/utils/vscode/package-lock.json +++ b/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", diff --git a/utils/vscode/package.json b/utils/vscode/package.json index b22024de1a5b..f032a9449518 100644 --- a/utils/vscode/package.json +++ b/utils/vscode/package.json @@ -38,5 +38,8 @@ }, "devDependencies": { "@vscode/vsce": "^2.19.0" + }, + "dependencies": { + "vscode-languageclient": "^8.1.0" } } diff --git a/utils/vscode/src/extension.js b/utils/vscode/src/extension.js index c5103e8ffb31..0bc57c9b7642 100644 --- a/utils/vscode/src/extension.js +++ b/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, };