handle_text_document.cpp 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "toolchain/language_server/handle.h"
  5. namespace Carbon::LanguageServer {
  6. // Implements `textDocument/didOpen`:
  7. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen
  8. auto HandleDidOpenTextDocument(
  9. Context& context, const clang::clangd::DidOpenTextDocumentParams& params)
  10. -> void {
  11. llvm::StringRef filename = params.textDocument.uri.file();
  12. if (!filename.ends_with(".carbon")) {
  13. // Ignore non-Carbon files.
  14. return;
  15. }
  16. auto insert_result = context.files().Insert(
  17. filename, [&] { return Context::File(params.textDocument.uri); });
  18. insert_result.value().SetText(context, params.textDocument.version,
  19. params.textDocument.text);
  20. if (!insert_result.is_inserted()) {
  21. CARBON_DIAGNOSTIC(LanguageServerOpenDuplicateFile, Warning,
  22. "duplicate open file request; updating content");
  23. context.file_emitter().Emit(filename, LanguageServerOpenDuplicateFile);
  24. }
  25. }
  26. // Returns the result of moving from `cursor_index` past `line_count` lines.
  27. static auto GetNthLineIndex(llvm::StringRef contents, size_t cursor_index,
  28. size_t line_count) -> size_t {
  29. for ([[maybe_unused]] auto _ : llvm::seq(line_count)) {
  30. const size_t newline_index = contents.find('\n', cursor_index);
  31. CARBON_CHECK(newline_index != std::string::npos,
  32. "Line number greater than number of lines in the file");
  33. cursor_index = newline_index + 1;
  34. }
  35. return cursor_index;
  36. }
  37. // Takes start and end positions and returns a tuple with start and end
  38. // offsets. Positions are based on row and column numbers in the source
  39. // code. We often need to know the offsets when modifying strings, so
  40. // this function helps us calculate the offsets. It assumes that the start
  41. // position comes before the end position.
  42. static auto PositionToIndex(llvm::StringRef contents,
  43. const clang::clangd::Position& start,
  44. const clang::clangd::Position& end)
  45. -> std::tuple<size_t, size_t> {
  46. size_t start_index =
  47. GetNthLineIndex(contents, /*cursor_index=*/0, start.line);
  48. size_t end_index = GetNthLineIndex(contents, /*cursor_index=*/start_index,
  49. end.line - start.line);
  50. start_index += start.character;
  51. end_index += end.character;
  52. CARBON_CHECK(end_index <= contents.size(),
  53. "Position greater than source code size");
  54. return {start_index, end_index};
  55. }
  56. // LSP allows full and incremental document synchronization. It sends the
  57. // entire source code when doing full sync. However, when doing incremental
  58. // sync, it only sends the list of changes to be performed on the source
  59. // code. It is necessary to apply changes in the order in which they are
  60. // received. These changes can have one of 1) full document or 2) start
  61. // position, end position, and the text which replaces original text between
  62. // these start and end positions. If the range property is absent, then we
  63. // do full sync. Otherwise, we calculate start and end offsets and replace
  64. // the contents between them with the text we received in change.text.
  65. static auto ApplyChanges(
  66. std::string& source,
  67. const std::vector<clang::clangd::TextDocumentContentChangeEvent>&
  68. content_changes) -> void {
  69. for (const auto& change : content_changes) {
  70. // If range is not present, then we replace entire text.
  71. if (!change.range) {
  72. source = change.text;
  73. continue;
  74. }
  75. // TODO: Use lexer line number data but avoid re-lexing multiple times.
  76. auto [start_index, end_index] =
  77. PositionToIndex(source, change.range->start, change.range->end);
  78. source.replace(start_index, end_index - start_index, change.text);
  79. }
  80. }
  81. // Implements `textDocument/didChange`:
  82. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didChange
  83. auto HandleDidChangeTextDocument(
  84. Context& context, const clang::clangd::DidChangeTextDocumentParams& params)
  85. -> void {
  86. llvm::StringRef filename = params.textDocument.uri.file();
  87. if (!filename.ends_with(".carbon")) {
  88. // Ignore non-Carbon files.
  89. return;
  90. }
  91. if (auto* file = context.LookupFile(filename)) {
  92. // We copy the document to a new string, apply changes to the string, and
  93. // set the string as the new text.
  94. std::string source = file->text().str();
  95. ApplyChanges(source, params.contentChanges);
  96. file->SetText(context, params.textDocument.version, source);
  97. }
  98. }
  99. // Implements `textDocument/didClose`:
  100. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didClose
  101. auto HandleDidCloseTextDocument(
  102. Context& context, const clang::clangd::DidCloseTextDocumentParams& params)
  103. -> void {
  104. llvm::StringRef filename = params.textDocument.uri.file();
  105. if (!filename.ends_with(".carbon")) {
  106. // Ignore non-Carbon files.
  107. return;
  108. }
  109. if (context.files().Erase(filename)) {
  110. // Clear diagnostics when the document closes. Otherwise, any diagnostics
  111. // will linger.
  112. context.PublishDiagnostics({.uri = params.textDocument.uri});
  113. } else {
  114. CARBON_DIAGNOSTIC(LanguageServerCloseUnknownFile, Warning,
  115. "tried closing unknown file; ignoring request");
  116. context.file_emitter().Emit(filename, LanguageServerCloseUnknownFile);
  117. }
  118. }
  119. } // namespace Carbon::LanguageServer