Branch: refs/heads/master Author: LarsGit223 LarsGit223@users.noreply.github.com Committer: GitHub noreply@github.com Date: Thu, 11 Jul 2019 17:37:35 UTC Commit: 42f0fc9a3adf5a79e8077406f87a42f0154af1a6 https://github.com/geany/geany-plugins/commit/42f0fc9a3adf5a79e8077406f87a42...
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).