handle_text_document.cpp 4.9 KB

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