Merge pull request #118598 from Finishxx/gdscript-lsp-color-bbcode-support
GDScript LSP: Convert [color] BBCode to <span> HTML for capable clients
This commit is contained in:
@@ -250,6 +250,13 @@ Variant GDScriptLanguageProtocol::initialize(const Dictionary &p_params) {
|
||||
client->behavior.use_snippets_for_brace_completion = get_deep(capabilities, false,
|
||||
"textDocument", "completion", "completionItem", "snippetSupport");
|
||||
|
||||
Array allowed_tags = get_deep(capabilities, Array(), "general", "markdown", "allowedTags");
|
||||
for (const Variant &tag : allowed_tags) {
|
||||
if (tag.is_string()) {
|
||||
client->behavior.markdown_allowed_html_tags.insert(tag);
|
||||
}
|
||||
}
|
||||
|
||||
return ret.to_json();
|
||||
}
|
||||
|
||||
@@ -440,6 +447,12 @@ ExtendGDScriptParser *GDScriptLanguageProtocol::get_parse_result(const String &p
|
||||
return *cached_parser;
|
||||
}
|
||||
|
||||
const HashSet<String> &GDScriptLanguageProtocol::get_client_markdown_allowed_html_tags() const {
|
||||
static const HashSet<String> default_tags = {};
|
||||
LSP_CLIENT_V(default_tags);
|
||||
return client->behavior.markdown_allowed_html_tags;
|
||||
}
|
||||
|
||||
void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
|
||||
LSP_CLIENT;
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
struct ClientBehavior {
|
||||
/** If `true` use snippet insert mode to position the cursor between braces of completion options. If `false` strip braces from completion options since we can't provide good UX for them. */
|
||||
bool use_snippets_for_brace_completion = false;
|
||||
/** HTML tags, e.g. `span`, which the client is capable of rendering inside Markdown content. Assumes full support for attributes e.g. `style`. */
|
||||
HashSet<String> markdown_allowed_html_tags;
|
||||
};
|
||||
|
||||
private:
|
||||
@@ -167,6 +169,14 @@ public:
|
||||
*/
|
||||
ExtendGDScriptParser *get_parse_result(const String &p_path);
|
||||
|
||||
/**
|
||||
* Returns the HTML tags the client can render inside Markdown content, e.g. `span`.
|
||||
* Defaults to an empty set if no client is connected.
|
||||
*
|
||||
* TODO: Remove after moving endpoints into unified class. Access non-null client directly.
|
||||
*/
|
||||
const HashSet<String> &get_client_markdown_allowed_html_tags() const;
|
||||
|
||||
GDScriptLanguageProtocol();
|
||||
~GDScriptLanguageProtocol() {
|
||||
clients.clear();
|
||||
|
||||
@@ -244,7 +244,8 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) {
|
||||
}
|
||||
|
||||
if (symbol) {
|
||||
item.documentation = symbol->render();
|
||||
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
|
||||
item.documentation = symbol->render(allowed_tags);
|
||||
}
|
||||
|
||||
if (item.kind == LSP::CompletionItemKind::Event) {
|
||||
@@ -299,7 +300,8 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
|
||||
const LSP::DocumentSymbol *symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params);
|
||||
if (symbol) {
|
||||
LSP::Hover hover;
|
||||
hover.contents = symbol->render();
|
||||
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
|
||||
hover.contents = symbol->render(allowed_tags);
|
||||
hover.range.start = params.position;
|
||||
hover.range.end = params.position;
|
||||
return hover.to_json();
|
||||
@@ -309,9 +311,10 @@ Variant GDScriptTextDocument::hover(const Dictionary &p_params) {
|
||||
Array contents;
|
||||
List<const LSP::DocumentSymbol *> list;
|
||||
GDScriptLanguageProtocol::get_singleton()->resolve_related_symbols(params, list);
|
||||
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
|
||||
for (const LSP::DocumentSymbol *&E : list) {
|
||||
if (const LSP::DocumentSymbol *s = E) {
|
||||
contents.push_back(s->render().value);
|
||||
contents.push_back(s->render(allowed_tags).value);
|
||||
}
|
||||
}
|
||||
ret["contents"] = contents;
|
||||
|
||||
@@ -771,7 +771,8 @@ Error GDScriptWorkspace::resolve_signature(const LSP::TextDocumentPositionParams
|
||||
if (symbol->kind == LSP::SymbolKind::Method || symbol->kind == LSP::SymbolKind::Function) {
|
||||
LSP::SignatureInformation signature_info;
|
||||
signature_info.label = symbol->detail;
|
||||
signature_info.documentation = symbol->render();
|
||||
const HashSet<String> &allowed_tags = GDScriptLanguageProtocol::get_singleton()->get_client_markdown_allowed_html_tags();
|
||||
signature_info.documentation = symbol->render(allowed_tags);
|
||||
|
||||
for (int i = 0; i < symbol->children.size(); i++) {
|
||||
const LSP::DocumentSymbol &arg = symbol->children[i];
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace LSP {
|
||||
typedef String DocumentUri;
|
||||
|
||||
/** Format BBCode documentation from DocData to markdown */
|
||||
static String marked_documentation(const String &p_bbcode);
|
||||
static String marked_documentation(const String &p_bbcode, const HashSet<String> &p_allowed_html_tags);
|
||||
|
||||
/**
|
||||
* Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
|
||||
@@ -1306,13 +1306,13 @@ struct DocumentSymbol {
|
||||
return dict;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ MarkupContent render() const {
|
||||
_FORCE_INLINE_ MarkupContent render(const HashSet<String> &p_allowed_html_tags) const {
|
||||
MarkupContent markdown;
|
||||
if (detail.length()) {
|
||||
markdown.value = "\t" + detail + "\n\n";
|
||||
}
|
||||
if (documentation.length()) {
|
||||
markdown.value += marked_documentation(documentation) + "\n\n";
|
||||
markdown.value += marked_documentation(documentation, p_allowed_html_tags) + "\n\n";
|
||||
}
|
||||
if (script_path.length()) {
|
||||
markdown.value += "Defined in [" + script_path + "](" + uri + ")";
|
||||
@@ -1916,7 +1916,8 @@ struct GodotCapabilities {
|
||||
};
|
||||
|
||||
/** Format BBCode documentation from DocData to markdown */
|
||||
static String marked_documentation(const String &p_bbcode) {
|
||||
static String marked_documentation(const String &p_bbcode, const HashSet<String> &p_allowed_html_tags) {
|
||||
bool span_allowed = p_allowed_html_tags.has("span");
|
||||
String markdown = p_bbcode.strip_edges();
|
||||
|
||||
Vector<String> lines = markdown.split("\n");
|
||||
@@ -1996,7 +1997,6 @@ static String marked_documentation(const String &p_bbcode) {
|
||||
line = line.replace("[center]", "");
|
||||
line = line.replace("[/center]", "");
|
||||
line = line.replace("[/font]", "");
|
||||
line = line.replace("[/color]", "");
|
||||
line = line.replace("[/img]", "");
|
||||
|
||||
// Convert remaining simple bracketed class names to backticks and literal brackets.
|
||||
@@ -2069,6 +2069,35 @@ static String marked_documentation(const String &p_bbcode) {
|
||||
pos += replacement.length();
|
||||
}
|
||||
|
||||
// Convert [color] BBCode to <span>, if the client is capable of rendering it, strip it otherwise.
|
||||
pos = 0;
|
||||
while ((pos = line.find("[color=", pos)) != -1) {
|
||||
constexpr int COLOR_OPEN_TAG_LENGTH = 7; // Length of "[color=".
|
||||
constexpr int COLOR_CLOSE_TAG_LENGTH = 8; // Length of "[/color]".
|
||||
|
||||
int color_end = line.find_char(']', pos);
|
||||
int close_start = line.find("[/color]", color_end);
|
||||
if (color_end == -1 || close_start == -1) {
|
||||
break;
|
||||
}
|
||||
String text = line.substr(color_end + 1, close_start - color_end - 1);
|
||||
String replacement;
|
||||
if (span_allowed) {
|
||||
const String color = line.substr(pos + COLOR_OPEN_TAG_LENGTH, color_end - pos - COLOR_OPEN_TAG_LENGTH).strip_edges();
|
||||
if (Color::html_is_valid(color)) {
|
||||
replacement = "<span style=\"color:" + color + "\">" + text + "</span>";
|
||||
} else if (int named_color_index = Color::find_named_color(color); named_color_index != -1) {
|
||||
replacement = "<span style=\"color:#" + Color::get_named_color(named_color_index).to_html(false) + "\">" + text + "</span>";
|
||||
}
|
||||
}
|
||||
// If no span replacement was produced (client can't render spans, or invalid color), just keep the inner text.
|
||||
if (replacement.is_empty()) {
|
||||
replacement = text;
|
||||
}
|
||||
line = line.substr(0, pos) + replacement + line.substr(close_start + COLOR_CLOSE_TAG_LENGTH);
|
||||
pos += replacement.length();
|
||||
}
|
||||
|
||||
// Replace the various link types with inline code ([class MyNode] to `MyNode`).
|
||||
// Uses a while loop because there can occasionally be multiple links of the same type in a single line.
|
||||
const Vector<String> link_start_patterns = {
|
||||
@@ -2090,10 +2119,10 @@ static String marked_documentation(const String &p_bbcode) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tags with attributes like [color=red], as they don't have a direct Markdown
|
||||
// Remove tags with attributes like [font=Arial], as they don't have a direct Markdown
|
||||
// equivalent supported by external tools.
|
||||
const String attribute_tags[] = {
|
||||
"color", "font", "img"
|
||||
"font", "img"
|
||||
};
|
||||
for (const String &tag_name : attribute_tags) {
|
||||
int tag_pos = 0;
|
||||
|
||||
Reference in New Issue
Block a user