[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