tsl-devkit/lsp-server/src/provider/text_document/formatting.cppm

209 lines
6.2 KiB
C++

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<Formatting, IRequestProvider>
{
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()<protocol::DocumentFormattingParams>(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();
}
}