gem-graph-client/libide/code/ide-source-iter.c

627 lines
14 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
/* ide-source-iter.c
* This file is part of GtkSourceView
*
* Copyright 2014 - Sébastien Wilmet <swilmet@gnome.org>
*
* GtkSourceView is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GtkSourceView is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/* GtkTextIter functions. Contains forward/backward functions for word
* movements, with custom word boundaries that are used for word selection
* (double-click) and cursor movements (Ctrl+left, Ctrl+right, etc). The
* initial idea was to use those word boundaries directly in GTK+, for all text
* widgets. But in the end only the GtkTextView::extend-selection signal has
* been added to be able to customize the boundaries for double- and
* triple-click (the ::move-cursor and ::delete-from-cursor signals were already
* present to customize boundaries for cursor movements). The GTK+ developers
* didn't want to change the word boundaries for text widgets. More information:
* https://mail.gnome.org/archives/gtk-devel-list/2014-September/msg00019.html
* https://bugzilla.gnome.org/show_bug.cgi?id=111503
*/
#include "config.h"
#include "ide-source-iter.h"
/* Go to the end of the next or current "full word". A full word is a group of
* non-blank chars.
* In other words, this function is the same as the 'E' Vim command.
*
* Examples ('|' is the iter position):
* "|---- abcd" -> "----| abcd"
* "| ---- abcd" -> " ----| abcd"
* "--|-- abcd" -> "----| abcd"
* "---- a|bcd" -> "---- abcd|"
*/
void
_ide_source_iter_forward_full_word_end (GtkTextIter *iter)
{
GtkTextIter pos;
gboolean non_blank_found = FALSE;
/* It would be better to use gtk_text_iter_forward_visible_char(), but
* it doesn't exist. So move by cursor position instead, it should be
* equivalent here.
*/
pos = *iter;
while (g_unichar_isspace (gtk_text_iter_get_char (&pos)))
{
gtk_text_iter_forward_visible_cursor_position (&pos);
}
while (!gtk_text_iter_is_end (&pos) &&
!g_unichar_isspace (gtk_text_iter_get_char (&pos)))
{
non_blank_found = TRUE;
gtk_text_iter_forward_visible_cursor_position (&pos);
}
if (non_blank_found)
{
*iter = pos;
}
}
/* Symmetric of iter_forward_full_word_end(). */
void
_ide_source_iter_backward_full_word_start (GtkTextIter *iter)
{
GtkTextIter pos;
GtkTextIter prev;
gboolean non_blank_found = FALSE;
pos = *iter;
while (!gtk_text_iter_is_start (&pos))
{
prev = pos;
gtk_text_iter_backward_visible_cursor_position (&prev);
if (!g_unichar_isspace (gtk_text_iter_get_char (&prev)))
{
break;
}
pos = prev;
}
while (!gtk_text_iter_is_start (&pos))
{
prev = pos;
gtk_text_iter_backward_visible_cursor_position (&prev);
if (g_unichar_isspace (gtk_text_iter_get_char (&prev)))
{
break;
}
non_blank_found = TRUE;
pos = prev;
}
if (non_blank_found)
{
*iter = pos;
}
}
gboolean
_ide_source_iter_starts_full_word (const GtkTextIter *iter)
{
GtkTextIter prev = *iter;
if (gtk_text_iter_is_end (iter))
{
return FALSE;
}
if (!gtk_text_iter_backward_visible_cursor_position (&prev))
{
return !g_unichar_isspace (gtk_text_iter_get_char (iter));
}
return (g_unichar_isspace (gtk_text_iter_get_char (&prev)) &&
!g_unichar_isspace (gtk_text_iter_get_char (iter)));
}
gboolean
_ide_source_iter_ends_full_word (const GtkTextIter *iter)
{
GtkTextIter prev = *iter;
if (!gtk_text_iter_backward_visible_cursor_position (&prev))
{
return FALSE;
}
return (!g_unichar_isspace (gtk_text_iter_get_char (&prev)) &&
(gtk_text_iter_is_end (iter) ||
g_unichar_isspace (gtk_text_iter_get_char (iter))));
}
/* Extends the definition of a natural-language word used by Pango. The
* underscore is added to the possible characters of a natural-language word.
*/
void
_ide_source_iter_forward_extra_natural_word_end (GtkTextIter *iter)
{
GtkTextIter next_word_end = *iter;
GtkTextIter next_underscore_end = *iter;
GtkTextIter *limit = NULL;
gboolean found;
if (gtk_text_iter_forward_visible_word_end (&next_word_end))
{
limit = &next_word_end;
}
found = gtk_text_iter_forward_search (iter,
"_",
GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY,
NULL,
&next_underscore_end,
limit);
if (found)
{
*iter = next_underscore_end;
}
else
{
*iter = next_word_end;
}
while (TRUE)
{
if (gtk_text_iter_get_char (iter) == '_')
{
gtk_text_iter_forward_visible_cursor_position (iter);
}
else if (gtk_text_iter_starts_word (iter))
{
gtk_text_iter_forward_visible_word_end (iter);
}
else
{
break;
}
}
}
/* Symmetric of iter_forward_extra_natural_word_end(). */
void
_ide_source_iter_backward_extra_natural_word_start (GtkTextIter *iter)
{
GtkTextIter prev_word_start = *iter;
GtkTextIter prev_underscore_start = *iter;
GtkTextIter *limit = NULL;
gboolean found;
if (gtk_text_iter_backward_visible_word_start (&prev_word_start))
{
limit = &prev_word_start;
}
found = gtk_text_iter_backward_search (iter,
"_",
GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY,
&prev_underscore_start,
NULL,
limit);
if (found)
{
*iter = prev_underscore_start;
}
else
{
*iter = prev_word_start;
}
while (!gtk_text_iter_is_start (iter))
{
GtkTextIter prev = *iter;
gtk_text_iter_backward_visible_cursor_position (&prev);
if (gtk_text_iter_get_char (&prev) == '_')
{
*iter = prev;
}
else if (gtk_text_iter_ends_word (iter))
{
gtk_text_iter_backward_visible_word_start (iter);
}
else
{
break;
}
}
}
gboolean
_ide_source_iter_starts_extra_natural_word (const GtkTextIter *iter)
{
gboolean starts_word;
GtkTextIter prev;
starts_word = gtk_text_iter_starts_word (iter);
prev = *iter;
if (!gtk_text_iter_backward_visible_cursor_position (&prev))
{
return starts_word || gtk_text_iter_get_char (iter) == '_';
}
if (starts_word)
{
return gtk_text_iter_get_char (&prev) != '_';
}
return (gtk_text_iter_get_char (iter) == '_' &&
gtk_text_iter_get_char (&prev) != '_' &&
!gtk_text_iter_ends_word (iter));
}
gboolean
_ide_source_iter_ends_extra_natural_word (const GtkTextIter *iter)
{
GtkTextIter prev;
gboolean ends_word;
prev = *iter;
if (!gtk_text_iter_backward_visible_cursor_position (&prev))
{
return FALSE;
}
ends_word = gtk_text_iter_ends_word (iter);
if (gtk_text_iter_is_end (iter))
{
return ends_word || gtk_text_iter_get_char (&prev) == '_';
}
if (ends_word)
{
return gtk_text_iter_get_char (iter) != '_';
}
return (gtk_text_iter_get_char (&prev) == '_' &&
gtk_text_iter_get_char (iter) != '_' &&
!gtk_text_iter_starts_word (iter));
}
/* Similar to gtk_text_iter_forward_visible_word_end, but with a custom
* definition of "word".
*
* It is normally the same word boundaries as in Vim. This function is the same
* as the 'e' command.
*
* With the custom word definition, a word can be:
* - a natural-language word as defined by Pango, plus the underscore. The
* underscore is added because it is often used in programming languages.
* - a group of contiguous non-blank characters.
*/
gboolean
_ide_source_iter_forward_visible_word_end (GtkTextIter *iter)
{
GtkTextIter orig = *iter;
GtkTextIter farthest = *iter;
GtkTextIter next_word_end = *iter;
GtkTextIter word_start;
/* 'farthest' is the farthest position that this function can return. Example:
* "|---- aaaa" -> "----| aaaa"
*/
_ide_source_iter_forward_full_word_end (&farthest);
/* Go to the next extra-natural word end. It can be farther than
* 'farthest':
* "|---- aaaa" -> "---- aaaa|"
*
* Or it can remain at the same place:
* "aaaa| ----" -> "aaaa| ----"
*/
_ide_source_iter_forward_extra_natural_word_end (&next_word_end);
if (gtk_text_iter_compare (&farthest, &next_word_end) < 0 ||
gtk_text_iter_equal (iter, &next_word_end))
{
*iter = farthest;
goto end;
}
/* From 'next_word_end', go to the previous extra-natural word start.
*
* Example 1:
* iter: "ab|cd"
* next_word_end: "abcd|" -> the good one
* word_start: "|abcd"
*
* Example 2:
* iter: "| abcd()"
* next_word_end: " abcd|()" -> the good one
* word_start: " |abcd()"
*
* Example 3:
* iter: "abcd|()efgh"
* next_word_end: "abcd()efgh|"
* word_start: "abcd()|efgh" -> the good one, at the end of the word "()".
*/
word_start = next_word_end;
_ide_source_iter_backward_extra_natural_word_start (&word_start);
/* Example 1 */
if (gtk_text_iter_compare (&word_start, iter) <= 0)
{
*iter = next_word_end;
}
/* Example 2 */
else if (_ide_source_iter_starts_full_word (&word_start))
{
*iter = next_word_end;
}
/* Example 3 */
else
{
*iter = word_start;
}
end:
return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter);
}
/* Symmetric of _ide_source_iter_forward_visible_word_end(). */
gboolean
_ide_source_iter_backward_visible_word_start (GtkTextIter *iter)
{
GtkTextIter orig = *iter;
GtkTextIter farthest = *iter;
GtkTextIter prev_word_start = *iter;
GtkTextIter word_end;
/* 'farthest' is the farthest position that this function can return. Example:
* "aaaa ----|" -> "aaaa |----"
*/
_ide_source_iter_backward_full_word_start (&farthest);
/* Go to the previous extra-natural word start. It can be farther than
* 'farthest':
* "aaaa ----|" -> "|aaaa ----"
*
* Or it can remain at the same place:
* "---- |aaaa" -> "---- |aaaa"
*/
_ide_source_iter_backward_extra_natural_word_start (&prev_word_start);
if (gtk_text_iter_compare (&prev_word_start, &farthest) < 0 ||
gtk_text_iter_equal (iter, &prev_word_start))
{
*iter = farthest;
goto end;
}
/* From 'prev_word_start', go to the next extra-natural word end.
*
* Example 1:
* iter: "ab|cd"
* prev_word_start: "|abcd" -> the good one
* word_end: "abcd|"
*
* Example 2:
* iter: "()abcd |"
* prev_word_start: "()|abcd " -> the good one
* word_end: "()abcd| "
*
* Example 3:
* iter: "abcd()|"
* prev_word_start: "|abcd()"
* word_end: "abcd|()" -> the good one, at the start of the word "()".
*/
word_end = prev_word_start;
_ide_source_iter_forward_extra_natural_word_end (&word_end);
/* Example 1 */
if (gtk_text_iter_compare (iter, &word_end) <= 0)
{
*iter = prev_word_start;
}
/* Example 2 */
else if (_ide_source_iter_ends_full_word (&word_end))
{
*iter = prev_word_start;
}
/* Example 3 */
else
{
*iter = word_end;
}
end:
return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter);
}
/* Similar to gtk_text_iter_forward_visible_word_ends(). */
gboolean
_ide_source_iter_forward_visible_word_ends (GtkTextIter *iter,
gint count)
{
GtkTextIter orig = *iter;
gint i;
if (count < 0)
{
return _ide_source_iter_backward_visible_word_starts (iter, -count);
}
for (i = 0; i < count; i++)
{
if (!_ide_source_iter_forward_visible_word_end (iter))
{
break;
}
}
return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter);
}
/* Similar to gtk_text_iter_backward_visible_word_starts(). */
gboolean
_ide_source_iter_backward_visible_word_starts (GtkTextIter *iter,
gint count)
{
GtkTextIter orig = *iter;
gint i;
if (count < 0)
{
return _ide_source_iter_forward_visible_word_ends (iter, -count);
}
for (i = 0; i < count; i++)
{
if (!_ide_source_iter_backward_visible_word_start (iter))
{
break;
}
}
return !gtk_text_iter_equal (&orig, iter) && !gtk_text_iter_is_end (iter);
}
gboolean
_ide_source_iter_starts_word (const GtkTextIter *iter)
{
if (_ide_source_iter_starts_full_word (iter) ||
_ide_source_iter_starts_extra_natural_word (iter))
{
return TRUE;
}
/* Example: "abcd|()", at the start of the word "()". */
return (!_ide_source_iter_ends_full_word (iter) &&
_ide_source_iter_ends_extra_natural_word (iter));
}
gboolean
_ide_source_iter_ends_word (const GtkTextIter *iter)
{
if (_ide_source_iter_ends_full_word (iter) ||
_ide_source_iter_ends_extra_natural_word (iter))
{
return TRUE;
}
/* Example: "abcd()|efgh", at the end of the word "()". */
return (!_ide_source_iter_starts_full_word (iter) &&
_ide_source_iter_starts_extra_natural_word (iter));
}
gboolean
_ide_source_iter_inside_word (const GtkTextIter *iter)
{
GtkTextIter prev_word_start;
GtkTextIter word_end;
if (_ide_source_iter_starts_word (iter))
{
return TRUE;
}
prev_word_start = *iter;
if (!_ide_source_iter_backward_visible_word_start (&prev_word_start))
{
return FALSE;
}
word_end = prev_word_start;
_ide_source_iter_forward_visible_word_end (&word_end);
return (gtk_text_iter_compare (&prev_word_start, iter) <= 0 &&
gtk_text_iter_compare (iter, &word_end) < 0);
}
/* Used for the GtkTextView::extend-selection signal. */
void
_ide_source_iter_extend_selection_word (const GtkTextIter *location,
GtkTextIter *start,
GtkTextIter *end)
{
/* Exactly the same algorithm as in GTK+, but with our custom word
* boundaries.
*/
*start = *location;
*end = *location;
if (_ide_source_iter_inside_word (start))
{
if (!_ide_source_iter_starts_word (start))
{
_ide_source_iter_backward_visible_word_start (start);
}
if (!_ide_source_iter_ends_word (end))
{
_ide_source_iter_forward_visible_word_end (end);
}
}
else
{
GtkTextIter tmp;
tmp = *start;
if (_ide_source_iter_backward_visible_word_start (&tmp))
{
_ide_source_iter_forward_visible_word_end (&tmp);
}
if (gtk_text_iter_get_line (&tmp) == gtk_text_iter_get_line (start))
{
*start = tmp;
}
else
{
gtk_text_iter_set_line_offset (start, 0);
}
tmp = *end;
if (!_ide_source_iter_forward_visible_word_end (&tmp))
{
gtk_text_iter_forward_to_end (&tmp);
}
if (_ide_source_iter_ends_word (&tmp))
{
_ide_source_iter_backward_visible_word_start (&tmp);
}
if (gtk_text_iter_get_line (&tmp) == gtk_text_iter_get_line (end))
{
*end = tmp;
}
else
{
gtk_text_iter_forward_to_line_end (end);
}
}
}