Merge pull request #34920 from GodotExplorer/lsp-signatureHelper
GDScript LSP: Implement signatureHelp
This commit is contained in:
commit
5a8107f70a
8 changed files with 232 additions and 1 deletions
|
@ -522,6 +522,51 @@ const lsp::DocumentSymbol *ExtendGDScriptParser::search_symbol_defined_at_line(i
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error ExtendGDScriptParser::get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const {
|
||||||
|
|
||||||
|
ERR_FAIL_INDEX_V(p_position.line, lines.size(), ERR_INVALID_PARAMETER);
|
||||||
|
|
||||||
|
int bracket_stack = 0;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
for (int l = p_position.line; l >= 0; --l) {
|
||||||
|
String line = lines[l];
|
||||||
|
int c = line.length() - 1;
|
||||||
|
if (l == p_position.line) {
|
||||||
|
c = MIN(c, p_position.character - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (c >= 0) {
|
||||||
|
const CharType &charactor = line[c];
|
||||||
|
if (charactor == ')') {
|
||||||
|
++bracket_stack;
|
||||||
|
} else if (charactor == '(') {
|
||||||
|
--bracket_stack;
|
||||||
|
if (bracket_stack < 0) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bracket_stack <= 0 && charactor == ',') {
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
--c;
|
||||||
|
if (found) {
|
||||||
|
r_func_pos.character = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
r_func_pos.line = l;
|
||||||
|
r_arg_index = index;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERR_METHOD_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
|
const lsp::DocumentSymbol *ExtendGDScriptParser::get_symbol_defined_at_line(int p_line) const {
|
||||||
if (p_line <= 0) {
|
if (p_line <= 0) {
|
||||||
return &class_symbol;
|
return &class_symbol;
|
||||||
|
|
|
@ -83,6 +83,8 @@ public:
|
||||||
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
|
_FORCE_INLINE_ const ClassMembers &get_members() const { return members; }
|
||||||
_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
|
_FORCE_INLINE_ const HashMap<String, ClassMembers> &get_inner_classes() const { return inner_classes; }
|
||||||
|
|
||||||
|
Error get_left_function_call(const lsp::Position &p_position, lsp::Position &r_func_pos, int &r_arg_index) const;
|
||||||
|
|
||||||
String get_text_for_completion(const lsp::Position &p_cursor) const;
|
String get_text_for_completion(const lsp::Position &p_cursor) const;
|
||||||
String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const;
|
String get_text_for_lookup_symbol(const lsp::Position &p_cursor, const String &p_symbol = "", bool p_func_requred = false) const;
|
||||||
String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
|
String get_identifier_under_position(const lsp::Position &p_position, Vector2i &p_offset) const;
|
||||||
|
|
|
@ -38,7 +38,7 @@ GDScriptLanguageServer::GDScriptLanguageServer() {
|
||||||
thread = NULL;
|
thread = NULL;
|
||||||
thread_exit = false;
|
thread_exit = false;
|
||||||
_EDITOR_DEF("network/language_server/remote_port", 6008);
|
_EDITOR_DEF("network/language_server/remote_port", 6008);
|
||||||
_EDITOR_DEF("network/language_server/enable_smart_resolve", false);
|
_EDITOR_DEF("network/language_server/enable_smart_resolve", true);
|
||||||
_EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
|
_EDITOR_DEF("network/language_server/show_native_symbols_in_editor", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ void GDScriptTextDocument::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
|
ClassDB::bind_method(D_METHOD("hover"), &GDScriptTextDocument::hover);
|
||||||
ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
|
ClassDB::bind_method(D_METHOD("definition"), &GDScriptTextDocument::definition);
|
||||||
ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
|
ClassDB::bind_method(D_METHOD("declaration"), &GDScriptTextDocument::declaration);
|
||||||
|
ClassDB::bind_method(D_METHOD("signatureHelp"), &GDScriptTextDocument::signatureHelp);
|
||||||
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
|
ClassDB::bind_method(D_METHOD("show_native_symbol_in_editor"), &GDScriptTextDocument::show_native_symbol_in_editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,6 +388,20 @@ Variant GDScriptTextDocument::declaration(const Dictionary &p_params) {
|
||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Variant GDScriptTextDocument::signatureHelp(const Dictionary &p_params) {
|
||||||
|
Variant ret;
|
||||||
|
|
||||||
|
lsp::TextDocumentPositionParams params;
|
||||||
|
params.load(p_params);
|
||||||
|
|
||||||
|
lsp::SignatureHelp s;
|
||||||
|
if (OK == GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_signature(params, s)) {
|
||||||
|
ret = s.to_json();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
GDScriptTextDocument::GDScriptTextDocument() {
|
GDScriptTextDocument::GDScriptTextDocument() {
|
||||||
file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
|
file_checker = FileAccess::create(FileAccess::ACCESS_RESOURCES);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ public:
|
||||||
Variant hover(const Dictionary &p_params);
|
Variant hover(const Dictionary &p_params);
|
||||||
Array definition(const Dictionary &p_params);
|
Array definition(const Dictionary &p_params);
|
||||||
Variant declaration(const Dictionary &p_params);
|
Variant declaration(const Dictionary &p_params);
|
||||||
|
Variant signatureHelp(const Dictionary &p_params);
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
|
|
|
@ -252,6 +252,12 @@ Error GDScriptWorkspace::initialize() {
|
||||||
bool arg_default_value_started = false;
|
bool arg_default_value_started = false;
|
||||||
for (int j = 0; j < data.arguments.size(); j++) {
|
for (int j = 0; j < data.arguments.size(); j++) {
|
||||||
const DocData::ArgumentDoc &arg = data.arguments[j];
|
const DocData::ArgumentDoc &arg = data.arguments[j];
|
||||||
|
|
||||||
|
lsp::DocumentSymbol symbol_arg;
|
||||||
|
symbol_arg.name = arg.name;
|
||||||
|
symbol_arg.kind = lsp::SymbolKind::Variable;
|
||||||
|
symbol_arg.detail = arg.type;
|
||||||
|
|
||||||
if (!arg_default_value_started && !arg.default_value.empty()) {
|
if (!arg_default_value_started && !arg.default_value.empty()) {
|
||||||
arg_default_value_started = true;
|
arg_default_value_started = true;
|
||||||
}
|
}
|
||||||
|
@ -263,6 +269,8 @@ Error GDScriptWorkspace::initialize() {
|
||||||
arg_str += ", ";
|
arg_str += ", ";
|
||||||
}
|
}
|
||||||
params += arg_str;
|
params += arg_str;
|
||||||
|
|
||||||
|
symbol.children.push_back(symbol_arg);
|
||||||
}
|
}
|
||||||
if (data.qualifiers.find("vararg") != -1) {
|
if (data.qualifiers.find("vararg") != -1) {
|
||||||
params += params.empty() ? "..." : ", ...";
|
params += params.empty() ? "..." : ", ...";
|
||||||
|
@ -513,6 +521,49 @@ Dictionary GDScriptWorkspace::generate_script_api(const String &p_path) {
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Error GDScriptWorkspace::resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature) {
|
||||||
|
if (const ExtendGDScriptParser *parser = get_parse_result(get_file_path(p_doc_pos.textDocument.uri))) {
|
||||||
|
|
||||||
|
lsp::TextDocumentPositionParams text_pos;
|
||||||
|
text_pos.textDocument = p_doc_pos.textDocument;
|
||||||
|
|
||||||
|
if (parser->get_left_function_call(p_doc_pos.position, text_pos.position, r_signature.activeParameter) == OK) {
|
||||||
|
|
||||||
|
List<const lsp::DocumentSymbol *> symbols;
|
||||||
|
|
||||||
|
if (const lsp::DocumentSymbol *symbol = resolve_symbol(text_pos)) {
|
||||||
|
symbols.push_back(symbol);
|
||||||
|
} else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) {
|
||||||
|
GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_related_symbols(text_pos, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (List<const lsp::DocumentSymbol *>::Element *E = symbols.front(); E; E = E->next()) {
|
||||||
|
const lsp::DocumentSymbol *symbol = E->get();
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (int i = 0; i < symbol->children.size(); i++) {
|
||||||
|
const lsp::DocumentSymbol &arg = symbol->children[i];
|
||||||
|
lsp::ParameterInformation arg_info;
|
||||||
|
arg_info.label = arg.name;
|
||||||
|
signature_info.parameters.push_back(arg_info);
|
||||||
|
}
|
||||||
|
r_signature.signatures.push_back(signature_info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r_signature.signatures.size()) {
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ERR_METHOD_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
GDScriptWorkspace::GDScriptWorkspace() {
|
GDScriptWorkspace::GDScriptWorkspace() {
|
||||||
ProjectSettings::get_singleton()->get_resource_path();
|
ProjectSettings::get_singleton()->get_resource_path();
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ public:
|
||||||
const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
|
const lsp::DocumentSymbol *resolve_native_symbol(const lsp::NativeSymbolInspectParams &p_params);
|
||||||
void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
|
void resolve_document_links(const String &p_uri, List<lsp::DocumentLink> &r_list);
|
||||||
Dictionary generate_script_api(const String &p_path);
|
Dictionary generate_script_api(const String &p_path);
|
||||||
|
Error resolve_signature(const lsp::TextDocumentPositionParams &p_doc_pos, lsp::SignatureHelp &r_signature);
|
||||||
|
|
||||||
GDScriptWorkspace();
|
GDScriptWorkspace();
|
||||||
~GDScriptWorkspace();
|
~GDScriptWorkspace();
|
||||||
|
|
|
@ -1402,6 +1402,120 @@ struct Hover {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a parameter of a callable-signature. A parameter can
|
||||||
|
* have a label and a doc-comment.
|
||||||
|
*/
|
||||||
|
struct ParameterInformation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label of this parameter information.
|
||||||
|
*
|
||||||
|
* Either a string or an inclusive start and exclusive end offsets within its containing
|
||||||
|
* signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
|
||||||
|
* string representation as `Position` and `Range` does.
|
||||||
|
*
|
||||||
|
* *Note*: a label of type string should be a substring of its containing signature label.
|
||||||
|
* Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
|
||||||
|
*/
|
||||||
|
String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The human-readable doc-comment of this parameter. Will be shown
|
||||||
|
* in the UI but can be omitted.
|
||||||
|
*/
|
||||||
|
MarkupContent documentation;
|
||||||
|
|
||||||
|
Dictionary to_json() const {
|
||||||
|
Dictionary dict;
|
||||||
|
dict["label"] = label;
|
||||||
|
dict["documentation"] = documentation.to_json();
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the signature of something callable. A signature
|
||||||
|
* can have a label, like a function-name, a doc-comment, and
|
||||||
|
* a set of parameters.
|
||||||
|
*/
|
||||||
|
struct SignatureInformation {
|
||||||
|
/**
|
||||||
|
* The label of this signature. Will be shown in
|
||||||
|
* the UI.
|
||||||
|
*/
|
||||||
|
String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The human-readable doc-comment of this signature. Will be shown
|
||||||
|
* in the UI but can be omitted.
|
||||||
|
*/
|
||||||
|
MarkupContent documentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parameters of this signature.
|
||||||
|
*/
|
||||||
|
Vector<ParameterInformation> parameters;
|
||||||
|
|
||||||
|
Dictionary to_json() const {
|
||||||
|
Dictionary dict;
|
||||||
|
dict["label"] = label;
|
||||||
|
dict["documentation"] = documentation.to_json();
|
||||||
|
Array args;
|
||||||
|
for (int i = 0; i < parameters.size(); i++) {
|
||||||
|
args.push_back(parameters[i].to_json());
|
||||||
|
}
|
||||||
|
dict["parameters"] = args;
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signature help represents the signature of something
|
||||||
|
* callable. There can be multiple signature but only one
|
||||||
|
* active and only one active parameter.
|
||||||
|
*/
|
||||||
|
struct SignatureHelp {
|
||||||
|
/**
|
||||||
|
* One or more signatures.
|
||||||
|
*/
|
||||||
|
Vector<SignatureInformation> signatures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active signature. If omitted or the value lies outside the
|
||||||
|
* range of `signatures` the value defaults to zero or is ignored if
|
||||||
|
* `signatures.length === 0`. Whenever possible implementors should
|
||||||
|
* make an active decision about the active signature and shouldn't
|
||||||
|
* rely on a default value.
|
||||||
|
* In future version of the protocol this property might become
|
||||||
|
* mandatory to better express this.
|
||||||
|
*/
|
||||||
|
int activeSignature = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active parameter of the active signature. If omitted or the value
|
||||||
|
* lies outside the range of `signatures[activeSignature].parameters`
|
||||||
|
* defaults to 0 if the active signature has parameters. If
|
||||||
|
* the active signature has no parameters it is ignored.
|
||||||
|
* In future version of the protocol this property might become
|
||||||
|
* mandatory to better express the active parameter if the
|
||||||
|
* active signature does have any.
|
||||||
|
*/
|
||||||
|
int activeParameter = 0;
|
||||||
|
|
||||||
|
Dictionary to_json() const {
|
||||||
|
Dictionary dict;
|
||||||
|
Array sigs;
|
||||||
|
for (int i = 0; i < signatures.size(); i++) {
|
||||||
|
sigs.push_back(signatures[i].to_json());
|
||||||
|
}
|
||||||
|
dict["signatures"] = sigs;
|
||||||
|
dict["activeSignature"] = activeSignature;
|
||||||
|
dict["activeParameter"] = activeParameter;
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct ServerCapabilities {
|
struct ServerCapabilities {
|
||||||
/**
|
/**
|
||||||
* Defines how text documents are synced. Is either a detailed structure defining each notification or
|
* Defines how text documents are synced. Is either a detailed structure defining each notification or
|
||||||
|
@ -1532,6 +1646,8 @@ struct ServerCapabilities {
|
||||||
Dictionary dict;
|
Dictionary dict;
|
||||||
dict["textDocumentSync"] = (int)textDocumentSync.change;
|
dict["textDocumentSync"] = (int)textDocumentSync.change;
|
||||||
dict["completionProvider"] = completionProvider.to_json();
|
dict["completionProvider"] = completionProvider.to_json();
|
||||||
|
signatureHelpProvider.triggerCharacters.push_back(",");
|
||||||
|
signatureHelpProvider.triggerCharacters.push_back("(");
|
||||||
dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
|
dict["signatureHelpProvider"] = signatureHelpProvider.to_json();
|
||||||
dict["codeLensProvider"] = false; // codeLensProvider.to_json();
|
dict["codeLensProvider"] = false; // codeLensProvider.to_json();
|
||||||
dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();
|
dict["documentOnTypeFormattingProvider"] = documentOnTypeFormattingProvider.to_json();
|
||||||
|
|
Loading…
Reference in a new issue