From 9e466b93353d32d1281eceabd4171da671b9253d Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 6 Feb 2025 17:04:42 -0800 Subject: [PATCH] Cache calculated file state in LSP (#4897) Add caching of parsed documents, and testing of the textDocument handlers. This is based on #4896, which splits out some of the boilerplate to calls. Note, this caches the entire parse state because we'll want to try to emit diagnostics when we see the update, without waiting. It may be helpful to do that asynchronously, but we don't want to wait for another call (such as documentSymbol). Really, we'll probably want to also add check for diagnostics, at least. --- toolchain/diagnostics/diagnostic_kind.def | 22 +++-- toolchain/language_server/BUILD | 10 +++ toolchain/language_server/context.cpp | 68 ++++++++++++++++ toolchain/language_server/context.h | 51 +++++++++++- toolchain/language_server/handle.h | 5 ++ .../handle_document_symbol.cpp | 35 ++++---- .../language_server/handle_text_document.cpp | 51 ++++++++++-- .../language_server/incoming_messages.cpp | 1 + toolchain/language_server/language_server.cpp | 2 +- .../testdata/{ => basics}/exit.carbon | 4 +- .../{ => basics}/fail_empty_stdin.carbon | 4 +- .../{ => basics}/fail_no_stdin.carbon | 4 +- .../testdata/{ => basics}/initialize.carbon | 4 +- .../{ => basics}/notify_parse_error.carbon | 4 +- .../{ => basics}/unexpected_reply.carbon | 4 +- .../{ => basics}/unsupported_call.carbon | 4 +- .../unsupported_notification.carbon | 4 +- .../testdata/{ => basics}/verbose.carbon | 0 .../testdata/document_symbol/basics.carbon | 80 +++++++++++++++++++ .../testdata/document_symbol/language.carbon | 21 +++++ .../testdata/document_symbol/unknown.carbon | 21 +++++ .../text_document/change_count.carbon | 39 +++++++++ .../text_document/change_unknown.carbon | 22 +++++ .../text_document/close_unknown.carbon | 21 +++++ .../text_document/open_change_close.carbon | 27 +++++++ .../text_document/open_duplicate.carbon | 26 ++++++ toolchain/source/source_buffer.cpp | 9 +++ toolchain/source/source_buffer.h | 6 ++ 28 files changed, 498 insertions(+), 51 deletions(-) create mode 100644 toolchain/language_server/context.cpp rename toolchain/language_server/testdata/{ => basics}/exit.carbon (78%) rename toolchain/language_server/testdata/{ => basics}/fail_empty_stdin.carbon (77%) rename toolchain/language_server/testdata/{ => basics}/fail_no_stdin.carbon (78%) rename toolchain/language_server/testdata/{ => basics}/initialize.carbon (85%) rename toolchain/language_server/testdata/{ => basics}/notify_parse_error.carbon (81%) rename toolchain/language_server/testdata/{ => basics}/unexpected_reply.carbon (82%) rename toolchain/language_server/testdata/{ => basics}/unsupported_call.carbon (83%) rename toolchain/language_server/testdata/{ => basics}/unsupported_notification.carbon (78%) rename toolchain/language_server/testdata/{ => basics}/verbose.carbon (100%) create mode 100644 toolchain/language_server/testdata/document_symbol/basics.carbon create mode 100644 toolchain/language_server/testdata/document_symbol/language.carbon create mode 100644 toolchain/language_server/testdata/document_symbol/unknown.carbon create mode 100644 toolchain/language_server/testdata/text_document/change_count.carbon create mode 100644 toolchain/language_server/testdata/text_document/change_unknown.carbon create mode 100644 toolchain/language_server/testdata/text_document/close_unknown.carbon create mode 100644 toolchain/language_server/testdata/text_document/open_change_close.carbon create mode 100644 toolchain/language_server/testdata/text_document/open_duplicate.carbon diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 2ff282f826b5e..02ea70cd01c3e 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -28,11 +28,6 @@ CARBON_DIAGNOSTIC_KIND(CompilePreludeManifestError) CARBON_DIAGNOSTIC_KIND(CompileInputNotRegularFile) CARBON_DIAGNOSTIC_KIND(CompileOutputFileOpenError) CARBON_DIAGNOSTIC_KIND(FormatMultipleFilesToOneOutput) -CARBON_DIAGNOSTIC_KIND(LanguageServerMissingInputStream) -CARBON_DIAGNOSTIC_KIND(LanguageServerNotificationParseError) -CARBON_DIAGNOSTIC_KIND(LanguageServerTransportError) -CARBON_DIAGNOSTIC_KIND(LanguageServerUnexpectedReply) -CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedNotification) // ============================================================================ // SourceBuffer diagnostics @@ -441,6 +436,23 @@ CARBON_DIAGNOSTIC_KIND(AssociatedConstantWithDifferentValues) CARBON_DIAGNOSTIC_KIND(ImplsOnNonFacetType) CARBON_DIAGNOSTIC_KIND(WhereOnNonFacetType) +// ============================================================================ +// Language server diagnostics +// ============================================================================ + +CARBON_DIAGNOSTIC_KIND(LanguageServerFileUnknown) +CARBON_DIAGNOSTIC_KIND(LanguageServerFileUnsupported) +CARBON_DIAGNOSTIC_KIND(LanguageServerMissingInputStream) +CARBON_DIAGNOSTIC_KIND(LanguageServerNotificationParseError) +CARBON_DIAGNOSTIC_KIND(LanguageServerTransportError) +CARBON_DIAGNOSTIC_KIND(LanguageServerUnexpectedReply) +CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedNotification) + +// Document handling. +CARBON_DIAGNOSTIC_KIND(LanguageServerOpenDuplicateFile) +CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedChanges) +CARBON_DIAGNOSTIC_KIND(LanguageServerCloseUnknownFile) + // ============================================================================ // Other diagnostics // ============================================================================ diff --git a/toolchain/language_server/BUILD b/toolchain/language_server/BUILD index 3becd4c83a11c..f956564218780 100644 --- a/toolchain/language_server/BUILD +++ b/toolchain/language_server/BUILD @@ -28,10 +28,20 @@ cc_library( cc_library( name = "context", + srcs = ["context.cpp"], hdrs = ["context.h"], deps = [ "//common:map", + "//toolchain/base:shared_value_stores", "//toolchain/diagnostics:diagnostic_emitter", + "//toolchain/diagnostics:file_diagnostics", + "//toolchain/diagnostics:null_diagnostics", + "//toolchain/lex", + "//toolchain/lex:tokenized_buffer", + "//toolchain/parse", + "//toolchain/parse:tree", + "//toolchain/sem_ir:file", + "//toolchain/source:source_buffer", ], ) diff --git a/toolchain/language_server/context.cpp b/toolchain/language_server/context.cpp new file mode 100644 index 0000000000000..5d00396589311 --- /dev/null +++ b/toolchain/language_server/context.cpp @@ -0,0 +1,68 @@ +// 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 "toolchain/language_server/context.h" + +#include + +#include "toolchain/base/shared_value_stores.h" +#include "toolchain/diagnostics/null_diagnostics.h" +#include "toolchain/lex/lex.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/parse.h" +#include "toolchain/parse/tree_and_subtrees.h" + +namespace Carbon::LanguageServer { + +auto Context::File::SetText(Context& context, llvm::StringRef text) -> void { + // Clear state dependent on the source text. + tree_and_subtrees_.reset(); + tree_.reset(); + tokens_.reset(); + value_stores_.reset(); + source_.reset(); + + // TODO: Make the processing asynchronous, to better handle rapid text + // updates. + CARBON_CHECK(!source_ && !value_stores_ && !tokens_ && !tree_, + "We currently cache everything together"); + // TODO: Diagnostics should be passed to the LSP instead of dropped. + auto& null_consumer = NullDiagnosticConsumer(); + std::optional source = + SourceBuffer::MakeFromStringCopy(filename_, text, null_consumer); + if (!source) { + // Failing here should be rare, but provide stub data for recovery so that + // we can have a simple API. + source = SourceBuffer::MakeFromStringCopy(filename_, "", null_consumer); + CARBON_CHECK(source, "Making an empty buffer should always succeed"); + } + source_ = std::make_unique(std::move(*source)); + value_stores_ = std::make_unique(); + tokens_ = std::make_unique( + Lex::Lex(*value_stores_, *source_, null_consumer)); + tree_ = std::make_unique( + Parse::Parse(*tokens_, null_consumer, context.vlog_stream())); + tree_and_subtrees_ = + std::make_unique(*tokens_, *tree_); +} + +auto Context::LookupFile(llvm::StringRef filename) -> File* { + if (!filename.ends_with(".carbon")) { + CARBON_DIAGNOSTIC(LanguageServerFileUnsupported, Warning, + "non-Carbon file requested"); + file_emitter_.Emit(filename, LanguageServerFileUnsupported); + return nullptr; + } + + if (auto lookup_result = files().Lookup(filename)) { + return &lookup_result.value(); + } else { + CARBON_DIAGNOSTIC(LanguageServerFileUnknown, Warning, + "unknown file requested"); + file_emitter_.Emit(filename, LanguageServerFileUnknown); + return nullptr; + } +} + +} // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/context.h b/toolchain/language_server/context.h index ab544e17a3d4b..90a8f844e212a 100644 --- a/toolchain/language_server/context.h +++ b/toolchain/language_server/context.h @@ -5,29 +5,72 @@ #ifndef CARBON_TOOLCHAIN_LANGUAGE_SERVER_CONTEXT_H_ #define CARBON_TOOLCHAIN_LANGUAGE_SERVER_CONTEXT_H_ +#include #include #include "common/map.h" +#include "toolchain/base/shared_value_stores.h" #include "toolchain/diagnostics/diagnostic_consumer.h" #include "toolchain/diagnostics/diagnostic_emitter.h" +#include "toolchain/diagnostics/file_diagnostics.h" +#include "toolchain/lex/tokenized_buffer.h" +#include "toolchain/parse/tree_and_subtrees.h" +#include "toolchain/sem_ir/file.h" +#include "toolchain/source/source_buffer.h" namespace Carbon::LanguageServer { // Context for LSP call handling. class Context { public: - // `consumer` is required. - explicit Context(DiagnosticConsumer* consumer) : no_loc_emitter_(consumer) {} + // Cached information for an open file. + class File { + public: + explicit File(std::string filename) : filename_(std::move(filename)) {} + // Changes the file's text, updating dependent state. + auto SetText(Context& context, llvm::StringRef text) -> void; + + auto tree_and_subtrees() const -> const Parse::TreeAndSubtrees& { + return *tree_and_subtrees_; + } + + private: + // The filename, stable across instances. + std::string filename_; + + // Current file content, and derived values. + std::unique_ptr source_; + std::unique_ptr value_stores_; + std::unique_ptr tokens_; + std::unique_ptr tree_; + std::unique_ptr tree_and_subtrees_; + }; + + // `consumer` and `emitter` are required. `vlog_stream` is optional. + explicit Context(llvm::raw_ostream* vlog_stream, DiagnosticConsumer* consumer) + : vlog_stream_(vlog_stream), + file_emitter_(consumer), + no_loc_emitter_(consumer) {} + + // Returns a reference to the file if it's known, or diagnoses and returns + // null. + auto LookupFile(llvm::StringRef filename) -> File*; + + auto file_emitter() -> FileDiagnosticEmitter& { return file_emitter_; } auto no_loc_emitter() -> NoLocDiagnosticEmitter& { return no_loc_emitter_; } + auto vlog_stream() -> llvm::raw_ostream* { return vlog_stream_; } - auto files() -> Map& { return files_; } + auto files() -> Map& { return files_; } private: + // Diagnostic and output streams. + llvm::raw_ostream* vlog_stream_; + FileDiagnosticEmitter file_emitter_; NoLocDiagnosticEmitter no_loc_emitter_; // Content of files managed by the language client. - Map files_; + Map files_; }; } // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/handle.h b/toolchain/language_server/handle.h index 77e26be0154ca..4161f20f4d068 100644 --- a/toolchain/language_server/handle.h +++ b/toolchain/language_server/handle.h @@ -15,6 +15,11 @@ auto HandleDidChangeTextDocument( Context& context, const clang::clangd::DidChangeTextDocumentParams& params) -> void; +// Closes a document. +auto HandleDidCloseTextDocument( + Context& context, const clang::clangd::DidCloseTextDocumentParams& params) + -> void; + // Updates the content of already-open documents. auto HandleDidOpenTextDocument( Context& context, const clang::clangd::DidOpenTextDocumentParams& params) diff --git a/toolchain/language_server/handle_document_symbol.cpp b/toolchain/language_server/handle_document_symbol.cpp index b9b9ff9ac1b14..200653829dea3 100644 --- a/toolchain/language_server/handle_document_symbol.cpp +++ b/toolchain/language_server/handle_document_symbol.cpp @@ -15,18 +15,17 @@ namespace Carbon::LanguageServer { // Returns the text of first child of kind IdentifierNameBeforeParams or // IdentifierNameNotBeforeParams. -static auto GetIdentifierName(const SharedValueStores& value_stores, - const Lex::TokenizedBuffer& tokens, - const Parse::TreeAndSubtrees& tree_and_subtrees, +static auto GetIdentifierName(const Parse::TreeAndSubtrees& tree_and_subtrees, Parse::NodeId node) -> std::optional { + const auto& tokens = tree_and_subtrees.tree().tokens(); for (auto child : tree_and_subtrees.children(node)) { switch (tree_and_subtrees.tree().node_kind(child)) { case Parse::NodeKind::IdentifierNameBeforeParams: case Parse::NodeKind::IdentifierNameNotBeforeParams: { auto token = tree_and_subtrees.tree().node_token(child); if (tokens.GetKind(token) == Lex::TokenKind::Identifier) { - return value_stores.identifiers().Get(tokens.GetIdentifier(token)); + return tokens.GetTokenText(token); } break; } @@ -42,22 +41,19 @@ auto HandleDocumentSymbol( llvm::function_ref< void(llvm::Expected>)> on_done) -> void { - SharedValueStores value_stores; - llvm::vfs::InMemoryFileSystem vfs; - auto lookup = context.files().Lookup(params.textDocument.uri.file()); - CARBON_CHECK(lookup); - vfs.addFile(lookup.key(), /*mtime=*/0, - llvm::MemoryBuffer::getMemBufferCopy(lookup.value())); + auto* file = context.LookupFile(params.textDocument.uri.file()); + if (!file) { + return; + } + + const auto& tree_and_subtrees = file->tree_and_subtrees(); + const auto& tree = tree_and_subtrees.tree(); + const auto& tokens = tree.tokens(); - auto source = - SourceBuffer::MakeFromFile(vfs, lookup.key(), NullDiagnosticConsumer()); - auto tokens = Lex::Lex(value_stores, *source, NullDiagnosticConsumer()); - auto tree = Parse::Parse(tokens, NullDiagnosticConsumer(), nullptr); - Parse::TreeAndSubtrees tree_and_subtrees(tokens, tree); std::vector result; - for (const auto& node : tree.postorder()) { + for (const auto& node_id : tree.postorder()) { clang::clangd::SymbolKind symbol_kind; - switch (tree.node_kind(node)) { + switch (tree.node_kind(node_id)) { case Parse::NodeKind::FunctionDecl: case Parse::NodeKind::FunctionDefinitionStart: symbol_kind = clang::clangd::SymbolKind::Function; @@ -76,9 +72,8 @@ auto HandleDocumentSymbol( continue; } - if (auto name = - GetIdentifierName(value_stores, tokens, tree_and_subtrees, node)) { - auto token = tree.node_token(node); + if (auto name = GetIdentifierName(tree_and_subtrees, node_id)) { + auto token = tree.node_token(node_id); clang::clangd::Position pos{tokens.GetLineNumber(token) - 1, tokens.GetColumnNumber(token) - 1}; diff --git a/toolchain/language_server/handle_text_document.cpp b/toolchain/language_server/handle_text_document.cpp index 9faa92af73652..350a178a055fe 100644 --- a/toolchain/language_server/handle_text_document.cpp +++ b/toolchain/language_server/handle_text_document.cpp @@ -9,17 +9,58 @@ namespace Carbon::LanguageServer { auto HandleDidOpenTextDocument( Context& context, const clang::clangd::DidOpenTextDocumentParams& params) -> void { - context.files().Update(params.textDocument.uri.file(), - params.textDocument.text); + llvm::StringRef filename = params.textDocument.uri.file(); + if (!filename.ends_with(".carbon")) { + // Ignore non-Carbon files. + return; + } + + auto insert_result = context.files().Insert( + filename, [&] { return Context::File(filename.str()); }); + insert_result.value().SetText(context, params.textDocument.text); + if (!insert_result.is_inserted()) { + CARBON_DIAGNOSTIC(LanguageServerOpenDuplicateFile, Warning, + "duplicate open file request; updating content"); + context.file_emitter().Emit(filename, LanguageServerOpenDuplicateFile); + } } auto HandleDidChangeTextDocument( Context& context, const clang::clangd::DidChangeTextDocumentParams& params) -> void { + llvm::StringRef filename = params.textDocument.uri.file(); + if (!filename.ends_with(".carbon")) { + // Ignore non-Carbon files. + return; + } + // Full text is sent if full sync is specified in capabilities. - CARBON_CHECK(params.contentChanges.size() == 1); - context.files().Update(params.textDocument.uri.file(), - params.contentChanges[0].text); + if (params.contentChanges.size() != 1) { + CARBON_DIAGNOSTIC(LanguageServerUnsupportedChanges, Warning, + "received unsupported contentChanges count: {0}", int); + context.file_emitter().Emit(filename, LanguageServerUnsupportedChanges, + params.contentChanges.size()); + return; + } + if (auto* file = context.LookupFile(filename)) { + file->SetText(context, params.contentChanges[0].text); + } +} + +auto HandleDidCloseTextDocument( + Context& context, const clang::clangd::DidCloseTextDocumentParams& params) + -> void { + llvm::StringRef filename = params.textDocument.uri.file(); + if (!filename.ends_with(".carbon")) { + // Ignore non-Carbon files. + return; + } + + if (!context.files().Erase(filename)) { + CARBON_DIAGNOSTIC(LanguageServerCloseUnknownFile, Warning, + "tried closing unknown file; ignoring request"); + context.file_emitter().Emit(filename, LanguageServerCloseUnknownFile); + } } } // namespace Carbon::LanguageServer diff --git a/toolchain/language_server/incoming_messages.cpp b/toolchain/language_server/incoming_messages.cpp index 45bd4291e09fc..70d404671f0a0 100644 --- a/toolchain/language_server/incoming_messages.cpp +++ b/toolchain/language_server/incoming_messages.cpp @@ -76,6 +76,7 @@ IncomingMessages::IncomingMessages(clang::clangd::Transport* transport, AddCallHandler("initialize", &HandleInitialize); AddNotificationHandler("textDocument/didChange", &HandleDidChangeTextDocument); + AddNotificationHandler("textDocument/didClose", &HandleDidCloseTextDocument); AddNotificationHandler("textDocument/didOpen", &HandleDidOpenTextDocument); } diff --git a/toolchain/language_server/language_server.cpp b/toolchain/language_server/language_server.cpp index e55182e98dd11..8eb4ad55d9455 100644 --- a/toolchain/language_server/language_server.cpp +++ b/toolchain/language_server/language_server.cpp @@ -55,7 +55,7 @@ auto Run(FILE* input_stream, llvm::raw_ostream& output_stream, clang::clangd::newJSONTransport(input_stream, output_stream, /*InMirror=*/nullptr, /*Pretty=*/true)); - Context context(&consumer); + Context context(vlog_stream, &consumer); // TODO: Use error_stream in IncomingMessages to report dropped errors. IncomingMessages incoming(transport.get(), &context); OutgoingMessages outgoing(transport.get()); diff --git a/toolchain/language_server/testdata/exit.carbon b/toolchain/language_server/testdata/basics/exit.carbon similarity index 78% rename from toolchain/language_server/testdata/exit.carbon rename to toolchain/language_server/testdata/basics/exit.carbon index 164ed5dd54ba5..3e2436d4dd3c7 100644 --- a/toolchain/language_server/testdata/exit.carbon +++ b/toolchain/language_server/testdata/basics/exit.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/exit.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/exit.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/exit.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/exit.carbon // --- STDIN [[@LSP-NOTIFY:exit]] diff --git a/toolchain/language_server/testdata/fail_empty_stdin.carbon b/toolchain/language_server/testdata/basics/fail_empty_stdin.carbon similarity index 77% rename from toolchain/language_server/testdata/fail_empty_stdin.carbon rename to toolchain/language_server/testdata/basics/fail_empty_stdin.carbon index e538d5062f45a..79f5e68b7b7eb 100644 --- a/toolchain/language_server/testdata/fail_empty_stdin.carbon +++ b/toolchain/language_server/testdata/basics/fail_empty_stdin.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/fail_empty_stdin.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/fail_empty_stdin.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/fail_empty_stdin.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/fail_empty_stdin.carbon // --- STDIN // --- AUTOUPDATE-SPLIT diff --git a/toolchain/language_server/testdata/fail_no_stdin.carbon b/toolchain/language_server/testdata/basics/fail_no_stdin.carbon similarity index 78% rename from toolchain/language_server/testdata/fail_no_stdin.carbon rename to toolchain/language_server/testdata/basics/fail_no_stdin.carbon index 71bc97b5f476c..292258929d946 100644 --- a/toolchain/language_server/testdata/fail_no_stdin.carbon +++ b/toolchain/language_server/testdata/basics/fail_no_stdin.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/fail_no_stdin.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/fail_no_stdin.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/fail_no_stdin.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/fail_no_stdin.carbon // --- AUTOUPDATE-SPLIT diff --git a/toolchain/language_server/testdata/initialize.carbon b/toolchain/language_server/testdata/basics/initialize.carbon similarity index 85% rename from toolchain/language_server/testdata/initialize.carbon rename to toolchain/language_server/testdata/basics/initialize.carbon index 8d0a23ca56392..4129c012b9906 100644 --- a/toolchain/language_server/testdata/initialize.carbon +++ b/toolchain/language_server/testdata/basics/initialize.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/initialize.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/initialize.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/initialize.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/initialize.carbon // --- STDIN [[@LSP-CALL:initialize]] diff --git a/toolchain/language_server/testdata/notify_parse_error.carbon b/toolchain/language_server/testdata/basics/notify_parse_error.carbon similarity index 81% rename from toolchain/language_server/testdata/notify_parse_error.carbon rename to toolchain/language_server/testdata/basics/notify_parse_error.carbon index 7d8f2f27f3832..088a963a5baf2 100644 --- a/toolchain/language_server/testdata/notify_parse_error.carbon +++ b/toolchain/language_server/testdata/basics/notify_parse_error.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/notify_parse_error.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/notify_parse_error.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/notify_parse_error.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/notify_parse_error.carbon // --- STDIN diff --git a/toolchain/language_server/testdata/unexpected_reply.carbon b/toolchain/language_server/testdata/basics/unexpected_reply.carbon similarity index 82% rename from toolchain/language_server/testdata/unexpected_reply.carbon rename to toolchain/language_server/testdata/basics/unexpected_reply.carbon index 143d2c35fdcdb..24ba9f278e66c 100644 --- a/toolchain/language_server/testdata/unexpected_reply.carbon +++ b/toolchain/language_server/testdata/basics/unexpected_reply.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/unexpected_reply.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/unexpected_reply.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/unexpected_reply.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/unexpected_reply.carbon // --- STDIN diff --git a/toolchain/language_server/testdata/unsupported_call.carbon b/toolchain/language_server/testdata/basics/unsupported_call.carbon similarity index 83% rename from toolchain/language_server/testdata/unsupported_call.carbon rename to toolchain/language_server/testdata/basics/unsupported_call.carbon index b2df38a8e4639..ac0bead331cd5 100644 --- a/toolchain/language_server/testdata/unsupported_call.carbon +++ b/toolchain/language_server/testdata/basics/unsupported_call.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/unsupported_call.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/unsupported_call.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/unsupported_call.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/unsupported_call.carbon // --- STDIN diff --git a/toolchain/language_server/testdata/unsupported_notification.carbon b/toolchain/language_server/testdata/basics/unsupported_notification.carbon similarity index 78% rename from toolchain/language_server/testdata/unsupported_notification.carbon rename to toolchain/language_server/testdata/basics/unsupported_notification.carbon index 7d48987348f0f..c27768bfaf784 100644 --- a/toolchain/language_server/testdata/unsupported_notification.carbon +++ b/toolchain/language_server/testdata/basics/unsupported_notification.carbon @@ -4,9 +4,9 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/unsupported_notification.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/basics/unsupported_notification.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/unsupported_notification.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/basics/unsupported_notification.carbon // --- STDIN diff --git a/toolchain/language_server/testdata/verbose.carbon b/toolchain/language_server/testdata/basics/verbose.carbon similarity index 100% rename from toolchain/language_server/testdata/verbose.carbon rename to toolchain/language_server/testdata/basics/verbose.carbon diff --git a/toolchain/language_server/testdata/document_symbol/basics.carbon b/toolchain/language_server/testdata/document_symbol/basics.carbon new file mode 100644 index 0000000000000..5d41fe04d20ce --- /dev/null +++ b/toolchain/language_server/testdata/document_symbol/basics.carbon @@ -0,0 +1,80 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/document_symbol/basics.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/document_symbol/basics.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/empty.carbon", "languageId": "carbon", + "text": ""} +]] +[[@LSP-CALL:textDocument/documentSymbol: + "textDocument": {"uri": "file:/empty.carbon"} +]] +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/invalid.carbon", "languageId": "carbon", + "text": "text"} +]] +[[@LSP-CALL:textDocument/documentSymbol: + "textDocument": {"uri": "file:/invalid.carbon"} +]] +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/fn.carbon", "languageId": "carbon", + "text": "fn F() {}"} +]] +[[@LSP-CALL:textDocument/documentSymbol: + "textDocument": {"uri": "file:/fn.carbon"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDOUT: Content-Length: 49{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "id": 1, +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "result": [] +// CHECK:STDOUT: }Content-Length: 49{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "id": 2, +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "result": [] +// CHECK:STDOUT: }Content-Length: 459{{\r}} +// CHECK:STDOUT: {{\r}} +// CHECK:STDOUT: { +// CHECK:STDOUT: "id": 3, +// CHECK:STDOUT: "jsonrpc": "2.0", +// CHECK:STDOUT: "result": [ +// CHECK:STDOUT: { +// CHECK:STDOUT: "kind": 12, +// CHECK:STDOUT: "name": "F", +// CHECK:STDOUT: "range": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 7, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 7, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: }, +// CHECK:STDOUT: "selectionRange": { +// CHECK:STDOUT: "end": { +// CHECK:STDOUT: "character": 7, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: }, +// CHECK:STDOUT: "start": { +// CHECK:STDOUT: "character": 7, +// CHECK:STDOUT: "line": 0 +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: ] +// CHECK:STDOUT: } diff --git a/toolchain/language_server/testdata/document_symbol/language.carbon b/toolchain/language_server/testdata/document_symbol/language.carbon new file mode 100644 index 0000000000000..0be2b96e77352 --- /dev/null +++ b/toolchain/language_server/testdata/document_symbol/language.carbon @@ -0,0 +1,21 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/document_symbol/language.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/document_symbol/language.carbon + +// --- STDIN +[[@LSP-CALL:textDocument/documentSymbol: + "textDocument": {"uri": "file:/test.cpp"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.cpp: warning: non-Carbon file requested [LanguageServerFileUnsupported] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/document_symbol/unknown.carbon b/toolchain/language_server/testdata/document_symbol/unknown.carbon new file mode 100644 index 0000000000000..2fca57d2e68cd --- /dev/null +++ b/toolchain/language_server/testdata/document_symbol/unknown.carbon @@ -0,0 +1,21 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/document_symbol/unknown.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/document_symbol/unknown.carbon + +// --- STDIN +[[@LSP-CALL:textDocument/documentSymbol: + "textDocument": {"uri": "file:/test.carbon"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/text_document/change_count.carbon b/toolchain/language_server/testdata/text_document/change_count.carbon new file mode 100644 index 0000000000000..4feae700916bc --- /dev/null +++ b/toolchain/language_server/testdata/text_document/change_count.carbon @@ -0,0 +1,39 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/change_count.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/change_count.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", + "text": "test"} +]] +[[@LSP-NOTIFY:textDocument/didChange: + "textDocument": {"uri": "file:/test.carbon"}, + "contentChanges": [] +]] +[[@LSP-NOTIFY:textDocument/didChange: + "textDocument": {"uri": "file:/test.carbon"}, + "contentChanges": [ + {"range": {"start": {"line": 5, "character": 23 }, + "end": {"line": 6, "character": 0 }}, + "text": "a"}, + {"range": {"start": {"line": 5, "character": 23 }, + "end": {"line": 6, "character": 0 }}, + "text": "a"} + ] +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.carbon: warning: received unsupported contentChanges count: 0 [LanguageServerUnsupportedChanges] +// CHECK:STDERR: +// CHECK:STDERR: /test.carbon: warning: received unsupported contentChanges count: 2 [LanguageServerUnsupportedChanges] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/text_document/change_unknown.carbon b/toolchain/language_server/testdata/text_document/change_unknown.carbon new file mode 100644 index 0000000000000..edf2b3bdafe7b --- /dev/null +++ b/toolchain/language_server/testdata/text_document/change_unknown.carbon @@ -0,0 +1,22 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/change_unknown.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/change_unknown.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didChange: + "textDocument": {"uri": "file:/test.carbon"}, + "contentChanges": [{"text": "new content"}] +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.carbon: warning: unknown file requested [LanguageServerFileUnknown] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/text_document/close_unknown.carbon b/toolchain/language_server/testdata/text_document/close_unknown.carbon new file mode 100644 index 0000000000000..2729b85efec77 --- /dev/null +++ b/toolchain/language_server/testdata/text_document/close_unknown.carbon @@ -0,0 +1,21 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/close_unknown.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/close_unknown.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didClose: + "textDocument": {"uri": "file:/test.carbon"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.carbon: warning: tried closing unknown file; ignoring request [LanguageServerCloseUnknownFile] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/text_document/open_change_close.carbon b/toolchain/language_server/testdata/text_document/open_change_close.carbon new file mode 100644 index 0000000000000..76d24c86b18ac --- /dev/null +++ b/toolchain/language_server/testdata/text_document/open_change_close.carbon @@ -0,0 +1,27 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/open_change_close.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/open_change_close.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", + "text": "test"} +]] +[[@LSP-NOTIFY:textDocument/didChange: + "textDocument": {"uri": "file:/test.carbon"}, + "contentChanges": [{"text": "new content"}] +]] +[[@LSP-NOTIFY:textDocument/didClose: + "textDocument": {"uri": "file:/test.carbon"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDOUT: diff --git a/toolchain/language_server/testdata/text_document/open_duplicate.carbon b/toolchain/language_server/testdata/text_document/open_duplicate.carbon new file mode 100644 index 0000000000000..ef0439a3c6562 --- /dev/null +++ b/toolchain/language_server/testdata/text_document/open_duplicate.carbon @@ -0,0 +1,26 @@ +// 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 +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/text_document/open_duplicate.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/text_document/open_duplicate.carbon + +// --- STDIN +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", + "text": "test"} +]] +[[@LSP-NOTIFY:textDocument/didOpen: + "textDocument": {"uri": "file:/test.carbon", "languageId": "carbon", + "text": "test"} +]] +[[@LSP-NOTIFY:exit]] + +// --- AUTOUPDATE-SPLIT + +// CHECK:STDERR: /test.carbon: warning: duplicate open file request; updating content [LanguageServerOpenDuplicateFile] +// CHECK:STDERR: +// CHECK:STDOUT: diff --git a/toolchain/source/source_buffer.cpp b/toolchain/source/source_buffer.cpp index 59de7378f4e59..fe98e04d9db49 100644 --- a/toolchain/source/source_buffer.cpp +++ b/toolchain/source/source_buffer.cpp @@ -51,6 +51,15 @@ auto SourceBuffer::MakeFromFile(llvm::vfs::FileSystem& fs, filename, is_regular_file, consumer); } +auto SourceBuffer::MakeFromStringCopy(llvm::StringRef filename, + llvm::StringRef text, + DiagnosticConsumer& consumer) + -> std::optional { + return MakeFromMemoryBuffer( + llvm::MemoryBuffer::getMemBufferCopy(text, filename), filename, + /*is_regular_file=*/true, consumer); +} + auto SourceBuffer::MakeFromMemoryBuffer( llvm::ErrorOr> buffer, llvm::StringRef filename, bool is_regular_file, diff --git a/toolchain/source/source_buffer.h b/toolchain/source/source_buffer.h index 93e3429bfd203..cc7a7b850aa29 100644 --- a/toolchain/source/source_buffer.h +++ b/toolchain/source/source_buffer.h @@ -57,6 +57,12 @@ class SourceBuffer { } } + // Returns a source buffer with the provided text content. Copies `filename` + // and `text` to take ownership. + static auto MakeFromStringCopy(llvm::StringRef filename, llvm::StringRef text, + DiagnosticConsumer& consumer) + -> std::optional; + // Use one of the factory functions above to create a source buffer. SourceBuffer() = delete;