Browse Source

Merge pull request #55416 from pycbouh/textedit-position-from-linecol-3.x

Rémi Verschelde 3 years ago
parent
commit
0bf78feb46
3 changed files with 216 additions and 5 deletions
  1. 66 0
      doc/classes/TextEdit.xml
  2. 116 2
      scene/gui/text_edit.cpp
  3. 34 3
      scene/gui/text_edit.h

+ 66 - 0
doc/classes/TextEdit.xml

@@ -137,12 +137,47 @@
 				Returns the text of a specific line.
 				Returns the text of a specific line.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_line_column_at_pos" qualifiers="const">
+			<return type="Vector2" />
+			<argument index="0" name="position" type="Vector2" />
+			<description>
+				Returns the line and column at the given position. In the returned vector, [code]x[/code] is the column, [code]y[/code] is the line.
+			</description>
+		</method>
 		<method name="get_line_count" qualifiers="const">
 		<method name="get_line_count" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<description>
 			<description>
 				Returns the amount of total lines in the text.
 				Returns the amount of total lines in the text.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_line_height" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns the height of a largest line.
+			</description>
+		</method>
+		<method name="get_line_width" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="line" type="int" />
+			<argument index="1" name="wrap_index" type="int" default="-1" />
+			<description>
+				Returns the width in pixels of the [code]wrap_index[/code] on [code]line[/code].
+			</description>
+		</method>
+		<method name="get_line_wrap_count" qualifiers="const">
+			<return type="int" />
+			<argument index="0" name="line" type="int" />
+			<description>
+				Returns the number of times the given line is wrapped.
+			</description>
+		</method>
+		<method name="get_line_wrapped_text" qualifiers="const">
+			<return type="PoolStringArray" />
+			<argument index="0" name="line" type="int" />
+			<description>
+				Returns an array of [String]s representing each wrapped index.
+			</description>
+		</method>
 		<method name="get_menu" qualifiers="const">
 		<method name="get_menu" qualifiers="const">
 			<return type="PopupMenu" />
 			<return type="PopupMenu" />
 			<description>
 			<description>
@@ -150,6 +185,24 @@
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property.
 				[b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. If you wish to hide it or any of its children, use their [member CanvasItem.visible] property.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_pos_at_line_column" qualifiers="const">
+			<return type="Vector2" />
+			<argument index="0" name="line" type="int" />
+			<argument index="1" name="column" type="int" />
+			<description>
+				Returns the local position for the given [code]line[/code] and [code]column[/code]. If [code]x[/code] or [code]y[/code] of the returned vector equal [code]-1[/code], the position is outside of the viewable area of the control.
+				[b]Note:[/b] The Y position corresponds to the bottom side of the line. Use [method get_rect_at_line_column] to get the top side position.
+			</description>
+		</method>
+		<method name="get_rect_at_line_column" qualifiers="const">
+			<return type="Rect2" />
+			<argument index="0" name="line" type="int" />
+			<argument index="1" name="column" type="int" />
+			<description>
+				Returns the local position and size for the grapheme at the given [code]line[/code] and [code]column[/code]. If [code]x[/code] or [code]y[/code] position of the returned rect equal [code]-1[/code], the position is outside of the viewable area of the control.
+				[b]Note:[/b] The Y position of the returned rect corresponds to the top side of the line, unlike [method get_pos_at_line_column] which returns the bottom side.
+			</description>
+		</method>
 		<method name="get_selection_from_column" qualifiers="const">
 		<method name="get_selection_from_column" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<description>
 			<description>
@@ -180,6 +233,12 @@
 				Returns the selection end line.
 				Returns the selection end line.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_total_gutter_width" qualifiers="const">
+			<return type="int" />
+			<description>
+				Returns the total width of all gutters and internal padding.
+			</description>
+		</method>
 		<method name="get_word_under_cursor" qualifiers="const">
 		<method name="get_word_under_cursor" qualifiers="const">
 			<return type="String" />
 			<return type="String" />
 			<description>
 			<description>
@@ -247,6 +306,13 @@
 				Returns [code]true[/code] when the specified [code]line[/code] is marked as safe.
 				Returns [code]true[/code] when the specified [code]line[/code] is marked as safe.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="is_line_wrapped" qualifiers="const">
+			<return type="bool" />
+			<argument index="0" name="line" type="int" />
+			<description>
+				Returns if the given line is wrapped.
+			</description>
+		</method>
 		<method name="is_selection_active" qualifiers="const">
 		<method name="is_selection_active" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<description>
 			<description>

+ 116 - 2
scene/gui/text_edit.cpp

@@ -922,7 +922,7 @@ void TextEdit::_notification(int p_what) {
 			int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
 			int draw_amount = visible_rows + (smooth_scroll_enabled ? 1 : 0);
 			draw_amount += times_line_wraps(first_visible_line + 1);
 			draw_amount += times_line_wraps(first_visible_line + 1);
 
 
-			// minimap
+			// Draw minimap.
 			if (draw_minimap) {
 			if (draw_minimap) {
 				int minimap_visible_lines = _get_minimap_visible_rows();
 				int minimap_visible_lines = _get_minimap_visible_rows();
 				int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
 				int minimap_line_height = (minimap_char_size.y + minimap_line_spacing);
@@ -1091,7 +1091,8 @@ void TextEdit::_notification(int p_what) {
 				bottom_limit_y -= cache.style_normal->get_margin(MARGIN_TOP);
 				bottom_limit_y -= cache.style_normal->get_margin(MARGIN_TOP);
 			}
 			}
 
 
-			// draw main text
+			// Draw main text.
+			line_drawing_cache.clear();
 			int line = first_visible_line;
 			int line = first_visible_line;
 			for (int i = 0; i < draw_amount; i++) {
 			for (int i = 0; i < draw_amount; i++) {
 				line++;
 				line++;
@@ -1112,6 +1113,7 @@ void TextEdit::_notification(int p_what) {
 				}
 				}
 
 
 				const String &fullstr = text[line];
 				const String &fullstr = text[line];
+				LineDrawingCache cache_entry;
 
 
 				Map<int, HighlighterInfo> color_map;
 				Map<int, HighlighterInfo> color_map;
 				if (syntax_coloring) {
 				if (syntax_coloring) {
@@ -1125,6 +1127,7 @@ void TextEdit::_notification(int p_what) {
 				Vector<String> wrap_rows = get_wrap_rows_text(line);
 				Vector<String> wrap_rows = get_wrap_rows_text(line);
 				int line_wrap_amount = times_line_wraps(line);
 				int line_wrap_amount = times_line_wraps(line);
 				int last_wrap_column = 0;
 				int last_wrap_column = 0;
+				int wrap_column_offset = 0;
 
 
 				for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) {
 				for (int line_wrap_index = 0; line_wrap_index < line_wrap_amount + 1; line_wrap_index++) {
 					if (line_wrap_index != 0) {
 					if (line_wrap_index != 0) {
@@ -1219,6 +1222,8 @@ void TextEdit::_notification(int p_what) {
 					if (line_wrap_index == 0) {
 					if (line_wrap_index == 0) {
 						// Only do these if we are on the first wrapped part of a line.
 						// Only do these if we are on the first wrapped part of a line.
 
 
+						cache_entry.y_offset = ofs_y;
+
 						if (text.is_breakpoint(line) && !draw_breakpoint_gutter) {
 						if (text.is_breakpoint(line) && !draw_breakpoint_gutter) {
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 							VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color);
 							VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y + get_row_height() - EDSCALE, xmargin_end - xmargin_beg, EDSCALE), cache.breakpoint_color);
@@ -1319,6 +1324,9 @@ void TextEdit::_notification(int p_what) {
 						}
 						}
 					}
 					}
 
 
+					int first_visible_char = str.length();
+					int last_visible_char = 0;
+
 					// Loop through characters in one line.
 					// Loop through characters in one line.
 					int j = 0;
 					int j = 0;
 					for (; j < str.length(); j++) {
 					for (; j < str.length(); j++) {
@@ -1542,6 +1550,13 @@ void TextEdit::_notification(int p_what) {
 								int yofs = (get_row_height() - cache.space_icon->get_height()) / 2;
 								int yofs = (get_row_height() - cache.space_icon->get_height()) / 2;
 								cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color);
 								cache.space_icon->draw(ci, Point2(char_ofs + char_margin + ofs_x, ofs_y + yofs), in_selection && override_selected_font_color ? cache.font_color_selected : color);
 							}
 							}
+
+							if (first_visible_char > j) {
+								first_visible_char = j;
+							}
+							if (last_visible_char < j) {
+								last_visible_char = j;
+							}
 						}
 						}
 
 
 						char_ofs += char_w;
 						char_ofs += char_w;
@@ -1615,7 +1630,14 @@ void TextEdit::_notification(int p_what) {
 							}
 							}
 						}
 						}
 					}
 					}
+
+					cache_entry.first_visible_char.push_back(wrap_column_offset + first_visible_char);
+					cache_entry.last_visible_char.push_back(wrap_column_offset + last_visible_char);
+
+					wrap_column_offset += str.length();
 				}
 				}
+
+				line_drawing_cache[line] = cache_entry;
 			}
 			}
 
 
 			bool completion_below = false;
 			bool completion_below = false;
@@ -4782,6 +4804,57 @@ int TextEdit::get_row_height() const {
 	return cache.font->get_height() + cache.line_spacing;
 	return cache.font->get_height() + cache.line_spacing;
 }
 }
 
 
+/* Line and character position. */
+Point2 TextEdit::get_pos_at_line_column(int p_line, int p_column) const {
+	Rect2i rect = get_rect_at_line_column(p_line, p_column);
+	return rect.position + Vector2i(0, get_line_height());
+}
+
+Rect2 TextEdit::get_rect_at_line_column(int p_line, int p_column) const {
+	ERR_FAIL_INDEX_V(p_line, text.size(), Rect2i(-1, -1, 0, 0));
+	ERR_FAIL_COND_V(p_column < 0, Rect2i(-1, -1, 0, 0));
+	ERR_FAIL_COND_V(p_column > text[p_line].length(), Rect2i(-1, -1, 0, 0));
+
+	if (line_drawing_cache.size() == 0 || !line_drawing_cache.has(p_line)) {
+		// Line is not in the cache, which means it's outside of the viewing area.
+		return Rect2i(-1, -1, 0, 0);
+	}
+	LineDrawingCache cache_entry = line_drawing_cache[p_line];
+
+	int wrap_index = get_line_wrap_index_at_col(p_line, p_column);
+	if (wrap_index >= cache_entry.first_visible_char.size()) {
+		// Line seems to be wrapped beyond the viewable area.
+		return Rect2i(-1, -1, 0, 0);
+	}
+
+	int first_visible_char = cache_entry.first_visible_char[wrap_index];
+	int last_visible_char = cache_entry.last_visible_char[wrap_index];
+	if (p_column < first_visible_char || p_column > last_visible_char) {
+		// Character is outside of the viewing area, no point calculating its position.
+		return Rect2i(-1, -1, 0, 0);
+	}
+
+	Point2i pos, size;
+	pos.y = cache_entry.y_offset + get_line_height() * wrap_index;
+	pos.x = get_total_gutter_width() + cache.style_normal->get_margin(MARGIN_LEFT) - get_h_scroll();
+
+	int start_x = get_column_x_offset_for_line(p_column, p_line);
+	pos.x += start_x;
+
+	String line = text[p_line];
+	size.x = cache.font->get_char_size(line[p_column]).width;
+	size.y = get_line_height();
+
+	return Rect2i(pos, size);
+}
+
+Point2 TextEdit::get_line_column_at_pos(const Point2 &p_pos) const {
+	int row, col;
+	_get_mouse_pos(p_pos, row, col);
+
+	return Point2i(col, row);
+}
+
 int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const {
 int TextEdit::get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) const {
 	ERR_FAIL_INDEX_V(p_line, text.size(), 0);
 	ERR_FAIL_INDEX_V(p_line, text.size(), 0);
 
 
@@ -6953,6 +7026,10 @@ int TextEdit::get_info_gutter_width() const {
 	return info_gutter_width;
 	return info_gutter_width;
 }
 }
 
 
+int TextEdit::get_total_gutter_width() const {
+	return cache.line_number_w + cache.breakpoint_gutter_width + cache.fold_gutter_width + cache.info_gutter_width;
+}
+
 void TextEdit::set_draw_minimap(bool p_draw) {
 void TextEdit::set_draw_minimap(bool p_draw) {
 	draw_minimap = p_draw;
 	draw_minimap = p_draw;
 	update();
 	update();
@@ -7080,6 +7157,30 @@ PopupMenu *TextEdit::get_menu() const {
 	return menu;
 	return menu;
 }
 }
 
 
+int TextEdit::get_line_width(int p_line, int p_wrap_index) const {
+	ERR_FAIL_INDEX_V(p_line, text.size(), 0);
+
+	if (p_wrap_index >= 0 && line_wraps(p_line)) {
+		Vector<String> rows = get_wrap_rows_text(p_line);
+		ERR_FAIL_INDEX_V(p_wrap_index, rows.size(), 0);
+
+		int w = 0;
+		int len = rows[p_wrap_index].length();
+		const CharType *str = rows[p_wrap_index].c_str();
+		for (int i = 0; i < len; i++) {
+			w += text.get_char_width(str[i], str[i + 1], w);
+		}
+
+		return w;
+	}
+
+	return text.get_line_width(p_line);
+}
+
+int TextEdit::get_line_height() const {
+	return get_row_height();
+}
+
 void TextEdit::_bind_methods() {
 void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input);
 	ClassDB::bind_method(D_METHOD("_gui_input"), &TextEdit::_gui_input);
 	ClassDB::bind_method(D_METHOD("_scroll_moved"), &TextEdit::_scroll_moved);
 	ClassDB::bind_method(D_METHOD("_scroll_moved"), &TextEdit::_scroll_moved);
@@ -7110,6 +7211,13 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text);
 	ClassDB::bind_method(D_METHOD("get_text"), &TextEdit::get_text);
 	ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
 	ClassDB::bind_method(D_METHOD("get_line", "line"), &TextEdit::get_line);
 	ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line);
 	ClassDB::bind_method(D_METHOD("set_line", "line", "new_text"), &TextEdit::set_line);
+	ClassDB::bind_method(D_METHOD("get_line_wrapped_text", "line"), &TextEdit::get_wrap_rows_text);
+
+	ClassDB::bind_method(D_METHOD("get_line_width", "line", "wrap_index"), &TextEdit::get_line_width, DEFVAL(-1));
+	ClassDB::bind_method(D_METHOD("get_line_height"), &TextEdit::get_line_height);
+
+	ClassDB::bind_method(D_METHOD("is_line_wrapped", "line"), &TextEdit::line_wraps);
+	ClassDB::bind_method(D_METHOD("get_line_wrap_count", "line"), &TextEdit::times_line_wraps);
 
 
 	ClassDB::bind_method(D_METHOD("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor);
 	ClassDB::bind_method(D_METHOD("center_viewport_to_cursor"), &TextEdit::center_viewport_to_cursor);
 	ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("cursor_set_column", "column", "adjust_viewport"), &TextEdit::cursor_set_column, DEFVAL(true));
@@ -7127,6 +7235,11 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_right_click_moves_caret", "enable"), &TextEdit::set_right_click_moves_caret);
 	ClassDB::bind_method(D_METHOD("set_right_click_moves_caret", "enable"), &TextEdit::set_right_click_moves_caret);
 	ClassDB::bind_method(D_METHOD("is_right_click_moving_caret"), &TextEdit::is_right_click_moving_caret);
 	ClassDB::bind_method(D_METHOD("is_right_click_moving_caret"), &TextEdit::is_right_click_moving_caret);
 
 
+	/* Line and character position. */
+	ClassDB::bind_method(D_METHOD("get_pos_at_line_column", "line", "column"), &TextEdit::get_pos_at_line_column);
+	ClassDB::bind_method(D_METHOD("get_rect_at_line_column", "line", "column"), &TextEdit::get_rect_at_line_column);
+	ClassDB::bind_method(D_METHOD("get_line_column_at_pos", "position"), &TextEdit::get_line_column_at_pos);
+
 	ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly);
 	ClassDB::bind_method(D_METHOD("set_readonly", "enable"), &TextEdit::set_readonly);
 	ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly);
 	ClassDB::bind_method(D_METHOD("is_readonly"), &TextEdit::is_readonly);
 
 
@@ -7182,6 +7295,7 @@ void TextEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled);
 	ClassDB::bind_method(D_METHOD("is_breakpoint_gutter_enabled"), &TextEdit::is_breakpoint_gutter_enabled);
 	ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter);
 	ClassDB::bind_method(D_METHOD("set_draw_fold_gutter"), &TextEdit::set_draw_fold_gutter);
 	ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter);
 	ClassDB::bind_method(D_METHOD("is_drawing_fold_gutter"), &TextEdit::is_drawing_fold_gutter);
+	ClassDB::bind_method(D_METHOD("get_total_gutter_width"), &TextEdit::get_total_gutter_width);
 
 
 	ClassDB::bind_method(D_METHOD("set_hiding_enabled", "enable"), &TextEdit::set_hiding_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);
 	ClassDB::bind_method(D_METHOD("is_hiding_enabled"), &TextEdit::is_hiding_enabled);

+ 34 - 3
scene/gui/text_edit.h

@@ -109,12 +109,14 @@ public:
 		void set_indent_size(int p_indent_size);
 		void set_indent_size(int p_indent_size);
 		void set_font(const Ref<Font> &p_font);
 		void set_font(const Ref<Font> &p_font);
 		void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; }
 		void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; }
+
 		int get_line_width(int p_line) const;
 		int get_line_width(int p_line) const;
 		int get_max_width(bool p_exclude_hidden = false) const;
 		int get_max_width(bool p_exclude_hidden = false) const;
 		int get_char_width(CharType c, CharType next_c, int px) const;
 		int get_char_width(CharType c, CharType next_c, int px) const;
 		void set_line_wrap_amount(int p_line, int p_wrap_amount) const;
 		void set_line_wrap_amount(int p_line, int p_wrap_amount) const;
 		int get_line_wrap_amount(int p_line) const;
 		int get_line_wrap_amount(int p_line) const;
 		const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const;
 		const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const;
+
 		void set(int p_line, const String &p_text);
 		void set(int p_line, const String &p_text);
 		void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; }
 		void set_marked(int p_line, bool p_marked) { text.write[p_line].marked = p_marked; }
 		bool is_marked(int p_line) const { return text[p_line].marked; }
 		bool is_marked(int p_line) const { return text[p_line].marked; }
@@ -138,14 +140,19 @@ public:
 		bool has_info_icon(int p_line) const { return text[p_line].has_info; }
 		bool has_info_icon(int p_line) const { return text[p_line].has_info; }
 		const Ref<Texture> &get_info_icon(int p_line) const { return text[p_line].info_icon; }
 		const Ref<Texture> &get_info_icon(int p_line) const { return text[p_line].info_icon; }
 		const String &get_info(int p_line) const { return text[p_line].info; }
 		const String &get_info(int p_line) const { return text[p_line].info; }
+
 		void insert(int p_at, const String &p_text);
 		void insert(int p_at, const String &p_text);
 		void remove(int p_at);
 		void remove(int p_at);
+
 		int size() const { return text.size(); }
 		int size() const { return text.size(); }
+
 		void clear();
 		void clear();
 		void clear_width_cache();
 		void clear_width_cache();
 		void clear_wrap_cache();
 		void clear_wrap_cache();
 		void clear_info_icons();
 		void clear_info_icons();
+
 		_FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; }
 		_FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; }
+
 		Text() { indent_size = 4; }
 		Text() { indent_size = 4; }
 	};
 	};
 
 
@@ -504,6 +511,15 @@ private:
 
 
 	void _push_current_op();
 	void _push_current_op();
 
 
+	/* Line and character position. */
+	struct LineDrawingCache {
+		int y_offset = 0;
+		Vector<int> first_visible_char;
+		Vector<int> last_visible_char;
+	};
+
+	Map<int, LineDrawingCache> line_drawing_cache;
+
 	/* super internal api, undo/redo builds on it */
 	/* super internal api, undo/redo builds on it */
 
 
 	void _base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column);
 	void _base_insert_text(int p_line, int p_char, const String &p_text, int &r_end_line, int &r_end_column);
@@ -586,21 +602,29 @@ public:
 	void set_text(String p_text);
 	void set_text(String p_text);
 	void insert_text_at_cursor(const String &p_text);
 	void insert_text_at_cursor(const String &p_text);
 	void insert_at(const String &p_text, int at);
 	void insert_at(const String &p_text, int at);
+
 	int get_line_count() const;
 	int get_line_count() const;
+
+	int get_line_width(int p_line, int p_wrap_index = -1) const;
+	int get_line_height() const;
+
 	void set_line_as_marked(int p_line, bool p_marked);
 	void set_line_as_marked(int p_line, bool p_marked);
 	void set_line_as_bookmark(int p_line, bool p_bookmark);
 	void set_line_as_bookmark(int p_line, bool p_bookmark);
 	bool is_line_set_as_bookmark(int p_line) const;
 	bool is_line_set_as_bookmark(int p_line) const;
 	void get_bookmarks(List<int> *p_bookmarks) const;
 	void get_bookmarks(List<int> *p_bookmarks) const;
 	Array get_bookmarks_array() const;
 	Array get_bookmarks_array() const;
+
 	void set_line_as_breakpoint(int p_line, bool p_breakpoint);
 	void set_line_as_breakpoint(int p_line, bool p_breakpoint);
 	bool is_line_set_as_breakpoint(int p_line) const;
 	bool is_line_set_as_breakpoint(int p_line) const;
+	void get_breakpoints(List<int> *p_breakpoints) const;
+	Array get_breakpoints_array() const;
+	void remove_breakpoints();
+
 	void set_executing_line(int p_line);
 	void set_executing_line(int p_line);
 	void clear_executing_line();
 	void clear_executing_line();
+
 	void set_line_as_safe(int p_line, bool p_safe);
 	void set_line_as_safe(int p_line, bool p_safe);
 	bool is_line_set_as_safe(int p_line) const;
 	bool is_line_set_as_safe(int p_line) const;
-	void get_breakpoints(List<int> *p_breakpoints) const;
-	Array get_breakpoints_array() const;
-	void remove_breakpoints();
 
 
 	void set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info = "");
 	void set_line_info_icon(int p_line, Ref<Texture> p_icon, String p_info = "");
 	void clear_info_icons();
 	void clear_info_icons();
@@ -703,6 +727,11 @@ public:
 	String get_word_under_cursor() const;
 	String get_word_under_cursor() const;
 	String get_word_at_pos(const Vector2 &p_pos) const;
 	String get_word_at_pos(const Vector2 &p_pos) const;
 
 
+	/* Line and character position. */
+	Point2 get_pos_at_line_column(int p_line, int p_column) const;
+	Rect2 get_rect_at_line_column(int p_line, int p_column) const;
+	Point2 get_line_column_at_pos(const Point2 &p_pos) const;
+
 	bool search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column, int &r_line, int &r_column) const;
 	bool search(const String &p_key, uint32_t p_search_flags, int p_from_line, int p_from_column, int &r_line, int &r_column) const;
 
 
 	bool has_undo() const;
 	bool has_undo() const;
@@ -788,6 +817,8 @@ public:
 	void set_info_gutter_width(int p_gutter_width);
 	void set_info_gutter_width(int p_gutter_width);
 	int get_info_gutter_width() const;
 	int get_info_gutter_width() const;
 
 
+	int get_total_gutter_width() const;
+
 	void set_draw_minimap(bool p_draw);
 	void set_draw_minimap(bool p_draw);
 	bool is_drawing_minimap() const;
 	bool is_drawing_minimap() const;