Skip to content

Commit d093dcb

Browse files
committed
Add refactor rename symbol functionality
1 parent eee39f0 commit d093dcb

28 files changed

+2719
-164
lines changed

core/object/script_language.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ class ScriptLanguage : public Object {
283283
virtual bool overrides_external_editor() { return false; }
284284
virtual ScriptNameCasing preferred_file_name_casing() const { return SCRIPT_NAME_CASING_SNAKE_CASE; }
285285

286+
// Code completion.
286287
// Keep enums in sync with:
287288
// scene/gui/code_edit.h - CodeEdit::CodeCompletionKind
288289
enum CodeCompletionKind {
@@ -339,6 +340,229 @@ class ScriptLanguage : public Object {
339340

340341
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; }
341342

343+
// Refactoring.
344+
// Keep enums in sync with:
345+
// scene/gui/code_edit.h - CodeEdit::RefactorKind
346+
enum RefactorKind {
347+
REFACTOR_KIND_RENAME_SYMBOL,
348+
};
349+
350+
enum RefactorRenameSymbolResultType {
351+
REFACTOR_RENAME_SYMBOL_RESULT_NONE,
352+
REFACTOR_RENAME_SYMBOL_RESULT_NATIVE,
353+
REFACTOR_RENAME_SYMBOL_RESULT_NOT_EXPOSED,
354+
REFACTOR_RENAME_SYMBOL_RESULT_SCRIPT,
355+
REFACTOR_RENAME_SYMBOL_RESULT_GLOBAL_CLASS_NAME,
356+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_NAME,
357+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_CONSTANT,
358+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_PROPERTY,
359+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_METHOD,
360+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_SIGNAL,
361+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_ENUM,
362+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_ENUM_VALUE,
363+
REFACTOR_RENAME_SYMBOL_RESULT_CLASS_ANNOTATION,
364+
REFACTOR_RENAME_SYMBOL_RESULT_LOCAL_CONSTANT,
365+
REFACTOR_RENAME_SYMBOL_RESULT_LOCAL_VARIABLE,
366+
REFACTOR_RENAME_SYMBOL_RESULT_LOCAL_FOR_VARIABLE,
367+
REFACTOR_RENAME_SYMBOL_RESULT_LOCAL_PATTERN_BIND,
368+
REFACTOR_RENAME_SYMBOL_RESULT_MAX,
369+
};
370+
371+
struct RefactorRenameSymbolResult {
372+
struct Match {
373+
int start_line = -1;
374+
int start_column = -1;
375+
int end_line = -1;
376+
int end_column = -1;
377+
378+
Match() {}
379+
Match(int p_start_line, int p_start_column, int p_end_line, int p_end_column) {
380+
start_line = p_start_line;
381+
start_column = p_start_column;
382+
end_line = p_end_line;
383+
end_column = p_end_column;
384+
}
385+
386+
String to_string() const {
387+
return vformat("Match{(%s,%s)=>(%s,%s)}", start_line, start_column, end_line, end_column);
388+
}
389+
390+
struct Compare {
391+
_FORCE_INLINE_ bool operator()(const Match &l, const Match &r) const {
392+
if (l.start_line != r.start_line) {
393+
return l.start_line < r.start_line;
394+
}
395+
if (l.start_column != r.start_column) {
396+
return l.start_column < r.start_column;
397+
}
398+
return false;
399+
}
400+
};
401+
};
402+
403+
String symbol;
404+
String new_symbol;
405+
String code;
406+
Error error = FAILED;
407+
bool outside_refactor = false;
408+
RefactorRenameSymbolResultType type = RefactorRenameSymbolResultType::REFACTOR_RENAME_SYMBOL_RESULT_NONE;
409+
HashMap<String, LocalVector<Match>> matches;
410+
411+
private:
412+
void _deep_copy(const RefactorRenameSymbolResult &p_result) {
413+
symbol = p_result.symbol;
414+
new_symbol = p_result.new_symbol;
415+
code = p_result.code;
416+
outside_refactor = p_result.outside_refactor;
417+
error = p_result.error;
418+
type = p_result.type;
419+
matches.clear();
420+
for (const KeyValue<String, LocalVector<Match>> &KV : p_result.matches) {
421+
for (const Match &match : KV.value) {
422+
matches[KV.key].push_back(match);
423+
}
424+
}
425+
}
426+
427+
public:
428+
String to_string() const {
429+
String matches_string;
430+
for (const KeyValue<String, LocalVector<Match>> &KV : matches) {
431+
LocalVector<String> match_entries;
432+
for (const Match &match : KV.value) {
433+
match_entries.push_back(match.to_string());
434+
}
435+
matches_string += vformat("\t\t%s: %s,\n", KV.key, String(", ").join(match_entries));
436+
}
437+
matches_string = matches_string.trim_suffix("\n");
438+
439+
return vformat(R"(RefactorRenameSymbolResult{
440+
"%s" -> "%s",
441+
(
442+
%s
443+
)
444+
})",
445+
symbol, new_symbol, matches_string);
446+
}
447+
448+
void add_match(const String &p_path, int p_start_line, int p_start_column, int p_end_line, int p_end_column) {
449+
matches[p_path].push_back({ p_start_line,
450+
p_start_column,
451+
p_end_line,
452+
p_end_column });
453+
matches[p_path].sort_custom<RefactorRenameSymbolResult::Match::Compare>();
454+
}
455+
456+
Dictionary to_dictionary() {
457+
Dictionary result;
458+
result["symbol"] = symbol;
459+
result["new_symbol"] = new_symbol;
460+
result["code"] = code;
461+
result["outside_refactor"] = outside_refactor;
462+
result["error"] = error;
463+
result["type"] = type;
464+
465+
Dictionary dictionary_matches;
466+
for (KeyValue<String, LocalVector<Match>> &KV : matches) {
467+
TypedArray<Dictionary> dictionary_matches_entries;
468+
for (Match &match : KV.value) {
469+
Dictionary dictionary_match;
470+
dictionary_match["start_line"] = match.start_line;
471+
dictionary_match["start_column"] = match.start_column;
472+
dictionary_match["end_line"] = match.end_line;
473+
dictionary_match["end_column"] = match.end_column;
474+
dictionary_matches_entries.push_back(dictionary_match);
475+
}
476+
dictionary_matches[KV.key] = dictionary_matches_entries;
477+
}
478+
result["matches"] = dictionary_matches;
479+
480+
return result;
481+
}
482+
483+
RefactorRenameSymbolResult get_undo() {
484+
RefactorRenameSymbolResult undo_result;
485+
undo_result.symbol = new_symbol;
486+
undo_result.new_symbol = symbol;
487+
undo_result.code = code;
488+
undo_result.outside_refactor = outside_refactor;
489+
undo_result.error = error;
490+
undo_result.type = type;
491+
492+
for (const KeyValue<String, LocalVector<Match>> &KV : matches) {
493+
int last_line = 0;
494+
int offset = 0;
495+
for (const Match &match : KV.value) {
496+
if (last_line < match.start_line) {
497+
offset = 0;
498+
last_line = match.start_line;
499+
}
500+
Match new_match = {
501+
match.start_line,
502+
match.start_column + offset,
503+
match.end_line,
504+
match.end_column,
505+
};
506+
offset += new_symbol.length() - symbol.length();
507+
undo_result.matches[KV.key].push_back(new_match);
508+
}
509+
}
510+
511+
return undo_result;
512+
}
513+
514+
bool has_failed() const {
515+
return error != OK || outside_refactor == true;
516+
}
517+
518+
void reset(bool p_keep_context = false) {
519+
matches.clear();
520+
outside_refactor = false;
521+
error = FAILED;
522+
type = REFACTOR_RENAME_SYMBOL_RESULT_NONE;
523+
if (!p_keep_context) {
524+
symbol = "";
525+
new_symbol = "";
526+
code = "";
527+
}
528+
}
529+
530+
void operator=(const RefactorRenameSymbolResult &p_result) {
531+
_deep_copy(p_result);
532+
}
533+
534+
RefactorRenameSymbolResult(const RefactorRenameSymbolResult &p_result) {
535+
_deep_copy(p_result);
536+
}
537+
538+
RefactorRenameSymbolResult(const Dictionary &p_result) {
539+
ERR_FAIL_COND(!p_result.has("symbol") || !p_result.has("new_symbol") || !p_result.has("code") || !p_result.has("error") || !p_result.has("outside_refactor") || !p_result.has("type") || !p_result.has("matches"));
540+
symbol = p_result["symbol"];
541+
new_symbol = p_result["new_symbol"];
542+
code = p_result["code"];
543+
outside_refactor = p_result["outside_refactor"];
544+
error = (Error)(int)p_result["error"];
545+
type = (RefactorRenameSymbolResultType)(int)p_result["type"];
546+
matches.clear();
547+
Dictionary dictionary_matches = p_result["matches"];
548+
for (const String key : dictionary_matches.keys()) {
549+
TypedArray<Dictionary> dictionary_match_entries = dictionary_matches[key];
550+
for (int i = 0; i < dictionary_match_entries.size(); i++) {
551+
Dictionary dictionar_match_entry = dictionary_match_entries[i];
552+
matches[key].push_back({ dictionar_match_entry["start_line"],
553+
dictionar_match_entry["start_column"],
554+
dictionar_match_entry["end_line"],
555+
dictionar_match_entry["end_column"] });
556+
}
557+
}
558+
}
559+
560+
RefactorRenameSymbolResult() = default;
561+
};
562+
563+
virtual Error refactor_rename_symbol_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, const HashMap<String, String> &p_unsaved_scripts_source_code, RefactorRenameSymbolResult &r_result) { return ERR_UNAVAILABLE; }
564+
565+
// Lookup.
342566
enum LookupResultType {
343567
LOOKUP_RESULT_SCRIPT_LOCATION, // Use if none of the options below apply.
344568
LOOKUP_RESULT_CLASS,

doc/classes/CodeEdit.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,12 @@
604604
Emitted when the user requests code completion. This signal will not be sent if [method _request_code_completion] is overridden or [member code_completion_enabled] is [code]false[/code].
605605
</description>
606606
</signal>
607+
<signal name="refactor_requested">
608+
<param index="0" name="type" type="int" />
609+
<description>
610+
Emitted when the user requests a refactor.
611+
</description>
612+
</signal>
607613
<signal name="symbol_hovered">
608614
<param index="0" name="symbol" type="String" />
609615
<param index="1" name="line" type="int" />

doc/classes/TextEdit.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,11 +684,21 @@
684684
Returns the total number of lines between [param from_line] and [param to_line] (inclusive) in the text. This includes wrapped lines and excludes folded lines. If the range covers all lines it is equivalent to [method get_total_visible_line_count].
685685
</description>
686686
</method>
687+
<method name="get_word_at_line_column" qualifiers="const">
688+
<return type="String" />
689+
<param index="0" name="line" type="int" />
690+
<param index="1" name="column" type="int" />
691+
<description>
692+
Returns the word at the specified [param line] and [param column].
693+
[b]Note:[/b] To get a word under a caret, use [method get_word_under_caret] instead.
694+
</description>
695+
</method>
687696
<method name="get_word_at_pos" qualifiers="const">
688697
<return type="String" />
689698
<param index="0" name="position" type="Vector2" />
690699
<description>
691700
Returns the word at [param position].
701+
[b]Note:[/b] To get a word at a certain line and column, use [method get_word_at_line_column] instead.
692702
</description>
693703
</method>
694704
<method name="get_word_under_caret" qualifiers="const">

0 commit comments

Comments
 (0)