module; export module lsp.provider.text_document.formatting; import spdlog; import std; import lsp.protocol; import lsp.codec.facade; import lsp.manager.manager_hub; import lsp.provider.base.interface; export namespace lsp::provider::text_document { class Formatting : public AutoRegisterProvider { public: static constexpr std::string_view kMethod = "textDocument/formatting"; static constexpr std::string_view kProviderName = "TextDocumentFormatting"; Formatting() = default; std::string ProvideResponse(const protocol::RequestMessage& request, ExecutionContext& context) override; }; } namespace lsp::provider::text_document { namespace { namespace codec = lsp::codec; protocol::Range FullDocumentRange(const protocol::string& content) { protocol::Range range{}; range.start.line = 0; range.start.character = 0; protocol::uinteger line_count = 0; protocol::uinteger last_line_len = 0; for (char ch : content) { if (ch == '\n') { line_count++; last_line_len = 0; } else { last_line_len++; } } range.end.line = line_count; range.end.character = last_line_len; return range; } std::string_view DetectNewlineStyle(std::string_view content) { if (content.find("\r\n") != std::string_view::npos) { return "\r\n"; } return "\n"; } std::string TrimTrailingWhitespace(std::string_view content) { std::string out; out.reserve(content.size()); std::size_t cursor = 0; while (cursor < content.size()) { auto newline_pos = content.find('\n', cursor); bool has_newline = newline_pos != std::string_view::npos; std::size_t line_end = has_newline ? newline_pos : content.size(); std::string_view newline_suffix; if (has_newline && line_end > cursor && content[line_end - 1] == '\r') { newline_suffix = "\r\n"; line_end -= 1; } else if (has_newline) { newline_suffix = "\n"; } else { newline_suffix = ""; } std::size_t trim_end = line_end; while (trim_end > cursor) { char ch = content[trim_end - 1]; if (ch != ' ' && ch != '\t') { break; } trim_end--; } out.append(content.substr(cursor, trim_end - cursor)); out.append(newline_suffix); if (!has_newline) { break; } cursor = newline_pos + 1; } return out; } void TrimFinalNewlines(std::string& text) { while (!text.empty()) { if (text.size() >= 2 && text.ends_with("\r\n")) { text.resize(text.size() - 2); continue; } if (text.back() == '\n') { text.pop_back(); continue; } break; } } std::string FormatDocumentText(std::string_view content, const protocol::FormattingOptions& options) { const bool trim_trailing = options.trimTrailingWhitespace.value_or(true); const bool trim_final_newlines = options.trimFinalNewlines.value_or(false); const bool insert_final_newline = options.insertFinalNewline.value_or(false); const auto newline = DetectNewlineStyle(content); std::string formatted = trim_trailing ? TrimTrailingWhitespace(content) : std::string(content); if (trim_final_newlines) { TrimFinalNewlines(formatted); } if (insert_final_newline) { if (!formatted.ends_with(newline)) { formatted.append(newline); } } return formatted; } } std::string Formatting::ProvideResponse(const protocol::RequestMessage& request, [[maybe_unused]] ExecutionContext& context) { spdlog::debug("TextDocumentFormattingProvider: Providing response for method {}", request.method); if (!request.params.has_value()) { spdlog::warn("{}: Missing params in request", GetProviderName()); return BuildErrorResponseMessage(request, protocol::ErrorCodes::InvalidParams, "Missing params"); } protocol::DocumentFormattingParams params = codec::FromLSPAny.template operator()(request.params.value()); auto& hub = context.GetManagerHub(); auto content = hub.documents().GetContent(params.textDocument.uri); protocol::LSPArray edits; if (content) { std::string formatted = FormatDocumentText(*content, params.options); if (formatted != *content) { protocol::LSPObject edit; edit["range"] = codec::ToLSPAny(FullDocumentRange(*content)); edit["newText"] = protocol::string(std::move(formatted)); edits.emplace_back(std::move(edit)); } } protocol::ResponseMessage response; response.id = request.id; response.result = protocol::LSPAny(std::move(edits)); auto json = codec::Serialize(response); if (!json.has_value()) { return BuildErrorResponseMessage(request, protocol::ErrorCodes::InternalError, "Failed to serialize response"); } return json.value(); } }