You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-14 13:41:12 +00:00
Move indent management to CodeEdit
This commit is contained in:
@@ -360,7 +360,7 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
|
||||
return;
|
||||
}
|
||||
if (k->is_action("ui_text_backspace", true)) {
|
||||
backspace_at_cursor();
|
||||
backspace();
|
||||
_filter_code_completion_candidates();
|
||||
accept_event();
|
||||
return;
|
||||
@@ -387,14 +387,34 @@ void CodeEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
|
||||
set_code_hint("");
|
||||
}
|
||||
|
||||
/* Override input to unfold lines where needed. */
|
||||
if (!is_readonly()) {
|
||||
if (k->is_action("ui_text_newline_above", true) || k->is_action("ui_text_newline_blank", true) || k->is_action("ui_text_newline", true)) {
|
||||
unfold_line(cursor_get_line());
|
||||
}
|
||||
if (cursor_get_line() > 0 && k->is_action("ui_text_backspace", true)) {
|
||||
unfold_line(cursor_get_line() - 1);
|
||||
}
|
||||
/* Indentation */
|
||||
if (k->is_action("ui_text_indent", true)) {
|
||||
do_indent();
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
|
||||
if (k->is_action("ui_text_dedent", true)) {
|
||||
do_unindent();
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
|
||||
// Override new line actions, for auto indent
|
||||
if (k->is_action("ui_text_newline_above", true)) {
|
||||
_new_line(false, true);
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
if (k->is_action("ui_text_newline_blank", true)) {
|
||||
_new_line(false);
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
if (k->is_action("ui_text_newline", true)) {
|
||||
_new_line();
|
||||
accept_event();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove shift otherwise actions will not match. */
|
||||
@@ -439,6 +459,443 @@ Control::CursorShape CodeEdit::get_cursor_shape(const Point2 &p_pos) const {
|
||||
return TextEdit::get_cursor_shape(p_pos);
|
||||
}
|
||||
|
||||
/* Indent management */
|
||||
void CodeEdit::set_indent_size(const int p_size) {
|
||||
ERR_FAIL_COND_MSG(p_size <= 0, "Indend size must be greater than 0.");
|
||||
if (indent_size == p_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
indent_size = p_size;
|
||||
if (indent_using_spaces) {
|
||||
indent_text = String(" ").repeat(p_size);
|
||||
} else {
|
||||
indent_text = "\t";
|
||||
}
|
||||
set_tab_size(p_size);
|
||||
}
|
||||
|
||||
int CodeEdit::get_indent_size() const {
|
||||
return indent_size;
|
||||
}
|
||||
|
||||
void CodeEdit::set_indent_using_spaces(const bool p_use_spaces) {
|
||||
indent_using_spaces = p_use_spaces;
|
||||
if (indent_using_spaces) {
|
||||
indent_text = String(" ").repeat(indent_size);
|
||||
} else {
|
||||
indent_text = "\t";
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeEdit::is_indent_using_spaces() const {
|
||||
return indent_using_spaces;
|
||||
}
|
||||
|
||||
void CodeEdit::set_auto_indent_enabled(bool p_enabled) {
|
||||
auto_indent = p_enabled;
|
||||
}
|
||||
|
||||
bool CodeEdit::is_auto_indent_enabled() const {
|
||||
return auto_indent;
|
||||
}
|
||||
|
||||
void CodeEdit::set_auto_indent_prefixes(const TypedArray<String> &p_prefixes) {
|
||||
auto_indent_prefixes.clear();
|
||||
for (int i = 0; i < p_prefixes.size(); i++) {
|
||||
const String prefix = p_prefixes[i];
|
||||
auto_indent_prefixes.insert(prefix[0]);
|
||||
}
|
||||
}
|
||||
|
||||
TypedArray<String> CodeEdit::get_auto_indent_prefixes() const {
|
||||
TypedArray<String> prefixes;
|
||||
for (const Set<char32_t>::Element *E = auto_indent_prefixes.front(); E; E = E->next()) {
|
||||
prefixes.push_back(String::chr(E->get()));
|
||||
}
|
||||
return prefixes;
|
||||
}
|
||||
|
||||
void CodeEdit::do_indent() {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_selection_active()) {
|
||||
indent_lines();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!indent_using_spaces) {
|
||||
_insert_text_at_cursor("\t");
|
||||
return;
|
||||
}
|
||||
|
||||
int spaces_to_add = _calculate_spaces_till_next_right_indent(cursor_get_column());
|
||||
if (spaces_to_add > 0) {
|
||||
_insert_text_at_cursor(String(" ").repeat(spaces_to_add));
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEdit::indent_lines() {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_complex_operation();
|
||||
|
||||
/* This value informs us by how much we changed selection position by indenting right. */
|
||||
/* Default is 1 for tab indentation. */
|
||||
int selection_offset = 1;
|
||||
|
||||
int start_line = cursor_get_line();
|
||||
int end_line = start_line;
|
||||
if (is_selection_active()) {
|
||||
start_line = get_selection_from_line();
|
||||
end_line = get_selection_to_line();
|
||||
|
||||
/* Ignore the last line if the selection is not past the first column. */
|
||||
if (get_selection_to_column() == 0) {
|
||||
selection_offset = 0;
|
||||
end_line--;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = start_line; i <= end_line; i++) {
|
||||
const String line_text = get_line(i);
|
||||
if (line_text.size() == 0 && is_selection_active()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!indent_using_spaces) {
|
||||
set_line(i, '\t' + line_text);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We don't really care where selection is - we just need to know indentation level at the beginning of the line. */
|
||||
/* Since we will add this many spaces, we want to move the whole selection and caret by this much. */
|
||||
int spaces_to_add = _calculate_spaces_till_next_right_indent(get_first_non_whitespace_column(i));
|
||||
set_line(i, String(" ").repeat(spaces_to_add) + line_text);
|
||||
selection_offset = spaces_to_add;
|
||||
}
|
||||
|
||||
/* Fix selection and caret being off after shifting selection right.*/
|
||||
if (is_selection_active()) {
|
||||
select(start_line, get_selection_from_column() + selection_offset, get_selection_to_line(), get_selection_to_column() + selection_offset);
|
||||
}
|
||||
cursor_set_column(cursor_get_column() + selection_offset, false);
|
||||
|
||||
end_complex_operation();
|
||||
}
|
||||
|
||||
void CodeEdit::do_unindent() {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cc = cursor_get_column();
|
||||
|
||||
if (is_selection_active() || cc <= 0) {
|
||||
unindent_lines();
|
||||
return;
|
||||
}
|
||||
|
||||
int cl = cursor_get_line();
|
||||
const String &line = get_line(cl);
|
||||
|
||||
if (line[cc - 1] == '\t') {
|
||||
_remove_text(cl, cc - 1, cl, cc);
|
||||
cursor_set_column(MAX(0, cc - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (line[cc - 1] != ' ') {
|
||||
return;
|
||||
}
|
||||
|
||||
int spaces_to_remove = _calculate_spaces_till_next_left_indent(cc);
|
||||
if (spaces_to_remove > 0) {
|
||||
for (int i = 1; i <= spaces_to_remove; i++) {
|
||||
if (line[cc - i] != ' ') {
|
||||
spaces_to_remove = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_remove_text(cl, cc - spaces_to_remove, cl, cc);
|
||||
cursor_set_column(MAX(0, cc - spaces_to_remove));
|
||||
}
|
||||
}
|
||||
|
||||
void CodeEdit::unindent_lines() {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_complex_operation();
|
||||
|
||||
/* Moving caret and selection after unindenting can get tricky because */
|
||||
/* changing content of line can move caret and selection on its own (if new line ends before previous position of either), */
|
||||
/* therefore we just remember initial values and at the end of the operation offset them by number of removed characters. */
|
||||
int removed_characters = 0;
|
||||
int initial_selection_end_column = 0;
|
||||
int initial_cursor_column = cursor_get_column();
|
||||
|
||||
int start_line = cursor_get_line();
|
||||
int end_line = start_line;
|
||||
if (is_selection_active()) {
|
||||
start_line = get_selection_from_line();
|
||||
end_line = get_selection_to_line();
|
||||
|
||||
/* Ignore the last line if the selection is not past the first column. */
|
||||
initial_selection_end_column = get_selection_to_column();
|
||||
if (initial_selection_end_column == 0) {
|
||||
end_line--;
|
||||
}
|
||||
}
|
||||
|
||||
bool first_line_edited = false;
|
||||
bool last_line_edited = false;
|
||||
|
||||
for (int i = start_line; i <= end_line; i++) {
|
||||
String line_text = get_line(i);
|
||||
|
||||
if (line_text.begins_with("\t")) {
|
||||
line_text = line_text.substr(1, line_text.length());
|
||||
|
||||
set_line(i, line_text);
|
||||
removed_characters = 1;
|
||||
|
||||
first_line_edited = (i == start_line) ? true : first_line_edited;
|
||||
last_line_edited = (i == end_line) ? true : last_line_edited;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line_text.begins_with(" ")) {
|
||||
/* When unindenting we aim to remove spaces before line that has selection no matter what is selected, */
|
||||
/* Here we remove only enough spaces to align text to nearest full multiple of indentation_size. */
|
||||
/* In case where selection begins at the start of indentation_size multiple we remove whole indentation level. */
|
||||
int spaces_to_remove = _calculate_spaces_till_next_left_indent(get_first_non_whitespace_column(i));
|
||||
line_text = line_text.substr(spaces_to_remove, line_text.length());
|
||||
|
||||
set_line(i, line_text);
|
||||
removed_characters = spaces_to_remove;
|
||||
|
||||
first_line_edited = (i == start_line) ? true : first_line_edited;
|
||||
last_line_edited = (i == end_line) ? true : last_line_edited;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_selection_active()) {
|
||||
/* Fix selection being off by one on the first line. */
|
||||
if (first_line_edited) {
|
||||
select(get_selection_from_line(), get_selection_from_column() - removed_characters, get_selection_to_line(), initial_selection_end_column);
|
||||
}
|
||||
|
||||
/* Fix selection being off by one on the last line. */
|
||||
if (last_line_edited) {
|
||||
select(get_selection_from_line(), get_selection_from_column(), get_selection_to_line(), initial_selection_end_column - removed_characters);
|
||||
}
|
||||
}
|
||||
cursor_set_column(initial_cursor_column - removed_characters, false);
|
||||
|
||||
end_complex_operation();
|
||||
}
|
||||
|
||||
int CodeEdit::_calculate_spaces_till_next_left_indent(int p_column) const {
|
||||
int spaces_till_indent = p_column % indent_size;
|
||||
if (spaces_till_indent == 0) {
|
||||
spaces_till_indent = indent_size;
|
||||
}
|
||||
return spaces_till_indent;
|
||||
}
|
||||
|
||||
int CodeEdit::_calculate_spaces_till_next_right_indent(int p_column) const {
|
||||
return indent_size - p_column % indent_size;
|
||||
}
|
||||
|
||||
/* TODO: remove once brace completion is refactored. */
|
||||
static char32_t _get_right_pair_symbol(char32_t c) {
|
||||
if (c == '"') {
|
||||
return '"';
|
||||
}
|
||||
if (c == '\'') {
|
||||
return '\'';
|
||||
}
|
||||
if (c == '(') {
|
||||
return ')';
|
||||
}
|
||||
if (c == '[') {
|
||||
return ']';
|
||||
}
|
||||
if (c == '{') {
|
||||
return '}';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool _is_pair_left_symbol(char32_t c) {
|
||||
return c == '"' ||
|
||||
c == '\'' ||
|
||||
c == '(' ||
|
||||
c == '[' ||
|
||||
c == '{';
|
||||
}
|
||||
|
||||
void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int cc = cursor_get_column();
|
||||
const int cl = cursor_get_line();
|
||||
const String line = get_line(cl);
|
||||
|
||||
String ins = "\n";
|
||||
|
||||
/* Append current indentation. */
|
||||
int space_count = 0;
|
||||
int line_col = 0;
|
||||
for (; line_col < cc; line_col++) {
|
||||
if (line[line_col] == '\t') {
|
||||
ins += indent_text;
|
||||
space_count = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line[line_col] == ' ') {
|
||||
space_count++;
|
||||
|
||||
if (space_count == indent_size) {
|
||||
ins += indent_text;
|
||||
space_count = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_line_folded(cl)) {
|
||||
unfold_line(cl);
|
||||
}
|
||||
|
||||
/* Indent once again if the previous line needs it, ie ':'. */
|
||||
/* Then add an addition new line for any closing pairs aka '()'. */
|
||||
/* Skip this in comments or if we are going above. */
|
||||
bool brace_indent = false;
|
||||
if (auto_indent && !p_above && cc > 0 && is_in_comment(cl) == -1) {
|
||||
bool should_indent = false;
|
||||
char32_t indent_char = ' ';
|
||||
|
||||
for (; line_col < cc; line_col++) {
|
||||
char32_t c = line[line_col];
|
||||
if (auto_indent_prefixes.has(c)) {
|
||||
should_indent = true;
|
||||
indent_char = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Make sure this is the last char, trailing whitespace or comments are okay. */
|
||||
if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
|
||||
should_indent = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (should_indent) {
|
||||
ins += indent_text;
|
||||
|
||||
/* TODO: Change when brace completion is refactored. */
|
||||
char32_t closing_char = _get_right_pair_symbol(indent_char);
|
||||
if (closing_char != 0 && closing_char == line[cc]) {
|
||||
/* No need to move the brace below if we are not taking the text with us. */
|
||||
if (p_split_current_line) {
|
||||
brace_indent = true;
|
||||
ins += "\n" + ins.substr(1, ins.length() - 2);
|
||||
} else {
|
||||
brace_indent = false;
|
||||
ins = "\n" + ins.substr(1, ins.length() - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
begin_complex_operation();
|
||||
|
||||
bool first_line = false;
|
||||
if (!p_split_current_line) {
|
||||
if (p_above) {
|
||||
if (cl > 0) {
|
||||
cursor_set_line(cl - 1, false);
|
||||
cursor_set_column(get_line(cursor_get_line()).length());
|
||||
} else {
|
||||
cursor_set_column(0);
|
||||
first_line = true;
|
||||
}
|
||||
} else {
|
||||
cursor_set_column(line.length());
|
||||
}
|
||||
}
|
||||
|
||||
insert_text_at_cursor(ins);
|
||||
|
||||
if (first_line) {
|
||||
cursor_set_line(0);
|
||||
} else if (brace_indent) {
|
||||
cursor_set_line(cursor_get_line() - 1, false);
|
||||
cursor_set_column(get_line(cursor_get_line()).length());
|
||||
}
|
||||
|
||||
end_complex_operation();
|
||||
}
|
||||
|
||||
void CodeEdit::backspace() {
|
||||
if (is_readonly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int cc = cursor_get_column();
|
||||
int cl = cursor_get_line();
|
||||
|
||||
if (cc == 0 && cl == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_selection_active()) {
|
||||
delete_selection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cl > 0 && is_line_hidden(cl - 1)) {
|
||||
unfold_line(cursor_get_line() - 1);
|
||||
}
|
||||
|
||||
int prev_line = cc ? cl : cl - 1;
|
||||
int prev_column = cc ? (cc - 1) : (get_line(cl - 1).length());
|
||||
|
||||
merge_gutters(cl, prev_line);
|
||||
|
||||
/* TODO: Change when brace completion is refactored. */
|
||||
if (auto_brace_completion_enabled && cc > 0 && _is_pair_left_symbol(get_line(cl)[cc - 1])) {
|
||||
_consume_backspace_for_pair_symbol(prev_line, prev_column);
|
||||
cursor_set_line(prev_line, false, true);
|
||||
cursor_set_column(prev_column);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For space indentation we need to do a simple unindent if there are no chars to the left, acting in the */
|
||||
/* same way as tabs. */
|
||||
if (indent_using_spaces && cc != 0) {
|
||||
if (get_first_non_whitespace_column(cl) > cc) {
|
||||
prev_column = cc - _calculate_spaces_till_next_left_indent(cc);
|
||||
prev_line = cl;
|
||||
}
|
||||
}
|
||||
|
||||
_remove_text(prev_line, prev_column, cl, cc);
|
||||
|
||||
cursor_set_line(prev_line, false, true);
|
||||
cursor_set_column(prev_column);
|
||||
}
|
||||
|
||||
/* Main Gutter */
|
||||
void CodeEdit::_update_draw_main_gutter() {
|
||||
set_gutter_draw(main_gutter, draw_breakpoints || draw_bookmarks || draw_executing_lines);
|
||||
@@ -1286,6 +1743,25 @@ void CodeEdit::cancel_code_completion() {
|
||||
}
|
||||
|
||||
void CodeEdit::_bind_methods() {
|
||||
/* Indent management */
|
||||
ClassDB::bind_method(D_METHOD("set_indent_size", "size"), &CodeEdit::set_indent_size);
|
||||
ClassDB::bind_method(D_METHOD("get_indent_size"), &CodeEdit::get_indent_size);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_indent_using_spaces", "use_spaces"), &CodeEdit::set_indent_using_spaces);
|
||||
ClassDB::bind_method(D_METHOD("is_indent_using_spaces"), &CodeEdit::is_indent_using_spaces);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_auto_indent_enabled", "enable"), &CodeEdit::set_auto_indent_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_auto_indent_enabled"), &CodeEdit::is_auto_indent_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_auto_indent_prefixes", "prefixes"), &CodeEdit::set_auto_indent_prefixes);
|
||||
ClassDB::bind_method(D_METHOD("get_auto_indent_prefixes"), &CodeEdit::get_auto_indent_prefixes);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("do_indent"), &CodeEdit::do_indent);
|
||||
ClassDB::bind_method(D_METHOD("do_unindent"), &CodeEdit::do_unindent);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("indent_lines"), &CodeEdit::indent_lines);
|
||||
ClassDB::bind_method(D_METHOD("unindent_lines"), &CodeEdit::unindent_lines);
|
||||
|
||||
/* Main Gutter */
|
||||
ClassDB::bind_method(D_METHOD("_main_gutter_draw_callback"), &CodeEdit::_main_gutter_draw_callback);
|
||||
|
||||
@@ -1436,6 +1912,12 @@ void CodeEdit::_bind_methods() {
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "code_completion_enabled"), "set_code_completion_enabled", "is_code_completion_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "code_completion_prefixes"), "set_code_completion_prefixes", "get_code_comletion_prefixes");
|
||||
|
||||
ADD_GROUP("Indentation", "indent_");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "indent_size"), "set_indent_size", "get_indent_size");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_use_spaces"), "set_indent_using_spaces", "is_indent_using_spaces");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indent_automatic"), "set_auto_indent_enabled", "is_auto_indent_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "indent_automatic_prefixes"), "set_auto_indent_prefixes", "get_auto_indent_prefixes");
|
||||
|
||||
/* Signals */
|
||||
ADD_SIGNAL(MethodInfo("breakpoint_toggled", PropertyInfo(Variant::INT, "line")));
|
||||
ADD_SIGNAL(MethodInfo("request_code_completion"));
|
||||
@@ -2062,6 +2544,12 @@ void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
|
||||
}
|
||||
|
||||
CodeEdit::CodeEdit() {
|
||||
/* Indent management */
|
||||
auto_indent_prefixes.insert(':');
|
||||
auto_indent_prefixes.insert('{');
|
||||
auto_indent_prefixes.insert('[');
|
||||
auto_indent_prefixes.insert('(');
|
||||
|
||||
/* Text Direction */
|
||||
set_layout_direction(LAYOUT_DIRECTION_LTR);
|
||||
set_text_direction(TEXT_DIRECTION_LTR);
|
||||
|
||||
Reference in New Issue
Block a user