[geany/geany-plugins] 42f0fc: Merge pull request #823 from LarsGit223/scope-improve-on-hover-evaluate

LarsGit223 git-noreply at xxxxx
Thu Jul 11 17:37:35 UTC 2019


Branch:      refs/heads/master
Author:      LarsGit223 <LarsGit223 at users.noreply.github.com>
Committer:   GitHub <noreply at github.com>
Date:        Thu, 11 Jul 2019 17:37:35 UTC
Commit:      42f0fc9a3adf5a79e8077406f87a42f0154af1a6
             https://github.com/geany/geany-plugins/commit/42f0fc9a3adf5a79e8077406f87a42f0154af1a6

Log Message:
-----------
Merge pull request #823 from LarsGit223/scope-improve-on-hover-evaluate

scope: Improved evaluation on-hover


Modified Paths:
--------------
    scope/src/Makefile.am
    scope/src/tests/unittests.c
    scope/src/tests/utils_test.c
    scope/src/tooltip.c
    scope/src/utils.c
    scope/src/utils.h

Modified: scope/src/Makefile.am
14 lines changed, 14 insertions(+), 0 deletions(-)
===================================================================
@@ -61,3 +61,17 @@ scope_la_CFLAGS = $(AM_CFLAGS) $(VTE_CFLAGS) \
 	-I$(top_srcdir)/utils/src
 
 include $(top_srcdir)/build/cppcheck.mk
+
+if UNITTESTS
+
+TESTS = unittests
+check_PROGRAMS = unittests
+
+unittests_SOURCES  = tests/unittests.c tests/utils_test.c \
+                     $(scope_la_SOURCES)
+unittests_CPPFLAGS = -DTEST $(scope_la_CPPFLAGS)
+unittests_CFLAGS  = $(GEANY_CFLAGS) -DUNITTESTS $(scope_la_CFLAGS)
+unittests_LDADD    = @GEANY_LIBS@ $(INTLLIBS) @CHECK_LIBS@ \
+                     $(scope_la_LIBADD)
+
+endif


Modified: scope/src/tests/unittests.c
28 lines changed, 28 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,28 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <check.h>
+
+#include <gtk/gtk.h>
+#include "geany.h"
+
+TCase *utils_create_tests(void);
+
+Suite *
+my_suite(void)
+{
+	Suite *s = suite_create("Scope");
+	suite_add_tcase(s, utils_create_tests());
+	return s;
+}
+
+int
+main(void)
+{
+	int nf;
+	Suite *s = my_suite();
+	SRunner *sr = srunner_create(s);
+	srunner_run_all(sr, CK_NORMAL);
+	nf = srunner_ntests_failed(sr);
+	srunner_free(sr);
+	return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}


Modified: scope/src/tests/utils_test.c
73 lines changed, 73 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,73 @@
+#include <check.h>
+#include "../src/common.h"
+#include "../src/utils.h"
+
+typedef struct S_EXPR_TEST_ITEM
+{
+	const gchar *input;
+	guint peek_index;
+	const gchar *expected;
+}EXPR_TEST_ITEM;
+
+EXPR_TEST_ITEM expr_test_items [] =
+{
+	{ " variable ",         sizeof(" var")-1,            "variable"           },
+	{ " (variable) ",       sizeof(" (var")-1,           "variable"           },
+	{ "struct.item",        sizeof("str")-1,             "struct"             },
+	{ "struct.item",        sizeof("str")-1,             "struct"             },
+	{ "struct.item",        sizeof("struct.it")-1,       "struct.item"        },
+	{ "struct->item",       sizeof("str")-1,             "struct"             },
+	{ "struct->item",       sizeof("struct->it")-1,      "struct->item"       },
+	{ "&(struct->item)",    sizeof("&(str")-1,           "struct"             },
+	{ "&(struct->item)",    sizeof("&(struct->it")-1,    "struct->item"       },
+	{ "foobar(item)",       sizeof("foobar(it")-1,       "item"               },
+	{ "sizeof(item)",       sizeof("sizeof(it")-1,       "sizeof(item)"       },
+	{ "array[5]",           sizeof("arr")-1,             "array"              },
+	{ "array[5]",           sizeof("array[")-1,          "array[5]"           },
+	{ "*pointer",           sizeof("*poi")-1,            "pointer"            },
+	{ "*pointer",           0,                           "*pointer"           },
+	{ "&variable",          sizeof("&var")-1,            "variable"           },
+	{ "&variable",          0,                           "&variable"          },
+	{ "foo variable",       sizeof("foo var")-1,         "variable"           },
+	{ "variable foo",       sizeof("var")-1,             "variable"           },
+	{ "int var_a, var_b;",  sizeof("int var")-1,         "var_a"              },
+	{ "int var_a, var_b;",  sizeof("int var_a, va")-1,   "var_b"              },
+	{ "foo(var_a, var_b);", sizeof("foo(var")-1,         "var_a"              },
+	{ "foo(var_a, var_b);", sizeof("foo(var_a, va")-1,   "var_b"              },
+	{ "array[index].item",  sizeof("array[index].i")-1,  "array[index].item"  },
+	{ "array[index]->item", sizeof("array[index]->i")-1, "array[index]->item" },
+	{ NULL,                 0,                         NULL           },
+};
+
+START_TEST(test_parser_for_evaluate)
+{
+	EXPR_TEST_ITEM *test_item;
+	guint index = 0;
+	gchar *expr, *chunk;
+
+	test_item = &(expr_test_items[index]);
+	while (test_item->input != NULL)
+	{
+		chunk = g_strdup(test_item->input);
+		expr = utils_evaluate_expr_from_string (chunk, test_item->peek_index);
+		ck_assert_ptr_ne(expr, NULL);
+		if (g_strcmp0(expr, test_item->expected) != 0)
+		{
+			ck_abort_msg("Index #%lu: Input: '%s', Peek-Ind.: %lu, Result: '%s' != '%s' (expected)",
+				index, test_item->input, test_item->peek_index, expr, test_item->expected);
+		}
+		g_free(chunk);
+		g_free(expr);
+
+		index++;
+		test_item = &(expr_test_items[index]);
+	}
+}
+END_TEST;
+
+TCase *utils_create_tests(void)
+{
+	TCase *tc_utils = tcase_create("utils");
+	tcase_add_test(tc_utils, test_parser_for_evaluate);
+	return tc_utils;
+}


Modified: scope/src/tooltip.c
40 lines changed, 34 insertions(+), 6 deletions(-)
===================================================================
@@ -22,6 +22,9 @@
 
 #include "common.h"
 
+/* Wordchars defining possible signs in an expression. */
+#define SCOPE_EXPR_WORDCHARS	"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.->[]"
+
 static void tooltip_trigger(void)
 {
 	GdkDisplay *display = gdk_display_get_default();
@@ -48,6 +51,7 @@ static void tooltip_trigger(void)
 	}
 }
 
+static gchar *last_expr = NULL;
 static gchar *output = NULL;
 static gint last_pos = -1;
 static gint peek_pos = -1;
@@ -69,6 +73,24 @@ static void tooltip_set(gchar *text)
 	}
 }
 
+static void tooltip_set_expr(gchar *expr, gchar *text)
+{
+	show = text != NULL;
+	g_free(output);
+	output = g_strdup_printf ("%s =\n %s", expr, text);
+	g_free(text);
+	g_free(expr);
+	last_pos = peek_pos;
+
+	if (show)
+	{
+		if (pref_tooltips_length && strlen(output) > (size_t) pref_tooltips_length + 3)
+			strcpy(output + pref_tooltips_length, "...");
+
+		tooltip_trigger();
+	}
+}
+
 static gint scid_gen = 0;
 
 void on_tooltip_error(GArray *nodes)
@@ -92,7 +114,7 @@ void on_tooltip_value(GArray *nodes)
 {
 	if (atoi(parse_grab_token(nodes)) == scid_gen)
 	{
-		tooltip_set(parse_get_display_from_7bit(parse_lead_value(nodes),
+		tooltip_set_expr(last_expr, parse_get_display_from_7bit(parse_lead_value(nodes),
 			parse_mode_get(input, MODE_HBIT), parse_mode_get(input, MODE_MEMBER)));
 	}
 }
@@ -107,17 +129,23 @@ static gboolean tooltip_launch(gpointer gdata)
 		(debug_state() & DS_SENDABLE))
 	{
 		ScintillaObject *sci = doc->editor->sci;
-		gchar *expr = sci_get_selection_mode(sci) == SC_SEL_STREAM &&
+		gchar *expr;
+		if (sci_get_selection_mode(sci) == SC_SEL_STREAM &&
 			peek_pos >= sci_get_selection_start(sci) &&
-			peek_pos < sci_get_selection_end(sci) ?
-			editor_get_default_selection(doc->editor, FALSE, NULL) :
-			editor_get_word_at_pos(doc->editor, peek_pos, NULL);
+			peek_pos < sci_get_selection_end(sci))
+		{
+			expr = editor_get_default_selection(doc->editor, FALSE, NULL);
+		}
+		else
+		{
+			expr = utils_read_evaluate_expr(doc->editor, peek_pos);
+		}
 
 		if ((expr = utils_verify_selection(expr)) != NULL)
 		{
 			g_free(input);
 			input = debug_send_evaluate('3', scid_gen, expr);
-			g_free(expr);
+			last_expr = expr;
 		}
 		else
 			tooltip_set(NULL);


Modified: scope/src/utils.c
347 lines changed, 347 insertions(+), 0 deletions(-)
===================================================================
@@ -36,6 +36,10 @@
 #ifdef G_OS_UNIX
 #include <fcntl.h>
 
+/* The maximum length of an expression for evaluating the value.
+   (Including the string terminator '\0') */
+#define SCOPE_MAX_EVALUATE_EXPR_LENGTH 256
+
 void show_errno(const char *prefix)
 {
 	show_error(_("%s: %s."), prefix, g_strerror(errno));
@@ -753,3 +757,346 @@ void utils_finalize(void)
 			utils_unlock(documents[i]);
 	}
 }
+
+/* checks whether @p c is an ASCII character (e.g. < 0x80) */
+#define IS_ASCII(c) (((unsigned char)(c)) < 0x80)
+
+/* This function is doing the parsing for 'utils_read_evaluate_expr()'.
+   It was mainly separated from it to support easy unit testing. */
+gchar *utils_evaluate_expr_from_string(gchar *chunk, guint peek_index)
+{
+	static const gchar *keep_prefix_list[] =
+		{ "sizeof", NULL };
+	static gchar expr[SCOPE_MAX_EVALUATE_EXPR_LENGTH];
+	static const gchar *wchars = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+	static const gchar *wspace = " \t";
+	gint startword, endword, temp_pos, round_brackets;
+	gint pos, left_bracket, cmp;
+	gboolean stop, oper, whitespace, brackets_exist;
+	gchar *expr_copy;
+
+	g_return_val_if_fail(chunk != NULL, NULL);
+
+	startword = peek_index;
+	endword = peek_index;
+	expr[0] = '\0';
+
+	if (chunk[startword] == ' ')
+	{
+		return NULL;
+	}
+
+	/* Find the left boundary of the expression. If the current sign
+	   is a '*' or a '&' then we already are at the left boundary. */
+	round_brackets = 0;
+	if (chunk[startword] != '*' && chunk[startword] != '&')
+	{
+		stop = FALSE;
+		oper = FALSE;
+		whitespace = FALSE;
+		while (startword > 0 && stop == FALSE)
+		{
+			/* the checks for "c < 0" are to allow any Unicode character which should make the code
+			 * a little bit more Unicode safe, anyway, this allows also any Unicode punctuation */
+			if (strchr(wchars, chunk[startword]) || ! IS_ASCII(chunk[startword]))
+			{
+				if (whitespace == TRUE && oper == FALSE)
+				{
+					startword++;
+					stop = TRUE;
+				}
+				else
+				{
+					startword--;
+					oper = FALSE;
+					whitespace = FALSE;
+				}
+			}
+			else
+			{
+				switch (chunk[startword])
+				{
+					case ' ':
+					case '\t':
+						startword--;
+						whitespace = TRUE;
+					break;
+
+					case '(':
+						round_brackets++;
+						startword--;
+					break;
+
+					case ')':
+						round_brackets--;
+						startword--;
+					break;
+
+					case '[':
+					case ']':
+						startword--;
+					break;
+
+					case '.':
+						/* Stop if there are no more signs before the current position,
+						   or if we already have seen an operator, or the sign before
+						   the operator is not a valid variable-name-char or whitespace. */
+						if (startword == 0 || oper == TRUE ||
+							(strchr(wchars, chunk[startword - 1]) == NULL &&
+							 strchr(wspace, chunk[startword - 1]) == NULL &&
+							 chunk[startword - 1] != ']'))
+						{
+							stop = TRUE;
+							break;
+						}
+						oper = TRUE;
+						startword--;
+					break;
+
+					case '>':
+						/* Stop if there are no more signs before the current position,
+						   or if we already have seen an operator, or the sign before
+						   the current sign is not a '-' (we expect a "->" here) */
+						if (startword < 2 || oper == TRUE || chunk[startword - 1] != '-')
+						{
+							stop = TRUE;
+							break;
+						}
+						oper = TRUE;
+						startword -= 2;
+					break;
+
+					case ':':
+						/* Stop if there are no more signs before the current position,
+						   or if we already have seen an operator, or the sign before
+						   the current sign is not a ':' (we expect a "::" here) */
+						if (startword < 2 || oper == TRUE || chunk[startword - 1] != ':')
+						{
+							stop = TRUE;
+							break;
+						}
+						oper = TRUE;
+						startword -= 2;
+					break;
+
+					default:
+						/* Not a valid variable-name-char or whitespace or operator. */
+						stop = TRUE;
+						if (whitespace == TRUE)
+						{
+							startword += 2;
+						}
+					break;
+				}
+			}
+		}
+		if (chunk[startword] == '*' || chunk[startword] == '&')
+		{
+			startword++;
+		}
+	}
+
+	/* Find the right boundary of the expression. */
+	stop = FALSE;
+	while (chunk[endword] != 0 && stop == FALSE)
+	{
+		/* the checks for "c < 0" are to allow any Unicode character which should make the code
+		 * a little bit more Unicode safe, anyway, this allows also any Unicode punctuation */
+		if (strchr(wchars, chunk[endword]) || ! IS_ASCII(chunk[endword]))
+		{
+			endword++;
+		}
+		else
+		{
+			switch (chunk[endword])
+			{
+				case ')':
+					round_brackets--;
+					if (round_brackets < 1)
+					{
+						/* We stop here in any case. 0 would be the normal case.
+						   If the value is below zero something went wrong in the
+						   loop above. Maybe this is just not a valid expression. */
+						stop = TRUE;
+					}
+				break;
+
+				case ']':
+				case '*':
+				case '&':
+					endword++;
+				break;
+
+				case ' ':
+				case '\t':
+					/* We should usually stop here. But we need to continue
+					   if the whitespace is followed by an array index "[...]"! */
+					temp_pos = endword + 1;
+					while (chunk[temp_pos] != 0 &&
+						   (chunk[temp_pos] == ' ' || chunk[temp_pos] == '\t'))
+					{
+						temp_pos++;
+					}
+					if (chunk[temp_pos] == '[' && chunk[temp_pos+1] != ']')
+					{
+						endword = temp_pos + 1;
+					}
+					else
+					{
+						stop = TRUE;
+					}
+				break;
+
+				default:
+					endword--;
+					stop = TRUE;
+				break;
+			}
+		}
+	}
+
+	/* Skip leading whitespace. */
+	while ((chunk[startword] == ' ' || chunk[startword] == '\t') &&
+		   startword < endword)
+	{
+		startword++;
+	}
+
+	/* Skip trailing whitespace. */
+	while (endword > 0 &&
+		   (chunk[endword] == ' ' || chunk[endword] == '\t') &&
+		   startword < endword)
+	{
+		endword--;
+	}
+
+	/* Validate/ensure balanced round brackets. */
+	round_brackets = 0;
+	left_bracket = 0;
+	stop = FALSE;
+	brackets_exist = FALSE;
+	for (pos = startword ; pos <= endword && stop == FALSE ; pos++)
+	{
+		switch (chunk[pos])
+		{
+			case '(':
+				round_brackets++;
+				brackets_exist = TRUE;
+				left_bracket = pos;
+			break;
+
+			case ')':
+				round_brackets--;
+				if (round_brackets == 0)
+				{
+					endword = pos;
+					stop = TRUE;
+				}
+				else if (round_brackets < 0)
+				{
+					endword = pos - 1;
+					stop = TRUE;
+				}
+			break;
+
+			case ' ':
+			case '\t':
+			break;
+
+			default:
+				temp_pos = pos;
+			break;
+		}
+	}
+
+	/* If brackets exist and the left-most sign is not a bracket itself,
+	   then it could be a function name or an operator (sizeof). We
+	   want to exclude the function name but include an operator name. */
+	if (brackets_exist == TRUE && chunk[startword] != '(')
+	{
+		pos = 0;
+		oper = FALSE;
+		while (keep_prefix_list[pos] != NULL)
+		{
+			cmp = strncmp(keep_prefix_list[pos], &(chunk[startword]),
+				strlen(keep_prefix_list[pos]));
+			if (cmp == 0)
+			{
+				/* Match with allowed operator/prefix. Keep it. */
+				oper = TRUE;
+				break;
+			}
+			pos++;
+		}
+
+		/* Did we find a wanted prefix? */
+		if (oper == FALSE)
+		{
+			/* No! Move startword back to position of left-most valid
+			   round bracket. */
+			startword = left_bracket;
+		}
+	}
+
+	/* Skip useless surrounding brackets if present. */
+	if (startword < endword)
+	{
+		guint skipped = 0;
+
+		while (chunk[startword] == '(' && startword < endword)
+		{
+			skipped++;
+			startword++;
+		}
+		while (chunk[endword] == ')' && skipped > 0)
+		{
+			skipped--;
+			endword--;
+		}
+	}
+
+	expr_copy = NULL;
+	if (startword < endword)
+	{
+		if (chunk[endword] != '\0')
+		{
+			chunk[endword+1] = '\0';
+		}
+
+		/* ensure null terminated */
+		g_strlcpy(expr, &(chunk[startword]), sizeof(expr));
+		expr_copy = g_strdup(expr);
+	}
+
+	return expr_copy;
+}
+
+/* Reads the expression for evaluate at the given cursor position and writes
+ * it into the given buffer. The buffer will be NULL terminated in any case,
+ * even when the word is truncated because wordlen is too small.
+ * Position can be -1, then the current position is used. */
+gchar *utils_read_evaluate_expr(GeanyEditor *editor, gint peek_pos)
+{
+	gint line, line_start;
+	gchar *chunk, *expr;
+	ScintillaObject *sci;
+
+	g_return_val_if_fail(editor != NULL, NULL);
+	sci = editor->sci;
+
+	if (peek_pos == -1)
+	{
+		peek_pos = sci_get_current_position(sci);
+	}
+
+	line = sci_get_line_from_position(sci, peek_pos);
+	line_start = sci_get_position_from_line(sci, line);
+	chunk = sci_get_line(sci, line);
+
+	/* Now that we got the content, let's parse it. */
+	expr = utils_evaluate_expr_from_string (chunk, peek_pos - line_start);
+
+	g_free(chunk);
+
+	return expr;
+}


Modified: scope/src/utils.h
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -107,5 +107,8 @@ void utils_tree_set_cursor(GtkTreeSelection *selection, GtkTreeIter *iter, gdoub
 void utils_init(void);
 void utils_finalize(void);
 
+gchar *utils_evaluate_expr_from_string(gchar *chunk, guint peek_index);
+gchar *utils_read_evaluate_expr(GeanyEditor *editor, gint pos);
+
 #define UTILS_H 1
 #endif



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Plugins-Commits mailing list