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;