209 lines
6.2 KiB
C++
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();
|
|
}
|
|
}
|