summaryrefslogtreecommitdiff
path: root/scene/gui/text_edit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui/text_edit.cpp')
-rw-r--r--scene/gui/text_edit.cpp201
1 files changed, 143 insertions, 58 deletions
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 55a650ff12..8926c1ec00 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -127,13 +127,13 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
w += get_char_width(str[i], str[i + 1], w);
}
- text[p_line].width_cache = w;
+ text.write[p_line].width_cache = w;
- text[p_line].wrap_amount_cache = -1;
+ text.write[p_line].wrap_amount_cache = -1;
//update regions
- text[p_line].region_info.clear();
+ text.write[p_line].region_info.clear();
for (int i = 0; i < len; i++) {
@@ -172,7 +172,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
ColorRegionInfo cri;
cri.end = false;
cri.region = j;
- text[p_line].region_info[i] = cri;
+ text.write[p_line].region_info[i] = cri;
i += lr - 1;
break;
@@ -200,7 +200,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
ColorRegionInfo cri;
cri.end = true;
cri.region = j;
- text[p_line].region_info[i] = cri;
+ text.write[p_line].region_info[i] = cri;
i += lr - 1;
break;
@@ -236,7 +236,7 @@ void TextEdit::Text::set_line_wrap_amount(int p_line, int p_wrap_amount) const {
ERR_FAIL_INDEX(p_line, text.size());
- text[p_line].wrap_amount_cache = p_wrap_amount;
+ text.write[p_line].wrap_amount_cache = p_wrap_amount;
}
int TextEdit::Text::get_line_wrap_amount(int p_line) const {
@@ -249,14 +249,14 @@ int TextEdit::Text::get_line_wrap_amount(int p_line) const {
void TextEdit::Text::clear_width_cache() {
for (int i = 0; i < text.size(); i++) {
- text[i].width_cache = -1;
+ text.write[i].width_cache = -1;
}
}
void TextEdit::Text::clear_wrap_cache() {
for (int i = 0; i < text.size(); i++) {
- text[i].wrap_amount_cache = -1;
+ text.write[i].wrap_amount_cache = -1;
}
}
@@ -281,15 +281,16 @@ void TextEdit::Text::set(int p_line, const String &p_text) {
ERR_FAIL_INDEX(p_line, text.size());
- text[p_line].width_cache = -1;
- text[p_line].wrap_amount_cache = -1;
- text[p_line].data = p_text;
+ text.write[p_line].width_cache = -1;
+ text.write[p_line].wrap_amount_cache = -1;
+ text.write[p_line].data = p_text;
}
void TextEdit::Text::insert(int p_at, const String &p_text) {
Line line;
line.marked = false;
+ line.safe = false;
line.breakpoint = false;
line.hidden = false;
line.width_cache = -1;
@@ -336,10 +337,6 @@ void TextEdit::_update_scrollbars() {
int hscroll_rows = ((hmin.height - 1) / get_row_height()) + 1;
int visible_rows = get_visible_rows();
- int first_vis_line = get_first_visible_line();
- int wi;
- int num_rows = MAX(visible_rows, num_lines_from_rows(first_vis_line, cursor.wrap_ofs, visible_rows, wi));
-
int total_rows = get_total_visible_rows();
if (scroll_past_end_of_file_enabled) {
total_rows += visible_rows - 1;
@@ -426,6 +423,9 @@ void TextEdit::_update_scrollbars() {
void TextEdit::_click_selection_held() {
+ // Warning: is_mouse_button_pressed(BUTTON_LEFT) returns false for double+ clicks, so this doesn't work for MODE_WORD
+ // and MODE_LINE. However, moving the mouse triggers _gui_input, which calls these functions too, so that's not a huge problem.
+ // I'm unsure if there's an actual fix that doesn't have a ton of side effects.
if (Input::get_singleton()->is_mouse_button_pressed(BUTTON_LEFT) && selection.selecting_mode != Selection::MODE_NONE) {
switch (selection.selecting_mode) {
case Selection::MODE_POINTER: {
@@ -447,7 +447,7 @@ void TextEdit::_click_selection_held() {
}
void TextEdit::_update_selection_mode_pointer() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -455,14 +455,14 @@ void TextEdit::_update_selection_mode_pointer() {
select(selection.selecting_line, selection.selecting_column, row, col);
cursor_set_line(row, false);
- cursor_set_column(col, false);
+ cursor_set_column(col);
update();
click_select_held->start();
}
void TextEdit::_update_selection_mode_word() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -496,26 +496,29 @@ void TextEdit::_update_selection_mode_word() {
selection.selected_word_beg = beg;
selection.selected_word_end = end;
selection.selected_word_origin = beg;
+ cursor_set_line(selection.to_line, false);
cursor_set_column(selection.to_column);
} else {
if ((col <= selection.selected_word_origin && row == selection.selecting_line) || row < selection.selecting_line) {
selection.selecting_column = selection.selected_word_end;
select(row, beg, selection.selecting_line, selection.selected_word_end);
+ cursor_set_line(selection.from_line, false);
cursor_set_column(selection.from_column);
} else {
selection.selecting_column = selection.selected_word_beg;
select(selection.selecting_line, selection.selected_word_beg, row, end);
+ cursor_set_line(selection.to_line, false);
cursor_set_column(selection.to_column);
}
}
- cursor_set_line(row, false);
update();
+
click_select_held->start();
}
void TextEdit::_update_selection_mode_line() {
- Point2 mp = Input::get_singleton()->get_mouse_position() - get_global_position();
+ Point2 mp = get_local_mouse_position();
int row, col;
_get_mouse_pos(Point2i(mp.x, mp.y), row, col);
@@ -531,7 +534,7 @@ void TextEdit::_update_selection_mode_line() {
selection.selecting_column = 0;
col = text[row].length();
}
- cursor_set_column(0, false);
+ cursor_set_column(0);
select(selection.selecting_line, selection.selecting_column, row, col);
update();
@@ -960,12 +963,13 @@ void TextEdit::_notification(int p_what) {
// draw line numbers
if (cache.line_number_w) {
+ int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
String fc = String::num(line + 1);
while (fc.length() < line_number_char_count) {
fc = line_num_padding + fc;
}
- cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color);
+ cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, yofs + cache.font->get_ascent()), fc, text.is_safe(line) ? cache.safe_line_number_color : cache.line_number_color);
}
}
@@ -1088,12 +1092,13 @@ void TextEdit::_notification(int p_what) {
}
if (brace_matching_enabled) {
+ int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
if ((brace_open_match_line == line && brace_open_match_column == last_wrap_column + j) ||
(cursor.column == last_wrap_column + j && cursor.line == line && cursor_wrap_index == line_wrap_index && (brace_open_matching || brace_open_mismatch))) {
if (brace_open_mismatch)
color = cache.brace_mismatch_color;
- drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
+ drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
if ((brace_close_match_line == line && brace_close_match_column == last_wrap_column + j) ||
@@ -1101,7 +1106,7 @@ void TextEdit::_notification(int p_what) {
if (brace_close_mismatch)
color = cache.brace_mismatch_color;
- drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
+ drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), '_', str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
}
@@ -1161,9 +1166,10 @@ void TextEdit::_notification(int p_what) {
}
if (str[j] >= 32) {
- int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, ofs_y + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
+ int yofs = ofs_y + (get_row_height() - cache.font->get_height()) / 2;
+ int w = drawer.draw_char(ci, Point2i(char_ofs + char_margin + ofs_x, yofs + ascent), str[j], str[j + 1], in_selection && override_selected_font_color ? cache.font_selected_color : color);
if (underlined) {
- draw_rect(Rect2(char_ofs + char_margin + ofs_x, ofs_y + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color);
+ draw_rect(Rect2(char_ofs + char_margin + ofs_x, yofs + ascent + 2, w, 1), in_selection && override_selected_font_color ? cache.font_selected_color : color);
}
} else if (draw_tabs && str[j] == '\t') {
int yofs = (get_row_height() - cache.tab_icon->get_height()) / 2;
@@ -1388,6 +1394,7 @@ void TextEdit::_notification(int p_what) {
}
if (has_focus()) {
+ OS::get_singleton()->set_ime_active(true);
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()));
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
}
@@ -1399,6 +1406,7 @@ void TextEdit::_notification(int p_what) {
draw_caret = true;
}
+ OS::get_singleton()->set_ime_active(true);
Point2 cursor_pos = Point2(cursor_get_column(), cursor_get_line()) * get_row_height();
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos);
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
@@ -1413,6 +1421,7 @@ void TextEdit::_notification(int p_what) {
OS::get_singleton()->set_ime_position(Point2());
OS::get_singleton()->set_ime_intermediate_text_callback(NULL, NULL);
+ OS::get_singleton()->set_ime_active(false);
ime_text = "";
ime_selection = Point2();
@@ -1525,8 +1534,11 @@ void TextEdit::backspace_at_cursor() {
if (is_line_hidden(cursor.line))
set_line_as_hidden(prev_line, true);
- if (is_line_set_as_breakpoint(cursor.line))
+ if (is_line_set_as_breakpoint(cursor.line)) {
+ if (!text.is_breakpoint(prev_line))
+ emit_signal("breakpoint_toggled", prev_line);
set_line_as_breakpoint(prev_line, true);
+ }
if (auto_brace_completion_enabled &&
cursor.column > 0 &&
@@ -1662,9 +1674,11 @@ void TextEdit::_get_mouse_pos(const Point2i &p_mouse, int &r_row, int &r_col) co
if (is_wrap_enabled() || is_hiding_enabled()) {
- int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + 1, wrap_index) - 1;
- row = first_vis_line + f_ofs;
- row = CLAMP(row, 0, get_last_visible_line() + 1);
+ int f_ofs = num_lines_from_rows(first_vis_line, cursor.wrap_ofs, rows + (1 * SGN(rows)), wrap_index) - 1;
+ if (rows < 0)
+ row = first_vis_line - f_ofs;
+ else
+ row = first_vis_line + f_ofs;
}
if (row < 0)
@@ -3298,22 +3312,37 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
ERR_FAIL_INDEX(p_line, text.size());
ERR_FAIL_COND(p_char < 0);
- /* STEP 1 add spaces if the char is greater than the end of the line */
+ /* STEP 1 remove \r from source text and separate in substrings */
+
+ Vector<String> substrings = p_text.replace("\r", "").split("\n");
+
+ /* STEP 2 fire breakpoint_toggled signals */
+
+ // Is this just a new empty line?
+ bool shift_first_line = p_char == 0 && p_text.replace("\r", "") == "\n";
+
+ int i = p_line + !shift_first_line;
+ int lines = substrings.size() - 1;
+ for (; i < text.size(); i++) {
+ if (text.is_breakpoint(i)) {
+ if ((i - lines < p_line || !text.is_breakpoint(i - lines)) || (i - lines == p_line && !shift_first_line))
+ emit_signal("breakpoint_toggled", i);
+ if (i + lines >= text.size() || !text.is_breakpoint(i + lines))
+ emit_signal("breakpoint_toggled", i + lines);
+ }
+ }
+
+ /* STEP 3 add spaces if the char is greater than the end of the line */
while (p_char > text[p_line].length()) {
text.set(p_line, text[p_line] + String::chr(' '));
}
- /* STEP 2 separate dest string in pre and post text */
+ /* STEP 4 separate dest string in pre and post text */
String preinsert_text = text[p_line].substr(0, p_char);
String postinsert_text = text[p_line].substr(p_char, text[p_line].size());
- /* STEP 3 remove \r from source text and separate in substrings */
-
- //buh bye \r and split
- Vector<String> substrings = p_text.replace("\r", "").split("\n");
-
for (int i = 0; i < substrings.size(); i++) {
//insert the substrings
@@ -3331,9 +3360,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i
}
}
- // if we are just making a new empty line, reset breakpoints and hidden status
- if (p_char == 0 && p_text.replace("\r", "") == "\n") {
-
+ if (shift_first_line) {
text.set_breakpoint(p_line + 1, text.is_breakpoint(p_line));
text.set_hidden(p_line + 1, text.is_hidden(p_line));
text.set_breakpoint(p_line, false);
@@ -3389,11 +3416,20 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li
String pre_text = text[p_from_line].substr(0, p_from_column);
String post_text = text[p_to_line].substr(p_to_column, text[p_to_line].length());
- for (int i = p_from_line; i < p_to_line; i++) {
+ int lines = p_to_line - p_from_line;
- text.remove(p_from_line + 1);
+ for (int i = p_from_line + 1; i < text.size(); i++) {
+ if (text.is_breakpoint(i)) {
+ if (i + lines >= text.size() || !text.is_breakpoint(i + lines))
+ emit_signal("breakpoint_toggled", i);
+ if (i > p_to_line && (i - lines < 0 || !text.is_breakpoint(i - lines)))
+ emit_signal("breakpoint_toggled", i - lines);
+ }
}
+ for (int i = p_from_line; i < p_to_line; i++) {
+ text.remove(p_from_line + 1);
+ }
text.set(p_from_line, pre_text + post_text);
text.set_line_wrap_amount(p_from_line, -1);
@@ -3759,7 +3795,6 @@ Vector<String> TextEdit::get_wrap_rows_text(int p_line) const {
}
// line ends before hit wrap_at; add this word to the substring
wrap_substring += word_str;
- px += word_px;
lines.push_back(wrap_substring);
return lines;
}
@@ -4274,6 +4309,7 @@ void TextEdit::_update_caches() {
cache.caret_color = get_color("caret_color");
cache.caret_background_color = get_color("caret_background_color");
cache.line_number_color = get_color("line_number_color");
+ cache.safe_line_number_color = get_color("safe_line_number_color");
cache.font_color = get_color("font_color");
cache.font_selected_color = get_color("font_selected_color");
cache.keyword_color = get_color("keyword_color");
@@ -4845,6 +4881,17 @@ void TextEdit::set_line_as_marked(int p_line, bool p_marked) {
update();
}
+void TextEdit::set_line_as_safe(int p_line, bool p_safe) {
+ ERR_FAIL_INDEX(p_line, text.size());
+ text.set_safe(p_line, p_safe);
+ update();
+}
+
+bool TextEdit::is_line_set_as_safe(int p_line) const {
+ ERR_FAIL_INDEX_V(p_line, text.size(), false);
+ return text.is_safe(p_line);
+}
+
bool TextEdit::is_line_set_as_breakpoint(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), false);
@@ -4866,6 +4913,24 @@ void TextEdit::get_breakpoints(List<int> *p_breakpoints) const {
}
}
+Array TextEdit::get_breakpoints_array() const {
+
+ Array arr;
+ for (int i = 0; i < text.size(); i++) {
+ if (text.is_breakpoint(i))
+ arr.append(i);
+ }
+ return arr;
+}
+
+void TextEdit::remove_breakpoints() {
+ for (int i = 0; i < text.size(); i++) {
+ if (text.is_breakpoint(i))
+ /* Should "breakpoint_toggled" be fired when breakpoints are removed this way? */
+ text.set_breakpoint(i, false);
+ }
+}
+
void TextEdit::set_line_as_hidden(int p_line, bool p_hidden) {
ERR_FAIL_INDEX(p_line, text.size());
@@ -5448,9 +5513,8 @@ int TextEdit::get_last_visible_line() const {
int TextEdit::get_last_visible_line_wrap_index() const {
int first_vis_line = get_first_visible_line();
- int last_vis_line = 0;
int wi;
- last_vis_line = first_vis_line + num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi) - 1;
+ num_lines_from_rows(first_vis_line, cursor.wrap_ofs, get_visible_rows() + 1, wi);
return wi;
}
@@ -5535,7 +5599,17 @@ void TextEdit::_confirm_completion() {
cursor_set_column(cursor.column - completion_base.length(), false);
insert_text_at_cursor(completion_current);
- if (completion_current.ends_with("(") && auto_brace_completion_enabled) {
+ // When inserted into the middle of an existing string, don't add an unnecessary quote
+ String line = text[cursor.line];
+ CharType next_char = line[cursor.column];
+ CharType last_completion_char = completion_current[completion_current.length() - 1];
+
+ if ((last_completion_char == '"' || last_completion_char == '\'') &&
+ last_completion_char == next_char) {
+ _base_remove_text(cursor.line, cursor.column, cursor.line, cursor.column + 1);
+ }
+
+ if (last_completion_char == '(' && auto_brace_completion_enabled) {
insert_text_at_cursor(")");
cursor.column--;
}
@@ -5655,7 +5729,7 @@ void TextEdit::_update_completion_candidates() {
for (int i = 0; i < completion_strings.size(); i++) {
if (single_quote && completion_strings[i].is_quoted()) {
- completion_strings[i] = completion_strings[i].unquote().quote("'");
+ completion_strings.write[i] = completion_strings[i].unquote().quote("'");
}
if (s == completion_strings[i]) {
@@ -5762,18 +5836,23 @@ String TextEdit::get_word_at_pos(const Vector2 &p_pos) const {
if (select_word(s, col, beg, end)) {
bool inside_quotes = false;
+ char selected_quote = '\0';
int qbegin = 0, qend = 0;
for (int i = 0; i < s.length(); i++) {
- if (s[i] == '"') {
- if (inside_quotes) {
- qend = i;
- inside_quotes = false;
- if (col >= qbegin && col <= qend) {
- return s.substr(qbegin, qend - qbegin);
+ if (s[i] == '"' || s[i] == '\'') {
+ if (i == 0 || s[i - 1] != '\\') {
+ if (inside_quotes && selected_quote == s[i]) {
+ qend = i;
+ inside_quotes = false;
+ selected_quote = '\0';
+ if (col >= qbegin && col <= qend) {
+ return s.substr(qbegin, qend - qbegin);
+ }
+ } else if (!inside_quotes) {
+ qbegin = i + 1;
+ inside_quotes = true;
+ selected_quote = s[i];
}
- } else {
- qbegin = i + 1;
- inside_quotes = true;
}
}
}
@@ -5854,12 +5933,12 @@ void TextEdit::set_line_length_guideline_column(int p_column) {
update();
}
-void TextEdit::set_draw_breakpoint_gutter(bool p_draw) {
+void TextEdit::set_breakpoint_gutter_enabled(bool p_draw) {
draw_breakpoint_gutter = p_draw;
update();
}
-bool TextEdit::is_drawing_breakpoint_gutter() const {
+bool TextEdit::is_breakpoint_gutter_enabled() const {
return draw_breakpoint_gutter;
}
@@ -6042,6 +6121,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_show_line_numbers", "enable"), &TextEdit::set_show_line_numbers);
ClassDB::bind_method(D_METHOD("is_show_line_numbers_enabled"), &TextEdit::is_show_line_numbers_enabled);
+ ClassDB::bind_method(D_METHOD("set_breakpoint_gutter_enabled", "enable"), &TextEdit::set_breakpoint_gutter_enabled);
+ ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled);
ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_enabled);
ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled);
@@ -6080,11 +6161,15 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
ClassDB::bind_method(D_METHOD("get_menu"), &TextEdit::get_menu);
+ ClassDB::bind_method(D_METHOD("get_breakpoints"), &TextEdit::get_breakpoints_array);
+ ClassDB::bind_method(D_METHOD("remove_breakpoints"), &TextEdit::remove_breakpoints);
+
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "readonly"), "set_readonly", "is_readonly");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "syntax_highlighting"), "set_syntax_coloring", "is_syntax_coloring_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_line_numbers"), "set_show_line_numbers", "is_show_line_numbers_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "breakpoint_gutter"), "set_breakpoint_gutter_enabled", "is_breakpoint_gutter_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "context_menu_enabled"), "set_context_menu_enabled", "is_context_menu_enabled");