diff --git a/data/elementary/themes/Makefile.am b/data/elementary/themes/Makefile.am index b251360024..855aa4d421 100644 --- a/data/elementary/themes/Makefile.am +++ b/data/elementary/themes/Makefile.am @@ -96,6 +96,7 @@ elementary/themes/edc/elm/button.edc \ elementary/themes/edc/elm/calendar.edc \ elementary/themes/edc/elm/check.edc \ elementary/themes/edc/elm/clock.edc \ +elementary/themes/edc/elm/code.edc \ elementary/themes/edc/elm/colorsel.edc \ elementary/themes/edc/elm/colorclass.edc \ elementary/themes/edc/elm/conform.edc \ diff --git a/data/elementary/themes/default.edc b/data/elementary/themes/default.edc index cf88829c97..de83033134 100644 --- a/data/elementary/themes/default.edc +++ b/data/elementary/themes/default.edc @@ -72,6 +72,7 @@ collections { #include "edc/elm/gengrid.edc" #include "edc/elm/hover.edc" #include "edc/elm/cursor.edc" +#include "edc/elm/code.edc" // desktop in general #include "edc/wallpaper.edc" diff --git a/data/elementary/themes/edc/elm/code.edc b/data/elementary/themes/edc/elm/code.edc new file mode 100644 index 0000000000..3f371fac14 --- /dev/null +++ b/data/elementary/themes/edc/elm/code.edc @@ -0,0 +1,16 @@ +/* simple layout to pack our scrolling content into an elm_layout */ +group { name: "elm_code/layout/default"; + parts { + part { name: "elm.swallow.content"; type: SWALLOW; + description { state: "default" 0.0; + align: 0.5 0.0; + fixed: 0 1; + + rel2 { + relative: 1.0 1.0; + offset: 0 0; + } + } + } + } +} diff --git a/src/Makefile_Elementary.am b/src/Makefile_Elementary.am index 9685694199..db805c7514 100644 --- a/src/Makefile_Elementary.am +++ b/src/Makefile_Elementary.am @@ -135,6 +135,7 @@ elm_public_eolian_files = \ lib/elementary/elm_progressbar_internal_part.eo \ lib/elementary/elm_popup_internal_part.eo \ lib/elementary/elm_scroller_internal_part.eo \ + lib/elementary/elm_code_widget.eo \ $(NULL) # Legacy classes - not part of public EO API @@ -252,7 +253,16 @@ includesunstable_HEADERS = \ lib/elementary/elm_widget_thumb.h \ lib/elementary/elm_widget_toolbar.h \ lib/elementary/elm_widget_video.h \ - lib/elementary/elm_widget_web.h + lib/elementary/elm_widget_web.h \ + lib/elementary/elm_code.h \ + lib/elementary/elm_code_widget_legacy.h \ + lib/elementary/elm_code_widget_selection.h \ + lib/elementary/elm_code_diff_widget.h \ + lib/elementary/elm_code_common.h \ + lib/elementary/elm_code_line.h \ + lib/elementary/elm_code_text.h \ + lib/elementary/elm_code_file.h \ + lib/elementary/elm_code_parse.h includesunstabledir = $(includedir)/elementary-@VMAJ@ nodist_includesunstable_HEADERS = \ @@ -562,6 +572,16 @@ lib_elementary_libelementary_la_SOURCES = \ lib/elementary/elm_check.c \ lib/elementary/elm_clock.c \ lib/elementary/elm_cnp.c \ + lib/elementary/elm_code_line.c \ + lib/elementary/elm_code_text.c \ + lib/elementary/elm_code_file.c \ + lib/elementary/elm_code_parse.c \ + lib/elementary/elm_code_widget_selection.c \ + lib/elementary/elm_code_widget.c \ + lib/elementary/elm_code_diff_widget.c \ + lib/elementary/elm_code.c \ + lib/elementary/elm_code_private.h \ + lib/elementary/elm_code_widget_private.h \ lib/elementary/elm_colorselector.c \ lib/elementary/elm_color_class.c \ lib/elementary/elc_combobox.c \ @@ -703,7 +723,8 @@ bin_PROGRAMS += \ bin/elementary/elementary_test \ bin/elementary/elementary_config \ bin/elementary/elementary_codegen \ -bin/elementary/elm_prefs_cc +bin/elementary/elm_prefs_cc \ +bin/elementary/elementary_code if BUILD_QUICKLAUNCH bin_PROGRAMS += \ @@ -842,6 +863,21 @@ bin_elementary_elementary_test_CPPFLAGS = \ -DPACKAGE_DATA_DIR=\"$(datadir)/elementary\" \ @ELEMENTARY_CFLAGS@ +bin_elementary_elementary_code_SOURCES = \ +bin/elementary/elm_code_test_main.c \ +bin/elementary/elm_code_test_private.h +bin_elementary_elementary_code_LDADD = @USE_ELEMENTARY_LIBS@ +bin_elementary_elementary_code_DEPENDENCIES = @USE_ELEMENTARY_INTERNAL_LIBS@ +bin_elementary_elementary_code_CPPFLAGS = \ +-I$(top_srcdir) \ +-I$(top_srcdir)/src/lib/elementary \ +-I$(top_builddir)/src/lib/elementary \ +-I$(top_srcdir)/src/bin/elementary \ +-DPACKAGE_BIN_DIR=\"$(bindir)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +-DPACKAGE_DATA_DIR=\"$(datadir)/elementary\" \ +-DLOCALE_DIR=\"$(localedir)\" \ +@ELEMENTARY_CFLAGS@ bin_elementary_elementary_config_SOURCES = bin/elementary/config.c bin_elementary_elementary_config_LDADD = @USE_ELEMENTARY_LIBS@ @@ -1214,6 +1250,11 @@ edje_external_elementary_module_la_LDFLAGS = -module @EFL_LTMODULE_FLAGS@ edje_external_elementary_module_la_LIBTOOLFLAGS = --tag=disable-static ### Tests +EXTRA_DIST += \ +tests/elementary/testfile.txt \ +tests/elementary/testfile-windows.txt \ +tests/elementary/testfile-withblanks.txt \ +tests/elementary/testdiff.diff if EFL_ENABLE_TESTS @@ -1291,12 +1332,24 @@ tests_elementary_elm_suite_SOURCES = \ tests/elementary/elm_test_panes.c \ tests/elementary/elm_test_slideshow.c \ tests/elementary/elm_test_spinner.c \ - tests/elementary/elm_test_plug.c + tests/elementary/elm_test_plug.c \ + tests/elementary/elm_code_file_test_load.c \ + tests/elementary/elm_code_file_test_memory.c \ + tests/elementary/elm_code_test_basic.c \ + tests/elementary/elm_code_test_line.c \ + tests/elementary/elm_code_test_parse.c \ + tests/elementary/elm_code_test_text.c \ + tests/elementary/elm_code_test_widget.c \ + tests/elementary/elm_code_test_widget_text.c \ + tests/elementary/elm_code_test_widget_selection.c \ + tests/elementary/elm_code_test_widget_undo.c tests_elementary_elm_suite_CPPFLAGS = \ -DTESTS_BUILD_DIR=\"${top_builddir}/src/tests/elementary\" \ + -DTESTS_SRC_DIR=\"${top_srcdir}/src/tests/elementary\" \ -DELM_IMAGE_DATA_DIR=\"${top_srcdir}/data/elementary\" \ -DELM_TEST_DATA_DIR=\"${abs_top_builddir}/data/elementary\" \ + -DPACKAGE_DATA_DIR=\"${abs_top_builddir}/data/elementary\" \ -I$(top_srcdir)/src/lib/elementary \ -I$(top_builddir)/src/lib/elementary \ @CHECK_CFLAGS@ \ @@ -1339,4 +1392,7 @@ tests/elementary/elm_suite.h \ tests/elementary/elm_test_helper.h \ lib/elementary/Makefile.am \ lib/elementary/Makefile.in \ -lib/elementary/Elementary.h.in +lib/elementary/Elementary.h.in \ +lib/elementary/elm_code_widget_text.c \ +lib/elementary/elm_code_widget_undo.c + diff --git a/src/bin/elementary/.gitignore b/src/bin/elementary/.gitignore index 5cbbb5c189..4f2e315ba3 100644 --- a/src/bin/elementary/.gitignore +++ b/src/bin/elementary/.gitignore @@ -5,3 +5,4 @@ /elementary_codegen /elementary_testql /elm_prefs_cc +/elementary_code diff --git a/src/bin/elementary/elm_code_test_main.c b/src/bin/elementary/elm_code_test_main.c new file mode 100644 index 0000000000..80588ae159 --- /dev/null +++ b/src/bin/elementary/elm_code_test_main.c @@ -0,0 +1,439 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +/* NOTE: Respecting header order is important for portability. + * Always put system first, then EFL, then your public header, + * and finally your private one. */ + +#if ENABLE_NLS +# include +#endif + +#include +#include + +#include "elm_code_test_private.h" + +#define COPYRIGHT "Copyright © 2014 andy and various contributors (see AUTHORS)." + +static void +_elm_code_test_win_del(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + elm_exit(); +} + +static void _append_line(Elm_Code_File *file, const char *line) +{ + int length; + + length = strlen(line); + elm_code_file_line_append(file, line, length, NULL); +} + +static Eina_Bool +_elm_code_test_line_clicked_cb(void *data EINA_UNUSED, const Eo_Event *event) +{ + Elm_Code_Line *line; + + line = (Elm_Code_Line *)event->info; + + printf("CLICKED line %d\n", line->number); + return EO_CALLBACK_CONTINUE; +} + +static Eina_Bool +_elm_code_test_line_done_cb(void *data EINA_UNUSED, const Eo_Event *event) +{ + Elm_Code_Line *line; + + line = (Elm_Code_Line *)event->info; + + if (line->number == 1) + elm_code_line_token_add(line, 17, 24, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + else if (line->number == 4) + line->status = ELM_CODE_STATUS_TYPE_ERROR; + + return EO_CALLBACK_STOP; +} + +static Evas_Object * +_elm_code_test_welcome_setup(Evas_Object *parent) +{ + Elm_Code *code; + Elm_Code_Widget *widget; + + code = elm_code_create(); + widget = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, code)); + elm_obj_code_widget_font_set(widget, NULL, 12); + eo_event_callback_add(widget, &ELM_CODE_EVENT_LINE_LOAD_DONE, _elm_code_test_line_done_cb, NULL); + eo_event_callback_add(widget, ELM_OBJ_CODE_WIDGET_EVENT_LINE_CLICKED, _elm_code_test_line_clicked_cb, code); + + _append_line(code->file, "❤ Hello World, Elm Code! ❤"); + _append_line(code->file, ""); + _append_line(code->file, "This is a demo of elm_code's capabilities."); + _append_line(code->file, "⚑ *** Currently experimental ***"); + + evas_object_size_hint_weight_set(widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 1, 13); + + return widget; +} + +static Evas_Object * +_elm_code_test_editor_setup(Evas_Object *parent, Eina_Bool log) +{ + Elm_Code *code; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + + code = elm_code_create(); + widget = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, code)); + elm_obj_code_widget_font_set(widget, NULL, 14); + elm_obj_code_widget_editable_set(widget, EINA_TRUE); + elm_obj_code_widget_show_whitespace_set(widget, EINA_TRUE); + elm_obj_code_widget_line_numbers_set(widget, EINA_TRUE); + + if (!log) + { + _append_line(code->file, "Edit me :)"); + _append_line(code->file, ""); + _append_line(code->file, ""); + _append_line(code->file, "...Please?"); + + line = elm_code_file_line_get(code->file, 1); + elm_code_line_token_add(line, 5, 6, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + elm_code_callback_fire(code, &ELM_CODE_EVENT_LINE_LOAD_DONE, line); + } + + evas_object_size_hint_weight_set(widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget); + + return widget; +} + +static Evas_Object * +_elm_code_test_mirror_setup(Elm_Code *code, char *font_name, Evas_Object *parent) +{ + Elm_Code_Widget *widget; + + widget = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, code)); + elm_obj_code_widget_font_set(widget, font_name, 11); + elm_obj_code_widget_line_numbers_set(widget, EINA_TRUE); + + evas_object_size_hint_weight_set(widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget); + + return widget; +} + +static Evas_Object * +_elm_code_test_diff_inline_setup(Evas_Object *parent) +{ + Evas_Object *diff; + Elm_Code *code; + + code = elm_code_create(); + diff = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, code)); + + evas_object_size_hint_weight_set(diff, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(diff, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(diff); + + elm_code_parser_standard_add(code, ELM_CODE_PARSER_STANDARD_DIFF); + elm_code_file_open(code, PACKAGE_DATA_DIR "testdiff.diff"); + + return diff; +} + +static Evas_Object * +_elm_code_test_diff_setup(Evas_Object *parent) +{ + Evas_Object *diff; + Elm_Code *code; + + code = elm_code_create(); + elm_code_file_open(code, PACKAGE_DATA_DIR "testdiff.diff"); + + diff = elm_code_diff_widget_add(parent, code); + return diff; +} + +static Eina_Bool +_elm_code_test_log_timer(void *data) +{ + Elm_Code *code = data; + static int line = 0; + char buf[250]; + + sprintf(buf, "line %d", ++line); + _append_line(code->file, buf); + + return ECORE_CALLBACK_RENEW; +} + +static void +_elm_code_test_log_clicked(void *data, Evas_Object *obj, void *event_info EINA_UNUSED) +{ + static Ecore_Timer *t = NULL; + + if (t) + { + elm_object_text_set(obj, "Start"); + ecore_timer_del(t); + t = NULL; + return; + } + + t = ecore_timer_add(0.05, _elm_code_test_log_timer, data); + elm_object_text_set(obj, "Stop"); +} + +static void +_elm_code_test_welcome_editor_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *naviframe, *screen; + + naviframe = (Evas_Object *)data; + screen = elm_box_add(naviframe); + evas_object_size_hint_weight_set(screen, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_box_pack_end(screen, _elm_code_test_editor_setup(screen, EINA_FALSE)); + evas_object_show(screen); + + elm_naviframe_item_push(naviframe, "Editor", + NULL, NULL, screen, NULL); +} + +static void +_elm_code_test_welcome_log_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *naviframe, *screen, *o, *code; + + naviframe = (Evas_Object *)data; + screen = elm_box_add(naviframe); + evas_object_size_hint_weight_set(screen, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + + code = _elm_code_test_editor_setup(screen, EINA_TRUE); + elm_box_pack_end(screen, code); + + o = elm_button_add(screen); + elm_object_text_set(o, "log"); + evas_object_smart_callback_add(o, "clicked", _elm_code_test_log_clicked, elm_obj_code_widget_code_get(code)); + elm_box_pack_end(screen, o); + evas_object_show(o); + + evas_object_show(screen); + + elm_naviframe_item_push(naviframe, "Editor", + NULL, NULL, screen, NULL); +} + +static void +_elm_code_test_welcome_mirror_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Elm_Code *code; + Evas_Object *naviframe, *screen, *widget; + + naviframe = (Evas_Object *)data; + screen = elm_box_add(naviframe); + elm_box_homogeneous_set(screen, EINA_TRUE); + evas_object_size_hint_weight_set(screen, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + + widget = _elm_code_test_editor_setup(screen, EINA_FALSE); + code = elm_obj_code_widget_code_get(widget); + elm_box_pack_end(screen, widget); + + elm_box_pack_end(screen, _elm_code_test_mirror_setup(code, "Mono:style=Oblique", screen)); + elm_box_pack_end(screen, _elm_code_test_mirror_setup(code, "Nimbus Mono", screen)); + + evas_object_show(screen); + elm_naviframe_item_push(naviframe, "Mirrored editor", + NULL, NULL, screen, NULL); +} + +static void +_elm_code_test_welcome_diff_inline_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *naviframe, *screen; + + naviframe = (Evas_Object *)data; + screen = elm_box_add(naviframe); + evas_object_size_hint_weight_set(screen, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_box_pack_end(screen, _elm_code_test_diff_inline_setup(screen)); + evas_object_show(screen); + + elm_naviframe_item_push(naviframe, "Diff widget (inline)", + NULL, NULL, screen, NULL); +} + +static void +_elm_code_test_welcome_diff_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *naviframe, *screen; + + naviframe = (Evas_Object *)data; + screen = elm_box_add(naviframe); + evas_object_size_hint_weight_set(screen, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_box_pack_end(screen, _elm_code_test_diff_setup(screen)); + evas_object_show(screen); + + elm_naviframe_item_push(naviframe, "Diff widget (comparison)", + NULL, NULL, screen, NULL); +} + +static Evas_Object * +elm_code_test_win_setup(void) +{ + Evas_Object *win, *vbox, *text, *button, *naviframe; + Elm_Object_Item *item; + + win = elm_win_util_standard_add("main", "Elm_Code Demo"); + if (!win) return NULL; + + naviframe = elm_naviframe_add(win); + evas_object_size_hint_weight_set(naviframe, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + elm_win_resize_object_add(win, naviframe); + evas_object_show(naviframe); + + vbox = elm_box_add(win); + evas_object_size_hint_weight_set(vbox, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(vbox, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(vbox); + + text = _elm_code_test_welcome_setup(vbox); + evas_object_size_hint_weight_set(text, EVAS_HINT_EXPAND, 0.5); + elm_box_pack_end(vbox, text); + + button = elm_button_add(vbox); + elm_object_text_set(button, "Editor"); + evas_object_size_hint_weight_set(button, 0.5, 0.25); + evas_object_size_hint_align_set(button, EVAS_HINT_FILL, 1.0); + evas_object_smart_callback_add(button, "clicked", + _elm_code_test_welcome_editor_cb, naviframe); + elm_box_pack_end(vbox, button); + evas_object_show(button); + + button = elm_button_add(vbox); + elm_object_text_set(button, "Log viewer"); + evas_object_size_hint_weight_set(button, 0.5, 0.0); + evas_object_size_hint_align_set(button, EVAS_HINT_FILL, 1.0); + evas_object_smart_callback_add(button, "clicked", + _elm_code_test_welcome_log_cb, naviframe); + elm_box_pack_end(vbox, button); + evas_object_show(button); + + button = elm_button_add(vbox); + elm_object_text_set(button, "Mirrored editor"); + evas_object_size_hint_weight_set(button, 0.5, 0.0); + evas_object_size_hint_align_set(button, EVAS_HINT_FILL, 0.5); + evas_object_smart_callback_add(button, "clicked", + _elm_code_test_welcome_mirror_cb, naviframe); + elm_box_pack_end(vbox, button); + evas_object_show(button); + + button = elm_button_add(vbox); + elm_object_text_set(button, "Diff (inline)"); + evas_object_size_hint_weight_set(button, 0.5, 0.0); + evas_object_size_hint_align_set(button, EVAS_HINT_FILL, 0.5); + evas_object_smart_callback_add(button, "clicked", + _elm_code_test_welcome_diff_inline_cb, naviframe); + elm_box_pack_end(vbox, button); + evas_object_show(button); + + button = elm_button_add(vbox); + elm_object_text_set(button, "Diff (comparison)"); + evas_object_size_hint_weight_set(button, 0.5, 0.25); + evas_object_size_hint_align_set(button, EVAS_HINT_FILL, 0.0); + evas_object_smart_callback_add(button, "clicked", + _elm_code_test_welcome_diff_cb, naviframe); + elm_box_pack_end(vbox, button); + evas_object_show(button); + + item = elm_naviframe_item_push(naviframe, "Choose Demo", + NULL, NULL,vbox, NULL); + elm_naviframe_item_title_enabled_set(item, EINA_FALSE, EINA_FALSE); + elm_win_resize_object_add(win, naviframe); + + evas_object_smart_callback_add(win, "delete,request", _elm_code_test_win_del, NULL); + evas_object_resize(win, 400 * elm_config_scale_get(), 320 * elm_config_scale_get()); + evas_object_show(win); + + return win; +} + +static const Ecore_Getopt optdesc = { + "elm_code_test", + "%prog [options]", + PACKAGE_VERSION, + COPYRIGHT, + "BSD with advertisement clause", + "An EFL elm_code_test program", + 0, + { + ECORE_GETOPT_LICENSE('L', "license"), + ECORE_GETOPT_COPYRIGHT('C', "copyright"), + ECORE_GETOPT_VERSION('V', "version"), + ECORE_GETOPT_HELP('h', "help"), + ECORE_GETOPT_SENTINEL + } +}; + +EAPI_MAIN int +elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED) +{ + Evas_Object *win; + int args; + Eina_Bool quit_option = EINA_FALSE; + + Ecore_Getopt_Value values[] = { + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_BOOL(quit_option), + ECORE_GETOPT_VALUE_NONE + }; + +#if ENABLE_NLS + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALE_DIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); +#endif + + elm_code_init(); + + args = ecore_getopt_parse(&optdesc, values, argc, argv); + if (args < 0) + { + EINA_LOG_CRIT("Could not parse arguments."); + goto end; + } + else if (quit_option) + { + goto end; + } + + /* tell elm about our app so it can figure out where to get files */ + elm_app_compile_bin_dir_set(PACKAGE_BIN_DIR); + elm_app_compile_lib_dir_set(PACKAGE_LIB_DIR); + elm_app_compile_data_dir_set(PACKAGE_DATA_DIR); + elm_app_info_set(elm_main, "elm_code_test", "images/elm_code.png"); + + if (!(win = elm_code_test_win_setup())) + goto end; + + elm_run(); + + end: + elm_code_shutdown(); + elm_shutdown(); + + return 0; +} +ELM_MAIN() diff --git a/src/bin/elementary/elm_code_test_private.h b/src/bin/elementary/elm_code_test_private.h new file mode 100644 index 0000000000..04fb817a02 --- /dev/null +++ b/src/bin/elementary/elm_code_test_private.h @@ -0,0 +1,6 @@ +#ifndef ELM_CODE_TEST_PRIVATE_H_ +# define ELM_CODE_TEST_PRIVATE_H_ + +// FIXME: put some private stuff related to your binary + +#endif diff --git a/src/lib/elementary/Elementary.h.in b/src/lib/elementary/Elementary.h.in index b4f4d44163..24c4cebcdd 100644 --- a/src/lib/elementary/Elementary.h.in +++ b/src/lib/elementary/Elementary.h.in @@ -203,6 +203,7 @@ EAPI extern Elm_Version *elm_version; #include #include #include +#include #include #include #include diff --git a/src/lib/elementary/elm_code.c b/src/lib/elementary/elm_code.c new file mode 100644 index 0000000000..612272eee3 --- /dev/null +++ b/src/lib/elementary/elm_code.c @@ -0,0 +1,115 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include +#include + +#include "Elementary.h" + +#include "elm_code_private.h" + +static int _elm_code_init = 0; +int _elm_code_lib_log_dom = -1; + +EAPI const Eo_Event_Description ELM_CODE_EVENT_LINE_LOAD_DONE = + EO_EVENT_DESCRIPTION("line,load,done"); +EAPI const Eo_Event_Description ELM_CODE_EVENT_FILE_LOAD_DONE = + EO_EVENT_DESCRIPTION("file,load,done"); + + +EAPI int +elm_code_init(void) +{ + _elm_code_init++; + if (_elm_code_init > 1) return _elm_code_init; + + eina_init(); + + _elm_code_lib_log_dom = eina_log_domain_register("elm_code", EINA_COLOR_CYAN); + if (_elm_code_lib_log_dom < 0) + { + EINA_LOG_ERR("Elm Code can not create its log domain."); + goto shutdown_eina; + } + + _elm_code_parse_setup(); + + eina_log_timing(_elm_code_lib_log_dom, EINA_LOG_STATE_STOP, EINA_LOG_STATE_INIT); + + return _elm_code_init; + + shutdown_eina: + eina_shutdown(); + _elm_code_init--; + + return _elm_code_init; +} + +EAPI int +elm_code_shutdown(void) +{ + _elm_code_init--; + if (_elm_code_init != 0) return _elm_code_init; + + eina_log_timing(_elm_code_lib_log_dom, + EINA_LOG_STATE_START, + EINA_LOG_STATE_SHUTDOWN); + + // Put here your shutdown logic + + eina_log_domain_unregister(_elm_code_lib_log_dom); + _elm_code_lib_log_dom = -1; + + eina_shutdown(); + + return _elm_code_init; +} + +EAPI Elm_Code * +elm_code_create() +{ + Elm_Code *ret; + + ret = calloc(1, sizeof(Elm_Code)); + + // create an in-memory backing for this elm_code by default + elm_code_file_new(ret); + return ret; +} + +EAPI void +elm_code_free(Elm_Code *code) +{ + Evas_Object *widget; + Elm_Code_Parser *parser; + + if (code->file) + elm_code_file_free(code->file); + + EINA_LIST_FREE(code->widgets, widget) + { + evas_object_hide(widget); + evas_object_del(widget); + } + + EINA_LIST_FREE(code->parsers, parser) + { + _elm_code_parser_free(parser); + } + + free(code); +} + +EAPI void +elm_code_callback_fire(Elm_Code *code, const Eo_Event_Description *signal, void *data) +{ + Eina_List *item; + Eo *widget; + + EINA_LIST_FOREACH(code->widgets, item, widget) + { + eo_event_callback_call(widget, signal, data); + } +} + diff --git a/src/lib/elementary/elm_code.h b/src/lib/elementary/elm_code.h new file mode 100644 index 0000000000..4e88fc3134 --- /dev/null +++ b/src/lib/elementary/elm_code.h @@ -0,0 +1,115 @@ +#ifndef ELM_CODE_H_ +# define ELM_CODE_H_ + +#ifdef EFL_BETA_API_SUPPORT + +#include "elm_code_common.h" +#include "elm_code_line.h" +#include "elm_code_text.h" +#include "elm_code_file.h" +#include "elm_code_parse.h" +#include "elm_code_widget.eo.h" +#include "elm_code_widget_legacy.h" +#include "elm_code_widget_selection.h" +#include "elm_code_diff_widget.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for loading Elm Code widgets. + */ + +/** + * @brief Init / shutdown functions. + * @defgroup Init Init / Shutdown + * + * @{ + * + * Functions of obligatory usage, handling proper initialization + * and shutdown routines. + * + * Before the usage of any other function, Elm Code should be properly + * initialized with @ref elm_code_init() and the last call to Elm Code's + * functions should be @ref elm_code_shutdown(), so everything will + * be correctly freed. + * + * Elm Code logs everything with Eina Log, using the "elm_code" log domain. + * + */ + +/** + * Initialize Elm Code. + * + * Initializes Elm Code, its dependencies and modules. Should be the first + * function of Elm Code to be called. + * + * @return The init counter value. + * + * @see elm_code_shutdown(). + * + * @ingroup Init + */ +EAPI int elm_code_init(void); + +/** + * Shutdown Elm Code + * + * Shutdown Elm Code. If init count reaches 0, all the internal structures will + * be freed. Any Elm Code library call after this point will leads to an error. + * + * @return Elm Code's init counter value. + * + * @see elm_code_init() + * + * @ingroup Init + */ +EAPI int elm_code_shutdown(void); + +/** + * Create a new Elm Code instance + * + * This method creates a new Elm Code instance using an in-memory file for backing changes. + * A regular file can be set after creation if required. + * Once an Elm Code has been created you can create widgets that render the content. + * + * @return an allocated Elm_Code that references the given file + * @see elm_code_file_open() + */ +EAPI Elm_Code *elm_code_create(); + +/** + * Free an Elm Code instance + * + * Releases the resources retained by the code instance and any files it references. + */ +EAPI void elm_code_free(Elm_Code *code); + +/** + * @} + * + * @brief Callbacks and message passing. + * @defgroup Callbacks Managing the information flow between Elm_Code objects and Evas_Object widgets + * + * @{ + * + * Managing the callbacks and other behaviours that cross the backend - frontend divide. + */ + + +EAPI void elm_code_callback_fire(Elm_Code *code, const Eo_Event_Description *signal, void *data); + + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* EFL_BETA_API_SUPPORT */ + +#endif /* ELM_CODE_H_ */ diff --git a/src/lib/elementary/elm_code_common.h b/src/lib/elementary/elm_code_common.h new file mode 100644 index 0000000000..163bbae072 --- /dev/null +++ b/src/lib/elementary/elm_code_common.h @@ -0,0 +1,87 @@ +#ifndef ELM_CODE_COMMON_H_ +# define ELM_CODE_COMMON_H_ + +typedef struct _Elm_Code Elm_Code; +typedef struct _Elm_Code_File Elm_Code_File; + +/** Event marking that a single line has loaded or changed */ +EAPI extern const Eo_Event_Description ELM_CODE_EVENT_LINE_LOAD_DONE; +/** Event that marks a file load has been completed */ +EAPI extern const Eo_Event_Description ELM_CODE_EVENT_FILE_LOAD_DONE; + +typedef enum { + ELM_CODE_STATUS_TYPE_DEFAULT = 0, + ELM_CODE_STATUS_TYPE_CURRENT, + ELM_CODE_STATUS_TYPE_IGNORED, + ELM_CODE_STATUS_TYPE_NOTE, + ELM_CODE_STATUS_TYPE_WARNING, + ELM_CODE_STATUS_TYPE_ERROR, + ELM_CODE_STATUS_TYPE_FATAL, + + ELM_CODE_STATUS_TYPE_ADDED, + ELM_CODE_STATUS_TYPE_REMOVED, + ELM_CODE_STATUS_TYPE_CHANGED, + + ELM_CODE_STATUS_TYPE_PASSED, + ELM_CODE_STATUS_TYPE_FAILED, + + ELM_CODE_STATUS_TYPE_TODO, + + ELM_CODE_STATUS_TYPE_COUNT +} Elm_Code_Status_Type; + + +typedef enum { + ELM_CODE_TOKEN_TYPE_DEFAULT = ELM_CODE_STATUS_TYPE_COUNT, + ELM_CODE_TOKEN_TYPE_COMMENT, + ELM_CODE_TOKEN_TYPE_STRING, + ELM_CODE_TOKEN_TYPE_NUMBER, + ELM_CODE_TOKEN_TYPE_BRACE, + ELM_CODE_TOKEN_TYPE_TYPE, + ELM_CODE_TOKEN_TYPE_CLASS, + ELM_CODE_TOKEN_TYPE_FUNCTION, + ELM_CODE_TOKEN_TYPE_PARAM, + ELM_CODE_TOKEN_TYPE_KEYWORD, + ELM_CODE_TOKEN_TYPE_PREPROCESSOR, + + ELM_CODE_TOKEN_TYPE_ADDED, + ELM_CODE_TOKEN_TYPE_REMOVED, + ELM_CODE_TOKEN_TYPE_CHANGED, + + ELM_CODE_TOKEN_TYPE_COUNT +} Elm_Code_Token_Type; + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** + * @file + * @brief Common data structures and constants. + */ + +struct _Elm_Code_Config +{ + Eina_Bool trim_whitespace; +}; + +struct _Elm_Code +{ + Elm_Code_File *file; + Eina_List *widgets; + Eina_List *parsers; + + struct _Elm_Code_Config config; +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_COMMON_H_ */ diff --git a/src/lib/elementary/elm_code_diff_widget.c b/src/lib/elementary/elm_code_diff_widget.c new file mode 100644 index 0000000000..c874805cab --- /dev/null +++ b/src/lib/elementary/elm_code_diff_widget.c @@ -0,0 +1,131 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_private.h" + +#define _ELM_CODE_DIFF_WIDGET_LEFT "diffwidgetleft" +#define _ELM_CODE_DIFF_WIDGET_RIGHT "diffwidgetright" + +#define _ELM_CODE_DIFF_WIDGET_TYPE_ADDED "added" +#define _ELM_CODE_DIFF_WIDGET_TYPE_REMOVED "removed" +#define _ELM_CODE_DIFF_WIDGET_TYPE_CHANGED "changed" + +static void +_elm_code_diff_widget_parse_diff_line(Elm_Code_Line *line, Elm_Code_File *left, Elm_Code_File *right) +{ + const char *content; + unsigned int length; + + if (line->length < 1) + { + elm_code_file_line_append(left, "", 0, NULL); + elm_code_file_line_append(right, "", 0, NULL); + } + + content = elm_code_line_text_get(line, &length); + if (content[0] == '+') + { + elm_code_file_line_append(left, "", 0, NULL); + elm_code_file_line_append(right, content, length, NULL); + } + else if (content[0] == '-') + { + elm_code_file_line_append(left, content, length, NULL); + elm_code_file_line_append(right, "", 0, NULL); + } + else + { + elm_code_file_line_append(left, content, length, NULL); + elm_code_file_line_append(right, content, length, NULL); + } +} + +static void +_elm_code_diff_widget_parse_diff(Elm_Code_File *diff, Elm_Code_File *left, Elm_Code_File *right) +{ + Eina_List *item; + Elm_Code_Line *line; + const char *content; + unsigned int offset, length; + + offset = 0; + EINA_LIST_FOREACH(diff->lines, item, line) + { + content = elm_code_line_text_get(line, &length); + + if (length > 0 && (content[0] == 'd' || content[0] == 'i' || content[0] == 'n')) + { + offset = 0; + elm_code_file_line_append(left, content, length, NULL); + elm_code_file_line_append(right, content, length, NULL); + + continue; + } + + if (offset == 0) + elm_code_file_line_append(left, content, length, NULL); + else if (offset == 1) + elm_code_file_line_append(right, content, length, NULL); + else + _elm_code_diff_widget_parse_diff_line(line, left, right); + + offset++; + } + + _elm_code_parse_file(left->parent, left); + _elm_code_parse_file(right->parent, right); +} + +EAPI Evas_Object * +elm_code_diff_widget_add(Evas_Object *parent, Elm_Code *code) +{ + Elm_Code *wcode1, *wcode2; + Elm_Code_Widget *widget_left, *widget_right; + Evas_Object *hbox; + + hbox = elm_panes_add(parent); + evas_object_size_hint_weight_set(hbox, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(hbox, EVAS_HINT_FILL, EVAS_HINT_FILL); + elm_panes_horizontal_set(hbox, EINA_FALSE); + evas_object_show(hbox); + + // left side of diff + wcode1 = elm_code_create(); + elm_code_parser_standard_add(wcode1, ELM_CODE_PARSER_STANDARD_DIFF); + widget_left = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, wcode1)); + + evas_object_size_hint_weight_set(widget_left, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget_left, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget_left); + evas_object_data_set(hbox, _ELM_CODE_DIFF_WIDGET_LEFT, widget_left); + elm_object_part_content_set(hbox, "left", widget_left); + + // right side of diff + wcode2 = elm_code_create(); + elm_code_parser_standard_add(wcode2, ELM_CODE_PARSER_STANDARD_DIFF); + widget_right = eo_add(ELM_CODE_WIDGET_CLASS, parent, elm_obj_code_widget_code_set(eo_self, wcode2)); + + evas_object_size_hint_weight_set(widget_right, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(widget_right, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(widget_right); + evas_object_data_set(hbox, _ELM_CODE_DIFF_WIDGET_RIGHT, widget_right); + elm_object_part_content_set(hbox, "right", widget_right); + + _elm_code_diff_widget_parse_diff(code->file, wcode1->file, wcode2->file); + return hbox; +} + +EAPI void +elm_code_diff_widget_font_set(Evas_Object *widget, const char *name, int size) +{ + Elm_Code_Widget *child; + + child = (Elm_Code_Widget *) evas_object_data_get(widget, _ELM_CODE_DIFF_WIDGET_LEFT); + elm_obj_code_widget_font_set(child, name, size); + child = (Elm_Code_Widget *) evas_object_data_get(widget, _ELM_CODE_DIFF_WIDGET_RIGHT); + elm_obj_code_widget_font_set(child, name, size); +} + diff --git a/src/lib/elementary/elm_code_diff_widget.h b/src/lib/elementary/elm_code_diff_widget.h new file mode 100644 index 0000000000..7a6f2d6412 --- /dev/null +++ b/src/lib/elementary/elm_code_diff_widget.h @@ -0,0 +1,37 @@ +#ifndef ELM_CODE_DIFF_WIDGET_H_ +# define ELM_CODE_DIFF_WIDGET_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for rendering diff instances of Elm Code. + */ + +/** + * @brief UI Loading functions. + * @defgroup Init Creating a diff widget to render an Elm Code backend + * when it's referencing a diff file + * + * @{ + * + * Functions for Diff UI loading. + * + */ + +EAPI Evas_Object *elm_code_diff_widget_add(Evas_Object *parent, Elm_Code *code); + +EAPI void elm_code_diff_widget_font_set(Evas_Object *widget, const char *name, + int size); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_DIFF_WIDGET_H_ */ diff --git a/src/lib/elementary/elm_code_file.c b/src/lib/elementary/elm_code_file.c new file mode 100644 index 0000000000..4e8dee46b0 --- /dev/null +++ b/src/lib/elementary/elm_code_file.c @@ -0,0 +1,321 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_private.h" + +static Elm_Code_Line *_elm_code_file_line_blank_create(Elm_Code_File *file, int line, void *data) +{ + Elm_Code_Line *ecl; + + ecl = calloc(1, sizeof(Elm_Code_Line)); + if (!ecl) return NULL; + + ecl->file = file; + ecl->number = line; + ecl->status = ELM_CODE_STATUS_TYPE_DEFAULT; + ecl->data = data; + + return ecl; +} + +static Elm_Code_File_Line_Ending _elm_code_line_ending_get(const char *ending) +{ + switch (*ending) + { + case '\r': + return ELM_CODE_FILE_LINE_ENDING_WINDOWS; + default: + return ELM_CODE_FILE_LINE_ENDING_UNIX; + } +} + +static void _elm_code_file_line_insert_data(Elm_Code_File *file, const char *content, unsigned int length, + unsigned int row, Eina_Bool mapped, void *data) +{ + Elm_Code_Line *line, *after; + + line = _elm_code_file_line_blank_create(file, row, data); + if (!line) return; + + if (mapped) + { + line->content = content; + line->length = length; + } + else + { + line->modified = malloc(sizeof(char)*(length+1)); + strncpy(line->modified, content, length); + line->modified[length] = 0; + line->length = length; + } + + if (row == 1) + file->lines = eina_list_prepend(file->lines, line); + else if (row == eina_list_count(file->lines) + 1) + file->lines = eina_list_append(file->lines, line); + else + { + after = eina_list_nth(file->lines, row - 2); + file->lines = eina_list_append_relative(file->lines, line, after); + } + + if (file->parent) + { + _elm_code_parse_line(file->parent, line); + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, line); + } +} + +EAPI const char *elm_code_file_filename_get(Elm_Code_File *file) +{ + return basename((char *)eina_file_filename_get(file->file)); +} + +EAPI const char *elm_code_file_path_get(Elm_Code_File *file) +{ + return eina_file_filename_get(file->file); +} + +EAPI char *_elm_code_file_tmp_path_get(Elm_Code_File *file) +{ + const char *name, *path; + char *tmp; + size_t dirlen; + + path = elm_code_file_path_get(file); + name = elm_code_file_filename_get(file); + dirlen = strlen(path) - strlen(name); + + tmp = malloc(sizeof(char) * (strlen(path) + 6)); + snprintf(tmp, dirlen + 1, "%s", path); + snprintf(tmp + dirlen, strlen(name) + 6, ".%s.tmp", name); + + return tmp; +} + +EAPI Elm_Code_File *elm_code_file_new(Elm_Code *code) +{ + Elm_Code_File *ret; + + if (code->file) + elm_code_file_free(code->file); + + ret = calloc(1, sizeof(Elm_Code_File)); + code->file = ret; + ret->parent = code; + + return ret; +} + +EAPI Elm_Code_File *elm_code_file_open(Elm_Code *code, const char *path) +{ + Elm_Code_File *ret; + Eina_File *file; + Eina_File_Line *line; + Eina_Iterator *it; + unsigned int lastindex; + + ret = elm_code_file_new(code); + file = eina_file_open(path, EINA_FALSE); + ret->file = file; + lastindex = 1; + + ret->map = eina_file_map_all(file, EINA_FILE_POPULATE); + it = eina_file_map_lines(file); + EINA_ITERATOR_FOREACH(it, line) + { + Elm_Code_Line *ecl; + + if (lastindex == 1) + ret->line_ending = _elm_code_line_ending_get(line->start + line->length); + + /* Working around the issue that eina_file_map_lines does not trigger an item for empty lines */ + /* This was fixed in 1.13.99 so once we depend on 1.14 minimum this can go */ + while (lastindex < line->index - 1) + { + ecl = _elm_code_file_line_blank_create(ret, ++lastindex, NULL); + if (!ecl) continue; + + ret->lines = eina_list_append(ret->lines, ecl); + } + + _elm_code_file_line_insert_data(ret, line->start, line->length, lastindex = line->index, EINA_TRUE, NULL); + } + eina_iterator_free(it); + + if (ret->parent) + { + _elm_code_parse_file(ret->parent, ret); + elm_code_callback_fire(ret->parent, &ELM_CODE_EVENT_FILE_LOAD_DONE, ret); + } + return ret; +} + +EAPI void elm_code_file_save(Elm_Code_File *file) +{ + Eina_List *item; + Elm_Code *code; + Elm_Code_Line *line_item; + const char *path, *content, *crchars; + char *tmp; + unsigned int length; + short crlength; + FILE *out; + + code = file->parent; + path = elm_code_file_path_get(file); + tmp = _elm_code_file_tmp_path_get(file); + crchars = elm_code_file_line_ending_chars_get(file, &crlength); + + out = fopen(tmp, "w"); + if (out == NULL) + { + free(tmp); + return; + } + + EINA_LIST_FOREACH(file->lines, item, line_item) + { + if (code && code->config.trim_whitespace && + !elm_code_line_contains_widget_cursor(line_item)) + elm_code_line_text_trailing_whitespace_strip(line_item); + content = elm_code_line_text_get(line_item, &length); + + fwrite(content, sizeof(char), length, out); + fwrite(crchars, sizeof(char), crlength, out); + } + fclose(out); + + ecore_file_mv(tmp, path); + free(tmp); + + if (file->parent) + { + _elm_code_parse_reset_file(file->parent, file); + _elm_code_parse_file(file->parent, file); + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_FILE_LOAD_DONE, file); + } +} + +EAPI void elm_code_file_free(Elm_Code_File *file) +{ + Elm_Code_Line *l; + + EINA_LIST_FREE(file->lines, l) + { + elm_code_line_free(l); + } + + if (file->file) + { + if (file->map) + eina_file_map_free(file->file, file->map); + + eina_file_close(file->file); + } + free(file); +} + +EAPI void elm_code_file_close(Elm_Code_File *file) +{ + eina_file_close(file->file); +} + +EAPI Elm_Code_File_Line_Ending elm_code_file_line_ending_get(Elm_Code_File *file) +{ + return file->line_ending; +} + +EAPI const char *elm_code_file_line_ending_chars_get(Elm_Code_File *file, short *length) +{ + if (length) + { + if (elm_code_file_line_ending_get(file) == ELM_CODE_FILE_LINE_ENDING_WINDOWS) + *length = 2; + else + *length = 1; + } + + if (elm_code_file_line_ending_get(file) == ELM_CODE_FILE_LINE_ENDING_WINDOWS) + return "\r\n"; + return "\n"; +} + +EAPI void elm_code_file_clear(Elm_Code_File *file) +{ + Elm_Code_Line *l; + + EINA_LIST_FREE(file->lines, l) + { + elm_code_line_free(l); + } + + if (file->parent) + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_FILE_LOAD_DONE, file); +} + +EAPI unsigned int elm_code_file_lines_get(Elm_Code_File *file) +{ + return eina_list_count(file->lines); +} + + +EAPI void elm_code_file_line_append(Elm_Code_File *file, const char *line, int length, void *data) +{ + int row; + + row = elm_code_file_lines_get(file) + 1; + _elm_code_file_line_insert_data(file, line, length, row, EINA_FALSE, data); +} + +EAPI void elm_code_file_line_insert(Elm_Code_File *file, unsigned int row, const char *line, int length, void *data) +{ + Eina_List *item; + Elm_Code_Line *line_item; + unsigned int r; + + _elm_code_file_line_insert_data(file, line, length, row, EINA_FALSE, data); + + r = row; + EINA_LIST_FOREACH(file->lines, item, line_item) + { + if (line_item->number < row) + continue; + + line_item->number = r++; + } +} + +EAPI void elm_code_file_line_remove(Elm_Code_File *file, unsigned int row) +{ + Eina_List *item, *next; + Elm_Code_Line *line_item, *tofree = NULL; + unsigned int r; + + r = row; + EINA_LIST_FOREACH_SAFE(file->lines, item, next, line_item) + { + if (line_item->number < row) + continue; + else if (line_item->number == row) + { + tofree = line_item; + file->lines = eina_list_remove_list(file->lines, item); + continue; + } + + line_item->number = r++; + } + + if (tofree) + elm_code_line_free(tofree); +} + +EAPI Elm_Code_Line *elm_code_file_line_get(Elm_Code_File *file, unsigned int number) +{ + return eina_list_nth(file->lines, number - 1); +} diff --git a/src/lib/elementary/elm_code_file.h b/src/lib/elementary/elm_code_file.h new file mode 100644 index 0000000000..3c3d4bd339 --- /dev/null +++ b/src/lib/elementary/elm_code_file.h @@ -0,0 +1,89 @@ +#ifndef ELM_CODE_FILE_H_ +# define ELM_CODE_FILE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for interacting with files using Elm Code. + */ + +typedef enum { + ELM_CODE_FILE_LINE_ENDING_UNIX = 0, + ELM_CODE_FILE_LINE_ENDING_WINDOWS +} Elm_Code_File_Line_Ending; + +struct _Elm_Code_File +{ + void *parent; + + Eina_List *lines; + Eina_File *file; + void *map; + + Elm_Code_File_Line_Ending line_ending; +}; + +/** + * @brief File handling functions. + * @defgroup File I/O at a file level + * + * @{ + * + * Functions for file handling within elm code. + * + */ + +EAPI Elm_Code_File *elm_code_file_new(Elm_Code *code); + +EAPI Elm_Code_File *elm_code_file_open(Elm_Code *code, const char *path); + +EAPI void elm_code_file_save(Elm_Code_File *file); + +EAPI void elm_code_file_free(Elm_Code_File *file); + +EAPI void elm_code_file_close(Elm_Code_File *file); + +EAPI const char *elm_code_file_filename_get(Elm_Code_File *file); + +EAPI const char *elm_code_file_path_get(Elm_Code_File *file); + +EAPI Elm_Code_File_Line_Ending elm_code_file_line_ending_get(Elm_Code_File *file); + +EAPI const char *elm_code_file_line_ending_chars_get(Elm_Code_File *file, short *length); + +/** + * @} + * + * @brief Content functions. + * @defgroup Content Functions for accessing file content + * + * @{ + * + * File content handling functions. + * + */ + +EAPI void elm_code_file_clear(Elm_Code_File *file); + +EAPI unsigned int elm_code_file_lines_get(Elm_Code_File *file); + +EAPI void elm_code_file_line_append(Elm_Code_File *file, const char *line, int length, void *data); + +EAPI void elm_code_file_line_insert(Elm_Code_File *file, unsigned int row, const char *line, int length, void *data); + +EAPI void elm_code_file_line_remove(Elm_Code_File *file, unsigned int row); + +EAPI Elm_Code_Line *elm_code_file_line_get(Elm_Code_File *file, unsigned int line); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_FILE_H_ */ diff --git a/src/lib/elementary/elm_code_line.c b/src/lib/elementary/elm_code_line.c new file mode 100644 index 0000000000..2b3281de66 --- /dev/null +++ b/src/lib/elementary/elm_code_line.c @@ -0,0 +1,234 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_private.h" + +EAPI void +elm_code_line_free(Elm_Code_Line *line) +{ + if (!line) + return; + + if (line->status_text) + free((char *)line->status_text); + if (line->modified) + free(line->modified); + + free(line); +} + +static void +_elm_code_line_tokens_split_at(Elm_Code_Line *oldline, Elm_Code_Line *newline, + Eina_List *tokens, int position) +{ + Eina_List *item, *next; + Elm_Code_Token *token, *newtoken; + + EINA_LIST_FOREACH_SAFE(tokens, item, next, token) + { + if (!token->continues && token->end < position) + { + oldline->tokens = eina_list_append(oldline->tokens, token); + continue; + } + if (token->start >= position) + { + token->start -= position; + token->end -= position; + newline->tokens = eina_list_append(newline->tokens, token); + continue; + } + + if (token->continues) + elm_code_line_token_add(newline, 0, token->end, 1, token->type); + else + { + elm_code_line_token_add(newline, 0, token->end - position, 1, token->type); + token->end = position - 1; + } + + newtoken = eina_list_data_get(newline->tokens); + newtoken->continues = token->continues; + token->continues = EINA_TRUE; + oldline->tokens = eina_list_append(oldline->tokens, token); + } + + elm_code_callback_fire(oldline->file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, oldline); + elm_code_callback_fire(newline->file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, newline); +} + +EAPI void elm_code_line_split_at(Elm_Code_Line *line, unsigned int position) +{ + Elm_Code_Line *newline; + Elm_Code_Token *token EINA_UNUSED; + Eina_List *tokens; + char *content; + unsigned int length; + + content = (char *) elm_code_line_text_get(line, &length); + content = strndup(content, length); + elm_code_file_line_insert(line->file, line->number + 1, "", 0, NULL); + newline = elm_code_file_line_get(line->file, line->number + 1); + + tokens = line->tokens; + line->tokens = NULL; + elm_code_line_text_set(newline, content + position, length - position); + elm_code_line_text_set(line, content, position); + _elm_code_line_tokens_split_at(line, newline, tokens, position); + + EINA_LIST_FREE(tokens, token) {} // don't free tokens, we re-used them + free(content); +} + +static void +_elm_code_line_merge_into(Elm_Code_Line *line1, Elm_Code_Line *line2) +{ + Eina_List *tokens1, *tokens2; + Elm_Code_Token *token; + const char *text1, *text2; + char *newtext; + unsigned int length1, length2; + + text1 = elm_code_line_text_get(line1, &length1); + text2 = elm_code_line_text_get(line2, &length2); + + newtext = malloc(sizeof(char) * (length1 + length2 + 1)); + snprintf(newtext, length1 + 1, "%s", text1); + snprintf(newtext + length1, length2 + 1, "%s", text2); + + tokens1 = line1->tokens; + line1->tokens = NULL; + tokens2 = line2->tokens; + line2->tokens = NULL; + elm_code_file_line_remove(line2->file, line2->number); + elm_code_line_text_set(line1, newtext, length1 + length2); + + EINA_LIST_FREE(tokens1, token) + { + token->continues = EINA_FALSE; + line1->tokens = eina_list_append(line1->tokens, token); + } + EINA_LIST_FREE(tokens2, token) + { + token->start += length1; + token->end += length1; + + line1->tokens = eina_list_append(line1->tokens, token); + } + + elm_code_callback_fire(line1->file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, line1); + free(newtext); +} + +EAPI void +elm_code_line_merge_up(Elm_Code_Line *line) +{ + Elm_Code_Line *other; + + other = elm_code_file_line_get(line->file, line->number - 1); + + if (other) + _elm_code_line_merge_into(other, line); +} + +EAPI void +elm_code_line_merge_down(Elm_Code_Line *line) +{ + Elm_Code_Line *other; + + other = elm_code_file_line_get(line->file, line->number + 1); + + if (other) + _elm_code_line_merge_into(line, other); +} + +EAPI void elm_code_line_status_set(Elm_Code_Line *line, Elm_Code_Status_Type status) +{ + if (!line) + return; + + line->status = status; +} + +EAPI void elm_code_line_status_text_set(Elm_Code_Line *line, const char *text) +{ + if (line->status_text) + free(line->status_text); + + if (text) + line->status_text = strdup(text); + else + line->status_text = NULL; +} + +EAPI void elm_code_line_token_add(Elm_Code_Line *line, int start, int end, int lines, + Elm_Code_Token_Type type) +{ + Elm_Code_Token *tok; + Elm_Code_Line *next_line; + + if (!line) + return; + + tok = calloc(1, sizeof(Elm_Code_Token)); + + tok->start = start; + tok->end = end; + tok->continues = lines > 1; + tok->type = type; + + line->tokens = eina_list_append(line->tokens, tok); + + if (lines > 1) + { + next_line = elm_code_file_line_get(line->file, line->number + 1); + elm_code_line_token_add(next_line, 0, end, lines - 1, type); + } +} + +EAPI void elm_code_line_tokens_clear(Elm_Code_Line *line) +{ + Elm_Code_Token *token; + + if (!line->tokens) + return; + + EINA_LIST_FREE(line->tokens, token) + free(token); + line->tokens = NULL; +} + +EAPI void elm_code_line_status_clear(Elm_Code_Line *line) +{ + line->status = ELM_CODE_STATUS_TYPE_DEFAULT; + if (line->status_text) + { + free((char *)line->status_text); + line->status_text = NULL; + } +} + +EAPI Eina_Bool +elm_code_line_contains_widget_cursor(Elm_Code_Line *line) +{ + Elm_Code *code = line->file->parent; + Eina_List *item; + Eo *widget; + unsigned int col, number; + + if (!code) + return EINA_FALSE; + + EINA_LIST_FOREACH(code->widgets, item, widget) + { + elm_code_widget_cursor_position_get(widget, &col, &number); + + if (number == line->number) + return EINA_TRUE; + } + + return EINA_FALSE; +} diff --git a/src/lib/elementary/elm_code_line.h b/src/lib/elementary/elm_code_line.h new file mode 100644 index 0000000000..88196893de --- /dev/null +++ b/src/lib/elementary/elm_code_line.h @@ -0,0 +1,117 @@ +#ifndef ELM_CODE_LINE_H_ +# define ELM_CODE_LINE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for interacting with lines of content using Elm Code. + */ + +typedef struct _Elm_Code_Token +{ + int start, end; + Eina_Bool continues; + + Elm_Code_Token_Type type; + +} Elm_Code_Token; + +typedef struct _Elm_Code_Line +{ + Elm_Code_File *file; + + const char *content; + unsigned int length; + unsigned int number; + char *modified; + + Elm_Code_Status_Type status; + Eina_List *tokens; + + void *data; + char *status_text; +} Elm_Code_Line; + +EAPI void elm_code_line_free(Elm_Code_Line *line); + +/** + * @brief Line manipulation functions. + * @defgroup Content + * @{ + * + * Functions for changing the content of lines in an Elm_Code_File + */ + +/** + * Split the given line into two at the specified character position. + * The additional line will be inserted into the file immediately below the specified line. + * + * @param line The line to split + * @param position The character position to split at + * + * @ingroup Content + */ +EAPI void elm_code_line_split_at(Elm_Code_Line *line, unsigned int position); + +/** + * Merge the specified line with the line above. + * The content of the specified line will be added to the end of the previous line. + * The specified line will then be removed from the file. + * + * If there is no previous line this method does nothing. + * + * @param line The line to merge with the previous line. + * + * @ingroup Content + */ +EAPI void elm_code_line_merge_up(Elm_Code_Line *line); + +/** + * Merge the specified line with the line below. + * The content of the specified line will have the contents of the next line added to the end. + * The next line will then be removed from the file. + * + * If there is no next line this method does nothing. + * + * @param line The line to merge with the next line. + * + * @ingroup Content + */ +EAPI void elm_code_line_merge_down(Elm_Code_Line *line); + +/** + * @} + * + * @brief Line markup functions. + * @defgroup Highlighting + * + * @{ + * + * Functions for handling styling and marking up lines within elm code. + * + */ + +EAPI void elm_code_line_status_set(Elm_Code_Line *line, Elm_Code_Status_Type status); + +EAPI void elm_code_line_status_text_set(Elm_Code_Line *line, const char *text); + +EAPI void elm_code_line_status_clear(Elm_Code_Line *line); + +EAPI void elm_code_line_token_add(Elm_Code_Line *line, int start, int end, int lines, Elm_Code_Token_Type type); + +EAPI void elm_code_line_tokens_clear(Elm_Code_Line *line); + +EAPI Eina_Bool elm_code_line_contains_widget_cursor(Elm_Code_Line *line); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_LINE_H_ */ diff --git a/src/lib/elementary/elm_code_parse.c b/src/lib/elementary/elm_code_parse.c new file mode 100644 index 0000000000..5869f8cf33 --- /dev/null +++ b/src/lib/elementary/elm_code_parse.c @@ -0,0 +1,206 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_private.h" + +struct _Elm_Code_Parser +{ + void (*parse_line)(Elm_Code_Line *, void *); + + void (*parse_file)(Elm_Code_File *, void *); + + void *data; + Eina_Bool standard; +}; + + +void +_elm_code_parse_line(Elm_Code *code, Elm_Code_Line *line) +{ + Elm_Code_Parser *parser; + Eina_List *item; + + elm_code_line_tokens_clear(line); + elm_code_line_status_clear(line); + + EINA_LIST_FOREACH(code->parsers, item, parser) + { + if (parser->parse_line) + parser->parse_line(line, parser->data); + } +} + +void +_elm_code_parse_file(Elm_Code *code, Elm_Code_File *file) +{ + Elm_Code_Parser *parser; + Eina_List *item; + + EINA_LIST_FOREACH(code->parsers, item, parser) + { + if (parser->parse_file) + parser->parse_file(file, parser->data); + } +} + +void +_elm_code_parse_reset_file(Elm_Code *code, Elm_Code_File *file) +{ + Elm_Code_Line *line; + Eina_List *item; + + EINA_LIST_FOREACH(file->lines, item, line) + { + _elm_code_parse_line(code, line); + } +} + +static Elm_Code_Parser * +_elm_code_parser_new(void (*parse_line)(Elm_Code_Line *, void *), + void (*parse_file)(Elm_Code_File *, void *)) +{ + Elm_Code_Parser *parser; + + parser = calloc(1, sizeof(Elm_Code_Parser)); + if (!parser) + return NULL; + + parser->parse_line = parse_line; + parser->parse_file = parse_file; + parser->standard = EINA_FALSE; + + return parser; +} + +EAPI void +elm_code_parser_add(Elm_Code *code, + void (*parse_line)(Elm_Code_Line *, void *), + void (*parse_file)(Elm_Code_File *, void *), void *data) +{ + Elm_Code_Parser *parser; + + parser = _elm_code_parser_new(parse_line, parse_file); + if (!parser) + return; + + parser->data = data; + + code->parsers = eina_list_append(code->parsers, parser); +} + +EAPI void +elm_code_parser_standard_add(Elm_Code *code, Elm_Code_Parser *parser) +{ + if (!parser || !code) + return; + + parser->standard = EINA_TRUE; + code->parsers = eina_list_append(code->parsers, parser); +} + +EAPI Elm_Code_Parser * +ELM_CODE_PARSER_STANDARD_DIFF; + +static void +_elm_code_parser_diff_trim_leading(Elm_Code_Line *line, unsigned int count) +{ + char *replace, *old = NULL; + + if (line->modified) + { + old = line->modified; + replace = malloc(sizeof(char) * (line->length - count)); + + strncpy(replace, old + count, line->length - count); + line->modified = replace; + free(old); + } + else + { + line->content += count; + } + + line->length -= count; +} + +static void +_elm_code_parser_diff_parse_line(Elm_Code_Line *line, void *data EINA_UNUSED) +{ + const char *content; + unsigned int length; + + content = elm_code_line_text_get(line, &length); + if (length < 1) + return; + + if (content[0] == 'd' || content[0] == 'i' || content[0] == 'n') + { + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_CHANGED); + return; + } + + if (content[0] == '+') + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_ADDED); + else if (content[0] == '-') + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_REMOVED); + + _elm_code_parser_diff_trim_leading(line, 1); +} + +static void +_elm_code_parser_diff_parse_file(Elm_Code_File *file, void *data EINA_UNUSED) +{ + Eina_List *item; + Elm_Code_Line *line; + const char *content; + unsigned int length, offset; + + offset = 0; + EINA_LIST_FOREACH(file->lines, item, line) + { + content = elm_code_line_text_get(line, &length); + + if (length > 0 && (content[0] == 'd' || content[0] == 'i' || content[0] == 'n')) + { + offset = 0; + continue; + } + + if (offset <= 1 && (content[0] == '+' || content[0] == '-')) + { + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_CHANGED); + _elm_code_parser_diff_trim_leading(line, 3); + } + + offset++; + } +} + +static void +_elm_code_parser_todo_parse_line(Elm_Code_Line *line, void *data EINA_UNUSED) +{ + if (elm_code_line_text_strpos(line, "TODO", 0) != ELM_CODE_TEXT_NOT_FOUND) + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_TODO); + else if (elm_code_line_text_strpos(line, "FIXME", 0) != ELM_CODE_TEXT_NOT_FOUND) + elm_code_line_status_set(line, ELM_CODE_STATUS_TYPE_TODO); +} + +void +_elm_code_parser_free(Elm_Code_Parser *parser) +{ + if (parser->standard) + return; + + free(parser); +} + +void +_elm_code_parse_setup() +{ + ELM_CODE_PARSER_STANDARD_DIFF = _elm_code_parser_new(_elm_code_parser_diff_parse_line, + _elm_code_parser_diff_parse_file); + ELM_CODE_PARSER_STANDARD_TODO = _elm_code_parser_new(_elm_code_parser_todo_parse_line, NULL); +} diff --git a/src/lib/elementary/elm_code_parse.h b/src/lib/elementary/elm_code_parse.h new file mode 100644 index 0000000000..bc674f8b41 --- /dev/null +++ b/src/lib/elementary/elm_code_parse.h @@ -0,0 +1,41 @@ +#ifndef ELM_CODE_PARSE_H_ +# define ELM_CODE_PARSE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for handling the parsing of Elm Code content. + */ + +typedef struct _Elm_Code_Parser Elm_Code_Parser; + +EAPI Elm_Code_Parser *ELM_CODE_PARSER_STANDARD_DIFF; /**< A provided parser that will mark up diff text */ +EAPI Elm_Code_Parser *ELM_CODE_PARSER_STANDARD_TODO; /**< A provided parser that will highlight TODO and FIXME lines */ + +/** + * @brief Parser helper functions. + * @defgroup Parser Hooking in and launching parsers + * + * @{ + * + * Parser functions for marking up elm code. + * + */ + +EAPI void elm_code_parser_add(Elm_Code *code, void (*parse_line)(Elm_Code_Line *, void *), + void (*parse_file)(Elm_Code_File *, void *), void *data); + +EAPI void elm_code_parser_standard_add(Elm_Code *code, Elm_Code_Parser *parser); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_PARSE_H_ */ diff --git a/src/lib/elementary/elm_code_private.h b/src/lib/elementary/elm_code_private.h new file mode 100644 index 0000000000..39f89bdd5e --- /dev/null +++ b/src/lib/elementary/elm_code_private.h @@ -0,0 +1,39 @@ +#ifndef ELM_CODE_PRIVATE_H +# define ELM_CODE_PRIVATE_H + +int _elm_code_lib_log_dom; + +#ifdef ERR +# undef ERR +#endif +#define ERR(...) EINA_LOG_DOM_ERR(_elm_code_lib_log_dom, __VA_ARGS__) +#ifdef INF +# undef INF +#endif +#define INF(...) EINA_LOG_DOM_INFO(_elm_code_lib_log_dom, __VA_ARGS__) +#ifdef WRN +# undef WRN +#endif +#define WRN(...) EINA_LOG_DOM_WARN(_elm_code_lib_log_dom, __VA_ARGS__) +#ifdef CRIT +# undef CRIT +#endif +#define CRIT(...) EINA_LOG_DOM_CRIT(_elm_code_lib_log_dom, __VA_ARGS__) +#ifdef DBG +# undef DBG +#endif +#define DBG(...) EINA_LOG_DOM_DBG(_elm_code_lib_log_dom, __VA_ARGS__) + +/* Private parser callbacks */ + +void _elm_code_parse_setup(); + +void _elm_code_parse_line(Elm_Code *code, Elm_Code_Line *line); + +void _elm_code_parse_file(Elm_Code *code, Elm_Code_File *file); + +void _elm_code_parse_reset_file(Elm_Code *code, Elm_Code_File *file); + +void _elm_code_parser_free(Elm_Code_Parser *parser); + +#endif diff --git a/src/lib/elementary/elm_code_text.c b/src/lib/elementary/elm_code_text.c new file mode 100644 index 0000000000..f1503e7d09 --- /dev/null +++ b/src/lib/elementary/elm_code_text.c @@ -0,0 +1,327 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_private.h" + +EAPI const char * +elm_code_line_text_get(Elm_Code_Line *line, unsigned int *length) +{ + if (!line) + return NULL; + + if (length) + *length = line->length; + + if (line->modified) + return line->modified; + return line->content; +} + +EAPI void +elm_code_line_text_set(Elm_Code_Line *line, const char *chars, unsigned int length) +{ + Elm_Code_File *file; + char *newtext, *oldtext = NULL; + + if (!line) + return; + + if (line->modified) + oldtext = line->modified; + + newtext = malloc(sizeof(char) * length); + strncpy(newtext, chars, length); + line->modified = newtext; + line->length = length; + + if (oldtext) + free(oldtext); + + file = line->file; + if (file->parent) + { + _elm_code_parse_line(file->parent, line); + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, line); + } +} + +EAPI int +elm_code_text_strnpos(const char *content, unsigned int length, const char *search, int offset) +{ + unsigned int searchlen, c; + char *ptr; + + searchlen = strlen(search); + ptr = (char *) content; + + if (searchlen > length) + return ELM_CODE_TEXT_NOT_FOUND; + + ptr += offset; + for (c = offset; c <= length - searchlen; c++) + { + if (!strncmp(ptr, search, searchlen)) + return c; + + ptr++; + } + + return ELM_CODE_TEXT_NOT_FOUND; +} + +EAPI int +elm_code_line_text_strpos(Elm_Code_Line *line, const char *search, int offset) +{ + unsigned int length = 0; + const char *content; + + content = elm_code_line_text_get(line, &length); + return elm_code_text_strnpos(content, length, search, offset); +} + +EAPI Eina_Bool +elm_code_line_text_contains(Elm_Code_Line *line, const char *search) +{ + return elm_code_line_text_strpos(line, search, 0) != ELM_CODE_TEXT_NOT_FOUND; +} + +EAPI char * +elm_code_line_text_substr(Elm_Code_Line *line, unsigned int position, int length) +{ + const char *content; + + if (!line || length < 1) + return strdup(""); + + if (position + length > line->length) + length = line->length - position; + + content = elm_code_line_text_get(line, NULL); + return strndup(content + position, length); +} + +static void +_elm_code_line_tokens_move_right(Elm_Code_Line *line, int position, int move) +{ + Eina_List *item; + Elm_Code_Token *token; + + EINA_LIST_FOREACH(line->tokens, item, token) + { + if (token->end >= position) + token->end += move; + + if (token->start > position) + token->start += move; + } +} + +static void +_elm_code_line_tokens_move_left(Elm_Code_Line *line, int position, int move) +{ + Eina_List *item, *next; + Elm_Code_Token *token; + + EINA_LIST_FOREACH_SAFE(line->tokens, item, next, token) + { + if (token->end >= position) + token->end -= move; + + if (token->start > position) + token->start -= move; + + if (token->end < token->start) + line->tokens = eina_list_remove_list(line->tokens, item); + } +} + +EAPI void +elm_code_line_text_insert(Elm_Code_Line *line, unsigned int position, const char *string, int length) +{ + Elm_Code_File *file; + char *inserted; + + if (!line) + return; + + inserted = malloc(sizeof(char) * line->length + length); + if (position > line->length) + position = line->length; + + _elm_code_line_tokens_move_right(line, position, length); + + if (line->modified) + { + strncpy(inserted, line->modified, position); + strncpy(inserted + position, string, length); + strncpy(inserted + position + length, line->modified + position, line->length - position); + + free(line->modified); + } + else + { + strncpy(inserted, line->content, position); + strncpy(inserted + position, string, length); + strncpy(inserted + position + length, line->content + position, line->length - position); + } + + line->modified = inserted; + line->length += length; + + file = line->file; + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, line); +} + +EAPI void +elm_code_line_text_remove(Elm_Code_Line *line, unsigned int position, int length) +{ + Elm_Code_File *file; + char *removed; + + if (!line) + return; + + removed = malloc(sizeof(char) * line->length - length); + if (position > line->length) + position = line->length; + + _elm_code_line_tokens_move_left(line, position, length); + + if (line->modified) + { + strncpy(removed, line->modified, position); + strncpy(removed + position, line->modified + position + length, line->length - position - length); + + free(line->modified); + } + else + { + strncpy(removed, line->content, position); + strncpy(removed + position, line->content + position + length, line->length - position - length); + } + + line->modified = removed; + line->length -= length; + + file = line->file; + elm_code_callback_fire(file->parent, &ELM_CODE_EVENT_LINE_LOAD_DONE, line); +} + +EAPI void elm_code_line_text_leading_whitespace_strip(Elm_Code_Line *line) +{ + unsigned int length = 0; + unsigned int leading; + const char *content; + + content = elm_code_line_text_get(line, &length); + leading = elm_code_text_leading_whitespace_length(content, length); + if (leading == 0) + return; + + elm_code_line_text_remove(line, 0, leading); +} + +EAPI void elm_code_line_text_trailing_whitespace_strip(Elm_Code_Line *line) +{ + unsigned int length = 0; + unsigned int trailing; + const char *content; + + content = elm_code_line_text_get(line, &length); + trailing = elm_code_text_trailing_whitespace_length(content, length); + if (trailing == 0) + return; + + elm_code_line_text_remove(line, length - trailing, trailing); +} + +/* generic text functions */ + +EAPI int +elm_code_text_newlinenpos(const char *text, unsigned int length, short *nllen) +{ + int lfpos, crpos; + int check; + + if (nllen) + *nllen = 1; + lfpos = elm_code_text_strnpos(text, length, "\n", 0); + check = length; + if (lfpos != ELM_CODE_TEXT_NOT_FOUND) + check = lfpos + 1; + crpos = elm_code_text_strnpos(text, check, "\r", 0); + + if (lfpos == ELM_CODE_TEXT_NOT_FOUND && crpos == ELM_CODE_TEXT_NOT_FOUND) + return ELM_CODE_TEXT_NOT_FOUND; + + if (crpos == ELM_CODE_TEXT_NOT_FOUND) + return lfpos; + if (lfpos == ELM_CODE_TEXT_NOT_FOUND) + return crpos; + + if (nllen) + *nllen = 2; + if (lfpos < crpos) + return lfpos; + return crpos; +} + +static Eina_Bool +_elm_code_text_char_is_whitespace(char c) +{ + return c == ' ' || c == '\t'; +} + +EAPI unsigned int +elm_code_text_leading_whitespace_length(const char *text, unsigned int length) +{ + unsigned int count = 0; + char *ptr = (char *)text; + + while (count < length) + { + if (!_elm_code_text_char_is_whitespace(*ptr)) + break; + + count++; + ptr++; + } + + return count; +} + +EAPI unsigned int +elm_code_text_trailing_whitespace_length(const char *text, unsigned int length) +{ + unsigned int count = 0; + char *ptr; + + if (length == 0) + return 0; + + ptr = (char *)text + length - 1; + while (count < length) + { + if (!_elm_code_text_char_is_whitespace(*ptr)) + break; + + count++; + ptr--; + } + + return count; +} + +EAPI unsigned int +elm_code_text_is_whitespace(const char *text, unsigned int length) +{ + unsigned int leading; + + leading = elm_code_text_trailing_whitespace_length(text, length); + + return leading == length; +} + diff --git a/src/lib/elementary/elm_code_text.h b/src/lib/elementary/elm_code_text.h new file mode 100644 index 0000000000..54b64d165a --- /dev/null +++ b/src/lib/elementary/elm_code_text.h @@ -0,0 +1,73 @@ +#ifndef ELM_CODE_TEXT_H_ +# define ELM_CODE_TEXT_H_ + +#define ELM_CODE_TEXT_NOT_FOUND -1 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file + * @brief These routines are used for interacting with the textual content of files/lines in Elm Code. + */ + +/** + * @brief Line text handling functions. + * @defgroup Text access and manipulation within lines + * + * @{ + * + * Functions for handling content of lines within elm code. + * + */ + +EAPI const char *elm_code_line_text_get(Elm_Code_Line *line, unsigned int *length); + +EAPI void elm_code_line_text_set(Elm_Code_Line *line, const char *chars, unsigned int length); + +EAPI int elm_code_line_text_strpos(Elm_Code_Line *line, const char *search, int offset); + +EAPI Eina_Bool elm_code_line_text_contains(Elm_Code_Line *line, const char *search); + +EAPI char *elm_code_line_text_substr(Elm_Code_Line *line, unsigned int position, int length); + +EAPI void elm_code_line_text_insert(Elm_Code_Line *line, unsigned int position, const char *string, int length); + +EAPI void elm_code_line_text_remove(Elm_Code_Line *line, unsigned int position, int length); + +EAPI void elm_code_line_text_leading_whitespace_strip(Elm_Code_Line *line); + +EAPI void elm_code_line_text_trailing_whitespace_strip(Elm_Code_Line *line); + +/** + * @} + * + * @brief Generic text handling functions. + * @defgroup Text helper functions + * + * @{ + * + * Functions for managing unicode text. + * + */ + +EAPI int elm_code_text_strnpos(const char *text, unsigned int length, const char *search, int offset); + +EAPI int elm_code_text_newlinenpos(const char *text, unsigned int length, short *nllen); + +EAPI unsigned int elm_code_text_leading_whitespace_length(const char *text, unsigned int length); + +EAPI unsigned int elm_code_text_trailing_whitespace_length(const char *text, unsigned int length); + +EAPI unsigned int elm_code_text_is_whitespace(const char *text, unsigned int length); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_TEXT_H_ */ diff --git a/src/lib/elementary/elm_code_widget.c b/src/lib/elementary/elm_code_widget.c new file mode 100644 index 0000000000..9a9418e1ff --- /dev/null +++ b/src/lib/elementary/elm_code_widget.c @@ -0,0 +1,1850 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include + +#include "elm_priv.h" + +#include "elm_code_private.h" +#include "elm_code_widget_private.h" + +#define MY_CLASS ELM_CODE_WIDGET_CLASS + +typedef enum { + ELM_CODE_WIDGET_COLOR_GUTTER_BG = ELM_CODE_TOKEN_TYPE_COUNT, + ELM_CODE_WIDGET_COLOR_GUTTER_FG, + ELM_CODE_WIDGET_COLOR_WHITESPACE, + ELM_CODE_WIDGET_COLOR_CURSOR, + ELM_CODE_WIDGET_COLOR_SELECTION, + + ELM_CODE_WIDGET_COLOR_COUNT +} Elm_Code_Widget_Colors; + +Eina_Unicode status_icons[] = { + ' ', + ' ', + ' ', + ' ', + '!', + '!', + '!', + + '+', + '-', + ' ', + + 0x2713, + 0x2717, + + 0x2691, + + 0 +}; + +#define EO_CONSTRUCTOR_CHECK_RETURN(obj) do { \ + Eina_Bool finalized; \ + \ + finalized = eo_finalized_get(obj); \ + if (finalized) \ + { \ + ERR("This function is only allowed during construction."); \ + return; \ + } \ +} while (0) + +static void _elm_code_widget_resize(Elm_Code_Widget *widget, Elm_Code_Line *newline); + +EAPI Evas_Object * +elm_code_widget_add(Evas_Object *parent, Elm_Code *code) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL); + Evas_Object *obj = NULL; + obj = eo_add(MY_CLASS, parent, elm_obj_code_widget_code_set(eo_self, code)); + return obj; +} + +EOLIAN static Eo * +_elm_code_widget_eo_base_constructor(Eo *obj, Elm_Code_Widget_Data *pd) +{ + obj = eo_constructor(eo_super(obj, ELM_CODE_WIDGET_CLASS)); + + pd->cursor_line = 1; + pd->cursor_col = 1; + + pd->tabstop = 8; + + return obj; +} + +EOLIAN static Eo * +_elm_code_widget_eo_base_finalize(Eo *obj, Elm_Code_Widget_Data *pd) +{ + obj = eo_finalize(eo_super(obj, ELM_CODE_WIDGET_CLASS)); + + if (pd->code) + return obj; + + ERR("Elm_Code_Widget cannot finalize without calling elm_code_widget_code_set."); + return NULL; +} + +EOLIAN static void +_elm_code_widget_class_constructor(Eo_Class *klass EINA_UNUSED) +{ + +} + +void +_elm_code_widget_cell_size_get(Elm_Code_Widget *widget, Evas_Coord *width, Evas_Coord *height) +{ + Elm_Code_Widget_Data *pd; + Evas_Object *grid; + Evas_Coord w = 0, h = 0; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + grid = eina_list_nth(pd->grids, 0); + evas_object_textgrid_cell_size_get(grid, &w, &h); + if (w == 0) w = 5; + if (h == 0) h = 10; + + if (width) *width = w; + if (height) *height = h; +} + +static void +_elm_code_widget_scroll_by(Elm_Code_Widget *widget, int by_x, int by_y) +{ + Elm_Code_Widget_Data *pd; + Evas_Coord x, y, w, h; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + elm_scroller_region_get(pd->scroller, &x, &y, &w, &h); + x += by_x; + y += by_y; + elm_scroller_region_show(pd->scroller, x, y, w, h); +} + +static void +_elm_code_widget_fill_line_token(Evas_Textgrid_Cell *cells, int count, int start, int end, Elm_Code_Token_Type type) +{ + int x; + + for (x = start; x <= end && x < count; x++) + { + cells[x - 1].fg = type; + } +} + +static unsigned int +_elm_code_widget_status_type_get(Elm_Code_Widget *widget, Elm_Code_Line *line, unsigned int col) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (line->status != ELM_CODE_STATUS_TYPE_DEFAULT) + return line->status; + + if (pd->editable && pd->focussed && pd->cursor_line == line->number) + return ELM_CODE_STATUS_TYPE_CURRENT; + + if (pd->line_width_marker == col) + return ELM_CODE_WIDGET_COLOR_GUTTER_BG; + + return ELM_CODE_STATUS_TYPE_DEFAULT; +} + +static void +_elm_code_widget_fill_line_tokens(Elm_Code_Widget *widget, Evas_Textgrid_Cell *cells, + unsigned int count, Elm_Code_Line *line) +{ + Eina_List *item; + Elm_Code_Token *token; + unsigned int start, end, length, offset; + unsigned int token_start_col, token_end_col; + + offset = elm_obj_code_widget_text_left_gutter_width_get(widget); + start = offset; + length = elm_code_widget_line_text_column_width_get(widget, line) + offset; + + EINA_LIST_FOREACH(line->tokens, item, token) + { + token_start_col = elm_code_widget_line_text_column_width_to_position(widget, line, token->start) + offset; + token_end_col = elm_code_widget_line_text_column_width_to_position(widget, line, token->end) + offset; + + if (token_start_col > start) + _elm_code_widget_fill_line_token(cells, count, start, token_start_col, ELM_CODE_TOKEN_TYPE_DEFAULT); + + // TODO handle a token starting before the previous finishes + end = token_end_col; + if (token->continues) + end = length + offset; + _elm_code_widget_fill_line_token(cells, count, token_start_col, end, token->type); + + start = end + 1; + } + + _elm_code_widget_fill_line_token(cells, count, start, length, ELM_CODE_TOKEN_TYPE_DEFAULT); +} + +static void +_elm_code_widget_fill_gutter(Elm_Code_Widget *widget, Evas_Textgrid_Cell *cells, + int width, Elm_Code_Status_Type status, int line) +{ + char *number = NULL; + int gutter, g; + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + gutter = elm_code_widget_text_left_gutter_width_get(widget); + + if (width < gutter) + return; + + cells[gutter-1].codepoint = status_icons[status]; + cells[gutter-1].bold = 1; + cells[gutter-1].fg = ELM_CODE_WIDGET_COLOR_GUTTER_FG; + cells[gutter-1].bg = (status == ELM_CODE_STATUS_TYPE_DEFAULT) ? ELM_CODE_WIDGET_COLOR_GUTTER_BG : status; + + if (pd->show_line_numbers) + { + if (line > 0) + { + number = malloc(sizeof(char) * gutter); + snprintf(number, gutter, "%*d", gutter - 1, line); + } + for (g = 0; g < gutter - 1; g++) + { + if (number) + cells[g].codepoint = *(number + g); + else + cells[g].codepoint = 0; + + cells[g].fg = ELM_CODE_WIDGET_COLOR_GUTTER_FG; + cells[g].bg = ELM_CODE_WIDGET_COLOR_GUTTER_BG; + } + + if (number) + free(number); + } +} + +static void +_elm_code_widget_fill_whitespace(Elm_Code_Widget *widget, Eina_Unicode character, Evas_Textgrid_Cell *cell) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + if (!pd->show_whitespace) + { + if (character== '\t') + cell->codepoint = 0; + return; + } + + switch (character) + { + case ' ': + cell->codepoint = 0x00b7; + break; + case '\t': + cell->codepoint = 0x2192; + break; + case '\n': + cell->codepoint = 0x23ce; + break; + default: + return; + } + + cell->fg = ELM_CODE_WIDGET_COLOR_WHITESPACE; +} + +static void +_elm_code_widget_fill_cursor(Elm_Code_Widget *widget, unsigned int number, + Evas_Textgrid_Cell *cells, int gutter, int w) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (pd->editable && pd->focussed && pd->cursor_line == number) + { + if (pd->cursor_col + gutter - 1 < (unsigned int) w) + cells[pd->cursor_col + gutter - 1].bg = ELM_CODE_WIDGET_COLOR_CURSOR; + } +} + +static void +_elm_code_widget_fill_selection(Elm_Code_Widget *widget, Elm_Code_Line *line, Evas_Textgrid_Cell *cells, + int gutter, int w) +{ + Elm_Code_Widget_Data *pd; + unsigned int x, start, end; + Elm_Code_Widget_Selection_Data *selection; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + if (!pd->selection) + return; + + selection = elm_code_widget_selection_normalized_get(widget); + if (selection->start_line > line->number || selection->end_line < line->number) + { + free(selection); + return; + } + + start = selection->start_col; + if (selection->start_line < line->number) + start = 1; + end = selection->end_col; + if (selection->end_line > line->number) + end = w; + free(selection); + + for (x = gutter + start - 1; x < gutter + end && x < (unsigned int) w; x++) + cells[x].bg = ELM_CODE_WIDGET_COLOR_SELECTION; +} + +static void +_elm_code_widget_fill_line(Elm_Code_Widget *widget, Elm_Code_Line *line) +{ + char *chr; + Eina_Unicode unichr; + unsigned int length, x, charwidth, i, w; + int chrpos, gutter; + Evas_Object *grid, *status; + Evas_Textgrid_Cell *cells; + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + gutter = elm_obj_code_widget_text_left_gutter_width_get(widget); + if (eina_list_count(pd->grids) < line->number) + return; + + w = elm_code_widget_columns_get(widget); + grid = eina_list_nth(pd->grids, line->number - 1); + cells = evas_object_textgrid_cellrow_get(grid, 0); + + _elm_code_widget_fill_gutter(widget, cells, w, line->status, line->number); + _elm_code_widget_fill_line_tokens(widget, cells, w, line); + + length = elm_code_widget_line_text_column_width_get(widget, line); + chrpos = 0; + chr = (char *)elm_code_line_text_get(line, NULL); + + for (x = gutter; x < (unsigned int) w && x < length + gutter; x+=charwidth) + { + unichr = eina_unicode_utf8_next_get(chr, &chrpos); + + cells[x].codepoint = unichr; + cells[x].bg = _elm_code_widget_status_type_get(widget, line, x - gutter + 1); + + charwidth = 1; + if (unichr == '\t') + charwidth = elm_code_widget_text_tabwidth_at_column_get(widget, x - gutter + 1); + for (i = x + 1; i < x + charwidth; i++) + { + cells[i].codepoint = 0; + cells[i].bg = _elm_code_widget_status_type_get(widget, line, i - gutter + 1); + } + + _elm_code_widget_fill_whitespace(widget, unichr, &cells[x]); + } + for (; x < (unsigned int) w; x++) + { + cells[x].codepoint = 0; + cells[x].bg = _elm_code_widget_status_type_get(widget, line, x - gutter + 1); + } + + _elm_code_widget_fill_selection(widget, line, cells, gutter, w); + _elm_code_widget_fill_cursor(widget, line->number, cells, gutter, w); + if (line->number < elm_code_file_lines_get(line->file)) + _elm_code_widget_fill_whitespace(widget, '\n', &cells[length + gutter]); + + evas_object_textgrid_update_add(grid, 0, 0, w, 1); + + // add a status below the line if needed (and remove those no longer needed) + status = evas_object_data_get(grid, "status"); + if (line->status_text) + { + if (!status) + { + status = elm_label_add(pd->gridbox); + evas_object_size_hint_weight_set(status, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(status, 0.05, EVAS_HINT_FILL); + evas_object_show(status); + + elm_box_pack_after(pd->gridbox, status, grid); + evas_object_data_set(grid, "status", status); + } + elm_object_text_set(status, line->status_text); + } + else if (status) + { + elm_box_unpack(pd->gridbox, status); + evas_object_hide(status); + evas_object_data_set(grid, "status", NULL); + } +} + +static void +_elm_code_widget_fill_range(Elm_Code_Widget *widget, unsigned int first_row, unsigned int last_row, + Elm_Code_Line *newline) +{ + Elm_Code_Line *line; + unsigned int y; + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + _elm_code_widget_resize(widget, newline); + + // if called from new line cb, no need to update whole range unless visible + if (newline && !elm_obj_code_widget_line_visible_get(widget, newline)) + return; + + for (y = first_row; y <= last_row; y++) + { + line = elm_code_file_line_get(pd->code->file, y); + + _elm_code_widget_fill_line(widget, line); + } +} + +static void +_elm_code_widget_refresh(Elm_Code_Widget *widget, Elm_Code_Line *line) +{ + Evas_Coord scroll_y, scroll_h, oy; + unsigned int first_row, last_row; + + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + evas_object_geometry_get(widget, NULL, &oy, NULL, NULL); + elm_scroller_region_get(pd->scroller, NULL, &scroll_y, NULL, &scroll_h); + if (scroll_h == 0) + return; + + elm_code_widget_position_at_coordinates_get(widget, 0, oy, &first_row, NULL); + elm_code_widget_position_at_coordinates_get(widget, 0, oy + scroll_h, &last_row, NULL); + + if (last_row > elm_code_file_lines_get(pd->code->file)) + last_row = elm_code_file_lines_get(pd->code->file); + + _elm_code_widget_fill_range(widget, first_row, last_row, line); +} + +static void +_elm_code_widget_fill(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + _elm_code_widget_fill_range(widget, 1, elm_code_file_lines_get(pd->code->file), NULL); +} + +static Eina_Bool +_elm_code_widget_line_cb(void *data, const Eo_Event *event) +{ + Elm_Code_Line *line; + Elm_Code_Widget *widget; + + line = (Elm_Code_Line *)event->info; + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_refresh(widget, line); + + return EO_CALLBACK_CONTINUE; +} + +static Eina_Bool +_elm_code_widget_file_cb(void *data, const Eo_Event *event EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_fill(widget); + return EO_CALLBACK_CONTINUE; +} + +static Eina_Bool +_elm_code_widget_selection_cb(void *data, const Eo_Event *event EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_refresh(widget, NULL); + return EO_CALLBACK_CONTINUE; +} + +static Eina_Bool +_elm_code_widget_selection_clear_cb(void *data, const Eo_Event *event EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_fill(widget); + return EO_CALLBACK_CONTINUE; +} + +static void +_elm_code_widget_resize_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_refresh(widget, NULL); +} + +static Eina_Bool +_elm_code_widget_cursor_key_will_move(Elm_Code_Widget *widget, const char *key) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + line = elm_code_file_line_get(pd->code->file, pd->cursor_line); + + if (!line) + return EINA_FALSE; + + if (!strcmp(key, "Up")) + return pd->cursor_line > 1; + else if (!strcmp(key, "Down")) + return pd->cursor_line < elm_code_file_lines_get(pd->code->file); + else if (!strcmp(key, "Left")) + return pd->cursor_col > 1 || pd->cursor_line > 1; + else if (!strcmp(key, "Right")) + return pd->cursor_col < elm_code_widget_line_text_column_width_get(widget, line) + 1 || + pd->cursor_line < elm_code_file_lines_get(pd->code->file); + else + return EINA_FALSE; +} + +static void +_elm_code_widget_update_focus_directions(Elm_Code_Widget *obj) +{ + if (_elm_code_widget_cursor_key_will_move(obj, "Up")) + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_UP); + else + elm_widget_focus_next_object_set(obj, NULL, ELM_FOCUS_UP); + + if (_elm_code_widget_cursor_key_will_move(obj, "Down")) + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_DOWN); + else + elm_widget_focus_next_object_set(obj, NULL, ELM_FOCUS_DOWN); + + if (_elm_code_widget_cursor_key_will_move(obj, "Left")) + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_LEFT); + else + elm_widget_focus_next_object_set(obj, NULL, ELM_FOCUS_LEFT); + + if (_elm_code_widget_cursor_key_will_move(obj, "Right")) + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_RIGHT); + else + elm_widget_focus_next_object_set(obj, NULL, ELM_FOCUS_RIGHT); + + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_PREVIOUS); + elm_widget_focus_next_object_set(obj, obj, ELM_FOCUS_NEXT); +} + +static void +_elm_code_widget_cursor_ensure_visible(Elm_Code_Widget *widget) +{ + Evas_Coord viewx, viewy, vieww, viewh, cellw, cellh; + Evas_Coord curx, cury, oy, rowy; + Evas_Object *grid; + Elm_Code_Widget_Data *pd; + int gutter; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + evas_object_geometry_get(widget, NULL, &oy, NULL, NULL); + elm_scroller_region_get(pd->scroller, &viewx, &viewy, &vieww, &viewh); + _elm_code_widget_cell_size_get(widget, &cellw, &cellh); + + grid = eina_list_data_get(eina_list_nth_list(pd->grids, pd->cursor_line - 1)); + evas_object_geometry_get(grid, NULL, &rowy, NULL, NULL); + + gutter = elm_obj_code_widget_text_left_gutter_width_get(widget); + curx = (pd->cursor_col + gutter - 1) * cellw; + cury = rowy + viewy - oy; + + if (curx >= viewx && cury >= viewy && curx + cellw <= viewx + vieww && cury + cellh <= viewy + viewh) + return; + + elm_scroller_region_show(pd->scroller, curx, cury, cellw, cellh); +} + +static void +_elm_code_widget_cursor_move(Elm_Code_Widget *widget, Elm_Code_Widget_Data *pd, unsigned int col, unsigned int line, + Eina_Bool was_key) +{ + Elm_Code *code; + unsigned int oldrow; + + oldrow = pd->cursor_line; + pd->cursor_col = col; + pd->cursor_line = line; + + if (!was_key) + _elm_code_widget_update_focus_directions(widget); + + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CURSOR_CHANGED, widget); + _elm_code_widget_cursor_ensure_visible(widget); + + if (oldrow != pd->cursor_line) + { + code = pd->code; + if (oldrow <= elm_code_file_lines_get(code->file)) + _elm_code_widget_fill_line(widget, elm_code_file_line_get(pd->code->file, oldrow)); + } + _elm_code_widget_fill_line(widget, elm_code_file_line_get(pd->code->file, pd->cursor_line)); +} + +EOLIAN static Eina_Bool +_elm_code_widget_position_at_coordinates_get(Eo *obj, Elm_Code_Widget_Data *pd, + Evas_Coord x, Evas_Coord y, + unsigned int *row, int *col) +{ + Elm_Code_Widget *widget; + Eina_List *item; + Elm_Code_Line *line; + Evas_Coord ox, oy, sx, sy, rowy; + Evas_Object *grid; + int cw, ch, gutter; + unsigned int guess, number; + + widget = (Elm_Code_Widget *)obj; + evas_object_geometry_get(widget, &ox, &oy, NULL, NULL); + elm_scroller_region_get(pd->scroller, &sx, &sy, NULL, NULL); + x = x + sx - ox; + y = y + sy - oy; + + _elm_code_widget_cell_size_get(widget, &cw, &ch); + gutter = elm_obj_code_widget_text_left_gutter_width_get(widget); + + guess = ((double) y / ch) + 1; + number = guess; + + // unfortunately EINA_LIST_REVERSE_FOREACH skips to the end of the list... + for (item = eina_list_nth_list(pd->grids, guess - 1), grid = eina_list_data_get(item); + item; + item = eina_list_prev(item), grid = eina_list_data_get(item)) + { + evas_object_geometry_get(grid, NULL, &rowy, NULL, NULL); + + if (rowy + sy - oy - 1<= y) + break; + + number--; + } + + if (col) + *col = ((double) x / cw) - gutter + 1; + if (row) + *row = number; + + line = elm_code_file_line_get(pd->code->file, number); + return !!line; +} + +static void +_elm_code_widget_clicked_gutter_cb(Elm_Code_Widget *widget, unsigned int row) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + line = elm_code_file_line_get(pd->code->file, row); + if (!line) + return; + + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_LINE_GUTTER_CLICKED, line); +} + +static void +_elm_code_widget_clicked_editable_cb(Elm_Code_Widget *widget, unsigned int row, unsigned int col) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int column_width; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + line = elm_code_file_line_get(pd->code->file, row); + if (!line) + return; + column_width = elm_code_widget_line_text_column_width_get(widget, line); + + if (col > column_width + 1) + col = column_width + 1; + else if (col <= 0) + col = 1; + + _elm_code_widget_cursor_move(widget, pd, col, row, EINA_FALSE); +} + +static void +_elm_code_widget_clicked_readonly_cb(Elm_Code_Widget *widget, unsigned int row) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + line = elm_code_file_line_get(pd->code->file, row); + if (!line) + return; + + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_LINE_CLICKED, line); +} + +static void +_elm_code_widget_mouse_down_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, + void *event_info) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + Evas_Event_Mouse_Down *event; + unsigned int row; + int col; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + event = (Evas_Event_Mouse_Down *)event_info; + _elm_code_widget_position_at_coordinates_get(widget, pd, event->canvas.x, event->canvas.y, &row, &col); + + elm_code_widget_selection_clear(widget); + if (event->flags & EVAS_BUTTON_TRIPLE_CLICK) + { + elm_code_widget_selection_select_line(widget, row); + return; + } + else if (event->flags & EVAS_BUTTON_DOUBLE_CLICK) + { + elm_code_widget_selection_select_word(widget, row, col); + return; + } + + if (pd->editable) + _elm_code_widget_clicked_editable_cb(widget, row, (unsigned int) col); +} + +static void +_elm_code_widget_mouse_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, + void *event_info) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + Evas_Event_Mouse_Move *event; + unsigned int row; + int col; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + event = (Evas_Event_Mouse_Move *)event_info; + + _elm_code_widget_position_at_coordinates_get(widget, pd, event->cur.canvas.x, event->cur.canvas.y, &row, &col); + + if (!pd->editable || !event->buttons) + return; + + if (!pd->selection) + if (col > 0 && row <= elm_code_file_lines_get(pd->code->file)) + elm_code_widget_selection_start(widget, row, col); + elm_code_widget_selection_end(widget, row, col); +} + +static void +_elm_code_widget_mouse_up_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED, + void *event_info) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + Evas_Event_Mouse_Up *event; + Evas_Coord x, y; + unsigned int row; + int col; + Eina_Bool hasline; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + event = (Evas_Event_Mouse_Up *)event_info; + + if (pd->selection) + { + if (pd->selection->start_line == pd->selection->end_line && + pd->selection->start_col == pd->selection->end_col) + elm_code_widget_selection_clear(widget); + else + return; + } + + x = event->canvas.x; + y = event->canvas.y; + hasline = _elm_code_widget_position_at_coordinates_get(widget, pd, x, y, &row, &col); + if (!hasline) + return; + + if (col <= 0) + _elm_code_widget_clicked_gutter_cb(widget, row); + else if (pd->editable) + _elm_code_widget_clicked_editable_cb(widget, row, (unsigned int) col); + else + _elm_code_widget_clicked_readonly_cb(widget, row); +} + +static void +_elm_code_widget_cursor_move_home(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + elm_code_widget_selection_clear(widget); + + if (pd->cursor_col <= 1) + return; + + _elm_code_widget_cursor_move(widget, pd, 1, pd->cursor_line, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_end(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int lastcol; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + elm_code_widget_selection_clear(widget); + + line = elm_code_file_line_get(pd->code->file, pd->cursor_line); + lastcol = elm_code_widget_line_text_column_width_get(widget, line); + if (pd->cursor_col > lastcol + 1) + return; + + _elm_code_widget_cursor_move(widget, pd, lastcol + 1, pd->cursor_line, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_up(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int row, col, column_width; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + row = pd->cursor_line; + col = pd->cursor_col; + elm_code_widget_selection_clear(widget); + + if (pd->cursor_line <= 1) + return; + + row--; + line = elm_code_file_line_get(pd->code->file, row); + column_width = elm_code_widget_line_text_column_width_get(widget, line); + if (col > column_width + 1) + col = column_width + 1; + + _elm_code_widget_cursor_move(widget, pd, col, row, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_down(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int row, col, column_width; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + row = pd->cursor_line; + col = pd->cursor_col; + elm_code_widget_selection_clear(widget); + + if (pd->cursor_line >= elm_code_file_lines_get(pd->code->file)) + return; + + row++; + line = elm_code_file_line_get(pd->code->file, row); + column_width = elm_code_widget_line_text_column_width_get(widget, line); + if (col > column_width + 1) + col = column_width + 1; + + _elm_code_widget_cursor_move(widget, pd, col, row, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_left(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + elm_code_widget_selection_clear(widget); + + if (pd->cursor_col <= 1) + { + if (pd->cursor_line > 1) + { + _elm_code_widget_cursor_move_up(widget); + _elm_code_widget_cursor_move_end(widget); + } + return; + } + + _elm_code_widget_cursor_move(widget, pd, pd->cursor_col-1, pd->cursor_line, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_right(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + elm_code_widget_selection_clear(widget); + + line = elm_code_file_line_get(pd->code->file, pd->cursor_line); + if (pd->cursor_col > elm_code_widget_line_text_column_width_get(widget, line)) + { + if (pd->cursor_line < elm_code_file_lines_get(pd->code->file)) + { + _elm_code_widget_cursor_move_down(widget); + _elm_code_widget_cursor_move_home(widget); + } + return; + } + + _elm_code_widget_cursor_move(widget, pd, pd->cursor_col+1, pd->cursor_line, EINA_TRUE); +} + +static unsigned int +_elm_code_widget_cursor_move_page_height_get(Elm_Code_Widget *widget) +{ + unsigned int lines; + + lines = elm_obj_code_widget_lines_visible_get(widget); + return lines * 0.85; +} + +static void +_elm_code_widget_cursor_move_pageup(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int row, col, column_width; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + row = pd->cursor_line; + col = pd->cursor_col; + + elm_code_widget_selection_clear(widget); + if (pd->cursor_line <= 1) + return; + + if (row > _elm_code_widget_cursor_move_page_height_get(widget)) + row -= _elm_code_widget_cursor_move_page_height_get(widget); + else + row = 1; + + line = elm_code_file_line_get(pd->code->file, row); + column_width = elm_code_widget_line_text_column_width_get(widget, line); + if (col > column_width + 1) + col = column_width + 1; + + _elm_code_widget_cursor_move(widget, pd, col, row, EINA_TRUE); +} + +static void +_elm_code_widget_cursor_move_pagedown(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *line; + unsigned int row, col, column_width; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + row = pd->cursor_line; + col = pd->cursor_col; + + elm_code_widget_selection_clear(widget); + if (pd->cursor_line >= elm_code_file_lines_get(pd->code->file)) + return; + + row += _elm_code_widget_cursor_move_page_height_get(widget); + if (row > elm_code_file_lines_get(pd->code->file)) + row = elm_code_file_lines_get(pd->code->file); + + line = elm_code_file_line_get(pd->code->file, row); + column_width = elm_code_widget_line_text_column_width_get(widget, line); + if (col > column_width + 1) + col = column_width + 1; + + _elm_code_widget_cursor_move(widget, pd, col, row, EINA_TRUE); +} + +static Eina_Bool +_elm_code_widget_delete_selection(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Selection_Data *selection; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (!pd->selection) + return EINA_FALSE; + + selection = elm_code_widget_selection_normalized_get(widget); + elm_code_widget_selection_delete(widget); + elm_code_widget_cursor_position_set(widget, selection->start_col, selection->start_line); + free(selection); + + return EINA_TRUE; +} + +static Elm_Code_Widget_Change_Info * +_elm_code_widget_change_create(unsigned int start_col, unsigned int start_line, + unsigned int end_col, unsigned int end_line, + const char *text, unsigned int length, Eina_Bool insert) +{ + Elm_Code_Widget_Change_Info *info; + + info = calloc(1, sizeof(*info)); + info->insert = insert; + + info->start_col = start_col; + info->start_line = start_line; + info->end_col = end_col; + info->end_line = end_line; + + info->content = malloc((length + 1) * sizeof(char)); + strncpy(info->content, text, length); + info->content[length] = '\0'; + info->length = length; + + return info; +} + +static void +_elm_code_widget_change_free(Elm_Code_Widget_Change_Info *info) +{ + free((char *)info->content); + free(info); +} + +void +_elm_code_widget_text_at_cursor_insert(Elm_Code_Widget *widget, const char *text, int length) +{ + Elm_Code *code; + Elm_Code_Line *line; + Elm_Code_Widget_Change_Info *change; + unsigned int row, col, position, col_width; + + _elm_code_widget_delete_selection(widget); + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + line = elm_code_file_line_get(code->file, row); + if (line == NULL) + { + elm_code_file_line_append(code->file, "", 0, NULL); + row = elm_code_file_lines_get(code->file); + line = elm_code_file_line_get(code->file, row); + } + + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + elm_code_line_text_insert(line, position, text, length); + col_width = elm_code_widget_line_text_column_width_to_position(widget, line, position + length) - + elm_code_widget_line_text_column_width_to_position(widget, line, position); + + // a workaround for when the cursor position would be off the line width + _elm_code_widget_resize(widget, line); + elm_obj_code_widget_cursor_position_set(widget, col + col_width, row); + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); + + change = _elm_code_widget_change_create(col, row, col + col_width - 1, row, text, length, EINA_TRUE); + _elm_code_widget_undo_change_add(widget, change); + _elm_code_widget_change_free(change); +} + +static void +_elm_code_widget_tab_at_cursor_insert(Elm_Code_Widget *widget) +{ + Elm_Code_Widget_Data *pd; + unsigned int col, row, rem; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + if (!pd->tab_inserts_spaces) + { + _elm_code_widget_text_at_cursor_insert(widget, "\t", 1); + return; + } + + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + rem = (col - 1) % pd->tabstop; + + while (rem < pd->tabstop) + { + _elm_code_widget_text_at_cursor_insert(widget, " ", 1); + rem++; + } +} + +void +_elm_code_widget_newline(Elm_Code_Widget *widget) +{ + Elm_Code *code; + Elm_Code_Line *line; + Elm_Code_Widget_Change_Info *change; + unsigned int row, col, position, oldlen, leading, width, indent; + char *oldtext; + + _elm_code_widget_delete_selection(widget); + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + line = elm_code_file_line_get(code->file, row); + if (line == NULL) + { + elm_code_file_line_append(code->file, "", 0, NULL); + row = elm_code_file_lines_get(code->file); + line = elm_code_file_line_get(code->file, row); + } + oldtext = (char *) elm_code_line_text_get(line, &oldlen); + oldtext = strndup(oldtext, oldlen); + + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + elm_code_line_split_at(line, position); + width = elm_code_widget_line_text_column_width_get(widget, line); + + line = elm_code_file_line_get(code->file, row + 1); + leading = elm_code_text_leading_whitespace_length(oldtext, oldlen); + elm_code_line_text_leading_whitespace_strip(line); + elm_code_line_text_insert(line, 0, oldtext, leading); + free(oldtext); + + indent = elm_obj_code_widget_line_text_column_width_to_position(widget, line, leading); + elm_obj_code_widget_cursor_position_set(widget, indent, row + 1); + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); + + change = _elm_code_widget_change_create(width + 1, row, indent - 1, row + 1, "\n", 1, EINA_TRUE); + _elm_code_widget_undo_change_add(widget, change); + _elm_code_widget_change_free(change); +} + +static void +_elm_code_widget_backspaceline(Elm_Code_Widget *widget, Eina_Bool nextline) +{ + Elm_Code *code; + Elm_Code_Line *line, *oldline; + unsigned int row, col, oldlength, position; + + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + line = elm_code_file_line_get(code->file, row); + + if (nextline) + { + elm_code_line_merge_down(line); + } + else + { + oldline = elm_code_file_line_get(code->file, row - 1); + elm_code_line_text_get(oldline, &oldlength); + elm_code_line_merge_up(line); + + position = elm_code_widget_line_text_column_width_to_position(widget, oldline, oldlength); + + elm_obj_code_widget_cursor_position_set(widget, position, row - 1); + } +// TODO construct and pass a change object + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); +} + +void +_elm_code_widget_backspace(Elm_Code_Widget *widget) +{ + Elm_Code *code; + Elm_Code_Line *line; + Elm_Code_Widget_Change_Info *change; + unsigned int row, col, position, start_col, end_col, char_width; + const char *text; + + if (_elm_code_widget_delete_selection(widget)) + return; // TODO fire the change and log it + + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + + if (col <= 1) + { + if (row == 1) + return; + + _elm_code_widget_backspaceline(widget, EINA_FALSE); + return; + } + + line = elm_code_file_line_get(code->file, row); + + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + end_col = elm_code_widget_line_text_column_width_to_position(widget, line, position); + start_col = elm_code_widget_line_text_column_width_to_position(widget, line, + elm_code_widget_line_text_position_for_column_get(widget, line, col - 1)); + char_width = position - elm_code_widget_line_text_position_for_column_get(widget, line, start_col); + + text = elm_code_widget_text_between_positions_get(widget, start_col, row, end_col, row); + elm_code_line_text_remove(line, position - char_width, char_width); + elm_obj_code_widget_cursor_position_set(widget, start_col, row); + + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); + + change = _elm_code_widget_change_create(start_col, row, end_col, row, text, char_width, EINA_FALSE); + _elm_code_widget_undo_change_add(widget, change); + _elm_code_widget_change_free(change); +} + +void +_elm_code_widget_delete(Elm_Code_Widget *widget) +{ + Elm_Code *code; + Elm_Code_Line *line; + Elm_Code_Widget_Change_Info *change; + unsigned int row, col, position, char_width, start_col, end_col; + const char *text; + + if (_elm_code_widget_delete_selection(widget)) + return; // TODO fire the change and log it + + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + line = elm_code_file_line_get(code->file, row); + if (col > elm_code_widget_line_text_column_width_get(widget, line)) + { + if (row == elm_code_file_lines_get(code->file)) + return; + + _elm_code_widget_backspaceline(widget, EINA_TRUE); + return; + } + + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + char_width = elm_code_widget_line_text_position_for_column_get(widget, line, col + 1) - position; + if (char_width == 0) // a partial tab + char_width = 1; + start_col = elm_code_widget_line_text_column_width_to_position(widget, line, position); + end_col = elm_code_widget_line_text_column_width_to_position(widget, line, position + char_width); + + text = elm_code_widget_text_between_positions_get(widget, start_col, row, end_col, row); + elm_code_line_text_remove(line, position, char_width); + elm_obj_code_widget_cursor_position_set(widget, start_col, row); + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); + + change = _elm_code_widget_change_create(start_col, row, col, row, text, char_width, EINA_FALSE); + _elm_code_widget_undo_change_add(widget, change); + _elm_code_widget_change_free(change); +} + +static void +_elm_code_widget_control_key_down_cb(Elm_Code_Widget *widget, const char *key) +{ + if (!key) + return; + + if (!strcmp("c", key)) + { + elm_code_widget_selection_copy(widget); + return; + } + + if (!strcmp("v", key)) + elm_code_widget_selection_paste(widget); + else if (!strcmp("x", key)) + elm_code_widget_selection_cut(widget); + else if (!strcmp("z", key)) + elm_code_widget_undo(widget); + + // TODO construct and pass a change object for cut and paste + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_CHANGED_USER, NULL); +} + +static void +_elm_code_widget_key_down_cb(void *data, Evas *evas EINA_UNUSED, + Evas_Object *obj EINA_UNUSED, void *event_info) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + Evas_Event_Key_Down *ev = event_info; + + if (!pd->editable) + return; + + _elm_code_widget_update_focus_directions((Elm_Code_Widget *)obj); + + if (evas_key_modifier_is_set(ev->modifiers, "Control")) + { + _elm_code_widget_control_key_down_cb(widget, ev->key); + return; + } + + if (!strcmp(ev->key, "Up")) + _elm_code_widget_cursor_move_up(widget); + else if (!strcmp(ev->key, "Down")) + _elm_code_widget_cursor_move_down(widget); + else if (!strcmp(ev->key, "Left")) + _elm_code_widget_cursor_move_left(widget); + else if (!strcmp(ev->key, "Right")) + _elm_code_widget_cursor_move_right(widget); + else if (!strcmp(ev->key, "Home")) + _elm_code_widget_cursor_move_home(widget); + else if (!strcmp(ev->key, "End")) + _elm_code_widget_cursor_move_end(widget); + else if (!strcmp(ev->key, "Prior")) + _elm_code_widget_cursor_move_pageup(widget); + else if (!strcmp(ev->key, "Next")) + _elm_code_widget_cursor_move_pagedown(widget); + + else if (!strcmp(ev->key, "KP_Enter") || !strcmp(ev->key, "Return")) + _elm_code_widget_newline(widget); + else if (!strcmp(ev->key, "BackSpace")) + _elm_code_widget_backspace(widget); + else if (!strcmp(ev->key, "Delete")) + _elm_code_widget_delete(widget); + else if (!strcmp(ev->key, "Tab")) + _elm_code_widget_tab_at_cursor_insert(widget); + + else if (!strcmp(ev->key, "Escape")) + DBG("TODO - Escape not yet captured"); + + else if (ev->string && strlen(ev->string) == 1) + _elm_code_widget_text_at_cursor_insert(widget, ev->string, 1); + else + INF("Unhandled key %s (%s) (%s)", ev->key, ev->keyname, ev->string); +} + +static void +_elm_code_widget_focused_event_cb(void *data, Evas_Object *obj, + void *event_info EINA_UNUSED) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + pd->focussed = EINA_TRUE; + + _elm_code_widget_update_focus_directions(widget); + _elm_code_widget_refresh(obj, NULL); +} + +static void +_elm_code_widget_unfocused_event_cb(void *data, Evas_Object *obj, + void *event_info EINA_UNUSED) +{ + Elm_Code_Widget *widget; + Elm_Code_Widget_Data *pd; + + widget = (Elm_Code_Widget *)data; + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + pd->focussed = EINA_FALSE; + _elm_code_widget_refresh(obj, NULL); +} + +static void +_elm_code_widget_scroll_event_cb(void *data, Evas_Object *obj EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; + + _elm_code_widget_refresh(widget, NULL); +} + +EOLIAN static Eina_Bool +_elm_code_widget_elm_widget_event(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd EINA_UNUSED, + Evas_Object *src EINA_UNUSED, Evas_Callback_Type type, void *event_info) +{ + Evas_Event_Key_Down *ev = event_info; + + if (type != EVAS_CALLBACK_KEY_DOWN) return EINA_FALSE; + + if (!strcmp(ev->key, "BackSpace")) + { + ev->event_flags |= EVAS_EVENT_FLAG_ON_HOLD; + return EINA_TRUE; + } + + return EINA_FALSE; +} + +EOLIAN static Eina_Bool +_elm_code_widget_elm_widget_focus_next_manager_is(Eo *obj EINA_UNUSED, + Elm_Code_Widget_Data *pd EINA_UNUSED) +{ + return EINA_FALSE; +} + +EOLIAN static Eina_Bool +_elm_code_widget_elm_widget_focus_direction_manager_is(Eo *obj EINA_UNUSED, + Elm_Code_Widget_Data *pd EINA_UNUSED) +{ + return EINA_TRUE; +} + +static void +_elm_code_widget_setup_palette(Evas_Object *o) +{ + double feint = 0.5; + + // setup status colors + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_DEFAULT, + 36, 36, 36, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_CURRENT, + 12, 12, 12, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_IGNORED, + 36, 36, 36, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_NOTE, + 255, 153, 0, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_WARNING, + 255, 153, 0, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_ERROR, + 205, 54, 54, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_FATAL, + 205, 54, 54, 255); + + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_ADDED, + 36, 96, 36, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_REMOVED, + 96, 36, 36, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_CHANGED, + 36, 36, 96, 255); + + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_PASSED, + 54, 96, 54, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_FAILED, + 96, 54, 54, 255); + + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_STATUS_TYPE_TODO, + 54, 54, 96, 255); + + // setup token colors + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_DEFAULT, + 205, 205, 205, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_COMMENT, + 51, 153, 255, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_STRING, + 255, 90, 53, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_NUMBER, + 212, 212, 42, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_BRACE, + 101, 101, 101, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_TYPE, + 51, 153, 255, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_CLASS, + 114, 170, 212, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_FUNCTION, + 114, 170, 212, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_PARAM, + 255, 255, 255, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_KEYWORD, + 255, 153, 0, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_PREPROCESSOR, + 0, 176, 0, 255); + + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_ADDED, + 54, 255, 54, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_REMOVED, + 255, 54, 54, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_TOKEN_TYPE_CHANGED, + 54, 54, 255, 255); + + // other styles that the widget uses + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_WIDGET_COLOR_CURSOR, + 205, 205, 54, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_WIDGET_COLOR_SELECTION, + 51, 153, 255, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_WIDGET_COLOR_GUTTER_BG, + 75, 75, 75, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_WIDGET_COLOR_GUTTER_FG, + 139, 139, 139, 255); + evas_object_textgrid_palette_set(o, EVAS_TEXTGRID_PALETTE_STANDARD, ELM_CODE_WIDGET_COLOR_WHITESPACE, + 101 * feint, 101 * feint, 101 * feint, 255 * feint); +} + +static void +_elm_code_widget_ensure_n_grid_rows(Elm_Code_Widget *widget, int rows) +{ + Evas_Object *grid; + int existing, i; + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + existing = eina_list_count(pd->grids); + + // trim unneeded rows in our rendering + if (rows < existing) + { + for (i = existing - rows; i > 0; i--) + { + grid = eina_list_data_get(eina_list_last(pd->grids)); + evas_object_hide(grid); + elm_box_unpack(pd->gridbox, grid); + pd->grids = eina_list_remove_list(pd->grids, eina_list_last(pd->grids)); + } + rows = existing; + } + + if (rows == existing) + return; + + for (i = existing; i < rows; i++) + { + grid = evas_object_textgrid_add(pd->gridbox); + evas_object_size_hint_weight_set(grid, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(grid, EVAS_HINT_FILL, 0.0); + evas_object_show(grid); + _elm_code_widget_setup_palette(grid); + + elm_box_pack_end(pd->gridbox, grid); + pd->grids = eina_list_append(pd->grids, grid); + + evas_object_event_callback_add(grid, EVAS_CALLBACK_MOUSE_DOWN, _elm_code_widget_mouse_down_cb, widget); + evas_object_event_callback_add(grid, EVAS_CALLBACK_MOUSE_MOVE, _elm_code_widget_mouse_move_cb, widget); + evas_object_event_callback_add(grid, EVAS_CALLBACK_MOUSE_UP, _elm_code_widget_mouse_up_cb, widget); + + evas_object_textgrid_font_set(grid, pd->font_name, pd->font_size * elm_config_scale_get()); + } +} + +static void +_elm_code_widget_resize(Elm_Code_Widget *widget, Elm_Code_Line *newline) +{ + Elm_Code_Line *line; + Eina_List *item; + Evas_Object *grid; + Evas_Coord ww, wh, old_width, old_height; + int w, h, cw, ch, gutter; + unsigned int line_width; + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + gutter = elm_obj_code_widget_text_left_gutter_width_get(widget); + + if (!pd->code) + return; + + evas_object_geometry_get(widget, NULL, NULL, &ww, &wh); + + old_width = ww; + old_height = wh; + w = 0; + h = elm_code_file_lines_get(pd->code->file); + + if (newline) + { + line = eina_list_data_get(pd->code->file->lines); + if (line) + { + line_width = elm_code_widget_line_text_column_width_get(widget, newline); + w = (int) line_width + gutter + 1; + } + line_width = elm_code_widget_line_text_column_width_get(widget, line); + if ((int) line_width + gutter + 1 > w) + { + w = (int) line_width + gutter + 1; + } + } + else + { + EINA_LIST_FOREACH(pd->code->file->lines, item, line) + { + line_width = elm_code_widget_line_text_column_width_get(widget, line); + if ((int) line_width + gutter + 1 > w) + w = (int) line_width + gutter + 1; + } + } + + _elm_code_widget_ensure_n_grid_rows(widget, h); + _elm_code_widget_cell_size_get(widget, &cw, &ch); + if (w*cw > ww) + ww = w*cw; + if (h*ch > wh) + wh = h*ch; + pd->col_count = ww/cw + 1; + + EINA_LIST_FOREACH(pd->grids, item, grid) + { + evas_object_textgrid_size_set(grid, pd->col_count, 1); + evas_object_size_hint_min_set(grid, w*cw, ch); + } + + if (!newline) return; + + if (pd->gravity_x == 1.0 || pd->gravity_y == 1.0) + _elm_code_widget_scroll_by(widget, + (pd->gravity_x == 1.0 && ww > old_width) ? ww - old_width : 0, + (pd->gravity_y == 1.0 && wh > old_height) ? wh - old_height : 0); +} + +EOAPI void +_elm_code_widget_line_refresh(Eo *obj, Elm_Code_Widget_Data *pd EINA_UNUSED, Elm_Code_Line *line) +{ + _elm_code_widget_fill_line(obj, line); +} + +EOAPI Eina_Bool +_elm_code_widget_line_visible_get(Eo *obj, Elm_Code_Widget_Data *pd, Elm_Code_Line *line) +{ + Evas_Coord cellh, viewy, viewh; + + elm_scroller_region_get(pd->scroller, NULL, &viewy, NULL, &viewh); + _elm_code_widget_cell_size_get(obj, NULL, &cellh); + + if (((int)line->number - 1) * cellh > viewy + viewh || (int)line->number * cellh < viewy) + return EINA_FALSE; + + return EINA_TRUE;; +} + +EOAPI unsigned int +_elm_code_widget_lines_visible_get(Eo *obj, Elm_Code_Widget_Data *pd) +{ + Evas_Coord cellh, viewh; + + elm_scroller_region_get(pd->scroller, NULL, NULL, NULL, &viewh); + _elm_code_widget_cell_size_get(obj, NULL, &cellh); + + return viewh / cellh + 1; +} + +EOLIAN static void +_elm_code_widget_font_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, + const char *name, Evas_Font_Size size) +{ + Eina_List *item; + Evas_Object *grid; + + const char *face = name; + if (!face) + face = "Mono"; + + EINA_LIST_FOREACH(pd->grids, item, grid) + { + evas_object_textgrid_font_set(grid, face, size * elm_config_scale_get()); + } + if (pd->font_name) + eina_stringshare_del((char *)pd->font_name); + pd->font_name = eina_stringshare_add(face); + pd->font_size = size; +} + +EOLIAN static void +_elm_code_widget_font_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, + const char **name, Evas_Font_Size *size) +{ + if (name) + *name = strdup((const char *)pd->font_name); + if (size) + *size = pd->font_size; +} + +EOLIAN static unsigned int +_elm_code_widget_columns_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->col_count; +} + +EOLIAN static void +_elm_code_widget_code_set(Eo *obj, Elm_Code_Widget_Data *pd, Elm_Code *code) +{ + EO_CONSTRUCTOR_CHECK_RETURN(obj); + + pd->code = code; + + code->widgets = eina_list_append(code->widgets, obj); +} + +EOLIAN static Elm_Code * +_elm_code_widget_code_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->code; +} + +EOLIAN static void +_elm_code_widget_gravity_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, double x, double y) +{ + pd->gravity_x = x; + pd->gravity_y = y; +} + +EOLIAN static void +_elm_code_widget_gravity_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, double *x, double *y) +{ + *x = pd->gravity_x; + *y = pd->gravity_y; +} + +EOLIAN static void +_elm_code_widget_policy_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, Elm_Scroller_Policy policy_h, Elm_Scroller_Policy policy_v) +{ + elm_scroller_policy_set(pd->scroller, policy_h, policy_v); +} + +EOLIAN static void +_elm_code_widget_policy_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, Elm_Scroller_Policy *policy_h, Elm_Scroller_Policy *policy_v) +{ + elm_scroller_policy_get(pd->scroller, policy_h, policy_v); +} + +EOLIAN static void +_elm_code_widget_tabstop_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, unsigned int tabstop) +{ + pd->tabstop = tabstop; + _elm_code_widget_fill(obj); +} + +EOLIAN static unsigned int +_elm_code_widget_tabstop_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->tabstop; +} + +EOLIAN static void +_elm_code_widget_editable_set(Eo *obj, Elm_Code_Widget_Data *pd, Eina_Bool editable) +{ + pd->editable = editable; + elm_object_focus_allow_set(obj, editable); +} + +EOLIAN static Eina_Bool +_elm_code_widget_editable_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->editable; +} + +EOLIAN static void +_elm_code_widget_line_numbers_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, Eina_Bool line_numbers) +{ + pd->show_line_numbers = line_numbers; +} + +EOLIAN static Eina_Bool +_elm_code_widget_line_numbers_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->show_line_numbers; +} + +EOLIAN static void +_elm_code_widget_line_width_marker_set(Eo *obj, Elm_Code_Widget_Data *pd, unsigned int col) +{ + pd->line_width_marker = col; + _elm_code_widget_fill(obj); +} + +EOLIAN static unsigned int +_elm_code_widget_line_width_marker_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->line_width_marker; +} + +EOLIAN static void +_elm_code_widget_show_whitespace_set(Eo *obj, Elm_Code_Widget_Data *pd, Eina_Bool show) +{ + pd->show_whitespace = show; + _elm_code_widget_fill(obj); +} + +EOLIAN static Eina_Bool +_elm_code_widget_show_whitespace_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->show_whitespace; +} + +EOLIAN static void +_elm_code_widget_tab_inserts_spaces_set(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, + Eina_Bool spaces) +{ + pd->tab_inserts_spaces = spaces; +} + +EOLIAN static Eina_Bool +_elm_code_widget_tab_inserts_spaces_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + return pd->tab_inserts_spaces; +} + +EOLIAN static void +_elm_code_widget_cursor_position_set(Eo *obj, Elm_Code_Widget_Data *pd, unsigned int col, unsigned int line) +{ + _elm_code_widget_cursor_move(obj, pd, col, line, EINA_FALSE); +} + +EOLIAN static void +_elm_code_widget_cursor_position_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, unsigned int *col, unsigned int *line) +{ + *col = pd->cursor_col; + *line = pd->cursor_line; +} + +EOLIAN static void +_elm_code_widget_evas_object_smart_add(Eo *obj, Elm_Code_Widget_Data *pd) +{ + Evas_Object *background, *gridrows, *scroller; + + evas_obj_smart_add(eo_super(obj, ELM_CODE_WIDGET_CLASS)); + elm_object_focus_allow_set(obj, EINA_TRUE); + + elm_layout_file_set(obj, PACKAGE_DATA_DIR "/themes/elm_code.edj", "elm_code/layout/default"); + + scroller = elm_scroller_add(obj); + evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(scroller); + elm_layout_content_set(obj, "elm.swallow.content", scroller); + elm_object_focus_allow_set(scroller, EINA_FALSE); + pd->scroller = scroller; + + background = elm_bg_add(scroller); + evas_object_color_set(background, 145, 145, 145, 255); + evas_object_size_hint_weight_set(background, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(background, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(background); + elm_object_part_content_set(scroller, "elm.swallow.background", background); + + gridrows = elm_box_add(scroller); + evas_object_size_hint_weight_set(gridrows, EVAS_HINT_EXPAND, 0.0); + evas_object_size_hint_align_set(gridrows, EVAS_HINT_FILL, 0.0); + elm_object_content_set(scroller, gridrows); + pd->gridbox = gridrows; + + evas_object_event_callback_add(obj, EVAS_CALLBACK_RESIZE, _elm_code_widget_resize_cb, obj); + evas_object_event_callback_add(obj, EVAS_CALLBACK_KEY_DOWN, _elm_code_widget_key_down_cb, obj); + + evas_object_smart_callback_add(obj, "focused", _elm_code_widget_focused_event_cb, obj); + evas_object_smart_callback_add(obj, "unfocused", _elm_code_widget_unfocused_event_cb, obj); + evas_object_smart_callback_add(scroller, "scroll", _elm_code_widget_scroll_event_cb, obj); + + eo_event_callback_add(obj, &ELM_CODE_EVENT_LINE_LOAD_DONE, _elm_code_widget_line_cb, obj); + eo_event_callback_add(obj, &ELM_CODE_EVENT_FILE_LOAD_DONE, _elm_code_widget_file_cb, obj); + eo_event_callback_add(obj, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CHANGED, _elm_code_widget_selection_cb, obj); + eo_event_callback_add(obj, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CLEARED, _elm_code_widget_selection_clear_cb, obj); +} + +#include "elm_code_widget_text.c" +#include "elm_code_widget_undo.c" +#include "elm_code_widget.eo.c" diff --git a/src/lib/elementary/elm_code_widget.eo b/src/lib/elementary/elm_code_widget.eo new file mode 100644 index 0000000000..2a977ab9ea --- /dev/null +++ b/src/lib/elementary/elm_code_widget.eo @@ -0,0 +1,266 @@ +import evas_types; +import edje_types; +import elm_interface_scrollable; +import elm_general; + +struct @extern Elm_Code; /* The main interface currently defined in code */ +struct @extern Elm_Code_Line; /* Parts of the interface currently defined in code */ + +class Elm.Code_Widget (Elm.Layout, Elm.Interface.Atspi.Text) +{ + eo_prefix: elm_obj_code_widget; + legacy_prefix: elm_code_widget; + methods { + @property code { + set { + [[Set the underlying code object that this widget renders. + This can only be set during construction, once the widget is created the + backing code object cannot be changed.]] + } + get { + [[Get the underlying code object we are rendering]] + } + values { + code: Elm_Code *; [[Our underlying Elm_Code object]] + } + } + @property font { + set { + [[Set the font that this widget uses, the font should be a monospaced scalable font. + Passing NULL will load the default system monospaced font.]] + } + get { + [[Get the font currently in use. + The font name is a copy ad should be freed once it is no longer needed]] + } + values { + name: const(char) *; [[The name of the font to load]] + size: Evas.Font.Size; [[The font size for the widget]] + } + } + @property columns { + get { + [[Get the number of columns in the widget currently. + This will be the max of the number of columns to represent the longest line and + the minimum required to fill the visible widget width.]] + } + values { + columns: uint; [[The number of columns required to render the widget]] + } + } + @property gravity { + set { + [[Set how this widget's scroller should respond to new lines being added. + + An x value of 0.0 will maintain the distance from the left edge, 1.0 will ensure the rightmost edge (of the longest line) is respected + With 0.0 for y the view will keep it's position relative to the top whereas 1.0 will scroll downward as lines are added.]] + } + get { + [[Get the current x and y gravity of the widget's scroller]] + } + values { + x: double; [[The horizontal value of the scroller gravity - valid values are 0.0 and 1.0]] + y: double; [[The vertical gravity of the widget's scroller - valid values are 0.0 and 1.0]] + } + } + @property policy { + set { + [[Set the policy for scrollbar visibility.]] + } + get { + [[Get the widget's policy for scrollbar visibility.]] + } + values { + policy_h: Elm.Scroller.Policy; [[The horizontal scrollbar visibility policy]] + policy_v: Elm.Scroller.Policy; [[The vertical scrollbar visibility policy]] + } + } + @property tabstop { + set { + [[Set the width of a tab stop, used purely for visual layout of tab characters. + + Recommended value is between 2 and 8.]] + } + get { + [[Get the current width of a tab stop. + This is used to determine where characters after a tab should appear in the line.]] + } + values { + tabstop: uint; [[Maximum width of a tab character]] + } + } + @property editable { + set { + [[Set whether this widget allows editing + + If editable then the widget will allow user input to manipulate + the underlying Elm_Code_File of this Elm_Code instance. + Any other Elm_Code_Widget's connected to this Elm_Code will + update to reflect the changes.]] + } + get { + [[Get the current editable state of this widget + + returns EINA_TRUE if the widget is editable, EINA_FALSE otherwise. + If this widget is not editable the underlying Elm_Code_File could + still be manipulated by a different widget or the filesystem.]] + } + values { + editable: bool; [[The editable state of the widget]] + } + } + @property line_numbers { + set { + [[Set whether line numbers should be displayed in the left gutter. + + Passing EINA_TRUE will reserve a space for showing line numbers, + EINA_FALSE will turn this off.]] + } + get { + [[Get the status of line number display for this widget.]] + } + values { + line_numbers: bool; [[Whether or not line numbers (or their placeholder) should be shown]] + } + } + @property line_width_marker { + set { + [[Set where the line width market should be shown. + + Passing a non-zero value will set which line width to mark with a vertical line. + Passing 0 will hide this marker.]] + } + get { + [[Get the position of the line width marker, any positive return indicates where the marker appears.]] + } + values { + line_width_marker: uint; [[Where to display a line width marker, if at all]] + } + } + @property show_whitespace { + set { + [[Set where white space should be shown.]] + } + get { + [[Get whether or not white space will be visible.]] + } + values { + show_whitespace: bool; [[Whether or not we show whitespace characters]] + } + } + @property tab_inserts_spaces { + set { + [[Set whether space characters should be inserted instead of tabs.]] + } + get { + [[Get whether or not space characters will be inserted instead of tabs.]] + } + values { + tab_inserts_spaces: bool; [[EINA_TRUE if we should insert space characters instead of a tab when the Tab key is pressed]] + } + } + @property cursor_position { + set { + [[Set the current location of the text cursor.]] + } + get { + [[Get the current x and y position of the widget's cursor.]] + } + values { + col: uint; [[The horizontal position of the cursor, starting from column 1]] + line: uint; [[The vertical position of the cursor - the top row is 1]] + } + } + line_refresh { + params { + line: Elm_Code_Line *; [[The line to refresh.]] + } + } + line_visible_get { + params { + line: Elm_Code_Line *; [[The line to test for visibility.]] + } + return: bool; [[true if the line specified is currently visible within the scroll region.]] + } + lines_visible_get { + return: uint; [[the number of lines currently visible in the widget.]] + } + position_at_coordinates_get { + [[get the row, col position for a given coordinate on the widget.]] + params { + x: Evas.Coord; [[the x coordinate in the widget]] + y: Evas.Coord; [[the y coordinate in the widget]] + row: uint *; [[the row for the coordinates]] + col: int *; [[the column for the coordinates]] + } + return: bool; [[true if a line exists at these coordinates]] + } + + //text functions + text_left_gutter_width_get { + return: int; [[the current column width of the gutter for the widget.]] + } + text_line_number_width_get { + return: int; [[the column width required to represent the number of lines in the widget.]] + } + text_between_positions_get { + params { + start_col: uint; [[the widget column of the first character to get]] + start_line: uint; [[the line of the first character to get]] + end_col: uint; [[the widget column of the last character to get]] + end_line: uint; [[the line of the last character to get]] + } + return: char *; [[the text content between start and end positions]] + } + + line_text_column_width_to_position { + params { + line: Elm_Code_Line *; + position: uint; + } + return: uint; + } + line_text_column_width_get { + params { + line: Elm_Code_Line *; + } + return: uint; + } + line_text_position_for_column_get { + params { + line: Elm_Code_Line *; + column: uint; + } + return: uint; + } + text_tabwidth_at_column_get { + params { + column: uint; + } + return: uint; + } + undo { + } + } + implements { + class.constructor; + Eo.Base.constructor; + Eo.Base.finalize; + Evas.Object.Smart.add; + Elm.Widget.event; + Elm.Widget.focus_next_manager_is; + Elm.Widget.focus_direction_manager_is; + } + constructors { + .code; + } + events { + line,clicked; + line,gutter,clicked; + cursor,changed; + changed,user; + selection,changed; + selection,cleared; + } + +} diff --git a/src/lib/elementary/elm_code_widget_legacy.h b/src/lib/elementary/elm_code_widget_legacy.h new file mode 100644 index 0000000000..5110e301e2 --- /dev/null +++ b/src/lib/elementary/elm_code_widget_legacy.h @@ -0,0 +1,13 @@ +/** + * @brief Add a new elm_code widget to the parent + * + * @param parent The parent object + * @return The new object or NULL if it cannot be created + * + * @see elm_code_widget_code_set + * + * @ingroup Data + */ +EAPI Evas_Object *elm_code_widget_add(Evas_Object *parent, Elm_Code *code); + +#include "elm_code_widget.eo.legacy.h" diff --git a/src/lib/elementary/elm_code_widget_private.h b/src/lib/elementary/elm_code_widget_private.h new file mode 100644 index 0000000000..09c5003994 --- /dev/null +++ b/src/lib/elementary/elm_code_widget_private.h @@ -0,0 +1,66 @@ +#ifndef ELM_CODE_WIDGET_PRIVATE_H +# define ELM_CODE_WIDGET_PRIVATE_H + +/** + * Structure holding the info about a selected region. + */ +typedef struct +{ + unsigned int start_line, end_line; + unsigned int start_col, end_col; +} Elm_Code_Widget_Selection_Data; + +typedef struct +{ + Elm_Code *code; + Eina_List *grids; + unsigned int col_count; + Evas_Object *scroller, *gridbox; + + const char *font_name; + Evas_Font_Size font_size; + double gravity_x, gravity_y; + + unsigned int cursor_line, cursor_col; + Eina_Bool editable, focussed; + Eina_Bool show_line_numbers; + unsigned int line_width_marker, tabstop; + Eina_Bool show_whitespace, tab_inserts_spaces; + + Elm_Code_Widget_Selection_Data *selection; + + /* Undo stack */ + Eina_List *undo_stack; + Eina_List *undo_stack_ptr; +} Elm_Code_Widget_Data; + +typedef struct +{ + char *content; + unsigned int length; + unsigned int start_line, start_col, end_line, end_col; + + Eina_Bool insert : 1; /**< True if the change is an insertion */ +} Elm_Code_Widget_Change_Info; + +/* Private widget methods */ + +void _elm_code_widget_cell_size_get(Elm_Code_Widget *widget, Evas_Coord *width, Evas_Coord *height); + +void _elm_code_widget_text_at_cursor_insert(Elm_Code_Widget *widget, const char *text, int length); + +void _elm_code_widget_newline(Elm_Code_Widget *widget); + +void _elm_code_widget_backspace(Elm_Code_Widget *widget); + +void _elm_code_widget_delete(Elm_Code_Widget *widget); + +void _elm_code_widget_tooltip_text_set(Evas_Object *widget, const char *text); + +void _elm_code_widget_tooltip_add(Evas_Object *widget); + +EAPI Elm_Code_Widget_Selection_Data *elm_code_widget_selection_normalized_get(Evas_Object *widget); + +void _elm_code_widget_undo_change_add(Evas_Object *widget, Elm_Code_Widget_Change_Info *info); + +#endif diff --git a/src/lib/elementary/elm_code_widget_selection.c b/src/lib/elementary/elm_code_widget_selection.c new file mode 100644 index 0000000000..33da3aaf9b --- /dev/null +++ b/src/lib/elementary/elm_code_widget_selection.c @@ -0,0 +1,454 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_widget_private.h" + +static char _breaking_chars[] = " \t,.?!;:*&()[]{}"; + +static Elm_Code_Widget_Selection_Data * +_elm_code_widget_selection_new() +{ + Elm_Code_Widget_Selection_Data *data; + + data = calloc(1, sizeof(Elm_Code_Widget_Selection_Data)); + + return data; +} + +static void +_elm_code_widget_selection_limit(Evas_Object *widget EINA_UNUSED, Elm_Code_Widget_Data *pd, + unsigned int *row, unsigned int *col) +{ + Elm_Code_Line *line; + Elm_Code_File *file; + unsigned int width; + + file = pd->code->file; + + if (*row > elm_code_file_lines_get(file)) + *row = elm_code_file_lines_get(file); + + line = elm_code_file_line_get(file, *row); + width = elm_code_widget_line_text_column_width_get(widget, line); + + if (*col > width + 1) + *col = width + 1; + if (*col < 1) + *col = 1; +} + +EAPI void +elm_code_widget_selection_start(Evas_Object *widget, + unsigned int line, unsigned int col) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Selection_Data *selection; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + _elm_code_widget_selection_limit(widget, pd, &line, &col); + if (!pd->selection) + { + selection = _elm_code_widget_selection_new(); + + selection->end_line = line; + selection->end_col = col; + + pd->selection = selection; + } + + pd->selection->start_line = line; + pd->selection->start_col = col; + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CHANGED, widget); + elm_obj_code_widget_cursor_position_set(widget, col, line); +} + +EAPI void +elm_code_widget_selection_end(Evas_Object *widget, + unsigned int line, unsigned int col) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Selection_Data *selection; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + _elm_code_widget_selection_limit(widget, pd, &line, &col); + if (!pd->selection) + { + selection = _elm_code_widget_selection_new(); + + selection->start_line = line; + selection->start_col = col; + + pd->selection = selection; + } + + pd->selection->end_line = line; + pd->selection->end_col = col; + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CHANGED, widget); +} + +EAPI Elm_Code_Widget_Selection_Data * +elm_code_widget_selection_normalized_get(Evas_Object *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Selection_Data *selection; + Eina_Bool reverse; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + selection = _elm_code_widget_selection_new(); + + if (!pd->selection) + { + selection->start_line = selection->end_line = 1; + selection->start_col = selection->end_col = 1; + + return selection; + } + + if (pd->selection->start_line == pd->selection->end_line) + reverse = pd->selection->start_col > pd->selection->end_col; + else + reverse = pd->selection->start_line > pd->selection->end_line; + + if (reverse) + { + selection->start_line = pd->selection->end_line; + selection->start_col = pd->selection->end_col; + selection->end_line = pd->selection->start_line; + selection->end_col = pd->selection->start_col; + } + else + { + selection->start_line = pd->selection->start_line; + selection->start_col = pd->selection->start_col; + selection->end_line = pd->selection->end_line; + selection->end_col = pd->selection->end_col; + } + + return selection; +} + +EAPI void +elm_code_widget_selection_clear(Evas_Object *widget) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (!pd->selection) + return; + + free(pd->selection); + pd->selection = NULL; + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CLEARED, widget); +} + +static void +_elm_code_widget_selection_delete_single(Elm_Code_Widget *widget, Elm_Code_Widget_Data *pd) +{ + Elm_Code_Line *line; + const char *old; + unsigned int old_length, start, end, length; + char *content; + Elm_Code_Widget_Selection_Data *selection; + + selection = elm_code_widget_selection_normalized_get(widget); + line = elm_code_file_line_get(pd->code->file, selection->start_line); + old = elm_code_line_text_get(line, &old_length); + start = elm_code_widget_line_text_position_for_column_get(widget, line, selection->start_col); + end = elm_code_widget_line_text_position_for_column_get(widget, line, selection->end_col); + length = line->length - (end - start + 1); + + if (end == line->length) + { + length = line->length - (end - start); + + content = malloc(sizeof(char) * length); + strncpy(content, old, start); + } + else + { + length = line->length - (end - start + 1); + + content = malloc(sizeof(char) * length); + strncpy(content, old, start); + strncpy(content + start, old + end + 1, + old_length - (end + 1)); + } + elm_code_line_text_set(line, content, length); + free(content); + free(selection); +} + +static void +_elm_code_widget_selection_delete_multi(Elm_Code_Widget *widget, Elm_Code_Widget_Data *pd) +{ + Elm_Code_Line *line; + const char *first, *last; + unsigned int last_length, start, end, length, i; + char *content; + Elm_Code_Widget_Selection_Data *selection; + + if (pd->selection->end_line == pd->selection->start_line) + return; + + selection = elm_code_widget_selection_normalized_get(widget); + line = elm_code_file_line_get(pd->code->file, selection->start_line); + first = elm_code_line_text_get(line, NULL); + start = elm_code_widget_line_text_position_for_column_get(widget, line, selection->start_col); + + line = elm_code_file_line_get(pd->code->file, selection->end_line); + last = elm_code_line_text_get(line, &last_length); + end = elm_code_widget_line_text_position_for_column_get(widget, line, selection->end_col); + + if (last_length == end) + { + length = start + last_length - end; + content = malloc(sizeof(char) * length); + strncpy(content, first, start); + } + else + { + length = start + last_length - (end + 1); + content = malloc(sizeof(char) * length); + strncpy(content, first, start); + + strncpy(content + start, last + end + 1, last_length - (end + 1)); + } + + for (i = line->number; i > selection->start_line; i--) + elm_code_file_line_remove(pd->code->file, i); + + line = elm_code_file_line_get(pd->code->file, selection->start_line); + elm_code_line_text_set(line, content, length); + free(content); + free(selection); +} + +EAPI void +elm_code_widget_selection_delete(Evas_Object *widget) +{ + Elm_Code_Widget_Data *pd; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (!pd->selection) + return; + + if (pd->selection->start_line == pd->selection->end_line) + _elm_code_widget_selection_delete_single(widget, pd); + else + _elm_code_widget_selection_delete_multi(widget, pd); + + free(pd->selection); + pd->selection = NULL; + eo_event_callback_call(widget, ELM_OBJ_CODE_WIDGET_EVENT_SELECTION_CLEARED, widget); +} + +EAPI void +elm_code_widget_selection_select_line(Evas_Object *widget, unsigned int line) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *lineobj; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + lineobj = elm_code_file_line_get(pd->code->file, line); + + if (!lineobj) + return; + + elm_code_widget_selection_start(widget, line, 1); + elm_code_widget_selection_end(widget, line, lineobj->length); +} + +static Eina_Bool +_elm_code_widget_selection_char_breaks(char chr) +{ + unsigned int i; + + if (chr == 0) + return EINA_TRUE; + + for (i = 0; i < sizeof(_breaking_chars); i++) + if (chr == _breaking_chars[i]) + return EINA_TRUE; + + + return EINA_FALSE; +} + +EAPI void +elm_code_widget_selection_select_word(Evas_Object *widget, unsigned int line, unsigned int col) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Line *lineobj; + unsigned int colpos, length, pos; + const char *content; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + lineobj = elm_code_file_line_get(pd->code->file, line); + content = elm_code_line_text_get(lineobj, &length); + + _elm_code_widget_selection_limit(widget, pd, &line, &col); + colpos = elm_code_widget_line_text_position_for_column_get(widget, lineobj, col); + + pos = colpos; + while (pos > 0) + { + if (_elm_code_widget_selection_char_breaks(content[pos - 1])) + break; + pos--; + } + elm_code_widget_selection_start(widget, line, + elm_code_widget_line_text_column_width_to_position(widget, lineobj, pos)); + + pos = colpos; + while (pos < length - 1) + { + if (_elm_code_widget_selection_char_breaks(content[pos + 1])) + break; + pos++; + } + elm_code_widget_selection_end(widget, line, + elm_code_widget_line_text_column_width_to_position(widget, lineobj, pos)); +} + +EAPI char * +elm_code_widget_selection_text_get(Evas_Object *widget) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Selection_Data *selection; + char *text; + + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + if (!pd->selection) + return strdup(""); + + selection = elm_code_widget_selection_normalized_get(widget); + + text = elm_code_widget_text_between_positions_get(widget, + selection->start_col, selection->start_line, + selection->end_col, selection->end_line); + + free(selection); + return text; +} + +static void +_selection_loss_cb(void *data, Elm_Sel_Type selection EINA_UNUSED) +{ + Elm_Code_Widget *widget; + + widget = (Elm_Code_Widget *)data; +// TODO we need to know whih selection we are clearing! +// elm_code_widget_selection_clear(widget); +} + +EAPI void +elm_code_widget_selection_cut(Evas_Object *widget) +{ + char *text; + + text = elm_code_widget_selection_text_get(widget); + elm_cnp_selection_set(widget, ELM_SEL_TYPE_CLIPBOARD, ELM_SEL_FORMAT_TEXT, text, strlen(text)); + elm_cnp_selection_loss_callback_set(widget, ELM_SEL_TYPE_CLIPBOARD, _selection_loss_cb, widget); + free(text); + + elm_code_widget_selection_delete(widget); +} + +EAPI void +elm_code_widget_selection_copy(Evas_Object *widget) +{ + char *text; + + text = elm_code_widget_selection_text_get(widget); + elm_cnp_selection_set(widget, ELM_SEL_TYPE_CLIPBOARD, ELM_SEL_FORMAT_TEXT, text, strlen(text)); + elm_cnp_selection_loss_callback_set(widget, ELM_SEL_TYPE_CLIPBOARD, _selection_loss_cb, widget); + free(text); +} + +static void +_selection_paste_single(Elm_Code_Widget *widget, Elm_Code *code, + unsigned int col, unsigned int row, const char *text, unsigned int len) +{ + Elm_Code_Line *line; + unsigned int position, newcol; + + line = elm_code_file_line_get(code->file, row); + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + elm_code_line_text_insert(line, position, text, len); + + newcol = elm_code_widget_line_text_column_width_to_position(widget, line, position + len); + elm_obj_code_widget_cursor_position_set(widget, newcol, row); +} + +static void +_selection_paste_multi(Elm_Code_Widget *widget, Elm_Code *code, + unsigned int col, unsigned int row, const char *text, unsigned int len) +{ + Elm_Code_Line *line; + unsigned int position, newrow, remain; + int nlpos; + short nllen; + char *ptr; + + line = elm_code_file_line_get(code->file, row); + position = elm_code_widget_line_text_position_for_column_get(widget, line, col); + elm_code_line_split_at(line, position); + + newrow = row; + ptr = (char *)text; + remain = len; + while ((nlpos = elm_code_text_newlinenpos(ptr, remain, &nllen)) != ELM_CODE_TEXT_NOT_FOUND) + { + if (newrow == row) + _selection_paste_single(widget, code, col, row, text, nlpos); + else + elm_code_file_line_insert(code->file, newrow, ptr, nlpos, NULL); + + remain -= nlpos + nllen; + ptr += nlpos + nllen; + newrow++; + } + + _selection_paste_single(widget, code, 1, newrow, ptr, len - (ptr - text)); +} + +static Eina_Bool +_selection_paste_cb(void *data, Evas_Object *obj EINA_UNUSED, Elm_Selection_Data *ev) +{ + Elm_Code *code; + Elm_Code_Widget *widget; + unsigned int row, col; + + widget = (Elm_Code_Widget *)data; + + if (ev->format != ELM_SEL_FORMAT_TEXT) + return EINA_TRUE; + if (ev->len <= 0) + return EINA_TRUE; + + code = elm_obj_code_widget_code_get(widget); + elm_obj_code_widget_cursor_position_get(widget, &col, &row); + + if (elm_code_text_newlinenpos(ev->data, ev->len, NULL) == ELM_CODE_TEXT_NOT_FOUND) + _selection_paste_single(widget, code, col, row, ev->data, ev->len - 1); + else + _selection_paste_multi(widget, code, col, row, ev->data, ev->len - 1); + + return EINA_TRUE; +} + +EAPI void +elm_code_widget_selection_paste(Evas_Object *widget) +{ + elm_code_widget_selection_delete(widget); + + elm_cnp_selection_get(widget, ELM_SEL_TYPE_CLIPBOARD, ELM_SEL_FORMAT_TEXT, _selection_paste_cb, widget); +} diff --git a/src/lib/elementary/elm_code_widget_selection.h b/src/lib/elementary/elm_code_widget_selection.h new file mode 100644 index 0000000000..b79abc763f --- /dev/null +++ b/src/lib/elementary/elm_code_widget_selection.h @@ -0,0 +1,44 @@ +#ifndef ELM_CODE_WIDGET_SELECTION_H_ +# define ELM_CODE_WIDGET_SELECTION_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Selection handling functions. + * @defgroup Managing the complexities of selecting text across seperate lines. + * + * @{ + * + * Functions for selection handling + * + */ + +EAPI void elm_code_widget_selection_start(Evas_Object *widget, unsigned int line, unsigned int col); + +EAPI void elm_code_widget_selection_end(Evas_Object *widget, unsigned int line, unsigned int col); + +EAPI void elm_code_widget_selection_clear(Evas_Object *widget); + +EAPI void elm_code_widget_selection_delete(Evas_Object *widget); + +EAPI void elm_code_widget_selection_select_line(Evas_Object *widget, unsigned int line); + +EAPI void elm_code_widget_selection_select_word(Evas_Object *widget, unsigned int line, unsigned int col); + +EAPI char *elm_code_widget_selection_text_get(Evas_Object *widget); + +EAPI void elm_code_widget_selection_cut(Evas_Object *widget); +EAPI void elm_code_widget_selection_copy(Evas_Object *widget); +EAPI void elm_code_widget_selection_paste(Evas_Object *widget); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ELM_CODE_WIDGET_SELECTION_H_ */ diff --git a/src/lib/elementary/elm_code_widget_text.c b/src/lib/elementary/elm_code_widget_text.c new file mode 100644 index 0000000000..bbcbdc3492 --- /dev/null +++ b/src/lib/elementary/elm_code_widget_text.c @@ -0,0 +1,197 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_widget_private.h" + +static int +_elm_code_widget_text_line_number_width_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + int max; + + max = elm_code_file_lines_get(pd->code->file); + if (max < 1) + max = 1; + + return floor(log10(max)) + 1; +} + +static int +_elm_code_widget_text_left_gutter_width_get(Eo *obj, Elm_Code_Widget_Data *pd) +{ + Elm_Code_Widget *widget; + int width = 1; // the status icon, for now + + widget = obj; + if (!widget) + return width; + + if (pd->show_line_numbers) + width += _elm_code_widget_text_line_number_width_get(widget, pd); + + return width; +} + +static char * +_elm_code_widget_text_multi_get(Elm_Code_Widget *widget, Elm_Code_Widget_Data *pd, + unsigned int start_col, unsigned int start_line, + unsigned int end_col, unsigned int end_line) +{ + Elm_Code_Line *line; + char *first, *last, *ret, *ptr; + const char *newline; + short newline_len; + int ret_len; + unsigned int row, start, end; + + newline = elm_code_file_line_ending_chars_get(pd->code->file, &newline_len); + + line = elm_code_file_line_get(pd->code->file, start_line); + start = elm_code_widget_line_text_position_for_column_get(widget, line, start_col); + first = elm_code_line_text_substr(line, start, line->length - start + 1); + + line = elm_code_file_line_get(pd->code->file, end_line); + end = elm_code_widget_line_text_position_for_column_get(widget, line, end_col + 1); + last = elm_code_line_text_substr(line, 0, end); + + ret_len = strlen(first) + strlen(last) + newline_len; + + for (row = pd->selection->start_line + 1; row < end_line; row++) + { + line = elm_code_file_line_get(pd->code->file, row); + ret_len += line->length + newline_len; + } + + ret = malloc(sizeof(char) * (ret_len + 1)); + + snprintf(ret, strlen(first) + newline_len + 1, "%s%s", first, newline); + + ptr = ret; + ptr += strlen(first) + newline_len; + + for (row = start_line + 1; row < end_line; row++) + { + line = elm_code_file_line_get(pd->code->file, row); + if (line->modified) + snprintf(ptr, line->length + 1, "%s", line->modified); + else + snprintf(ptr, line->length + 1, "%s", line->content); + + snprintf(ptr + line->length, newline_len + 1, "%s", newline); + ptr += line->length + newline_len; + } + snprintf(ptr, strlen(last) + 1, "%s", last); + + free(first); + free(last); + return ret; +} + +static char * +_elm_code_widget_text_single_get(Elm_Code_Widget *widget, Elm_Code_Widget_Data *pd, + unsigned int start_col, unsigned int start_line, + unsigned int end_col) +{ + Elm_Code_Line *line; + unsigned int start, end; + + line = elm_code_file_line_get(pd->code->file, start_line); + start = elm_code_widget_line_text_position_for_column_get(widget, line, start_col); + end = elm_code_widget_line_text_position_for_column_get(widget, line, end_col + 1); + + return elm_code_line_text_substr(line, start, end - start); +} + +static char * +_elm_code_widget_text_between_positions_get(Eo *widget, Elm_Code_Widget_Data *pd, + unsigned int start_col, unsigned int start_line, + unsigned int end_col, unsigned int end_line) +{ + if (start_line == end_line) + return _elm_code_widget_text_single_get(widget, pd, start_col, start_line, end_col); + else + return _elm_code_widget_text_multi_get(widget, pd, start_col, start_line, end_col, end_line); +} + +static unsigned int +_elm_code_widget_line_text_column_width_to_position(Eo *obj, Elm_Code_Widget_Data *pd EINA_UNUSED, Elm_Code_Line *line, unsigned int position) +{ + Eina_Unicode unicode; + unsigned int count = 1; + int index = 0; + const char *chars; + + if (line->length == 0) + return 1; + + if (line->modified) + chars = line->modified; + else + chars = line->content; + if (position > line->length) + position = line->length; + + while ((unsigned int) index < position) + { + unicode = eina_unicode_utf8_next_get(chars, &index); + if (unicode == 0) + break; + + if (unicode == '\t') + count += elm_code_widget_text_tabwidth_at_column_get(obj, count); + else + count++; + } + + return count; +} + +static unsigned int +_elm_code_widget_line_text_column_width_get(Eo *obj, Elm_Code_Widget_Data *pd, Elm_Code_Line *line) +{ + if (!line) + return 0; + + return _elm_code_widget_line_text_column_width_to_position(obj, pd, line, line->length) - 1; +} + +static unsigned int +_elm_code_widget_line_text_position_for_column_get(Eo *obj, Elm_Code_Widget_Data *pd EINA_UNUSED, Elm_Code_Line *line, unsigned int column) +{ + Eina_Unicode unicode; + unsigned int count = 1, position = 0; + int index = 0; + const char *chars; + + if (line->length == 0 || column == 1) + return 0; + + if (line->modified) + chars = line->modified; + else + chars = line->content; + + while ((unsigned int) count <= column && index <= (int) line->length) + { + position = (unsigned int) index; + unicode = eina_unicode_utf8_next_get(chars, &index); + + if (unicode == 0) + return line->length; + else if (unicode == '\t') + count += elm_code_widget_text_tabwidth_at_column_get(obj, count); + else + count++; + } + + return position; +} + +static unsigned int +_elm_code_widget_text_tabwidth_at_column_get(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd, unsigned int column) +{ + return pd->tabstop - ((column - 1) % pd->tabstop); +} + diff --git a/src/lib/elementary/elm_code_widget_undo.c b/src/lib/elementary/elm_code_widget_undo.c new file mode 100644 index 0000000000..ccf621b300 --- /dev/null +++ b/src/lib/elementary/elm_code_widget_undo.c @@ -0,0 +1,69 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include "Elementary.h" + +#include "elm_code_widget_private.h" + +Elm_Code_Widget_Change_Info * +_elm_code_widget_undo_info_copy(Elm_Code_Widget_Change_Info *info) +{ + Elm_Code_Widget_Change_Info *copy; + + copy = calloc(1, sizeof(*info)); + memcpy(copy, info, sizeof(*info)); + copy->content = malloc(sizeof(char) * (info->length + 1)); + strncpy(copy->content, info->content, info->length); + + return copy; +} + +void +_elm_code_widget_undo_change_add(Evas_Object *widget, + Elm_Code_Widget_Change_Info *info) +{ + Elm_Code_Widget_Data *pd; + Elm_Code_Widget_Change_Info *info_copy; + + info_copy = _elm_code_widget_undo_info_copy(info); + pd = eo_data_scope_get(widget, ELM_CODE_WIDGET_CLASS); + + pd->undo_stack_ptr = eina_list_prepend(pd->undo_stack_ptr, info_copy); + pd->undo_stack = pd->undo_stack_ptr; +} + +static void +_elm_code_widget_undo_change(Evas_Object *widget, + Elm_Code_Widget_Change_Info *info) +{ + if (info->insert) + { + elm_code_widget_selection_start(widget, info->start_line, info->start_col); + elm_code_widget_selection_end(widget, info->end_line, info->end_col); + elm_code_widget_selection_delete(widget); + } + else + { + elm_code_widget_cursor_position_set(widget, info->start_col, info->start_line); + _elm_code_widget_text_at_cursor_insert(widget, info->content, info->length); + } +} + +static void +_elm_code_widget_undo(Eo *obj EINA_UNUSED, Elm_Code_Widget_Data *pd) +{ + Elm_Code_Widget_Change_Info *info; + + if (!pd->undo_stack_ptr) + return; + + info = eina_list_data_get(pd->undo_stack_ptr); + _elm_code_widget_undo_change(obj, info); + + if (eina_list_next(pd->undo_stack_ptr)) + pd->undo_stack_ptr = eina_list_next(pd->undo_stack_ptr); + else + pd->undo_stack_ptr = NULL; +} + diff --git a/src/tests/elementary/elm_code_file_test_load.c b/src/tests/elementary/elm_code_file_test_load.c new file mode 100644 index 0000000000..7e21b235f1 --- /dev/null +++ b/src/tests/elementary/elm_code_file_test_load.c @@ -0,0 +1,162 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" + +START_TEST (elm_code_file_load) +{ + char *path = TESTS_SRC_DIR "/testfile.txt"; + char real[EINA_PATH_MAX]; + Elm_Code_File *file; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + realpath(path, real); + + ck_assert_str_eq(basename(path), elm_code_file_filename_get(file)); + ck_assert_str_eq(real, elm_code_file_path_get(file)); + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_load_lines) +{ + char *path = TESTS_SRC_DIR "/testfile.txt"; + Elm_Code_File *file; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + ck_assert_uint_eq(4, elm_code_file_lines_get(file)); + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_load_blank_lines) +{ + char *path = TESTS_SRC_DIR "/testfile-withblanks.txt"; + Elm_Code_File *file; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + ck_assert_uint_eq(8, elm_code_file_lines_get(file)); + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_load_windows) +{ + char *path = TESTS_SRC_DIR "/testfile-windows.txt"; + Elm_Code_File *file; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + ck_assert_uint_eq(4, elm_code_file_lines_get(file)); + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +static void _assert_line_content_eq(const char *content, Elm_Code_Line *line) +{ + int length; + int c; + + length = strlen(content); + ck_assert_int_eq(length, line->length); + + for (c = 0; c < length; c++) + ck_assert_uint_eq(content[c], line->content[c]); +} + +START_TEST (elm_code_file_load_content) +{ + char *path = TESTS_SRC_DIR "/testfile.txt"; + Elm_Code_File *file; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + _assert_line_content_eq("line2", elm_code_file_line_get(file, 2)); + _assert_line_content_eq("another line", elm_code_file_line_get(file, 4)); + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_line_ending_unix) +{ + char *path = TESTS_SRC_DIR "/testfile.txt"; + Elm_Code_File *file; + Elm_Code *code; + short len; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + ck_assert_int_eq(ELM_CODE_FILE_LINE_ENDING_UNIX, elm_code_file_line_ending_get(file)); + ck_assert_str_eq("\n", elm_code_file_line_ending_chars_get(file, &len)); + ck_assert_int_eq(1, len); + + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_line_ending_windows) +{ + char *path = TESTS_SRC_DIR "/testfile-windows.txt"; + Elm_Code_File *file; + Elm_Code *code; + short len; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_open(code, path); + + ck_assert_int_eq(ELM_CODE_FILE_LINE_ENDING_WINDOWS, elm_code_file_line_ending_get(file)); + ck_assert_str_eq("\r\n", elm_code_file_line_ending_chars_get(file, &len)); + ck_assert_int_eq(2, len); + + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +void elm_code_file_test_load(TCase *tc) +{ + tcase_add_test(tc, elm_code_file_load); + tcase_add_test(tc, elm_code_file_load_lines); + tcase_add_test(tc, elm_code_file_load_blank_lines); + tcase_add_test(tc, elm_code_file_load_windows); + tcase_add_test(tc, elm_code_file_load_content); + tcase_add_test(tc, elm_code_file_line_ending_unix); + tcase_add_test(tc, elm_code_file_line_ending_windows); +} diff --git a/src/tests/elementary/elm_code_file_test_memory.c b/src/tests/elementary/elm_code_file_test_memory.c new file mode 100644 index 0000000000..f39228d427 --- /dev/null +++ b/src/tests/elementary/elm_code_file_test_memory.c @@ -0,0 +1,49 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" + +START_TEST (elm_code_file_memory_lines) +{ + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + ck_assert_uint_eq(0, elm_code_file_lines_get(code->file)); + + elm_code_file_line_append(code->file, "a line", 6, NULL); + + ck_assert_uint_eq(1, elm_code_file_lines_get(code->file)); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_file_memory_tokens) +{ + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + file = code->file; + elm_code_file_line_append(file, "a line", 6, NULL); + + line = elm_code_file_line_get(file, 1); + elm_code_line_token_add(line, 2, 5, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + ck_assert_uint_eq(1, eina_list_count(line->tokens)); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +void elm_code_file_test_memory(TCase *tc) +{ + tcase_add_test(tc, elm_code_file_memory_lines); + tcase_add_test(tc, elm_code_file_memory_tokens); +} diff --git a/src/tests/elementary/elm_code_test_basic.c b/src/tests/elementary/elm_code_test_basic.c new file mode 100644 index 0000000000..9805a7508b --- /dev/null +++ b/src/tests/elementary/elm_code_test_basic.c @@ -0,0 +1,28 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" + +START_TEST (elm_code_create_test) +{ + char *path = TESTS_SRC_DIR "/testfile.txt"; + Elm_Code *code; + + elm_init(1, NULL); + code = elm_code_create(); + elm_code_file_open(code, path); + + ck_assert(!!code); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +void elm_code_test_basic(TCase *tc) +{ + tcase_add_test(tc, elm_code_create_test); +} diff --git a/src/tests/elementary/elm_code_test_line.c b/src/tests/elementary/elm_code_test_line.c new file mode 100644 index 0000000000..cdbc628079 --- /dev/null +++ b/src/tests/elementary/elm_code_test_line.c @@ -0,0 +1,83 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" + +START_TEST (elm_code_line_create_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "a test string...", 16, NULL); + line = elm_code_file_line_get(file, 1); + + ck_assert(!!line); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_line_token_count_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "a test string...", 16, NULL); + line = elm_code_file_line_get(file, 1); + + ck_assert_int_eq(0, eina_list_count(line->tokens)); + elm_code_line_token_add(line, 2, 5, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + ck_assert_int_eq(1, eina_list_count(line->tokens)); + elm_code_line_tokens_clear(line); + ck_assert_int_eq(0, eina_list_count(line->tokens)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_line_split_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line, *newline; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "line1line2", 10, NULL); + line = elm_code_file_line_get(file, 1); + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + ck_assert_int_eq(10, line->length); + + elm_code_line_split_at(line, 5); + ck_assert_int_eq(2, elm_code_file_lines_get(file)); + newline = elm_code_file_line_get(file, 2); + ck_assert_int_eq(5, line->length); + ck_assert_int_eq(5, newline->length); + elm_shutdown(); +} +END_TEST + +void elm_code_test_line(TCase *tc) +{ + tcase_add_test(tc, elm_code_line_create_test); + tcase_add_test(tc, elm_code_line_token_count_test); + tcase_add_test(tc, elm_code_line_split_test); +} diff --git a/src/tests/elementary/elm_code_test_parse.c b/src/tests/elementary/elm_code_test_parse.c new file mode 100644 index 0000000000..4bd35b4b88 --- /dev/null +++ b/src/tests/elementary/elm_code_test_parse.c @@ -0,0 +1,102 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" +#include "elm_code_parse.h" + +static int line_calls, file_calls; + +static void _parser_line_callback(Elm_Code_Line *line EINA_UNUSED, void *data EINA_UNUSED) +{ + line_calls++; +} + +static void _parser_file_callback(Elm_Code_File *file EINA_UNUSED, void *data EINA_UNUSED) +{ + file_calls++; +} + +START_TEST (elm_code_parse_hook_memory_test) +{ + Elm_Code *code; + Elm_Code_File *file; + + line_calls = 0; + file_calls = 0; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_parser_add(code, _parser_line_callback, _parser_file_callback, NULL); + elm_code_file_line_append(file, "some \"test content\" for parsing", 31, NULL); + + ck_assert_int_eq(1, line_calls); + ck_assert_int_eq(0, file_calls); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_parse_hook_file_test) +{ + Elm_Code *code; + Elm_Code_File *file; + char *path = TESTS_SRC_DIR "testfile.txt"; + + line_calls = 0; + file_calls = 0; + + elm_init(1, NULL); + code = elm_code_create(); + + elm_code_parser_add(code, _parser_line_callback, _parser_file_callback, NULL); + file = elm_code_file_open(code, path); + + ck_assert_int_eq(4, line_calls); + ck_assert_int_eq(1, file_calls); + + elm_code_file_close(file); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_parse_todo_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + elm_code_init(); + + code = elm_code_create(); + elm_code_parser_standard_add(code, ELM_CODE_PARSER_STANDARD_TODO); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "xxx TODO line", 13, NULL); + line = elm_code_file_line_get(file, 1); + ck_assert_int_eq(ELM_CODE_STATUS_TYPE_TODO, line->status); + + elm_code_line_text_set(line, "FIXME too", 9); + ck_assert_int_eq(ELM_CODE_STATUS_TYPE_TODO, line->status); + + elm_code_line_text_set(line, "TOFIX", 5); + ck_assert_int_eq(ELM_CODE_STATUS_TYPE_DEFAULT, line->status); + elm_code_shutdown(); + elm_shutdown(); +} +END_TEST + +void elm_code_test_parse(TCase *tc) +{ + tcase_add_test(tc, elm_code_parse_hook_memory_test); + tcase_add_test(tc, elm_code_parse_hook_file_test); + tcase_add_test(tc, elm_code_parse_todo_test); +} diff --git a/src/tests/elementary/elm_code_test_text.c b/src/tests/elementary/elm_code_test_text.c new file mode 100644 index 0000000000..934b98b425 --- /dev/null +++ b/src/tests/elementary/elm_code_test_text.c @@ -0,0 +1,177 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" +#include "elm_code_text.h" + +START_TEST (elm_code_text_get_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "test", 4, NULL); + line = elm_code_file_line_get(file, 1); + ck_assert_str_eq("test", elm_code_line_text_get(line, NULL)); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_insert_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "test", 4, NULL); + line = elm_code_file_line_get(file, 1); + + elm_code_line_text_insert(line, 4, "ing", 3); + ck_assert_str_eq("testing", elm_code_line_text_get(line, NULL)); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_contains_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "a test string...", 16, NULL); + line = elm_code_file_line_get(file, 1); + + ck_assert_int_eq(EINA_TRUE, elm_code_line_text_contains(line, "test")); + ck_assert_int_eq(EINA_FALSE, elm_code_line_text_contains(line, "text")); + + ck_assert_int_eq(EINA_TRUE, elm_code_line_text_contains(line, "a t")); + ck_assert_int_eq(EINA_TRUE, elm_code_line_text_contains(line, "...")); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_strpos_test) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + + elm_code_file_line_append(file, "a test string...", 16, NULL); + line = elm_code_file_line_get(file, 1); + + ck_assert_int_eq(2, elm_code_line_text_strpos(line, "test", 0)); + ck_assert_int_eq(2, elm_code_line_text_strpos(line, "test", 1)); + ck_assert_int_eq(2, elm_code_line_text_strpos(line, "test", 2)); + ck_assert_int_eq(ELM_CODE_TEXT_NOT_FOUND, elm_code_line_text_strpos(line, "test", 5)); + ck_assert_int_eq(ELM_CODE_TEXT_NOT_FOUND, elm_code_line_text_strpos(line, "text", 0)); + + ck_assert_int_eq(0, elm_code_line_text_strpos(line, "a t", 0)); + ck_assert_int_eq(ELM_CODE_TEXT_NOT_FOUND, elm_code_line_text_strpos(line, "a t", 2)); + ck_assert_int_eq(13, elm_code_line_text_strpos(line, "...", 0)); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_newline_position_test) +{ + short nllen; + const char *unixtext = "a test\nwith newline"; + const char *wintext = "a windows\r\nnewline"; + + elm_init(1, NULL); + ck_assert_int_eq(6, elm_code_text_newlinenpos(unixtext, strlen(unixtext), &nllen)); + ck_assert_int_eq(1, nllen); + ck_assert_int_eq(9, elm_code_text_newlinenpos(wintext, strlen(wintext), &nllen)); + ck_assert_int_eq(2, nllen); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_leading_whitespace_test) +{ + const char *text; + + elm_init(1, NULL); + text = "testing"; + ck_assert_int_eq(0, elm_code_text_leading_whitespace_length(text, strlen(text))); + + text = " spaces"; + ck_assert_int_eq(2, elm_code_text_leading_whitespace_length(text, strlen(text))); + + text = "\t\ttabs"; + ck_assert_int_eq(2, elm_code_text_leading_whitespace_length(text, strlen(text))); + + text = " \t mix"; + ck_assert_int_eq(3, elm_code_text_leading_whitespace_length(text, strlen(text))); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_trailing_whitespace_test) +{ + const char *text; + + elm_init(1, NULL); + text = "testing"; + ck_assert_int_eq(0, elm_code_text_trailing_whitespace_length(text, strlen(text))); + + text = "spaces "; + ck_assert_int_eq(2, elm_code_text_trailing_whitespace_length(text, strlen(text))); + + text = "tabs\t\t"; + ck_assert_int_eq(2, elm_code_text_trailing_whitespace_length(text, strlen(text))); + + text = "mix \t "; + ck_assert_int_eq(3, elm_code_text_trailing_whitespace_length(text, strlen(text))); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_text_is_whitespace_test) +{ + const char *text; + + elm_init(1, NULL); + text = " "; + ck_assert_int_eq(1, elm_code_text_is_whitespace(text, strlen(text))); + + text = " \t\t "; + ck_assert_int_eq(1, elm_code_text_is_whitespace(text, strlen(text))); + + text = " . "; + ck_assert_int_eq(0, elm_code_text_is_whitespace(text, strlen(text))); + elm_shutdown(); +} +END_TEST + +void elm_code_test_text(TCase *tc) +{ + tcase_add_test(tc, elm_code_text_get_test); + tcase_add_test(tc, elm_code_text_insert_test); + tcase_add_test(tc, elm_code_text_contains_test); + tcase_add_test(tc, elm_code_text_strpos_test); + tcase_add_test(tc, elm_code_text_newline_position_test); + tcase_add_test(tc, elm_code_text_leading_whitespace_test); + tcase_add_test(tc, elm_code_text_trailing_whitespace_test); + tcase_add_test(tc, elm_code_text_is_whitespace_test); +} diff --git a/src/tests/elementary/elm_code_test_widget.c b/src/tests/elementary/elm_code_test_widget.c new file mode 100644 index 0000000000..660015363e --- /dev/null +++ b/src/tests/elementary/elm_code_test_widget.c @@ -0,0 +1,93 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" +#include "elm_code_widget.c" + +static void _assert_cell_type(Evas_Textgrid_Cell cell, Elm_Code_Token_Type type, int id) +{ + ck_assert_msg(cell.fg == type, "Wrong type for cell %d", id); +} + +START_TEST (elm_code_widget_token_render_simple_test) +{ + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code *code; + Elm_Code_Widget *widget; + Evas_Object *win; + + int length; + + Evas_Textgrid_Cell cells[25]; + + elm_init(1, NULL); + code = elm_code_create(); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + file = code->file; + elm_code_file_line_append(file, "some \"test content\", 45", 23, NULL); + line = elm_code_file_line_get(file, 1); + length = line->length; + + elm_code_line_token_add(line, 6, 17, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + elm_code_line_token_add(line, 21, 22, 1, ELM_CODE_TOKEN_TYPE_COMMENT); + + _elm_code_widget_fill_line_tokens(widget, cells, length+1, line); + _assert_cell_type(cells[1], ELM_CODE_TOKEN_TYPE_DEFAULT, 1); + _assert_cell_type(cells[4], ELM_CODE_TOKEN_TYPE_DEFAULT, 4); + _assert_cell_type(cells[5], ELM_CODE_TOKEN_TYPE_DEFAULT, 5); + _assert_cell_type(cells[16], ELM_CODE_TOKEN_TYPE_COMMENT, 16); + _assert_cell_type(cells[20], ELM_CODE_TOKEN_TYPE_DEFAULT, 20); + _assert_cell_type(cells[22], ELM_CODE_TOKEN_TYPE_COMMENT, 22); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_widget_construct) +{ + Elm_Code *code; + Elm_Code_Widget *widget; + Evas_Object *win; + + elm_init(1, NULL); + code = elm_code_create(); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + ck_assert(!!widget); + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_widget_construct_nocode) +{ + Elm_Code_Widget *widget; + Evas_Object *win; + + elm_init(1, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = eo_add(ELM_CODE_WIDGET_CLASS, win); + ck_assert(!widget); + + elm_shutdown(); +} +END_TEST + +void elm_code_test_widget(TCase *tc) +{ + tcase_add_test(tc, elm_code_widget_token_render_simple_test); + tcase_add_test(tc, elm_code_widget_construct); + tcase_add_test(tc, elm_code_widget_construct_nocode); +} diff --git a/src/tests/elementary/elm_code_test_widget_selection.c b/src/tests/elementary/elm_code_test_widget_selection.c new file mode 100644 index 0000000000..e181e6e14d --- /dev/null +++ b/src/tests/elementary/elm_code_test_widget_selection.c @@ -0,0 +1,643 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" +#include "elm_code_widget_private.h" +#include "elm_code_widget_selection.h" + +START_TEST (elm_code_test_widget_selection_set) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 1, 2); + elm_code_widget_selection_end(widget, 1, 3); + elm_code_widget_selection_clear(widget); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_normalized_get) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Elm_Code_Widget_Selection_Data *selection; + Evas_Object *win; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 1, 2); + selection = elm_code_widget_selection_normalized_get(widget); + + ck_assert_int_eq(selection->start_col, 2); + ck_assert_int_eq(selection->end_col, 3); + elm_code_widget_selection_clear(widget); + free(selection); + + elm_code_file_line_append(file, "another", 7, NULL); + elm_code_widget_selection_start(widget, 2, 2); + elm_code_widget_selection_end(widget, 1, 3); + selection = elm_code_widget_selection_normalized_get(widget); + + ck_assert_int_eq(selection->start_line, 1); + ck_assert_int_eq(selection->start_col, 3); + ck_assert_int_eq(selection->end_line, 2); + ck_assert_int_eq(selection->end_col, 2); + elm_code_widget_selection_clear(widget); + free(selection); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_text_get) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + ck_assert_str_eq("", elm_code_widget_selection_text_get(widget)); + + elm_code_widget_selection_start(widget, 1, 2); + elm_code_widget_selection_end(widget, 1, 3); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("es", selection); + free(selection); + + elm_code_widget_selection_clear(widget); + ck_assert_str_eq("", elm_code_widget_selection_text_get(widget)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_text_get) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + ck_assert_str_eq("", elm_code_widget_selection_text_get(widget)); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 1, 2); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("es", selection); + free(selection); + + elm_code_widget_selection_clear(widget); + ck_assert_str_eq("", elm_code_widget_selection_text_get(widget)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_text_get_twoline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 2, 2); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("st\nte", selection); + free(selection); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_text_get_twoline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 2, 2); + elm_code_widget_selection_end(widget, 1, 3); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("st\nte", selection); + free(selection); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_text_get_multiline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 3, 2); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("st\ntest\nte", selection); + free(selection); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_text_get_multiline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_start(widget, 3, 2); + elm_code_widget_selection_end(widget, 1, 3); + + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("st\ntest\nte", selection); + free(selection); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_delete) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + unsigned int length; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, &length); + ck_assert_int_eq(4, length); + ck_assert_strn_eq("text", text, length); + + elm_code_widget_selection_start(widget, 1, 2); + elm_code_widget_selection_end(widget, 1, 3); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, &length); + ck_assert_int_eq(2, length); + ck_assert_strn_eq("tt", text, length); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_delete) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + unsigned int length; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, &length); + ck_assert_int_eq(4, length); + ck_assert_strn_eq("text", text, length); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 1, 2); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, &length); + ck_assert_int_eq(2, length); + ck_assert_strn_eq("tt", text, length); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_delete_twoline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + elm_code_file_line_append(file, "TEXT", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("text", text); + ck_assert_int_eq(2, elm_code_file_lines_get(file)); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 2, 2); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("teXT", text); + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_delete_twoline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + elm_code_file_line_append(file, "TEXT", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("text", text); + ck_assert_int_eq(2, elm_code_file_lines_get(file)); + + elm_code_widget_selection_start(widget, 2, 2); + elm_code_widget_selection_end(widget, 1, 3); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("teXT", text); + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_delete_multiline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + elm_code_file_line_append(file, "remove", 6, NULL); + elm_code_file_line_append(file, "TEXT", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("text", text); + ck_assert_int_eq(3, elm_code_file_lines_get(file)); + + elm_code_widget_selection_start(widget, 1, 3); + elm_code_widget_selection_end(widget, 3, 2); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("teXT", text); + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_reverse_delete_multiline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + const char *text; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "text", 4, NULL); + elm_code_file_line_append(file, "remove", 6, NULL); + elm_code_file_line_append(file, "TEXT", 4, NULL); + + win = elm_win_add(NULL, "code", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("text", text); + ck_assert_int_eq(3, elm_code_file_lines_get(file)); + + elm_code_widget_selection_start(widget, 3, 2); + elm_code_widget_selection_end(widget, 1, 3); + elm_code_widget_selection_delete(widget); + + line = elm_code_file_line_get(file, 1); + text = elm_code_line_text_get(line, NULL); + ck_assert_str_eq("teXT", text); + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_select_line) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "line selection", 14, NULL); + elm_code_file_line_append(file, "line2", 5, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_select_line(widget, 1); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("line selection", selection); + free(selection); + + elm_code_widget_selection_select_line(widget, 2); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("line2", selection); + free(selection); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_select_word) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "word selection test", 19, NULL); + elm_code_file_line_append(file, "more stuff\tto test", 18, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_select_word(widget, 1, 3); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("word", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 16); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("test", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 2, 9); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("stuff", selection); + free(selection); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_select_word_punctuation) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "comma, stop. question? mark!", 38, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_select_word(widget, 1, 3); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("comma", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 10); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("stop", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 20); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("question", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 25); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("mark", selection); + free(selection); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_selection_select_word_symbols) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Widget *widget; + Evas_Object *win; + char *selection; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "colon: [array] (brackets) {braces}", 38, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_selection_select_word(widget, 1, 3); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("colon", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 10); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("array", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 20); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("brackets", selection); + free(selection); + + elm_code_widget_selection_select_word(widget, 1, 30); + selection = elm_code_widget_selection_text_get(widget); + ck_assert_str_eq("braces", selection); + free(selection); + elm_shutdown(); +} +END_TEST + +void elm_code_test_widget_selection(TCase *tc) +{ + tcase_add_test(tc, elm_code_test_widget_selection_set); + tcase_add_test(tc, elm_code_test_widget_selection_normalized_get); + tcase_add_test(tc, elm_code_test_widget_selection_text_get); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_text_get); + tcase_add_test(tc, elm_code_test_widget_selection_text_get_twoline); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_text_get_twoline); + tcase_add_test(tc, elm_code_test_widget_selection_text_get_multiline); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_text_get_multiline); + tcase_add_test(tc, elm_code_test_widget_selection_delete); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_delete); + tcase_add_test(tc, elm_code_test_widget_selection_delete_twoline); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_delete_twoline); + tcase_add_test(tc, elm_code_test_widget_selection_delete_multiline); + tcase_add_test(tc, elm_code_test_widget_selection_reverse_delete_multiline); + tcase_add_test(tc, elm_code_test_widget_selection_select_line); + tcase_add_test(tc, elm_code_test_widget_selection_select_word); + tcase_add_test(tc, elm_code_test_widget_selection_select_word_punctuation); + tcase_add_test(tc, elm_code_test_widget_selection_select_word_symbols); +} diff --git a/src/tests/elementary/elm_code_test_widget_text.c b/src/tests/elementary/elm_code_test_widget_text.c new file mode 100644 index 0000000000..e11ce6fad4 --- /dev/null +++ b/src/tests/elementary/elm_code_test_widget_text.c @@ -0,0 +1,62 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" + +START_TEST (elm_code_test_widget_text_tab_width) +{ + Elm_Code *code; + Elm_Code_Widget *widget; + Evas_Object *win; + + elm_init(1, NULL); + code = elm_code_create(); + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + elm_code_widget_tabstop_set(widget, 8); + + ck_assert_int_eq(8, elm_code_widget_text_tabwidth_at_column_get(widget, 1)); + ck_assert_int_eq(8, elm_code_widget_text_tabwidth_at_column_get(widget, 9)); + ck_assert_int_eq(6, elm_code_widget_text_tabwidth_at_column_get(widget, 3)); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_text_position) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "a\tb", 4, NULL); + line = elm_code_file_line_get(file, 1); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + elm_code_widget_tabstop_set(widget, 8); + + ck_assert_int_eq(0, elm_code_widget_line_text_position_for_column_get(widget, line, 1)); + ck_assert_int_eq(1, elm_code_widget_line_text_position_for_column_get(widget, line, 2)); + + ck_assert_int_eq(2, elm_code_widget_line_text_position_for_column_get(widget, line, 9)); + ck_assert_int_eq(1, elm_code_widget_line_text_position_for_column_get(widget, line, 7)); + elm_shutdown(); +} +END_TEST + +void elm_code_test_widget_text(TCase *tc) +{ + tcase_add_test(tc, elm_code_test_widget_text_tab_width); + tcase_add_test(tc, elm_code_test_widget_text_position); +} diff --git a/src/tests/elementary/elm_code_test_widget_undo.c b/src/tests/elementary/elm_code_test_widget_undo.c new file mode 100644 index 0000000000..b07a294eb5 --- /dev/null +++ b/src/tests/elementary/elm_code_test_widget_undo.c @@ -0,0 +1,165 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_INTERNAL_API_ARGESFSDFEFC + +#include "elm_suite.h" +#include "Elementary.h" +#include "elm_code_widget_private.h" + +START_TEST (elm_code_test_widget_undo_text_insert) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + unsigned int length; + const char *content; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + _elm_code_widget_text_at_cursor_insert(widget, "a", 1); + line = elm_code_file_line_get(file, 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("atest", content, length); + + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + + elm_code_widget_cursor_position_set(widget, 3, 1); + _elm_code_widget_text_at_cursor_insert(widget, "r", 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("terst", content, length); + + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + + elm_code_widget_cursor_position_set(widget, 4, 1); + _elm_code_widget_text_at_cursor_insert(widget, "\t", 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("tes\tt", content, length); + + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_undo_newline) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + unsigned int length; + const char *content; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_cursor_position_set(widget, 5, 1); + _elm_code_widget_newline(widget); + ck_assert_int_eq(2, elm_code_file_lines_get(file)); + line = elm_code_file_line_get(file, 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, 1); + + elm_code_widget_undo(widget); + + ck_assert_int_eq(1, elm_code_file_lines_get(file)); + line = elm_code_file_line_get(file, 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, 4); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +START_TEST (elm_code_test_widget_undo_delete) +{ + Elm_Code *code; + Elm_Code_File *file; + Elm_Code_Line *line; + Elm_Code_Widget *widget; + Evas_Object *win; + unsigned int length; + const char *content; + + elm_init(1, NULL); + code = elm_code_create(); + file = elm_code_file_new(code); + elm_code_file_line_append(file, "test", 4, NULL); + + win = elm_win_add(NULL, "entry", ELM_WIN_BASIC); + widget = elm_code_widget_add(win, code); + + elm_code_widget_cursor_position_set(widget, 4, 1); + _elm_code_widget_backspace(widget); + + line = elm_code_file_line_get(file, 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("tet", content, length); + + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + + elm_code_widget_cursor_position_set(widget, 2, 1); + _elm_code_widget_delete(widget); + + line = elm_code_file_line_get(file, 1); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("tst", content, length); + + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + + elm_code_widget_cursor_position_set(widget, 4, 1); + _elm_code_widget_text_at_cursor_insert(widget, "\t", 1); + _elm_code_widget_backspace(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("tes\tt", content, length); + + elm_code_widget_cursor_position_set(widget, 4, 1); + _elm_code_widget_delete(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("test", content, length); + elm_code_widget_undo(widget); + content = elm_code_line_text_get(line, &length); + ck_assert_strn_eq("tes\tt", content, length); + + elm_code_free(code); + elm_shutdown(); +} +END_TEST + +void elm_code_test_widget_undo(TCase *tc) +{ + tcase_add_test(tc, elm_code_test_widget_undo_text_insert); + tcase_add_test(tc, elm_code_test_widget_undo_newline); + tcase_add_test(tc, elm_code_test_widget_undo_delete); +} diff --git a/src/tests/elementary/elm_suite.c b/src/tests/elementary/elm_suite.c index 75facc6e37..a612e7edc3 100644 --- a/src/tests/elementary/elm_suite.c +++ b/src/tests/elementary/elm_suite.c @@ -71,6 +71,15 @@ static const Efl_Test_Case etc[] = { { "elm_slideshow", elm_test_slideshow}, { "elm_spinner", elm_test_spinner}, { "elm_plug", elm_test_plug}, + { "file_load", elm_code_file_test_load }, + { "file_memory", elm_code_file_test_memory }, + { "parse", elm_code_test_parse }, + { "text", elm_code_test_text }, + { "basic", elm_code_test_basic }, + { "widget", elm_code_test_widget }, + { "widget_text", elm_code_test_widget_text }, + { "widget_selection", elm_code_test_widget_selection }, + { "widget_undo", elm_code_test_widget_undo }, { NULL, NULL } }; diff --git a/src/tests/elementary/elm_suite.h b/src/tests/elementary/elm_suite.h index 1a191793bf..bf87d1b622 100644 --- a/src/tests/elementary/elm_suite.h +++ b/src/tests/elementary/elm_suite.h @@ -69,4 +69,15 @@ void elm_test_slideshow(TCase *tc); void elm_test_spinner(TCase *tc); void elm_test_plug(TCase *tc); +void elm_code_file_test_load(TCase *tc); +void elm_code_file_test_memory(TCase *tc); +void elm_code_test_basic(TCase *tc); +void elm_code_test_line(TCase *tc); +void elm_code_test_parse(TCase *tc); +void elm_code_test_text(TCase *tc); +void elm_code_test_widget(TCase *tc); +void elm_code_test_widget_text(TCase *tc); +void elm_code_test_widget_selection(TCase *tc); +void elm_code_test_widget_undo(TCase *tc); + #endif /* _ELM_SUITE_H */ diff --git a/src/tests/elementary/elm_test_helper.h b/src/tests/elementary/elm_test_helper.h index 0bfc5ca0e6..f0b44bb8ed 100644 --- a/src/tests/elementary/elm_test_helper.h +++ b/src/tests/elementary/elm_test_helper.h @@ -3,6 +3,19 @@ #include +#define ck_assert_strn_eq(s1, s2, len) \ + { \ + char expected[len+1], actual[len+1]; \ + \ + strncpy(expected, s1, len); \ + expected[len] = '\0'; \ + strncpy(actual, s2, len); \ + actual[len] = '\0'; \ + \ + ck_assert_str_eq(expected, actual); \ + } + + Eina_Bool elm_test_helper_wait_flag(double in, Eina_Bool *done); #endif /* _ELM_TEST_HELPER_H */ diff --git a/src/tests/elementary/testdiff.diff b/src/tests/elementary/testdiff.diff new file mode 100644 index 0000000000..157cbb7b98 --- /dev/null +++ b/src/tests/elementary/testdiff.diff @@ -0,0 +1,10 @@ +--- testdiff1.txt 2014-11-22 21:16:16.279872989 +0000 ++++ testdiff2.txt 2014-11-22 21:16:34.406052375 +0000 +@@ -1,5 +1,5 @@ + Some content to diff ++added + more +-removed +-will change ++changed + unchanged diff --git a/src/tests/elementary/testfile-windows.txt b/src/tests/elementary/testfile-windows.txt new file mode 100644 index 0000000000..c397f82dcb --- /dev/null +++ b/src/tests/elementary/testfile-windows.txt @@ -0,0 +1,4 @@ +line 1 +line2 +a third +another line diff --git a/src/tests/elementary/testfile-withblanks.txt b/src/tests/elementary/testfile-withblanks.txt new file mode 100644 index 0000000000..0f2ead3796 --- /dev/null +++ b/src/tests/elementary/testfile-withblanks.txt @@ -0,0 +1,8 @@ +line 1 +line2 + +another link + + +double blank +8 diff --git a/src/tests/elementary/testfile.txt b/src/tests/elementary/testfile.txt new file mode 100644 index 0000000000..8fd6a8b140 --- /dev/null +++ b/src/tests/elementary/testfile.txt @@ -0,0 +1,4 @@ +line 1 +line2 +a third +another line