Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sort code autocompletion with rules #75746

Merged
merged 1 commit into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion core/object/script_language.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
#include "core/variant/typed_array.h"

#include <stdint.h>

Expand Down Expand Up @@ -461,6 +460,52 @@ void ScriptLanguage::get_core_type_words(List<String> *p_core_type_words) const
void ScriptLanguage::frame() {
}

TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics(const String &p_base) {
// Return characacteristics of the match found by order of importance.
// Matches will be ranked by a lexicographical order on the vector returned by this function.
// The lower values indicate better matches and that they should go before in the order of appearance.
if (last_matches == matches) {
return charac;
}
charac.clear();
// Ensure base is not empty and at the same time that matches is not empty too.
if (p_base.length() == 0) {
last_matches = matches;
charac.push_back(location);
return charac;
}
charac.push_back(matches.size());
charac.push_back((matches[0].first == 0) ? 0 : 1);
charac.push_back(location);
const char32_t *target_char = &p_base[0];
int bad_case = 0;
for (const Pair<int, int> &match_segment : matches) {
const char32_t *string_to_complete_char = &display[match_segment.first];
for (int j = 0; j < match_segment.second; j++, string_to_complete_char++, target_char++) {
if (*string_to_complete_char != *target_char) {
bad_case++;
}
}
}
charac.push_back(bad_case);
charac.push_back(matches[0].first);
last_matches = matches;
return charac;
}

void ScriptLanguage::CodeCompletionOption::clear_characteristics() {
charac = TypedArray<int>();
}

TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_cached_characteristics() const {
// Only returns the cached value and warns if it was not updated since the last change of matches.
if (last_matches != matches) {
WARN_PRINT("Characteristics are not up to date.");
}

return charac;
}

bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
if (script->is_placeholder_fallback_enabled()) {
return false;
Expand Down
14 changes: 12 additions & 2 deletions core/object/script_language.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/io/resource.h"
#include "core/templates/pair.h"
#include "core/templates/rb_map.h"
#include "core/variant/typed_array.h"

class ScriptLanguage;
template <typename T>
Expand Down Expand Up @@ -305,8 +306,8 @@ class ScriptLanguage : public Object {
virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
virtual bool overrides_external_editor() { return false; }

/* Keep enum in Sync with: */
/* /scene/gui/code_edit.h - CodeEdit::CodeCompletionKind */
// Keep enums in sync with:
// scene/gui/code_edit.h - CodeEdit::CodeCompletionKind
enum CodeCompletionKind {
CODE_COMPLETION_KIND_CLASS,
CODE_COMPLETION_KIND_FUNCTION,
Expand All @@ -321,6 +322,7 @@ class ScriptLanguage : public Object {
CODE_COMPLETION_KIND_MAX
};

// scene/gui/code_edit.h - CodeEdit::CodeCompletionLocation
enum CodeCompletionLocation {
LOCATION_LOCAL = 0,
LOCATION_PARENT_MASK = 1 << 8,
Expand All @@ -336,6 +338,7 @@ class ScriptLanguage : public Object {
Ref<Resource> icon;
Variant default_value;
Vector<Pair<int, int>> matches;
Vector<Pair<int, int>> last_matches;
int location = LOCATION_OTHER;

CodeCompletionOption() {}
Expand All @@ -346,6 +349,13 @@ class ScriptLanguage : public Object {
kind = p_kind;
location = p_location;
}

TypedArray<int> get_option_characteristics(const String &p_base);
void clear_characteristics();
TypedArray<int> get_option_cached_characteristics() const;

private:
TypedArray<int> charac;
};

virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
Expand Down
14 changes: 14 additions & 0 deletions doc/classes/CodeEdit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@
<param index="3" name="text_color" type="Color" default="Color(1, 1, 1, 1)" />
<param index="4" name="icon" type="Resource" default="null" />
<param index="5" name="value" type="Variant" default="0" />
<param index="6" name="location" type="int" default="1024" />
<description>
Submits an item to the queue of potential candidates for the autocomplete menu. Call [method update_code_completion_options] to update the list.
[param location] indicates location of the option relative to the location of the code completion query. See [enum CodeEdit.CodeCompletionLocation] for how to set this value.
[b]Note:[/b] This list will replace all current candidates.
</description>
</method>
Expand Down Expand Up @@ -560,6 +562,18 @@
<constant name="KIND_PLAIN_TEXT" value="9" enum="CodeCompletionKind">
Marks the option as unclassified or plain text.
</constant>
<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes).
</constant>
<constant name="LOCATION_PARENT_MASK" value="256" enum="CodeCompletionLocation">
The option is from the containing class or a parent class, relative to the location of the code completion query. Perform a bitwise OR with the class depth (e.g. 0 for the local class, 1 for the parent, 2 for the grandparent, etc) to store the depth of an option in the class or a parent class.
</constant>
<constant name="LOCATION_OTHER_USER_CODE" value="512" enum="CodeCompletionLocation">
The option is from user code which is not local and not in a derived class (e.g. Autoload Singletons).
</constant>
<constant name="LOCATION_OTHER" value="1024" enum="CodeCompletionLocation">
The option is from other engine code, not covered by the other enum constants - e.g. built-in classes.
</constant>
</constants>
<theme_items>
<theme_item name="background_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)">
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/ScriptLanguageExtension.xml
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@
<constant name="LOOKUP_RESULT_MAX" value="9" enum="LookupResultType">
</constant>
<constant name="LOCATION_LOCAL" value="0" enum="CodeCompletionLocation">
The option is local to the location of the code completion query - e.g. a local variable.
The option is local to the location of the code completion query - e.g. a local variable. Subsequent value of location represent options from the outer class, the exact value represent how far they are (in terms of inner classes).
</constant>
<constant name="LOCATION_PARENT_MASK" value="256" enum="CodeCompletionLocation">
The option is from the containing class or a parent class, relative to the location of the code completion query. Perform a bitwise OR with the class depth (e.g. 0 for the local class, 1 for the parent, 2 for the grandparent, etc) to store the depth of an option in the class or a parent class.
Expand Down
2 changes: 1 addition & 1 deletion editor/code_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ void CodeTextEditor::_complete_request() {
} else if (e.insert_text.begins_with("#") || e.insert_text.begins_with("//")) {
font_color = completion_comment_color;
}
text_editor->add_code_completion_option((CodeEdit::CodeCompletionKind)e.kind, e.display, e.insert_text, font_color, _get_completion_icon(e), e.default_value);
text_editor->add_code_completion_option((CodeEdit::CodeCompletionKind)e.kind, e.display, e.insert_text, font_color, _get_completion_icon(e), e.default_value, e.location);
}
text_editor->update_code_completion_options(forced);
}
Expand Down
2 changes: 0 additions & 2 deletions editor/plugins/script_text_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,6 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLa
String hint;
Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint);

r_options->sort_custom_inplace<CodeCompletionOptionCompare>();

if (err == OK) {
code_editor->get_text_editor()->set_code_hint(hint);
}
Expand Down
47 changes: 0 additions & 47 deletions editor/plugins/script_text_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,51 +259,4 @@ class ScriptTextEditor : public ScriptEditorBase {
~ScriptTextEditor();
};

const int KIND_COUNT = 10;
// The order in which to sort code completion options.
const ScriptLanguage::CodeCompletionKind KIND_SORT_ORDER[KIND_COUNT] = {
ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE,
ScriptLanguage::CODE_COMPLETION_KIND_MEMBER,
ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION,
ScriptLanguage::CODE_COMPLETION_KIND_ENUM,
ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL,
ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT,
ScriptLanguage::CODE_COMPLETION_KIND_CLASS,
ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH,
ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH,
ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT,
};

// The custom comparer which will sort completion options.
struct CodeCompletionOptionCompare {
_FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const {
if (l.location == r.location) {
// If locations are same, sort on kind
if (l.kind == r.kind) {
// If kinds are same, sort alphanumeric
return l.display < r.display;
}

// Sort kinds based on the const sorting array defined above. Lower index = higher priority.
int l_index = -1;
int r_index = -1;
for (int i = 0; i < KIND_COUNT; i++) {
const ScriptLanguage::CodeCompletionKind kind = KIND_SORT_ORDER[i];
l_index = kind == l.kind ? i : l_index;
r_index = kind == r.kind ? i : r_index;

if (l_index != -1 && r_index != -1) {
return l_index < r_index;
}
}

// This return should never be hit unless something goes wrong.
// l and r should always have a Kind which is in the sort order array.
return l.display < r.display;
}

return l.location < r.location;
}
};

#endif // SCRIPT_TEXT_EDITOR_H
15 changes: 8 additions & 7 deletions modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,19 +906,20 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
}
}

static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result) {
static void _find_identifiers_in_suite(const GDScriptParser::SuiteNode *p_suite, HashMap<String, ScriptLanguage::CodeCompletionOption> &r_result, int p_recursion_depth = 0) {
for (int i = 0; i < p_suite->locals.size(); i++) {
ScriptLanguage::CodeCompletionOption option;
int location = p_recursion_depth == 0 ? ScriptLanguage::LOCATION_LOCAL : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
if (p_suite->locals[i].type == GDScriptParser::SuiteNode::Local::CONSTANT) {
option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, ScriptLanguage::LOCATION_LOCAL);
option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, location);
option.default_value = p_suite->locals[i].constant->initializer->reduced_value;
} else {
option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, ScriptLanguage::LOCATION_LOCAL);
option = ScriptLanguage::CodeCompletionOption(p_suite->locals[i].name, ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, location);
}
r_result.insert(option.display, option);
}
if (p_suite->parent_block) {
_find_identifiers_in_suite(p_suite->parent_block, r_result);
_find_identifiers_in_suite(p_suite->parent_block, r_result, p_recursion_depth + 1);
}
}

Expand All @@ -933,7 +934,7 @@ static void _find_identifiers_in_class(const GDScriptParser::ClassNode *p_class,
int classes_processed = 0;
while (clss) {
for (int i = 0; i < clss->members.size(); i++) {
const int location = (classes_processed + p_recursion_depth) | ScriptLanguage::LOCATION_PARENT_MASK;
const int location = p_recursion_depth == 0 ? classes_processed : (p_recursion_depth | ScriptLanguage::LOCATION_PARENT_MASK);
const GDScriptParser::ClassNode::Member &member = clss->members[i];
ScriptLanguage::CodeCompletionOption option;
switch (member.type) {
Expand Down Expand Up @@ -1025,7 +1026,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
while (!base_type.has_no_type()) {
switch (base_type.kind) {
case GDScriptParser::DataType::CLASS: {
_find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth + 1);
_find_identifiers_in_class(base_type.class_type, p_only_functions, base_type.is_meta_type, false, r_result, p_recursion_depth);
// This already finds all parent identifiers, so we are done.
base_type = GDScriptParser::DataType();
} break;
Expand Down Expand Up @@ -1205,7 +1206,7 @@ static void _find_identifiers(const GDScriptParser::CompletionContext &p_context
}

if (p_context.current_class) {
_find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth + 1);
_find_identifiers_in_class(p_context.current_class, p_only_functions, (!p_context.current_function || p_context.current_function->is_static), false, r_result, p_recursion_depth);
}

List<StringName> functions;
Expand Down
Loading