# HG changeset patch # User Christian Brabandt # Date 1504124104 -7200 # Node ID 1ff5e5dfa9b02effb4e21b6132a7f0ef7cf644eb # Parent eddea00bc625914f65190b9b45dc1ecde67c0bef patch 8.0.1026: GTK on-the-spot input has problems commit https://github.com/vim/vim/commit/5c6dbcb03fa552d7b0e61c8fcf425147eb6bf7d5 Author: Bram Moolenaar Date: Wed Aug 30 22:00:20 2017 +0200 patch 8.0.1026: GTK on-the-spot input has problems Problem: GTK on-the-spot input has problems. (Gerd Wachsmuth) Solution: Support over-the-spot. (Yukihiro Nakadaira, Ketn Takata, closes #1215) diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -832,6 +832,9 @@ Use the RPM or port for your system. Currently, GUI Vim supports three styles, |OverTheSpot|, |OffTheSpot| and |Root|. + When compiled with |+GUI_GTK| feature, GUI Vim supports two styles, + |OnTheSpot| and |OverTheSpot|. You can select the style with the 'imstyle' + option. *. on-the-spot *OnTheSpot* Preedit Area and Status Area are performed by the client application in diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4356,6 +4356,23 @@ A jump table for the options with a shor < NOTE: This function is invoked very often. Keep it fast. + *'imstyle'* *'imst'* +'imstyle' 'imst' number (default 1) + global + {not in Vi} + {only available when compiled with |+xim| and + |+GUI_GTK|} + This option specifies the input style of Input Method. + Set to zero if you want to use on-the-spot style. + Set to one if you want to use over-the-spot style. + See: |xim-input-style| + + For a long time on-the-spot sytle had been used in GTK version of vim, + however, it is known that it causes troubles when using mappings, + |single-repeat|, etc. Therefore over-the-spot style becomes the + default now. This should work fine for most people, however if you + have any problem with it, try using on-the-spot style. + *'include'* *'inc'* 'include' 'inc' string (default "^\s*#\s*include") global or local to buffer |global-local| diff --git a/src/edit.c b/src/edit.c --- a/src/edit.c +++ b/src/edit.c @@ -9683,7 +9683,7 @@ ins_left( #if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) /* Only call start_arrow() when not busy with preediting, it will * break undo. K_LEFT is inserted in im_correct_cursor(). */ - if (!im_is_preediting()) + if (p_imst == IM_OVER_THE_SPOT || !im_is_preediting()) #endif { start_arrow_with_change(&tpos, end_change); diff --git a/src/ex_getln.c b/src/ex_getln.c --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -3468,7 +3468,8 @@ cursorcmd(void) windgoto(msg_row, msg_col); #if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) - redrawcmd_preedit(); + if (p_imst == IM_ON_THE_SPOT) + redrawcmd_preedit(); #endif #ifdef MCH_CURSOR_SHAPE mch_update_cursor(); diff --git a/src/mbyte.c b/src/mbyte.c --- a/src/mbyte.c +++ b/src/mbyte.c @@ -4788,6 +4788,11 @@ static unsigned long im_commit_handler_i static unsigned int im_activatekey_keyval = GDK_VoidSymbol; static unsigned int im_activatekey_state = 0; +static GtkWidget *preedit_window = NULL; +static GtkWidget *preedit_label = NULL; + +static void im_preedit_window_set_position(void); + void im_set_active(int active) { @@ -4825,6 +4830,9 @@ im_set_position(int row, int col) area.height = gui.char_height; gtk_im_context_set_cursor_location(xic, &area); + + if (p_imst == IM_OVER_THE_SPOT) + im_preedit_window_set_position(); } } @@ -4855,12 +4863,107 @@ im_add_to_input(char_u *str, int len) gui_mch_mousehide(TRUE); } + static void +im_preedit_window_set_position(void) +{ + int x, y, w, h, sw, sh; + + if (preedit_window == NULL) + return; + + sw = gdk_screen_get_width(gtk_widget_get_screen(preedit_window)); + sh = gdk_screen_get_height(gtk_widget_get_screen(preedit_window)); +#if GTK_CHECK_VERSION(3,0,0) + gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y); +#else + gdk_window_get_origin(gui.drawarea->window, &x, &y); +#endif + gtk_window_get_size(GTK_WINDOW(preedit_window), &w, &h); + x = x + FILL_X(gui.col); + y = y + FILL_Y(gui.row); + if (x + w > sw) + x = sw - w; + if (y + h > sh) + y = sh - h; + gtk_window_move(GTK_WINDOW(preedit_window), x, y); +} + + static void +im_preedit_window_open() +{ + char *preedit_string; + char buf[8]; + PangoAttrList *attr_list; + PangoLayout *layout; + GdkColor color; + gint w, h; + + if (preedit_window == NULL) + { + preedit_window = gtk_window_new(GTK_WINDOW_POPUP); + preedit_label = gtk_label_new(""); + gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label); + } + + gtk_widget_modify_font(preedit_label, gui.norm_font); + + vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel); + gdk_color_parse(buf, &color); + gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color); + + vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel); + gdk_color_parse(buf, &color); + gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color); + + gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL); + + if (preedit_string[0] != NUL) + { + gtk_label_set_text(GTK_LABEL(preedit_label), preedit_string); + gtk_label_set_attributes(GTK_LABEL(preedit_label), attr_list); + + layout = gtk_label_get_layout(GTK_LABEL(preedit_label)); + pango_layout_get_pixel_size(layout, &w, &h); + h = MAX(h, gui.char_height); + gtk_window_resize(GTK_WINDOW(preedit_window), w, h); + + gtk_widget_show_all(preedit_window); + + im_preedit_window_set_position(); + } + + g_free(preedit_string); + pango_attr_list_unref(attr_list); +} + + static void +im_preedit_window_close() +{ + if (preedit_window != NULL) + gtk_widget_hide(preedit_window); +} + + static void +im_show_preedit() +{ + im_preedit_window_open(); + + if (p_mh) /* blank out the pointer if necessary */ + gui_mch_mousehide(TRUE); +} + static void im_delete_preedit(void) { char_u bskey[] = {CSI, 'k', 'b'}; char_u delkey[] = {CSI, 'k', 'D'}; + if (p_imst == IM_OVER_THE_SPOT) + { + im_preedit_window_close(); + return; + } + if (State & NORMAL) { im_preedit_cursor = 0; @@ -4933,40 +5036,43 @@ im_commit_cb(GtkIMContext *context UNUSE xim_log("im_commit_cb(): %s\n", str); #endif - /* The imhangul module doesn't reset the preedit string before - * committing. Call im_delete_preedit() to work around that. */ - im_delete_preedit(); - - /* Indicate that preediting has finished. */ - if (preedit_start_col == MAXCOL) + if (p_imst == IM_ON_THE_SPOT) { - init_preedit_start_col(); - commit_with_preedit = FALSE; + /* The imhangul module doesn't reset the preedit string before + * committing. Call im_delete_preedit() to work around that. */ + im_delete_preedit(); + + /* Indicate that preediting has finished. */ + if (preedit_start_col == MAXCOL) + { + init_preedit_start_col(); + commit_with_preedit = FALSE; + } + + /* The thing which setting "preedit_start_col" to MAXCOL means that + * "preedit_start_col" will be set forcedly when calling + * preedit_changed_cb() next time. + * "preedit_start_col" should not reset with MAXCOL on this part. Vim + * is simulating the preediting by using add_to_input_str(). when + * preedit begin immediately before committed, the typebuf is not + * flushed to screen, then it can't get correct "preedit_start_col". + * Thus, it should calculate the cells by adding cells of the committed + * string. */ + if (input_conv.vc_type != CONV_NONE) + { + im_str = string_convert(&input_conv, (char_u *)str, &len); + g_return_if_fail(im_str != NULL); + } + else + im_str = (char_u *)str; + + clen = mb_string2cells(im_str, len); + + if (input_conv.vc_type != CONV_NONE) + vim_free(im_str); + preedit_start_col += clen; } - /* The thing which setting "preedit_start_col" to MAXCOL means that - * "preedit_start_col" will be set forcedly when calling - * preedit_changed_cb() next time. - * "preedit_start_col" should not reset with MAXCOL on this part. Vim - * is simulating the preediting by using add_to_input_str(). when - * preedit begin immediately before committed, the typebuf is not - * flushed to screen, then it can't get correct "preedit_start_col". - * Thus, it should calculate the cells by adding cells of the committed - * string. */ - if (input_conv.vc_type != CONV_NONE) - { - im_str = string_convert(&input_conv, (char_u *)str, &len); - g_return_if_fail(im_str != NULL); - } - else - im_str = (char_u *)str; - - clen = mb_string2cells(im_str, len); - - if (input_conv.vc_type != CONV_NONE) - vim_free(im_str); - preedit_start_col += clen; - /* Is this a single character that matches a keypad key that's just * been pressed? If so, we don't want it to be entered as such - let * us carry on processing the raw keycode so that it may be used in @@ -4990,14 +5096,17 @@ im_commit_cb(GtkIMContext *context UNUSE if (add_to_input) im_add_to_input((char_u *)str, slen); - /* Inserting chars while "im_is_active" is set does not cause a change of - * buffer. When the chars are committed the buffer must be marked as - * changed. */ - if (!commit_with_preedit) - preedit_start_col = MAXCOL; - - /* This flag is used in changed() at next call. */ - xim_changed_while_preediting = TRUE; + if (p_imst == IM_ON_THE_SPOT) + { + /* Inserting chars while "im_is_active" is set does not cause a + * change of buffer. When the chars are committed the buffer must be + * marked as changed. */ + if (!commit_with_preedit) + preedit_start_col = MAXCOL; + + /* This flag is used in changed() at next call. */ + xim_changed_while_preediting = TRUE; + } if (gtk_main_level() > 0) gtk_main_quit(); @@ -5031,7 +5140,8 @@ im_preedit_end_cb(GtkIMContext *context im_delete_preedit(); /* Indicate that preediting has finished */ - preedit_start_col = MAXCOL; + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; xim_has_preediting = FALSE; #if 0 @@ -5092,9 +5202,14 @@ im_preedit_changed_cb(GtkIMContext *cont char_u *p; int i; - gtk_im_context_get_preedit_string(context, - &preedit_string, NULL, - &cursor_index); + if (p_imst == IM_ON_THE_SPOT) + gtk_im_context_get_preedit_string(context, + &preedit_string, NULL, + &cursor_index); + else + gtk_im_context_get_preedit_string(context, + &preedit_string, NULL, + NULL); #ifdef XIM_DEBUG xim_log("im_preedit_changed_cb(): %s\n", preedit_string); @@ -5102,66 +5217,82 @@ im_preedit_changed_cb(GtkIMContext *cont g_return_if_fail(preedit_string != NULL); /* just in case */ - /* If preedit_start_col is MAXCOL set it to the current cursor position. */ - if (preedit_start_col == MAXCOL && preedit_string[0] != '\0') + if (p_imst == IM_OVER_THE_SPOT) { - xim_has_preediting = TRUE; - - /* Urgh, this breaks if the input buffer isn't empty now */ - init_preedit_start_col(); - } - else if (cursor_index == 0 && preedit_string[0] == '\0') - { - xim_has_preediting = FALSE; - - /* If at the start position (after typing backspace) - * preedit_start_col must be reset. */ - preedit_start_col = MAXCOL; + if (preedit_string[0] == NUL) + { + xim_has_preediting = FALSE; + im_delete_preedit(); + } + else + { + xim_has_preediting = TRUE; + im_show_preedit(); + } } - - im_delete_preedit(); - - /* - * Compute the end of the preediting area: "preedit_end_col". - * According to the documentation of gtk_im_context_get_preedit_string(), - * the cursor_pos output argument returns the offset in bytes. This is - * unfortunately not true -- real life shows the offset is in characters, - * and the GTK+ source code agrees with me. Will file a bug later. - */ - if (preedit_start_col != MAXCOL) - preedit_end_col = preedit_start_col; - str = (char_u *)preedit_string; - for (p = str, i = 0; *p != NUL; p += utf_byte2len(*p), ++i) + else { - int is_composing; - - is_composing = ((*p & 0x80) != 0 && utf_iscomposing(utf_ptr2char(p))); + /* If preedit_start_col is MAXCOL set it to the current cursor position. */ + if (preedit_start_col == MAXCOL && preedit_string[0] != '\0') + { + xim_has_preediting = TRUE; + + /* Urgh, this breaks if the input buffer isn't empty now */ + init_preedit_start_col(); + } + else if (cursor_index == 0 && preedit_string[0] == '\0') + { + xim_has_preediting = FALSE; + + /* If at the start position (after typing backspace) + * preedit_start_col must be reset. */ + preedit_start_col = MAXCOL; + } + + im_delete_preedit(); + /* - * These offsets are used as counters when generating and - * to delete the preedit string. So don't count composing characters - * unless 'delcombine' is enabled. + * Compute the end of the preediting area: "preedit_end_col". + * According to the documentation of gtk_im_context_get_preedit_string(), + * the cursor_pos output argument returns the offset in bytes. This is + * unfortunately not true -- real life shows the offset is in characters, + * and the GTK+ source code agrees with me. Will file a bug later. */ - if (!is_composing || p_deco) - { - if (i < cursor_index) - ++im_preedit_cursor; - else - ++im_preedit_trailing; - } - if (!is_composing && i >= cursor_index) + if (preedit_start_col != MAXCOL) + preedit_end_col = preedit_start_col; + str = (char_u *)preedit_string; + for (p = str, i = 0; *p != NUL; p += utf_byte2len(*p), ++i) { - /* This is essentially the same as im_preedit_trailing, except - * composing characters are not counted even if p_deco is set. */ - ++num_move_back; + int is_composing; + + is_composing = ((*p & 0x80) != 0 && utf_iscomposing(utf_ptr2char(p))); + /* + * These offsets are used as counters when generating and + * to delete the preedit string. So don't count composing characters + * unless 'delcombine' is enabled. + */ + if (!is_composing || p_deco) + { + if (i < cursor_index) + ++im_preedit_cursor; + else + ++im_preedit_trailing; + } + if (!is_composing && i >= cursor_index) + { + /* This is essentially the same as im_preedit_trailing, except + * composing characters are not counted even if p_deco is set. */ + ++num_move_back; + } + if (preedit_start_col != MAXCOL) + preedit_end_col += utf_ptr2cells(p); } - if (preedit_start_col != MAXCOL) - preedit_end_col += utf_ptr2cells(p); - } - - if (p > str) - { - im_add_to_input(str, (int)(p - str)); - im_correct_cursor(num_move_back); + + if (p > str) + { + im_add_to_input(str, (int)(p - str)); + im_correct_cursor(num_move_back); + } } g_free(preedit_string); @@ -5310,7 +5441,8 @@ im_shutdown(void) } im_is_active = FALSE; im_commit_handler_id = 0; - preedit_start_col = MAXCOL; + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; xim_has_preediting = FALSE; } @@ -5465,7 +5597,8 @@ xim_reset(void) } } - preedit_start_col = MAXCOL; + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; xim_has_preediting = FALSE; } @@ -5570,19 +5703,22 @@ xim_queue_key_press_event(GdkEventKey *e { int imresult = gtk_im_context_filter_keypress(xic, event); - /* Some XIM send following sequence: - * 1. preedited string. - * 2. committed string. - * 3. line changed key. - * 4. preedited string. - * 5. remove preedited string. - * if 3, Vim can't move back the above line for 5. - * thus, this part should not parse the key. */ - if (!imresult && preedit_start_col != MAXCOL - && event->keyval == GDK_Return) + if (p_imst == IM_ON_THE_SPOT) { - im_synthesize_keypress(GDK_Return, 0U); - return FALSE; + /* Some XIM send following sequence: + * 1. preedited string. + * 2. committed string. + * 3. line changed key. + * 4. preedited string. + * 5. remove preedited string. + * if 3, Vim can't move back the above line for 5. + * thus, this part should not parse the key. */ + if (!imresult && preedit_start_col != MAXCOL + && event->keyval == GDK_Return) + { + im_synthesize_keypress(GDK_Return, 0U); + return FALSE; + } } /* If XIM tried to commit a keypad key as a single char., diff --git a/src/misc1.c b/src/misc1.c --- a/src/misc1.c +++ b/src/misc1.c @@ -2730,12 +2730,15 @@ skip_to_option_part(char_u *p) changed(void) { #if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) - /* The text of the preediting area is inserted, but this doesn't - * mean a change of the buffer yet. That is delayed until the - * text is committed. (this means preedit becomes empty) */ - if (im_is_preediting() && !xim_changed_while_preediting) - return; - xim_changed_while_preediting = FALSE; + if (p_imst == IM_ON_THE_SPOT) + { + /* The text of the preediting area is inserted, but this doesn't + * mean a change of the buffer yet. That is delayed until the + * text is committed. (this means preedit becomes empty) */ + if (im_is_preediting() && !xim_changed_while_preediting) + return; + xim_changed_while_preediting = FALSE; + } #endif if (!curbuf->b_changed) diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -1606,13 +1606,22 @@ static struct vimoption options[] = #endif SCRIPTID_INIT}, {"imstatusfunc","imsf",P_STRING|P_VI_DEF|P_SECURE, -# if defined(FEAT_EVAL) && defined(FEAT_XIM) && defined(FEAT_GUI_GTK) +#if defined(FEAT_EVAL) && defined(FEAT_XIM) && defined(FEAT_GUI_GTK) (char_u *)&p_imsf, PV_NONE, {(char_u *)"", (char_u *)NULL} -# else +#else (char_u *)NULL, PV_NONE, {(char_u *)NULL, (char_u *)0L} -# endif +#endif + SCRIPTID_INIT}, + {"imstyle", "imst", P_NUM|P_VI_DEF|P_SECURE, +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + (char_u *)&p_imst, PV_NONE, + {(char_u *)IM_OVER_THE_SPOT, (char_u *)0L} +#else + (char_u *)NULL, PV_NONE, + {(char_u *)0L, (char_u *)0L} +#endif SCRIPTID_INIT}, {"include", "inc", P_STRING|P_ALLOCED|P_VI_DEF, #ifdef FEAT_FIND_ID @@ -8990,6 +8999,15 @@ set_num_option( #endif } +#if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) + /* 'imstyle' */ + else if (pp == &p_imst) + { + if (p_imst != IM_ON_THE_SPOT && p_imst != IM_OVER_THE_SPOT) + errmsg = e_invarg; + } +#endif + else if (pp == &p_window) { if (p_window < 1) diff --git a/src/option.h b/src/option.h --- a/src/option.h +++ b/src/option.h @@ -581,6 +581,9 @@ EXTERN int p_ic; /* 'ignorecase' */ EXTERN char_u *p_imak; /* 'imactivatekey' */ EXTERN char_u *p_imaf; /* 'imactivatefunc' */ EXTERN char_u *p_imsf; /* 'imstatusfunc' */ +EXTERN long p_imst; /* 'imstyle' */ +# define IM_ON_THE_SPOT 0L +# define IM_OVER_THE_SPOT 1L #endif #ifdef USE_IM_CONTROL EXTERN int p_imcmdline; /* 'imcmdline' */ diff --git a/src/screen.c b/src/screen.c --- a/src/screen.c +++ b/src/screen.c @@ -5197,7 +5197,8 @@ win_line( /* XIM don't send preedit_start and preedit_end, but they send * preedit_changed and commit. Thus Vim can't set "im_is_active", use * im_is_preediting() here. */ - if (xic != NULL + if (p_imst == IM_ON_THE_SPOT + && xic != NULL && lnum == wp->w_cursor.lnum && (State & INSERT) && !p_imdisable diff --git a/src/testdir/gen_opt_test.vim b/src/testdir/gen_opt_test.vim --- a/src/testdir/gen_opt_test.vim +++ b/src/testdir/gen_opt_test.vim @@ -31,6 +31,7 @@ let test_values = { \ 'history': [[0, 1, 100], [-1, 10001]], \ 'iminsert': [[0, 1], [-1, 3, 999]], \ 'imsearch': [[-1, 0, 1], [-2, 3, 999]], + \ 'imstyle': [[0, 1], [-1, 2, 999]], \ 'lines': [[2, 24], [-1, 0, 1]], \ 'linespace': [[0, 2, 4], ['']], \ 'numberwidth': [[1, 4, 8, 10], [-1, 0, 11]], diff --git a/src/undo.c b/src/undo.c --- a/src/undo.c +++ b/src/undo.c @@ -2984,7 +2984,7 @@ u_sync( if (curbuf->b_u_synced || (!force && no_u_sync > 0)) return; #if defined(FEAT_XIM) && defined(FEAT_GUI_GTK) - if (im_is_preediting()) + if (p_imst == IM_ON_THE_SPOT && im_is_preediting()) return; /* XIM is busy, don't break an undo sequence */ #endif if (get_undolevel() < 0) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -770,6 +770,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1026, +/**/ 1025, /**/ 1024,