To: vim_dev@googlegroups.com Subject: Patch 8.2.0872 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0872 Problem: XIM code is mixed with multi-byte code. Solution: Move the XIM code to a separate file. (Yegappan Lakshmanan, closes #6177) Files: Filelist, src/Make_cyg_ming.mak, src/Make_morph.mak, src/Make_mvc.mak, src/Make_vms.mms, src/Makefile, src/gui_xim.c, src/mbyte.c, src/proto.h, src/proto/gui_xim.pro, src/proto/mbyte.pro *** ../vim-8.2.0871/Filelist 2020-05-31 14:24:39.592878605 +0200 --- Filelist 2020-06-01 14:18:57.754948914 +0200 *************** *** 419,424 **** --- 419,425 ---- src/gui_gtk_x11.c \ src/gui_gtk_res.xml \ src/gui_motif.c \ + src/gui_xim.c \ src/gui_xmdlg.c \ src/gui_xmebw.c \ src/gui_xmebw.h \ *************** *** 442,447 **** --- 443,449 ---- src/proto/gui_gtk_x11.pro \ src/proto/gui_gtk_gresources.pro \ src/proto/gui_motif.pro \ + src/proto/gui_xim.pro \ src/proto/gui_xmdlg.pro \ src/proto/gui_x11.pro \ src/proto/if_xcmdsrv.pro \ *** ../vim-8.2.0871/src/Make_cyg_ming.mak 2020-05-30 17:05:57.028692410 +0200 --- src/Make_cyg_ming.mak 2020-06-01 14:18:57.754948914 +0200 *************** *** 741,746 **** --- 741,747 ---- $(OUTDIR)/findfile.o \ $(OUTDIR)/fold.o \ $(OUTDIR)/getchar.o \ + $(OUTDIR)/gui_xim.o \ $(OUTDIR)/hardcopy.o \ $(OUTDIR)/hashtab.o \ $(OUTDIR)/highlight.o \ *** ../vim-8.2.0871/src/Make_morph.mak 2020-05-30 17:05:57.028692410 +0200 --- src/Make_morph.mak 2020-06-01 14:18:57.754948914 +0200 *************** *** 61,66 **** --- 61,67 ---- findfile.c \ fold.c \ getchar.c \ + gui_xim.c \ hardcopy.c \ hashtab.c \ highlight.c \ *** ../vim-8.2.0871/src/Make_mvc.mak 2020-05-30 17:05:57.028692410 +0200 --- src/Make_mvc.mak 2020-06-01 14:18:57.754948914 +0200 *************** *** 761,766 **** --- 761,767 ---- $(OUTDIR)\findfile.obj \ $(OUTDIR)\fold.obj \ $(OUTDIR)\getchar.obj \ + $(OUTDIR)\gui_xim.obj \ $(OUTDIR)\hardcopy.obj \ $(OUTDIR)\hashtab.obj \ $(OUTDIR)\highlight.obj \ *************** *** 1591,1596 **** --- 1592,1599 ---- $(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL) + $(OUTDIR)/gui_xim.obj: $(OUTDIR) gui_xim.c $(INCL) + $(OUTDIR)/hardcopy.obj: $(OUTDIR) hardcopy.c $(INCL) version.h $(OUTDIR)/hashtab.obj: $(OUTDIR) hashtab.c $(INCL) *************** *** 1908,1913 **** --- 1911,1917 ---- proto/filepath.pro \ proto/findfile.pro \ proto/getchar.pro \ + proto/gui_xim.pro \ proto/hardcopy.pro \ proto/hashtab.pro \ proto/highlight.pro \ *** ../vim-8.2.0871/src/Make_vms.mms 2020-05-30 17:05:57.028692410 +0200 --- src/Make_vms.mms 2020-06-01 14:18:57.754948914 +0200 *************** *** 334,339 **** --- 334,340 ---- findfile.c \ fold.c \ getchar.c \ + gui_xim.c \ hardcopy.c \ hashtab.c \ highlight.c \ *************** *** 445,450 **** --- 446,452 ---- findfile.obj \ fold.obj \ getchar.obj \ + gui_xim.obj \ hardcopy.obj \ hashtab.obj \ highlight.obj \ *************** *** 818,823 **** --- 820,829 ---- ascii.h keymap.h term.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ globals.h + gui_xim.obj : gui_xim.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h term.h macros.h structs.h regexp.h \ + gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ + globals.h hardcopy.obj : hardcopy.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ *** ../vim-8.2.0871/src/Makefile 2020-05-31 14:24:39.596878596 +0200 --- src/Makefile 2020-06-01 14:25:05.405585410 +0200 *************** *** 1636,1641 **** --- 1636,1642 ---- findfile.c \ fold.c \ getchar.c \ + gui_xim.c \ hardcopy.c \ hashtab.c \ highlight.c \ *************** *** 1785,1790 **** --- 1786,1792 ---- objects/findfile.o \ objects/fold.o \ objects/getchar.o \ + objects/gui_xim.o \ objects/hardcopy.o \ objects/hashtab.o \ objects/highlight.o \ *************** *** 1950,1955 **** --- 1952,1958 ---- findfile.pro \ fold.pro \ getchar.pro \ + gui_xim.pro \ gui_beval.pro \ hardcopy.pro \ hashtab.pro \ *************** *** 3301,3306 **** --- 3304,3312 ---- objects/gui_x11.o: gui_x11.c $(CCC) -o $@ gui_x11.c + objects/gui_xim.o: gui_xim.c + $(CCC) -o $@ gui_xim.c + objects/gui_photon.o: gui_photon.c $(CCC) -o $@ gui_photon.c *************** *** 4238,4243 **** --- 4244,4254 ---- auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h ../runtime/vim32x32.xpm ../runtime/vim16x16.xpm \ + ../runtime/vim48x48.xpm + objects/gui_xim.o: gui_xim.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h ../runtime/vim32x32.xpm ../runtime/vim16x16.xpm \ ../runtime/vim48x48.xpm objects/gui_at_sb.o: gui_at_sb.c vim.h protodef.h auto/config.h feature.h \ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ *** ../vim-8.2.0871/src/gui_xim.c 2020-06-01 14:33:33.391649376 +0200 --- src/gui_xim.c 2020-06-01 14:21:59.518280765 +0200 *************** *** 0 **** --- 1,1777 ---- + /* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + + /* + * gui_xim.c: functions for the X Input Method + */ + + #include "vim.h" + + #if defined(FEAT_GUI_GTK) && defined(FEAT_XIM) + # if GTK_CHECK_VERSION(3,0,0) + # include + # else + # include + # endif + # ifdef MSWIN + # include + # else + # include + # endif + #endif + + /* + * XIM often causes trouble. Define XIM_DEBUG to get a log of XIM callbacks + * in the "xim.log" file. + */ + // #define XIM_DEBUG + #ifdef XIM_DEBUG + static void + xim_log(char *s, ...) + { + va_list arglist; + static FILE *fd = NULL; + + if (fd == (FILE *)-1) + return; + if (fd == NULL) + { + fd = mch_fopen("xim.log", "w"); + if (fd == NULL) + { + emsg("Cannot open xim.log"); + fd = (FILE *)-1; + return; + } + } + + va_start(arglist, s); + vfprintf(fd, s, arglist); + va_end(arglist); + } + #endif + + #ifdef FEAT_GUI + # define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL) + # define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL) + #else + # define USE_IMACTIVATEFUNC (*p_imaf != NUL) + # define USE_IMSTATUSFUNC (*p_imsf != NUL) + #endif + + #if defined(FEAT_EVAL) && \ + (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) + static void + call_imactivatefunc(int active) + { + typval_T argv[2]; + + argv[0].v_type = VAR_NUMBER; + argv[0].vval.v_number = active ? 1 : 0; + argv[1].v_type = VAR_UNKNOWN; + (void)call_func_retnr(p_imaf, 1, argv); + } + + static int + call_imstatusfunc(void) + { + int is_active; + + // FIXME: Don't execute user function in unsafe situation. + if (exiting || is_autocmd_blocked()) + return FALSE; + // FIXME: :py print 'xxx' is shown duplicate result. + // Use silent to avoid it. + ++msg_silent; + is_active = call_func_retnr(p_imsf, 0, NULL); + --msg_silent; + return (is_active > 0); + } + #endif + + #if defined(FEAT_XIM) || defined(PROTO) + + # if defined(FEAT_GUI_GTK) || defined(PROTO) + static int xim_has_preediting INIT(= FALSE); // IM current status + + /* + * Set preedit_start_col to the current cursor position. + */ + static void + init_preedit_start_col(void) + { + if (State & CMDLINE) + preedit_start_col = cmdline_getvcol_cursor(); + else if (curwin != NULL && curwin->w_buffer != NULL) + getvcol(curwin, &curwin->w_cursor, &preedit_start_col, NULL, NULL); + // Prevent that preediting marks the buffer as changed. + xim_changed_while_preediting = curbuf->b_changed; + } + + static int im_is_active = FALSE; // IM is enabled for current mode + static int preedit_is_active = FALSE; + static int im_preedit_cursor = 0; // cursor offset in characters + static int im_preedit_trailing = 0; // number of characters after cursor + + static unsigned long im_commit_handler_id = 0; + 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) + { + int was_active; + + was_active = !!im_get_status(); + im_is_active = (active && !p_imdisable); + + if (im_is_active != was_active) + xim_reset(); + } + + void + xim_set_focus(int focus) + { + if (xic != NULL) + { + if (focus) + gtk_im_context_focus_in(xic); + else + gtk_im_context_focus_out(xic); + } + } + + void + im_set_position(int row, int col) + { + if (xic != NULL) + { + GdkRectangle area; + + area.x = FILL_X(col); + area.y = FILL_Y(row); + area.width = gui.char_width * (mb_lefthalve(row, col) ? 2 : 1); + 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(); + } + } + + # if 0 || defined(PROTO) // apparently only used in gui_x11.c + void + xim_set_preedit(void) + { + im_set_position(gui.row, gui.col); + } + # endif + + static void + im_add_to_input(char_u *str, int len) + { + // Convert from 'termencoding' (always "utf-8") to 'encoding' + if (input_conv.vc_type != CONV_NONE) + { + str = string_convert(&input_conv, str, &len); + g_return_if_fail(str != NULL); + } + + add_to_input_buf_csi(str, len); + + if (input_conv.vc_type != CONV_NONE) + vim_free(str); + + if (p_mh) // blank out the pointer if necessary + gui_mch_mousehide(TRUE); + } + + static void + im_preedit_window_set_position(void) + { + int x, y, width, height; + int screen_x, screen_y, screen_width, screen_height; + + if (preedit_window == NULL) + return; + + gui_gtk_get_screen_geom_of_win(gui.drawarea, + &screen_x, &screen_y, &screen_width, &screen_height); + gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y); + gtk_window_get_size(GTK_WINDOW(preedit_window), &width, &height); + x = x + FILL_X(gui.col); + y = y + FILL_Y(gui.row); + if (x + width > screen_x + screen_width) + x = screen_x + screen_width - width; + if (y + height > screen_y + screen_height) + y = screen_y + screen_height - height; + gtk_window_move(GTK_WINDOW(preedit_window), x, y); + } + + static void + im_preedit_window_open() + { + char *preedit_string; + #if !GTK_CHECK_VERSION(3,16,0) + char buf[8]; + #endif + PangoAttrList *attr_list; + PangoLayout *layout; + #if GTK_CHECK_VERSION(3,0,0) + # if !GTK_CHECK_VERSION(3,16,0) + GdkRGBA color; + # endif + #else + GdkColor color; + #endif + gint w, h; + + if (preedit_window == NULL) + { + preedit_window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_window_set_transient_for(GTK_WINDOW(preedit_window), + GTK_WINDOW(gui.mainwin)); + preedit_label = gtk_label_new(""); + gtk_widget_set_name(preedit_label, "vim-gui-preedit-area"); + gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label); + } + + #if GTK_CHECK_VERSION(3,16,0) + { + GtkStyleContext * const context + = gtk_widget_get_style_context(gui.drawarea); + GtkCssProvider * const provider = gtk_css_provider_new(); + gchar *css = NULL; + const char * const fontname + = pango_font_description_get_family(gui.norm_font); + gint fontsize + = pango_font_description_get_size(gui.norm_font) / PANGO_SCALE; + gchar *fontsize_propval = NULL; + + if (!pango_font_description_get_size_is_absolute(gui.norm_font)) + { + // fontsize was given in points. Convert it into that in pixels + // to use with CSS. + GdkScreen * const screen + = gdk_window_get_screen(gtk_widget_get_window(gui.mainwin)); + const gdouble dpi = gdk_screen_get_resolution(screen); + fontsize = dpi * fontsize / 72; + } + if (fontsize > 0) + fontsize_propval = g_strdup_printf("%dpx", fontsize); + else + fontsize_propval = g_strdup_printf("inherit"); + + css = g_strdup_printf( + "widget#vim-gui-preedit-area {\n" + " font-family: %s,monospace;\n" + " font-size: %s;\n" + " color: #%.2lx%.2lx%.2lx;\n" + " background-color: #%.2lx%.2lx%.2lx;\n" + "}\n", + fontname != NULL ? fontname : "inherit", + fontsize_propval, + (gui.norm_pixel >> 16) & 0xff, + (gui.norm_pixel >> 8) & 0xff, + gui.norm_pixel & 0xff, + (gui.back_pixel >> 16) & 0xff, + (gui.back_pixel >> 8) & 0xff, + gui.back_pixel & 0xff); + + gtk_css_provider_load_from_data(provider, css, -1, NULL); + gtk_style_context_add_provider(context, + GTK_STYLE_PROVIDER(provider), G_MAXUINT); + + g_free(css); + g_free(fontsize_propval); + g_object_unref(provider); + } + #elif GTK_CHECK_VERSION(3,0,0) + gtk_widget_override_font(preedit_label, gui.norm_font); + + vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel); + gdk_rgba_parse(&color, buf); + gtk_widget_override_color(preedit_label, GTK_STATE_FLAG_NORMAL, &color); + + vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel); + gdk_rgba_parse(&color, buf); + gtk_widget_override_background_color(preedit_label, GTK_STATE_FLAG_NORMAL, + &color); + #else + gtk_widget_modify_font(preedit_label, gui.norm_font); + + vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.norm_pixel); + gdk_color_parse(buf, &color); + gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color); + + vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.back_pixel); + gdk_color_parse(buf, &color); + gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color); + #endif + + 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 + #ifdef FEAT_TERMINAL + && !term_use_loop() + #endif + ) + { + im_preedit_cursor = 0; + return; + } + for (; im_preedit_cursor > 0; --im_preedit_cursor) + add_to_input_buf(bskey, (int)sizeof(bskey)); + + for (; im_preedit_trailing > 0; --im_preedit_trailing) + add_to_input_buf(delkey, (int)sizeof(delkey)); + } + + /* + * Move the cursor left by "num_move_back" characters. + * Note that ins_left() checks im_is_preediting() to avoid breaking undo for + * these K_LEFT keys. + */ + static void + im_correct_cursor(int num_move_back) + { + char_u backkey[] = {CSI, 'k', 'l'}; + + if (State & NORMAL) + return; + # ifdef FEAT_RIGHTLEFT + if ((State & CMDLINE) == 0 && curwin != NULL && curwin->w_p_rl) + backkey[2] = 'r'; + # endif + for (; num_move_back > 0; --num_move_back) + add_to_input_buf(backkey, (int)sizeof(backkey)); + } + + static int xim_expected_char = NUL; + static int xim_ignored_char = FALSE; + + /* + * Update the mode and cursor while in an IM callback. + */ + static void + im_show_info(void) + { + int old_vgetc_busy; + + old_vgetc_busy = vgetc_busy; + vgetc_busy = TRUE; + showmode(); + vgetc_busy = old_vgetc_busy; + if ((State & NORMAL) || (State & INSERT)) + setcursor(); + out_flush(); + } + + /* + * Callback invoked when the user finished preediting. + * Put the final string into the input buffer. + */ + static void + im_commit_cb(GtkIMContext *context UNUSED, + const gchar *str, + gpointer data UNUSED) + { + int slen = (int)STRLEN(str); + int add_to_input = TRUE; + int clen; + int len = slen; + int commit_with_preedit = TRUE; + char_u *im_str; + + #ifdef XIM_DEBUG + xim_log("im_commit_cb(): %s\n", str); + #endif + + if (p_imst == IM_ON_THE_SPOT) + { + // 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; + } + + // 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 + // mappings as . + if (xim_expected_char != NUL) + { + // We're currently processing a keypad or other special key + if (slen == 1 && str[0] == xim_expected_char) + { + // It's a match - don't do it here + xim_ignored_char = TRUE; + add_to_input = FALSE; + } + else + { + // Not a match + xim_ignored_char = FALSE; + } + } + + if (add_to_input) + im_add_to_input((char_u *)str, slen); + + 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(); + } + + /* + * Callback invoked after start to the preedit. + */ + static void + im_preedit_start_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) + { + #ifdef XIM_DEBUG + xim_log("im_preedit_start_cb()\n"); + #endif + + im_is_active = TRUE; + preedit_is_active = TRUE; + gui_update_cursor(TRUE, FALSE); + im_show_info(); + } + + /* + * Callback invoked after end to the preedit. + */ + static void + im_preedit_end_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) + { + #ifdef XIM_DEBUG + xim_log("im_preedit_end_cb()\n"); + #endif + im_delete_preedit(); + + // Indicate that preediting has finished + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; + xim_has_preediting = FALSE; + + #if 0 + // Removal of this line suggested by Takuhiro Nishioka. Fixes that IM was + // switched off unintentionally. We now use preedit_is_active (added by + // SungHyun Nam). + im_is_active = FALSE; + #endif + preedit_is_active = FALSE; + gui_update_cursor(TRUE, FALSE); + im_show_info(); + } + + /* + * Callback invoked after changes to the preedit string. If the preedit + * string was empty before, remember the preedit start column so we know + * where to apply feedback attributes. Delete the previous preedit string + * if there was one, save the new preedit cursor offset, and put the new + * string into the input buffer. + * + * TODO: The pragmatic "put into input buffer" approach used here has + * several fundamental problems: + * + * - The characters in the preedit string are subject to remapping. + * That's broken, only the finally committed string should be remapped. + * + * - There is a race condition involved: The retrieved value for the + * current cursor position will be wrong if any unprocessed characters + * are still queued in the input buffer. + * + * - Due to the lack of synchronization between the file buffer in memory + * and any typed characters, it's practically impossible to implement the + * "retrieve_surrounding" and "delete_surrounding" signals reliably. IM + * modules for languages such as Thai are likely to rely on this feature + * for proper operation. + * + * Conclusions: I think support for preediting needs to be moved to the + * core parts of Vim. Ideally, until it has been committed, the preediting + * string should only be displayed and not affect the buffer content at all. + * The question how to deal with the synchronization issue still remains. + * Circumventing the input buffer is probably not desirable. Anyway, I think + * implementing "retrieve_surrounding" is the only hard problem. + * + * One way to solve all of this in a clean manner would be to queue all key + * press/release events "as is" in the input buffer, and apply the IM filtering + * at the receiving end of the queue. This, however, would have a rather large + * impact on the code base. If there is an easy way to force processing of all + * remaining input from within the "retrieve_surrounding" signal handler, this + * might not be necessary. Gotta ask on vim-dev for opinions. + */ + static void + im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED) + { + char *preedit_string = NULL; + int cursor_index = 0; + int num_move_back = 0; + char_u *str; + char_u *p; + int i; + + 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); + #endif + + g_return_if_fail(preedit_string != NULL); // just in case + + if (p_imst == IM_OVER_THE_SPOT) + { + if (preedit_string[0] == NUL) + { + xim_has_preediting = FALSE; + im_delete_preedit(); + } + else + { + xim_has_preediting = TRUE; + im_show_preedit(); + } + } + else + { + // 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(); + + /* + * 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) + { + 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 (p > str) + { + im_add_to_input(str, (int)(p - str)); + im_correct_cursor(num_move_back); + } + } + + g_free(preedit_string); + + if (gtk_main_level() > 0) + gtk_main_quit(); + } + + /* + * Translate the Pango attributes at iter to Vim highlighting attributes. + * Ignore attributes not supported by Vim highlighting. This shouldn't have + * too much impact -- right now we handle even more attributes than necessary + * for the IM modules I tested with. + */ + static int + translate_pango_attributes(PangoAttrIterator *iter) + { + PangoAttribute *attr; + int char_attr = HL_NORMAL; + + attr = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); + if (attr != NULL && ((PangoAttrInt *)attr)->value + != (int)PANGO_UNDERLINE_NONE) + char_attr |= HL_UNDERLINE; + + attr = pango_attr_iterator_get(iter, PANGO_ATTR_WEIGHT); + if (attr != NULL && ((PangoAttrInt *)attr)->value >= (int)PANGO_WEIGHT_BOLD) + char_attr |= HL_BOLD; + + attr = pango_attr_iterator_get(iter, PANGO_ATTR_STYLE); + if (attr != NULL && ((PangoAttrInt *)attr)->value + != (int)PANGO_STYLE_NORMAL) + char_attr |= HL_ITALIC; + + attr = pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND); + if (attr != NULL) + { + const PangoColor *color = &((PangoAttrColor *)attr)->color; + + // Assume inverse if black background is requested + if ((color->red | color->green | color->blue) == 0) + char_attr |= HL_INVERSE; + } + + return char_attr; + } + + /* + * Retrieve the highlighting attributes at column col in the preedit string. + * Return -1 if not in preediting mode or if col is out of range. + */ + int + im_get_feedback_attr(int col) + { + char *preedit_string = NULL; + PangoAttrList *attr_list = NULL; + int char_attr = -1; + + if (xic == NULL) + return char_attr; + + gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL); + + if (preedit_string != NULL && attr_list != NULL) + { + int idx; + + // Get the byte index as used by PangoAttrIterator + for (idx = 0; col > 0 && preedit_string[idx] != '\0'; --col) + idx += utfc_ptr2len((char_u *)preedit_string + idx); + + if (preedit_string[idx] != '\0') + { + PangoAttrIterator *iter; + int start, end; + + char_attr = HL_NORMAL; + iter = pango_attr_list_get_iterator(attr_list); + + // Extract all relevant attributes from the list. + do + { + pango_attr_iterator_range(iter, &start, &end); + + if (idx >= start && idx < end) + char_attr |= translate_pango_attributes(iter); + } + while (pango_attr_iterator_next(iter)); + + pango_attr_iterator_destroy(iter); + } + } + + if (attr_list != NULL) + pango_attr_list_unref(attr_list); + g_free(preedit_string); + + return char_attr; + } + + void + xim_init(void) + { + #ifdef XIM_DEBUG + xim_log("xim_init()\n"); + #endif + + g_return_if_fail(gui.drawarea != NULL); + g_return_if_fail(gtk_widget_get_window(gui.drawarea) != NULL); + + xic = gtk_im_multicontext_new(); + g_object_ref(xic); + + im_commit_handler_id = g_signal_connect(G_OBJECT(xic), "commit", + G_CALLBACK(&im_commit_cb), NULL); + g_signal_connect(G_OBJECT(xic), "preedit_changed", + G_CALLBACK(&im_preedit_changed_cb), NULL); + g_signal_connect(G_OBJECT(xic), "preedit_start", + G_CALLBACK(&im_preedit_start_cb), NULL); + g_signal_connect(G_OBJECT(xic), "preedit_end", + G_CALLBACK(&im_preedit_end_cb), NULL); + + gtk_im_context_set_client_window(xic, gtk_widget_get_window(gui.drawarea)); + } + + void + im_shutdown(void) + { + #ifdef XIM_DEBUG + xim_log("im_shutdown()\n"); + #endif + + if (xic != NULL) + { + gtk_im_context_focus_out(xic); + g_object_unref(xic); + xic = NULL; + } + im_is_active = FALSE; + im_commit_handler_id = 0; + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; + xim_has_preediting = FALSE; + } + + /* + * Convert the string argument to keyval and state for GdkEventKey. + * If str is valid return TRUE, otherwise FALSE. + * + * See 'imactivatekey' for documentation of the format. + */ + static int + im_string_to_keyval(const char *str, unsigned int *keyval, unsigned int *state) + { + const char *mods_end; + unsigned tmp_keyval; + unsigned tmp_state = 0; + + mods_end = strrchr(str, '-'); + mods_end = (mods_end != NULL) ? mods_end + 1 : str; + + // Parse modifier keys + while (str < mods_end) + switch (*str++) + { + case '-': break; + case 'S': case 's': tmp_state |= (unsigned)GDK_SHIFT_MASK; break; + case 'L': case 'l': tmp_state |= (unsigned)GDK_LOCK_MASK; break; + case 'C': case 'c': tmp_state |= (unsigned)GDK_CONTROL_MASK;break; + case '1': tmp_state |= (unsigned)GDK_MOD1_MASK; break; + case '2': tmp_state |= (unsigned)GDK_MOD2_MASK; break; + case '3': tmp_state |= (unsigned)GDK_MOD3_MASK; break; + case '4': tmp_state |= (unsigned)GDK_MOD4_MASK; break; + case '5': tmp_state |= (unsigned)GDK_MOD5_MASK; break; + default: + return FALSE; + } + + tmp_keyval = gdk_keyval_from_name(str); + + if (tmp_keyval == 0 || tmp_keyval == GDK_VoidSymbol) + return FALSE; + + if (keyval != NULL) + *keyval = tmp_keyval; + if (state != NULL) + *state = tmp_state; + + return TRUE; + } + + /* + * Return TRUE if p_imak is valid, otherwise FALSE. As a special case, an + * empty string is also regarded as valid. + * + * Note: The numerical key value of p_imak is cached if it was valid; thus + * boldly assuming im_xim_isvalid_imactivate() will always be called whenever + * 'imak' changes. This is currently the case but not obvious -- should + * probably rename the function for clarity. + */ + int + im_xim_isvalid_imactivate(void) + { + if (p_imak[0] == NUL) + { + im_activatekey_keyval = GDK_VoidSymbol; + im_activatekey_state = 0; + return TRUE; + } + + return im_string_to_keyval((const char *)p_imak, + &im_activatekey_keyval, + &im_activatekey_state); + } + + static void + im_synthesize_keypress(unsigned int keyval, unsigned int state) + { + GdkEventKey *event; + + event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS); + g_object_ref(gtk_widget_get_window(gui.drawarea)); + // unreffed by gdk_event_free() + event->window = gtk_widget_get_window(gui.drawarea); + event->send_event = TRUE; + event->time = GDK_CURRENT_TIME; + event->state = state; + event->keyval = keyval; + event->hardware_keycode = // needed for XIM + XKeysymToKeycode(GDK_WINDOW_XDISPLAY(event->window), (KeySym)keyval); + event->length = 0; + event->string = NULL; + + gtk_im_context_filter_keypress(xic, event); + + // For consistency, also send the corresponding release event. + event->type = GDK_KEY_RELEASE; + event->send_event = FALSE; + gtk_im_context_filter_keypress(xic, event); + + gdk_event_free((GdkEvent *)event); + } + + void + xim_reset(void) + { + # ifdef FEAT_EVAL + if (USE_IMACTIVATEFUNC) + call_imactivatefunc(im_is_active); + else + # endif + if (xic != NULL) + { + gtk_im_context_reset(xic); + + if (p_imdisable) + im_shutdown(); + else + { + xim_set_focus(gui.in_focus); + + if (im_activatekey_keyval != GDK_VoidSymbol) + { + if (im_is_active) + { + g_signal_handler_block(xic, im_commit_handler_id); + im_synthesize_keypress(im_activatekey_keyval, + im_activatekey_state); + g_signal_handler_unblock(xic, im_commit_handler_id); + } + } + else + { + im_shutdown(); + xim_init(); + xim_set_focus(gui.in_focus); + } + } + } + + if (p_imst == IM_ON_THE_SPOT) + preedit_start_col = MAXCOL; + xim_has_preediting = FALSE; + } + + int + xim_queue_key_press_event(GdkEventKey *event, int down) + { + if (down) + { + /* + * Workaround GTK2 XIM 'feature' that always converts keypad keys to + * chars., even when not part of an IM sequence (ref. feature of + * gdk/gdkkeyuni.c). + * Flag any keypad keys that might represent a single char. + * If this (on its own - i.e., not part of an IM sequence) is + * committed while we're processing one of these keys, we can ignore + * that commit and go ahead & process it ourselves. That way we can + * still distinguish keypad keys for use in mappings. + * Also add GDK_space to make work. + */ + switch (event->keyval) + { + case GDK_KP_Add: xim_expected_char = '+'; break; + case GDK_KP_Subtract: xim_expected_char = '-'; break; + case GDK_KP_Divide: xim_expected_char = '/'; break; + case GDK_KP_Multiply: xim_expected_char = '*'; break; + case GDK_KP_Decimal: xim_expected_char = '.'; break; + case GDK_KP_Equal: xim_expected_char = '='; break; + case GDK_KP_0: xim_expected_char = '0'; break; + case GDK_KP_1: xim_expected_char = '1'; break; + case GDK_KP_2: xim_expected_char = '2'; break; + case GDK_KP_3: xim_expected_char = '3'; break; + case GDK_KP_4: xim_expected_char = '4'; break; + case GDK_KP_5: xim_expected_char = '5'; break; + case GDK_KP_6: xim_expected_char = '6'; break; + case GDK_KP_7: xim_expected_char = '7'; break; + case GDK_KP_8: xim_expected_char = '8'; break; + case GDK_KP_9: xim_expected_char = '9'; break; + case GDK_space: xim_expected_char = ' '; break; + default: xim_expected_char = NUL; + } + xim_ignored_char = FALSE; + } + + /* + * When typing fFtT, XIM may be activated. Thus it must pass + * gtk_im_context_filter_keypress() in Normal mode. + * And while doing :sh too. + */ + if (xic != NULL && !p_imdisable + && (State & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0) + { + /* + * Filter 'imactivatekey' and map it to CTRL-^. This way, Vim is + * always aware of the current status of IM, and can even emulate + * the activation key for modules that don't support one. + */ + if (event->keyval == im_activatekey_keyval + && (event->state & im_activatekey_state) == im_activatekey_state) + { + unsigned int state_mask; + + // Require the state of the 3 most used modifiers to match exactly. + // Otherwise e.g. would be unusable for other purposes + // if the IM activate key is . + state_mask = im_activatekey_state; + state_mask |= ((int)GDK_SHIFT_MASK | (int)GDK_CONTROL_MASK + | (int)GDK_MOD1_MASK); + + if ((event->state & state_mask) != im_activatekey_state) + return FALSE; + + // Don't send it a second time on GDK_KEY_RELEASE. + if (event->type != GDK_KEY_PRESS) + return TRUE; + + if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) + { + im_set_active(FALSE); + + // ":lmap" mappings exists, toggle use of mappings. + State ^= LANGMAP; + if (State & LANGMAP) + { + curbuf->b_p_iminsert = B_IMODE_NONE; + State &= ~LANGMAP; + } + else + { + curbuf->b_p_iminsert = B_IMODE_LMAP; + State |= LANGMAP; + } + return TRUE; + } + + return gtk_im_context_filter_keypress(xic, event); + } + + // Don't filter events through the IM context if IM isn't active + // right now. Unlike with GTK+ 1.2 we cannot rely on the IM module + // not doing anything before the activation key was sent. + if (im_activatekey_keyval == GDK_VoidSymbol || im_is_active) + { + int imresult = gtk_im_context_filter_keypress(xic, event); + + if (p_imst == IM_ON_THE_SPOT) + { + // 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., + // ignore it so we can use the keypad key 'raw', for mappings. + if (xim_expected_char != NUL && xim_ignored_char) + // We had a keypad key, and XIM tried to thieve it + return FALSE; + + // This is supposed to fix a problem with iBus, that space + // characters don't work in input mode. + xim_expected_char = NUL; + + // Normal processing + return imresult; + } + } + + return FALSE; + } + + int + im_get_status(void) + { + # ifdef FEAT_EVAL + if (USE_IMSTATUSFUNC) + return call_imstatusfunc(); + # endif + return im_is_active; + } + + int + preedit_get_status(void) + { + return preedit_is_active; + } + + int + im_is_preediting(void) + { + return xim_has_preediting; + } + + # else // !FEAT_GUI_GTK + + static int xim_is_active = FALSE; // XIM should be active in the current + // mode + static int xim_has_focus = FALSE; // XIM is really being used for Vim + # ifdef FEAT_GUI_X11 + static XIMStyle input_style; + static int status_area_enabled = TRUE; + # endif + + /* + * Switch using XIM on/off. This is used by the code that changes "State". + * When 'imactivatefunc' is defined use that function instead. + */ + void + im_set_active(int active_arg) + { + int active = active_arg; + + // If 'imdisable' is set, XIM is never active. + if (p_imdisable) + active = FALSE; + else if (input_style & XIMPreeditPosition) + // There is a problem in switching XIM off when preediting is used, + // and it is not clear how this can be solved. For now, keep XIM on + // all the time, like it was done in Vim 5.8. + active = TRUE; + + # if defined(FEAT_EVAL) + if (USE_IMACTIVATEFUNC) + { + if (active != im_get_status()) + { + call_imactivatefunc(active); + xim_has_focus = active; + } + return; + } + # endif + + if (xic == NULL) + return; + + // Remember the active state, it is needed when Vim gets keyboard focus. + xim_is_active = active; + xim_set_preedit(); + } + + /* + * Adjust using XIM for gaining or losing keyboard focus. Also called when + * "xim_is_active" changes. + */ + void + xim_set_focus(int focus) + { + if (xic == NULL) + return; + + /* + * XIM only gets focus when the Vim window has keyboard focus and XIM has + * been set active for the current mode. + */ + if (focus && xim_is_active) + { + if (!xim_has_focus) + { + xim_has_focus = TRUE; + XSetICFocus(xic); + } + } + else + { + if (xim_has_focus) + { + xim_has_focus = FALSE; + XUnsetICFocus(xic); + } + } + } + + void + im_set_position(int row UNUSED, int col UNUSED) + { + xim_set_preedit(); + } + + /* + * Set the XIM to the current cursor position. + */ + void + xim_set_preedit(void) + { + XVaNestedList attr_list; + XRectangle spot_area; + XPoint over_spot; + int line_space; + + if (xic == NULL) + return; + + xim_set_focus(TRUE); + + if (!xim_has_focus) + { + // hide XIM cursor + over_spot.x = 0; + over_spot.y = -100; // arbitrary invisible position + attr_list = (XVaNestedList) XVaCreateNestedList(0, + XNSpotLocation, + &over_spot, + NULL); + XSetICValues(xic, XNPreeditAttributes, attr_list, NULL); + XFree(attr_list); + return; + } + + if (input_style & XIMPreeditPosition) + { + if (xim_fg_color == INVALCOLOR) + { + xim_fg_color = gui.def_norm_pixel; + xim_bg_color = gui.def_back_pixel; + } + over_spot.x = TEXT_X(gui.col); + over_spot.y = TEXT_Y(gui.row); + spot_area.x = 0; + spot_area.y = 0; + spot_area.height = gui.char_height * Rows; + spot_area.width = gui.char_width * Columns; + line_space = gui.char_height; + attr_list = (XVaNestedList) XVaCreateNestedList(0, + XNSpotLocation, &over_spot, + XNForeground, (Pixel) xim_fg_color, + XNBackground, (Pixel) xim_bg_color, + XNArea, &spot_area, + XNLineSpace, line_space, + NULL); + if (XSetICValues(xic, XNPreeditAttributes, attr_list, NULL)) + emsg(_("E284: Cannot set IC values")); + XFree(attr_list); + } + } + + # if defined(FEAT_GUI_X11) + static char e_xim[] = N_("E285: Failed to create input context"); + # endif + + # if defined(FEAT_GUI_X11) || defined(PROTO) + # if defined(XtSpecificationRelease) && XtSpecificationRelease >= 6 && !defined(SUN_SYSTEM) + # define USE_X11R6_XIM + # endif + + static int xim_real_init(Window x11_window, Display *x11_display); + + + # ifdef USE_X11R6_XIM + static void + xim_instantiate_cb( + Display *display, + XPointer client_data UNUSED, + XPointer call_data UNUSED) + { + Window x11_window; + Display *x11_display; + + # ifdef XIM_DEBUG + xim_log("xim_instantiate_cb()\n"); + # endif + + gui_get_x11_windis(&x11_window, &x11_display); + if (display != x11_display) + return; + + xim_real_init(x11_window, x11_display); + gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); + if (xic != NULL) + XUnregisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, + xim_instantiate_cb, NULL); + } + + static void + xim_destroy_cb( + XIM im UNUSED, + XPointer client_data UNUSED, + XPointer call_data UNUSED) + { + Window x11_window; + Display *x11_display; + + # ifdef XIM_DEBUG + xim_log("xim_destroy_cb()\n"); + #endif + gui_get_x11_windis(&x11_window, &x11_display); + + xic = NULL; + status_area_enabled = FALSE; + + gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); + + XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, + xim_instantiate_cb, NULL); + } + # endif + + void + xim_init(void) + { + Window x11_window; + Display *x11_display; + + # ifdef XIM_DEBUG + xim_log("xim_init()\n"); + # endif + + gui_get_x11_windis(&x11_window, &x11_display); + + xic = NULL; + + if (xim_real_init(x11_window, x11_display)) + return; + + gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); + + # ifdef USE_X11R6_XIM + XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, + xim_instantiate_cb, NULL); + # endif + } + + static int + xim_real_init(Window x11_window, Display *x11_display) + { + int i; + char *p, + *s, + *ns, + *end, + tmp[1024]; + # define IMLEN_MAX 40 + char buf[IMLEN_MAX + 7]; + XIM xim = NULL; + XIMStyles *xim_styles; + XIMStyle this_input_style = 0; + Boolean found; + XPoint over_spot; + XVaNestedList preedit_list, status_list; + + input_style = 0; + status_area_enabled = FALSE; + + if (xic != NULL) + return FALSE; + + if (gui.rsrc_input_method != NULL && *gui.rsrc_input_method != NUL) + { + strcpy(tmp, gui.rsrc_input_method); + for (ns = s = tmp; ns != NULL && *s != NUL;) + { + s = (char *)skipwhite((char_u *)s); + if (*s == NUL) + break; + if ((ns = end = strchr(s, ',')) == NULL) + end = s + strlen(s); + while (isspace(((char_u *)end)[-1])) + end--; + *end = NUL; + + if (strlen(s) <= IMLEN_MAX) + { + strcpy(buf, "@im="); + strcat(buf, s); + if ((p = XSetLocaleModifiers(buf)) != NULL && *p != NUL + && (xim = XOpenIM(x11_display, NULL, NULL, NULL)) + != NULL) + break; + } + + s = ns + 1; + } + } + + if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL && *p != NUL) + xim = XOpenIM(x11_display, NULL, NULL, NULL); + + // This is supposed to be useful to obtain characters through + // XmbLookupString() without really using a XIM. + if (xim == NULL && (p = XSetLocaleModifiers("@im=none")) != NULL + && *p != NUL) + xim = XOpenIM(x11_display, NULL, NULL, NULL); + + if (xim == NULL) + { + // Only give this message when verbose is set, because too many people + // got this message when they didn't want to use a XIM. + if (p_verbose > 0) + { + verbose_enter(); + emsg(_("E286: Failed to open input method")); + verbose_leave(); + } + return FALSE; + } + + # ifdef USE_X11R6_XIM + { + XIMCallback destroy_cb; + + destroy_cb.callback = xim_destroy_cb; + destroy_cb.client_data = NULL; + if (XSetIMValues(xim, XNDestroyCallback, &destroy_cb, NULL)) + emsg(_("E287: Warning: Could not set destroy callback to IM")); + } + # endif + + if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles) + { + emsg(_("E288: input method doesn't support any style")); + XCloseIM(xim); + return FALSE; + } + + found = False; + strcpy(tmp, gui.rsrc_preedit_type_name); + for (s = tmp; s && !found; ) + { + while (*s && isspace((unsigned char)*s)) + s++; + if (!*s) + break; + if ((ns = end = strchr(s, ',')) != 0) + ns++; + else + end = s + strlen(s); + while (isspace((unsigned char)*end)) + end--; + *end = '\0'; + + if (!strcmp(s, "OverTheSpot")) + this_input_style = (XIMPreeditPosition | XIMStatusArea); + else if (!strcmp(s, "OffTheSpot")) + this_input_style = (XIMPreeditArea | XIMStatusArea); + else if (!strcmp(s, "Root")) + this_input_style = (XIMPreeditNothing | XIMStatusNothing); + + for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) + { + if (this_input_style == xim_styles->supported_styles[i]) + { + found = True; + break; + } + } + if (!found) + for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) + { + if ((xim_styles->supported_styles[i] & this_input_style) + == (this_input_style & ~XIMStatusArea)) + { + this_input_style &= ~XIMStatusArea; + found = True; + break; + } + } + + s = ns; + } + XFree(xim_styles); + + if (!found) + { + // Only give this message when verbose is set, because too many people + // got this message when they didn't want to use a XIM. + if (p_verbose > 0) + { + verbose_enter(); + emsg(_("E289: input method doesn't support my preedit type")); + verbose_leave(); + } + XCloseIM(xim); + return FALSE; + } + + over_spot.x = TEXT_X(gui.col); + over_spot.y = TEXT_Y(gui.row); + input_style = this_input_style; + + // A crash was reported when trying to pass gui.norm_font as XNFontSet, + // thus that has been removed. Hopefully the default works... + # ifdef FEAT_XFONTSET + if (gui.fontset != NOFONTSET) + { + preedit_list = XVaCreateNestedList(0, + XNSpotLocation, &over_spot, + XNForeground, (Pixel)gui.def_norm_pixel, + XNBackground, (Pixel)gui.def_back_pixel, + XNFontSet, (XFontSet)gui.fontset, + NULL); + status_list = XVaCreateNestedList(0, + XNForeground, (Pixel)gui.def_norm_pixel, + XNBackground, (Pixel)gui.def_back_pixel, + XNFontSet, (XFontSet)gui.fontset, + NULL); + } + else + # endif + { + preedit_list = XVaCreateNestedList(0, + XNSpotLocation, &over_spot, + XNForeground, (Pixel)gui.def_norm_pixel, + XNBackground, (Pixel)gui.def_back_pixel, + NULL); + status_list = XVaCreateNestedList(0, + XNForeground, (Pixel)gui.def_norm_pixel, + XNBackground, (Pixel)gui.def_back_pixel, + NULL); + } + + xic = XCreateIC(xim, + XNInputStyle, input_style, + XNClientWindow, x11_window, + XNFocusWindow, gui.wid, + XNPreeditAttributes, preedit_list, + XNStatusAttributes, status_list, + NULL); + XFree(status_list); + XFree(preedit_list); + if (xic != NULL) + { + if (input_style & XIMStatusArea) + { + xim_set_status_area(); + status_area_enabled = TRUE; + } + else + gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); + } + else + { + if (!is_not_a_term()) + emsg(_(e_xim)); + XCloseIM(xim); + return FALSE; + } + + return TRUE; + } + + # endif // FEAT_GUI_X11 + + /* + * Get IM status. When IM is on, return TRUE. Else return FALSE. + * FIXME: This doesn't work correctly: Having focus doesn't always mean XIM is + * active, when not having focus XIM may still be active (e.g., when using a + * tear-off menu item). + */ + int + im_get_status(void) + { + # ifdef FEAT_EVAL + if (USE_IMSTATUSFUNC) + return call_imstatusfunc(); + # endif + return xim_has_focus; + } + + # endif // !FEAT_GUI_GTK + + # if !defined(FEAT_GUI_GTK) || defined(PROTO) + /* + * Set up the status area. + * + * This should use a separate Widget, but that seems not possible, because + * preedit_area and status_area should be set to the same window as for the + * text input. Unfortunately this means the status area pollutes the text + * window... + */ + void + xim_set_status_area(void) + { + XVaNestedList preedit_list = 0, status_list = 0, list = 0; + XRectangle pre_area, status_area; + + if (xic == NULL) + return; + + if (input_style & XIMStatusArea) + { + if (input_style & XIMPreeditArea) + { + XRectangle *needed_rect; + + // to get status_area width + status_list = XVaCreateNestedList(0, XNAreaNeeded, + &needed_rect, NULL); + XGetICValues(xic, XNStatusAttributes, status_list, NULL); + XFree(status_list); + + status_area.width = needed_rect->width; + } + else + status_area.width = gui.char_width * Columns; + + status_area.x = 0; + status_area.y = gui.char_height * Rows + gui.border_offset; + if (gui.which_scrollbars[SBAR_BOTTOM]) + status_area.y += gui.scrollbar_height; + #ifdef FEAT_MENU + if (gui.menu_is_active) + status_area.y += gui.menu_height; + #endif + status_area.height = gui.char_height; + status_list = XVaCreateNestedList(0, XNArea, &status_area, NULL); + } + else + { + status_area.x = 0; + status_area.y = gui.char_height * Rows + gui.border_offset; + if (gui.which_scrollbars[SBAR_BOTTOM]) + status_area.y += gui.scrollbar_height; + #ifdef FEAT_MENU + if (gui.menu_is_active) + status_area.y += gui.menu_height; + #endif + status_area.width = 0; + status_area.height = gui.char_height; + } + + if (input_style & XIMPreeditArea) // off-the-spot + { + pre_area.x = status_area.x + status_area.width; + pre_area.y = gui.char_height * Rows + gui.border_offset; + pre_area.width = gui.char_width * Columns - pre_area.x; + if (gui.which_scrollbars[SBAR_BOTTOM]) + pre_area.y += gui.scrollbar_height; + #ifdef FEAT_MENU + if (gui.menu_is_active) + pre_area.y += gui.menu_height; + #endif + pre_area.height = gui.char_height; + preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); + } + else if (input_style & XIMPreeditPosition) // over-the-spot + { + pre_area.x = 0; + pre_area.y = 0; + pre_area.height = gui.char_height * Rows; + pre_area.width = gui.char_width * Columns; + preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); + } + + if (preedit_list && status_list) + list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, + XNStatusAttributes, status_list, NULL); + else if (preedit_list) + list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, + NULL); + else if (status_list) + list = XVaCreateNestedList(0, XNStatusAttributes, status_list, + NULL); + else + list = NULL; + + if (list) + { + XSetICValues(xic, XNVaNestedList, list, NULL); + XFree(list); + } + if (status_list) + XFree(status_list); + if (preedit_list) + XFree(preedit_list); + } + + int + xim_get_status_area_height(void) + { + if (status_area_enabled) + return gui.char_height; + return 0; + } + # endif + + #else // !defined(FEAT_XIM) + + # if defined(IME_WITHOUT_XIM) || defined(VIMDLL) || defined(PROTO) + static int im_was_set_active = FALSE; + + int + # ifdef VIMDLL + mbyte_im_get_status(void) + # else + im_get_status(void) + # endif + { + # if defined(FEAT_EVAL) + if (USE_IMSTATUSFUNC) + return call_imstatusfunc(); + # endif + return im_was_set_active; + } + + void + # ifdef VIMDLL + mbyte_im_set_active(int active_arg) + # else + im_set_active(int active_arg) + # endif + { + # if defined(FEAT_EVAL) + int active = !p_imdisable && active_arg; + + if (USE_IMACTIVATEFUNC && active != im_get_status()) + { + call_imactivatefunc(active); + im_was_set_active = active; + } + # endif + } + + # if defined(FEAT_GUI) && !defined(FEAT_GUI_HAIKU) && !defined(VIMDLL) + void + im_set_position(int row UNUSED, int col UNUSED) + { + } + # endif + # endif + + #endif // FEAT_XIM *** ../vim-8.2.0871/src/mbyte.c 2020-02-26 16:15:31.072386953 +0100 --- src/mbyte.c 2020-06-01 14:18:57.758948897 +0200 *************** *** 111,129 **** # endif #endif - #if defined(FEAT_GUI_GTK) && defined(FEAT_XIM) - # if GTK_CHECK_VERSION(3,0,0) - # include - # else - # include - # endif - # ifdef MSWIN - # include - # else - # include - # endif - #endif - #ifdef HAVE_WCHAR_H # include #endif --- 111,116 ---- *************** *** 179,215 **** 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, }; - /* - * XIM often causes trouble. Define XIM_DEBUG to get a log of XIM callbacks - * in the "xim.log" file. - */ - // #define XIM_DEBUG - #ifdef XIM_DEBUG - static void - xim_log(char *s, ...) - { - va_list arglist; - static FILE *fd = NULL; - - if (fd == (FILE *)-1) - return; - if (fd == NULL) - { - fd = mch_fopen("xim.log", "w"); - if (fd == NULL) - { - emsg("Cannot open xim.log"); - fd = (FILE *)-1; - return; - } - } - - va_start(arglist, s); - vfprintf(fd, s, arglist); - va_end(arglist); - } - #endif - /* * Canonical encoding names and their properties. --- 166,171 ---- *************** *** 4778,6503 **** # endif // DYNAMIC_ICONV # endif // USE_ICONV - - #ifdef FEAT_GUI - # define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL) - # define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL) - #else - # define USE_IMACTIVATEFUNC (*p_imaf != NUL) - # define USE_IMSTATUSFUNC (*p_imsf != NUL) - #endif - - #if defined(FEAT_EVAL) && \ - (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)) - static void - call_imactivatefunc(int active) - { - typval_T argv[2]; - - argv[0].v_type = VAR_NUMBER; - argv[0].vval.v_number = active ? 1 : 0; - argv[1].v_type = VAR_UNKNOWN; - (void)call_func_retnr(p_imaf, 1, argv); - } - - static int - call_imstatusfunc(void) - { - int is_active; - - // FIXME: Don't execute user function in unsafe situation. - if (exiting || is_autocmd_blocked()) - return FALSE; - // FIXME: :py print 'xxx' is shown duplicate result. - // Use silent to avoid it. - ++msg_silent; - is_active = call_func_retnr(p_imsf, 0, NULL); - --msg_silent; - return (is_active > 0); - } - #endif - - #if defined(FEAT_XIM) || defined(PROTO) - - # if defined(FEAT_GUI_GTK) || defined(PROTO) - static int xim_has_preediting INIT(= FALSE); // IM current status - - /* - * Set preedit_start_col to the current cursor position. - */ - static void - init_preedit_start_col(void) - { - if (State & CMDLINE) - preedit_start_col = cmdline_getvcol_cursor(); - else if (curwin != NULL && curwin->w_buffer != NULL) - getvcol(curwin, &curwin->w_cursor, &preedit_start_col, NULL, NULL); - // Prevent that preediting marks the buffer as changed. - xim_changed_while_preediting = curbuf->b_changed; - } - - static int im_is_active = FALSE; // IM is enabled for current mode - static int preedit_is_active = FALSE; - static int im_preedit_cursor = 0; // cursor offset in characters - static int im_preedit_trailing = 0; // number of characters after cursor - - static unsigned long im_commit_handler_id = 0; - 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) - { - int was_active; - - was_active = !!im_get_status(); - im_is_active = (active && !p_imdisable); - - if (im_is_active != was_active) - xim_reset(); - } - - void - xim_set_focus(int focus) - { - if (xic != NULL) - { - if (focus) - gtk_im_context_focus_in(xic); - else - gtk_im_context_focus_out(xic); - } - } - - void - im_set_position(int row, int col) - { - if (xic != NULL) - { - GdkRectangle area; - - area.x = FILL_X(col); - area.y = FILL_Y(row); - area.width = gui.char_width * (mb_lefthalve(row, col) ? 2 : 1); - 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(); - } - } - - # if 0 || defined(PROTO) // apparently only used in gui_x11.c - void - xim_set_preedit(void) - { - im_set_position(gui.row, gui.col); - } - # endif - - static void - im_add_to_input(char_u *str, int len) - { - // Convert from 'termencoding' (always "utf-8") to 'encoding' - if (input_conv.vc_type != CONV_NONE) - { - str = string_convert(&input_conv, str, &len); - g_return_if_fail(str != NULL); - } - - add_to_input_buf_csi(str, len); - - if (input_conv.vc_type != CONV_NONE) - vim_free(str); - - if (p_mh) // blank out the pointer if necessary - gui_mch_mousehide(TRUE); - } - - static void - im_preedit_window_set_position(void) - { - int x, y, width, height; - int screen_x, screen_y, screen_width, screen_height; - - if (preedit_window == NULL) - return; - - gui_gtk_get_screen_geom_of_win(gui.drawarea, - &screen_x, &screen_y, &screen_width, &screen_height); - gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y); - gtk_window_get_size(GTK_WINDOW(preedit_window), &width, &height); - x = x + FILL_X(gui.col); - y = y + FILL_Y(gui.row); - if (x + width > screen_x + screen_width) - x = screen_x + screen_width - width; - if (y + height > screen_y + screen_height) - y = screen_y + screen_height - height; - gtk_window_move(GTK_WINDOW(preedit_window), x, y); - } - - static void - im_preedit_window_open() - { - char *preedit_string; - #if !GTK_CHECK_VERSION(3,16,0) - char buf[8]; - #endif - PangoAttrList *attr_list; - PangoLayout *layout; - #if GTK_CHECK_VERSION(3,0,0) - # if !GTK_CHECK_VERSION(3,16,0) - GdkRGBA color; - # endif - #else - GdkColor color; - #endif - gint w, h; - - if (preedit_window == NULL) - { - preedit_window = gtk_window_new(GTK_WINDOW_POPUP); - gtk_window_set_transient_for(GTK_WINDOW(preedit_window), - GTK_WINDOW(gui.mainwin)); - preedit_label = gtk_label_new(""); - gtk_widget_set_name(preedit_label, "vim-gui-preedit-area"); - gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label); - } - - #if GTK_CHECK_VERSION(3,16,0) - { - GtkStyleContext * const context - = gtk_widget_get_style_context(gui.drawarea); - GtkCssProvider * const provider = gtk_css_provider_new(); - gchar *css = NULL; - const char * const fontname - = pango_font_description_get_family(gui.norm_font); - gint fontsize - = pango_font_description_get_size(gui.norm_font) / PANGO_SCALE; - gchar *fontsize_propval = NULL; - - if (!pango_font_description_get_size_is_absolute(gui.norm_font)) - { - // fontsize was given in points. Convert it into that in pixels - // to use with CSS. - GdkScreen * const screen - = gdk_window_get_screen(gtk_widget_get_window(gui.mainwin)); - const gdouble dpi = gdk_screen_get_resolution(screen); - fontsize = dpi * fontsize / 72; - } - if (fontsize > 0) - fontsize_propval = g_strdup_printf("%dpx", fontsize); - else - fontsize_propval = g_strdup_printf("inherit"); - - css = g_strdup_printf( - "widget#vim-gui-preedit-area {\n" - " font-family: %s,monospace;\n" - " font-size: %s;\n" - " color: #%.2lx%.2lx%.2lx;\n" - " background-color: #%.2lx%.2lx%.2lx;\n" - "}\n", - fontname != NULL ? fontname : "inherit", - fontsize_propval, - (gui.norm_pixel >> 16) & 0xff, - (gui.norm_pixel >> 8) & 0xff, - gui.norm_pixel & 0xff, - (gui.back_pixel >> 16) & 0xff, - (gui.back_pixel >> 8) & 0xff, - gui.back_pixel & 0xff); - - gtk_css_provider_load_from_data(provider, css, -1, NULL); - gtk_style_context_add_provider(context, - GTK_STYLE_PROVIDER(provider), G_MAXUINT); - - g_free(css); - g_free(fontsize_propval); - g_object_unref(provider); - } - #elif GTK_CHECK_VERSION(3,0,0) - gtk_widget_override_font(preedit_label, gui.norm_font); - - vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel); - gdk_rgba_parse(&color, buf); - gtk_widget_override_color(preedit_label, GTK_STATE_FLAG_NORMAL, &color); - - vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel); - gdk_rgba_parse(&color, buf); - gtk_widget_override_background_color(preedit_label, GTK_STATE_FLAG_NORMAL, - &color); - #else - gtk_widget_modify_font(preedit_label, gui.norm_font); - - vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.norm_pixel); - gdk_color_parse(buf, &color); - gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color); - - vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.back_pixel); - gdk_color_parse(buf, &color); - gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color); - #endif - - 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 - #ifdef FEAT_TERMINAL - && !term_use_loop() - #endif - ) - { - im_preedit_cursor = 0; - return; - } - for (; im_preedit_cursor > 0; --im_preedit_cursor) - add_to_input_buf(bskey, (int)sizeof(bskey)); - - for (; im_preedit_trailing > 0; --im_preedit_trailing) - add_to_input_buf(delkey, (int)sizeof(delkey)); - } - - /* - * Move the cursor left by "num_move_back" characters. - * Note that ins_left() checks im_is_preediting() to avoid breaking undo for - * these K_LEFT keys. - */ - static void - im_correct_cursor(int num_move_back) - { - char_u backkey[] = {CSI, 'k', 'l'}; - - if (State & NORMAL) - return; - # ifdef FEAT_RIGHTLEFT - if ((State & CMDLINE) == 0 && curwin != NULL && curwin->w_p_rl) - backkey[2] = 'r'; - # endif - for (; num_move_back > 0; --num_move_back) - add_to_input_buf(backkey, (int)sizeof(backkey)); - } - - static int xim_expected_char = NUL; - static int xim_ignored_char = FALSE; - - /* - * Update the mode and cursor while in an IM callback. - */ - static void - im_show_info(void) - { - int old_vgetc_busy; - - old_vgetc_busy = vgetc_busy; - vgetc_busy = TRUE; - showmode(); - vgetc_busy = old_vgetc_busy; - if ((State & NORMAL) || (State & INSERT)) - setcursor(); - out_flush(); - } - - /* - * Callback invoked when the user finished preediting. - * Put the final string into the input buffer. - */ - static void - im_commit_cb(GtkIMContext *context UNUSED, - const gchar *str, - gpointer data UNUSED) - { - int slen = (int)STRLEN(str); - int add_to_input = TRUE; - int clen; - int len = slen; - int commit_with_preedit = TRUE; - char_u *im_str; - - #ifdef XIM_DEBUG - xim_log("im_commit_cb(): %s\n", str); - #endif - - if (p_imst == IM_ON_THE_SPOT) - { - // 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; - } - - // 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 - // mappings as . - if (xim_expected_char != NUL) - { - // We're currently processing a keypad or other special key - if (slen == 1 && str[0] == xim_expected_char) - { - // It's a match - don't do it here - xim_ignored_char = TRUE; - add_to_input = FALSE; - } - else - { - // Not a match - xim_ignored_char = FALSE; - } - } - - if (add_to_input) - im_add_to_input((char_u *)str, slen); - - 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(); - } - - /* - * Callback invoked after start to the preedit. - */ - static void - im_preedit_start_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) - { - #ifdef XIM_DEBUG - xim_log("im_preedit_start_cb()\n"); - #endif - - im_is_active = TRUE; - preedit_is_active = TRUE; - gui_update_cursor(TRUE, FALSE); - im_show_info(); - } - - /* - * Callback invoked after end to the preedit. - */ - static void - im_preedit_end_cb(GtkIMContext *context UNUSED, gpointer data UNUSED) - { - #ifdef XIM_DEBUG - xim_log("im_preedit_end_cb()\n"); - #endif - im_delete_preedit(); - - // Indicate that preediting has finished - if (p_imst == IM_ON_THE_SPOT) - preedit_start_col = MAXCOL; - xim_has_preediting = FALSE; - - #if 0 - // Removal of this line suggested by Takuhiro Nishioka. Fixes that IM was - // switched off unintentionally. We now use preedit_is_active (added by - // SungHyun Nam). - im_is_active = FALSE; - #endif - preedit_is_active = FALSE; - gui_update_cursor(TRUE, FALSE); - im_show_info(); - } - - /* - * Callback invoked after changes to the preedit string. If the preedit - * string was empty before, remember the preedit start column so we know - * where to apply feedback attributes. Delete the previous preedit string - * if there was one, save the new preedit cursor offset, and put the new - * string into the input buffer. - * - * TODO: The pragmatic "put into input buffer" approach used here has - * several fundamental problems: - * - * - The characters in the preedit string are subject to remapping. - * That's broken, only the finally committed string should be remapped. - * - * - There is a race condition involved: The retrieved value for the - * current cursor position will be wrong if any unprocessed characters - * are still queued in the input buffer. - * - * - Due to the lack of synchronization between the file buffer in memory - * and any typed characters, it's practically impossible to implement the - * "retrieve_surrounding" and "delete_surrounding" signals reliably. IM - * modules for languages such as Thai are likely to rely on this feature - * for proper operation. - * - * Conclusions: I think support for preediting needs to be moved to the - * core parts of Vim. Ideally, until it has been committed, the preediting - * string should only be displayed and not affect the buffer content at all. - * The question how to deal with the synchronization issue still remains. - * Circumventing the input buffer is probably not desirable. Anyway, I think - * implementing "retrieve_surrounding" is the only hard problem. - * - * One way to solve all of this in a clean manner would be to queue all key - * press/release events "as is" in the input buffer, and apply the IM filtering - * at the receiving end of the queue. This, however, would have a rather large - * impact on the code base. If there is an easy way to force processing of all - * remaining input from within the "retrieve_surrounding" signal handler, this - * might not be necessary. Gotta ask on vim-dev for opinions. - */ - static void - im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED) - { - char *preedit_string = NULL; - int cursor_index = 0; - int num_move_back = 0; - char_u *str; - char_u *p; - int i; - - 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); - #endif - - g_return_if_fail(preedit_string != NULL); // just in case - - if (p_imst == IM_OVER_THE_SPOT) - { - if (preedit_string[0] == NUL) - { - xim_has_preediting = FALSE; - im_delete_preedit(); - } - else - { - xim_has_preediting = TRUE; - im_show_preedit(); - } - } - else - { - // 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(); - - /* - * 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) - { - 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 (p > str) - { - im_add_to_input(str, (int)(p - str)); - im_correct_cursor(num_move_back); - } - } - - g_free(preedit_string); - - if (gtk_main_level() > 0) - gtk_main_quit(); - } - - /* - * Translate the Pango attributes at iter to Vim highlighting attributes. - * Ignore attributes not supported by Vim highlighting. This shouldn't have - * too much impact -- right now we handle even more attributes than necessary - * for the IM modules I tested with. - */ - static int - translate_pango_attributes(PangoAttrIterator *iter) - { - PangoAttribute *attr; - int char_attr = HL_NORMAL; - - attr = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); - if (attr != NULL && ((PangoAttrInt *)attr)->value - != (int)PANGO_UNDERLINE_NONE) - char_attr |= HL_UNDERLINE; - - attr = pango_attr_iterator_get(iter, PANGO_ATTR_WEIGHT); - if (attr != NULL && ((PangoAttrInt *)attr)->value >= (int)PANGO_WEIGHT_BOLD) - char_attr |= HL_BOLD; - - attr = pango_attr_iterator_get(iter, PANGO_ATTR_STYLE); - if (attr != NULL && ((PangoAttrInt *)attr)->value - != (int)PANGO_STYLE_NORMAL) - char_attr |= HL_ITALIC; - - attr = pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND); - if (attr != NULL) - { - const PangoColor *color = &((PangoAttrColor *)attr)->color; - - // Assume inverse if black background is requested - if ((color->red | color->green | color->blue) == 0) - char_attr |= HL_INVERSE; - } - - return char_attr; - } - - /* - * Retrieve the highlighting attributes at column col in the preedit string. - * Return -1 if not in preediting mode or if col is out of range. - */ - int - im_get_feedback_attr(int col) - { - char *preedit_string = NULL; - PangoAttrList *attr_list = NULL; - int char_attr = -1; - - if (xic == NULL) - return char_attr; - - gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL); - - if (preedit_string != NULL && attr_list != NULL) - { - int idx; - - // Get the byte index as used by PangoAttrIterator - for (idx = 0; col > 0 && preedit_string[idx] != '\0'; --col) - idx += utfc_ptr2len((char_u *)preedit_string + idx); - - if (preedit_string[idx] != '\0') - { - PangoAttrIterator *iter; - int start, end; - - char_attr = HL_NORMAL; - iter = pango_attr_list_get_iterator(attr_list); - - // Extract all relevant attributes from the list. - do - { - pango_attr_iterator_range(iter, &start, &end); - - if (idx >= start && idx < end) - char_attr |= translate_pango_attributes(iter); - } - while (pango_attr_iterator_next(iter)); - - pango_attr_iterator_destroy(iter); - } - } - - if (attr_list != NULL) - pango_attr_list_unref(attr_list); - g_free(preedit_string); - - return char_attr; - } - - void - xim_init(void) - { - #ifdef XIM_DEBUG - xim_log("xim_init()\n"); - #endif - - g_return_if_fail(gui.drawarea != NULL); - g_return_if_fail(gtk_widget_get_window(gui.drawarea) != NULL); - - xic = gtk_im_multicontext_new(); - g_object_ref(xic); - - im_commit_handler_id = g_signal_connect(G_OBJECT(xic), "commit", - G_CALLBACK(&im_commit_cb), NULL); - g_signal_connect(G_OBJECT(xic), "preedit_changed", - G_CALLBACK(&im_preedit_changed_cb), NULL); - g_signal_connect(G_OBJECT(xic), "preedit_start", - G_CALLBACK(&im_preedit_start_cb), NULL); - g_signal_connect(G_OBJECT(xic), "preedit_end", - G_CALLBACK(&im_preedit_end_cb), NULL); - - gtk_im_context_set_client_window(xic, gtk_widget_get_window(gui.drawarea)); - } - - void - im_shutdown(void) - { - #ifdef XIM_DEBUG - xim_log("im_shutdown()\n"); - #endif - - if (xic != NULL) - { - gtk_im_context_focus_out(xic); - g_object_unref(xic); - xic = NULL; - } - im_is_active = FALSE; - im_commit_handler_id = 0; - if (p_imst == IM_ON_THE_SPOT) - preedit_start_col = MAXCOL; - xim_has_preediting = FALSE; - } - - /* - * Convert the string argument to keyval and state for GdkEventKey. - * If str is valid return TRUE, otherwise FALSE. - * - * See 'imactivatekey' for documentation of the format. - */ - static int - im_string_to_keyval(const char *str, unsigned int *keyval, unsigned int *state) - { - const char *mods_end; - unsigned tmp_keyval; - unsigned tmp_state = 0; - - mods_end = strrchr(str, '-'); - mods_end = (mods_end != NULL) ? mods_end + 1 : str; - - // Parse modifier keys - while (str < mods_end) - switch (*str++) - { - case '-': break; - case 'S': case 's': tmp_state |= (unsigned)GDK_SHIFT_MASK; break; - case 'L': case 'l': tmp_state |= (unsigned)GDK_LOCK_MASK; break; - case 'C': case 'c': tmp_state |= (unsigned)GDK_CONTROL_MASK;break; - case '1': tmp_state |= (unsigned)GDK_MOD1_MASK; break; - case '2': tmp_state |= (unsigned)GDK_MOD2_MASK; break; - case '3': tmp_state |= (unsigned)GDK_MOD3_MASK; break; - case '4': tmp_state |= (unsigned)GDK_MOD4_MASK; break; - case '5': tmp_state |= (unsigned)GDK_MOD5_MASK; break; - default: - return FALSE; - } - - tmp_keyval = gdk_keyval_from_name(str); - - if (tmp_keyval == 0 || tmp_keyval == GDK_VoidSymbol) - return FALSE; - - if (keyval != NULL) - *keyval = tmp_keyval; - if (state != NULL) - *state = tmp_state; - - return TRUE; - } - - /* - * Return TRUE if p_imak is valid, otherwise FALSE. As a special case, an - * empty string is also regarded as valid. - * - * Note: The numerical key value of p_imak is cached if it was valid; thus - * boldly assuming im_xim_isvalid_imactivate() will always be called whenever - * 'imak' changes. This is currently the case but not obvious -- should - * probably rename the function for clarity. - */ - int - im_xim_isvalid_imactivate(void) - { - if (p_imak[0] == NUL) - { - im_activatekey_keyval = GDK_VoidSymbol; - im_activatekey_state = 0; - return TRUE; - } - - return im_string_to_keyval((const char *)p_imak, - &im_activatekey_keyval, - &im_activatekey_state); - } - - static void - im_synthesize_keypress(unsigned int keyval, unsigned int state) - { - GdkEventKey *event; - - event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS); - g_object_ref(gtk_widget_get_window(gui.drawarea)); - // unreffed by gdk_event_free() - event->window = gtk_widget_get_window(gui.drawarea); - event->send_event = TRUE; - event->time = GDK_CURRENT_TIME; - event->state = state; - event->keyval = keyval; - event->hardware_keycode = // needed for XIM - XKeysymToKeycode(GDK_WINDOW_XDISPLAY(event->window), (KeySym)keyval); - event->length = 0; - event->string = NULL; - - gtk_im_context_filter_keypress(xic, event); - - // For consistency, also send the corresponding release event. - event->type = GDK_KEY_RELEASE; - event->send_event = FALSE; - gtk_im_context_filter_keypress(xic, event); - - gdk_event_free((GdkEvent *)event); - } - - void - xim_reset(void) - { - # ifdef FEAT_EVAL - if (USE_IMACTIVATEFUNC) - call_imactivatefunc(im_is_active); - else - # endif - if (xic != NULL) - { - gtk_im_context_reset(xic); - - if (p_imdisable) - im_shutdown(); - else - { - xim_set_focus(gui.in_focus); - - if (im_activatekey_keyval != GDK_VoidSymbol) - { - if (im_is_active) - { - g_signal_handler_block(xic, im_commit_handler_id); - im_synthesize_keypress(im_activatekey_keyval, - im_activatekey_state); - g_signal_handler_unblock(xic, im_commit_handler_id); - } - } - else - { - im_shutdown(); - xim_init(); - xim_set_focus(gui.in_focus); - } - } - } - - if (p_imst == IM_ON_THE_SPOT) - preedit_start_col = MAXCOL; - xim_has_preediting = FALSE; - } - - int - xim_queue_key_press_event(GdkEventKey *event, int down) - { - if (down) - { - /* - * Workaround GTK2 XIM 'feature' that always converts keypad keys to - * chars., even when not part of an IM sequence (ref. feature of - * gdk/gdkkeyuni.c). - * Flag any keypad keys that might represent a single char. - * If this (on its own - i.e., not part of an IM sequence) is - * committed while we're processing one of these keys, we can ignore - * that commit and go ahead & process it ourselves. That way we can - * still distinguish keypad keys for use in mappings. - * Also add GDK_space to make work. - */ - switch (event->keyval) - { - case GDK_KP_Add: xim_expected_char = '+'; break; - case GDK_KP_Subtract: xim_expected_char = '-'; break; - case GDK_KP_Divide: xim_expected_char = '/'; break; - case GDK_KP_Multiply: xim_expected_char = '*'; break; - case GDK_KP_Decimal: xim_expected_char = '.'; break; - case GDK_KP_Equal: xim_expected_char = '='; break; - case GDK_KP_0: xim_expected_char = '0'; break; - case GDK_KP_1: xim_expected_char = '1'; break; - case GDK_KP_2: xim_expected_char = '2'; break; - case GDK_KP_3: xim_expected_char = '3'; break; - case GDK_KP_4: xim_expected_char = '4'; break; - case GDK_KP_5: xim_expected_char = '5'; break; - case GDK_KP_6: xim_expected_char = '6'; break; - case GDK_KP_7: xim_expected_char = '7'; break; - case GDK_KP_8: xim_expected_char = '8'; break; - case GDK_KP_9: xim_expected_char = '9'; break; - case GDK_space: xim_expected_char = ' '; break; - default: xim_expected_char = NUL; - } - xim_ignored_char = FALSE; - } - - /* - * When typing fFtT, XIM may be activated. Thus it must pass - * gtk_im_context_filter_keypress() in Normal mode. - * And while doing :sh too. - */ - if (xic != NULL && !p_imdisable - && (State & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0) - { - /* - * Filter 'imactivatekey' and map it to CTRL-^. This way, Vim is - * always aware of the current status of IM, and can even emulate - * the activation key for modules that don't support one. - */ - if (event->keyval == im_activatekey_keyval - && (event->state & im_activatekey_state) == im_activatekey_state) - { - unsigned int state_mask; - - // Require the state of the 3 most used modifiers to match exactly. - // Otherwise e.g. would be unusable for other purposes - // if the IM activate key is . - state_mask = im_activatekey_state; - state_mask |= ((int)GDK_SHIFT_MASK | (int)GDK_CONTROL_MASK - | (int)GDK_MOD1_MASK); - - if ((event->state & state_mask) != im_activatekey_state) - return FALSE; - - // Don't send it a second time on GDK_KEY_RELEASE. - if (event->type != GDK_KEY_PRESS) - return TRUE; - - if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) - { - im_set_active(FALSE); - - // ":lmap" mappings exists, toggle use of mappings. - State ^= LANGMAP; - if (State & LANGMAP) - { - curbuf->b_p_iminsert = B_IMODE_NONE; - State &= ~LANGMAP; - } - else - { - curbuf->b_p_iminsert = B_IMODE_LMAP; - State |= LANGMAP; - } - return TRUE; - } - - return gtk_im_context_filter_keypress(xic, event); - } - - // Don't filter events through the IM context if IM isn't active - // right now. Unlike with GTK+ 1.2 we cannot rely on the IM module - // not doing anything before the activation key was sent. - if (im_activatekey_keyval == GDK_VoidSymbol || im_is_active) - { - int imresult = gtk_im_context_filter_keypress(xic, event); - - if (p_imst == IM_ON_THE_SPOT) - { - // 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., - // ignore it so we can use the keypad key 'raw', for mappings. - if (xim_expected_char != NUL && xim_ignored_char) - // We had a keypad key, and XIM tried to thieve it - return FALSE; - - // This is supposed to fix a problem with iBus, that space - // characters don't work in input mode. - xim_expected_char = NUL; - - // Normal processing - return imresult; - } - } - - return FALSE; - } - - int - im_get_status(void) - { - # ifdef FEAT_EVAL - if (USE_IMSTATUSFUNC) - return call_imstatusfunc(); - # endif - return im_is_active; - } - - int - preedit_get_status(void) - { - return preedit_is_active; - } - - int - im_is_preediting(void) - { - return xim_has_preediting; - } - - # else // !FEAT_GUI_GTK - - static int xim_is_active = FALSE; // XIM should be active in the current - // mode - static int xim_has_focus = FALSE; // XIM is really being used for Vim - # ifdef FEAT_GUI_X11 - static XIMStyle input_style; - static int status_area_enabled = TRUE; - # endif - - /* - * Switch using XIM on/off. This is used by the code that changes "State". - * When 'imactivatefunc' is defined use that function instead. - */ - void - im_set_active(int active_arg) - { - int active = active_arg; - - // If 'imdisable' is set, XIM is never active. - if (p_imdisable) - active = FALSE; - else if (input_style & XIMPreeditPosition) - // There is a problem in switching XIM off when preediting is used, - // and it is not clear how this can be solved. For now, keep XIM on - // all the time, like it was done in Vim 5.8. - active = TRUE; - - # if defined(FEAT_EVAL) - if (USE_IMACTIVATEFUNC) - { - if (active != im_get_status()) - { - call_imactivatefunc(active); - xim_has_focus = active; - } - return; - } - # endif - - if (xic == NULL) - return; - - // Remember the active state, it is needed when Vim gets keyboard focus. - xim_is_active = active; - xim_set_preedit(); - } - - /* - * Adjust using XIM for gaining or losing keyboard focus. Also called when - * "xim_is_active" changes. - */ - void - xim_set_focus(int focus) - { - if (xic == NULL) - return; - - /* - * XIM only gets focus when the Vim window has keyboard focus and XIM has - * been set active for the current mode. - */ - if (focus && xim_is_active) - { - if (!xim_has_focus) - { - xim_has_focus = TRUE; - XSetICFocus(xic); - } - } - else - { - if (xim_has_focus) - { - xim_has_focus = FALSE; - XUnsetICFocus(xic); - } - } - } - - void - im_set_position(int row UNUSED, int col UNUSED) - { - xim_set_preedit(); - } - - /* - * Set the XIM to the current cursor position. - */ - void - xim_set_preedit(void) - { - XVaNestedList attr_list; - XRectangle spot_area; - XPoint over_spot; - int line_space; - - if (xic == NULL) - return; - - xim_set_focus(TRUE); - - if (!xim_has_focus) - { - // hide XIM cursor - over_spot.x = 0; - over_spot.y = -100; // arbitrary invisible position - attr_list = (XVaNestedList) XVaCreateNestedList(0, - XNSpotLocation, - &over_spot, - NULL); - XSetICValues(xic, XNPreeditAttributes, attr_list, NULL); - XFree(attr_list); - return; - } - - if (input_style & XIMPreeditPosition) - { - if (xim_fg_color == INVALCOLOR) - { - xim_fg_color = gui.def_norm_pixel; - xim_bg_color = gui.def_back_pixel; - } - over_spot.x = TEXT_X(gui.col); - over_spot.y = TEXT_Y(gui.row); - spot_area.x = 0; - spot_area.y = 0; - spot_area.height = gui.char_height * Rows; - spot_area.width = gui.char_width * Columns; - line_space = gui.char_height; - attr_list = (XVaNestedList) XVaCreateNestedList(0, - XNSpotLocation, &over_spot, - XNForeground, (Pixel) xim_fg_color, - XNBackground, (Pixel) xim_bg_color, - XNArea, &spot_area, - XNLineSpace, line_space, - NULL); - if (XSetICValues(xic, XNPreeditAttributes, attr_list, NULL)) - emsg(_("E284: Cannot set IC values")); - XFree(attr_list); - } - } - - # if defined(FEAT_GUI_X11) - static char e_xim[] = N_("E285: Failed to create input context"); - # endif - - # if defined(FEAT_GUI_X11) || defined(PROTO) - # if defined(XtSpecificationRelease) && XtSpecificationRelease >= 6 && !defined(SUN_SYSTEM) - # define USE_X11R6_XIM - # endif - - static int xim_real_init(Window x11_window, Display *x11_display); - - - # ifdef USE_X11R6_XIM - static void - xim_instantiate_cb( - Display *display, - XPointer client_data UNUSED, - XPointer call_data UNUSED) - { - Window x11_window; - Display *x11_display; - - # ifdef XIM_DEBUG - xim_log("xim_instantiate_cb()\n"); - # endif - - gui_get_x11_windis(&x11_window, &x11_display); - if (display != x11_display) - return; - - xim_real_init(x11_window, x11_display); - gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); - if (xic != NULL) - XUnregisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, - xim_instantiate_cb, NULL); - } - - static void - xim_destroy_cb( - XIM im UNUSED, - XPointer client_data UNUSED, - XPointer call_data UNUSED) - { - Window x11_window; - Display *x11_display; - - # ifdef XIM_DEBUG - xim_log("xim_destroy_cb()\n"); - #endif - gui_get_x11_windis(&x11_window, &x11_display); - - xic = NULL; - status_area_enabled = FALSE; - - gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); - - XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, - xim_instantiate_cb, NULL); - } - # endif - - void - xim_init(void) - { - Window x11_window; - Display *x11_display; - - # ifdef XIM_DEBUG - xim_log("xim_init()\n"); - # endif - - gui_get_x11_windis(&x11_window, &x11_display); - - xic = NULL; - - if (xim_real_init(x11_window, x11_display)) - return; - - gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); - - # ifdef USE_X11R6_XIM - XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL, - xim_instantiate_cb, NULL); - # endif - } - - static int - xim_real_init(Window x11_window, Display *x11_display) - { - int i; - char *p, - *s, - *ns, - *end, - tmp[1024]; - # define IMLEN_MAX 40 - char buf[IMLEN_MAX + 7]; - XIM xim = NULL; - XIMStyles *xim_styles; - XIMStyle this_input_style = 0; - Boolean found; - XPoint over_spot; - XVaNestedList preedit_list, status_list; - - input_style = 0; - status_area_enabled = FALSE; - - if (xic != NULL) - return FALSE; - - if (gui.rsrc_input_method != NULL && *gui.rsrc_input_method != NUL) - { - strcpy(tmp, gui.rsrc_input_method); - for (ns = s = tmp; ns != NULL && *s != NUL;) - { - s = (char *)skipwhite((char_u *)s); - if (*s == NUL) - break; - if ((ns = end = strchr(s, ',')) == NULL) - end = s + strlen(s); - while (isspace(((char_u *)end)[-1])) - end--; - *end = NUL; - - if (strlen(s) <= IMLEN_MAX) - { - strcpy(buf, "@im="); - strcat(buf, s); - if ((p = XSetLocaleModifiers(buf)) != NULL && *p != NUL - && (xim = XOpenIM(x11_display, NULL, NULL, NULL)) - != NULL) - break; - } - - s = ns + 1; - } - } - - if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL && *p != NUL) - xim = XOpenIM(x11_display, NULL, NULL, NULL); - - // This is supposed to be useful to obtain characters through - // XmbLookupString() without really using a XIM. - if (xim == NULL && (p = XSetLocaleModifiers("@im=none")) != NULL - && *p != NUL) - xim = XOpenIM(x11_display, NULL, NULL, NULL); - - if (xim == NULL) - { - // Only give this message when verbose is set, because too many people - // got this message when they didn't want to use a XIM. - if (p_verbose > 0) - { - verbose_enter(); - emsg(_("E286: Failed to open input method")); - verbose_leave(); - } - return FALSE; - } - - # ifdef USE_X11R6_XIM - { - XIMCallback destroy_cb; - - destroy_cb.callback = xim_destroy_cb; - destroy_cb.client_data = NULL; - if (XSetIMValues(xim, XNDestroyCallback, &destroy_cb, NULL)) - emsg(_("E287: Warning: Could not set destroy callback to IM")); - } - # endif - - if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles) - { - emsg(_("E288: input method doesn't support any style")); - XCloseIM(xim); - return FALSE; - } - - found = False; - strcpy(tmp, gui.rsrc_preedit_type_name); - for (s = tmp; s && !found; ) - { - while (*s && isspace((unsigned char)*s)) - s++; - if (!*s) - break; - if ((ns = end = strchr(s, ',')) != 0) - ns++; - else - end = s + strlen(s); - while (isspace((unsigned char)*end)) - end--; - *end = '\0'; - - if (!strcmp(s, "OverTheSpot")) - this_input_style = (XIMPreeditPosition | XIMStatusArea); - else if (!strcmp(s, "OffTheSpot")) - this_input_style = (XIMPreeditArea | XIMStatusArea); - else if (!strcmp(s, "Root")) - this_input_style = (XIMPreeditNothing | XIMStatusNothing); - - for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) - { - if (this_input_style == xim_styles->supported_styles[i]) - { - found = True; - break; - } - } - if (!found) - for (i = 0; (unsigned short)i < xim_styles->count_styles; i++) - { - if ((xim_styles->supported_styles[i] & this_input_style) - == (this_input_style & ~XIMStatusArea)) - { - this_input_style &= ~XIMStatusArea; - found = True; - break; - } - } - - s = ns; - } - XFree(xim_styles); - - if (!found) - { - // Only give this message when verbose is set, because too many people - // got this message when they didn't want to use a XIM. - if (p_verbose > 0) - { - verbose_enter(); - emsg(_("E289: input method doesn't support my preedit type")); - verbose_leave(); - } - XCloseIM(xim); - return FALSE; - } - - over_spot.x = TEXT_X(gui.col); - over_spot.y = TEXT_Y(gui.row); - input_style = this_input_style; - - // A crash was reported when trying to pass gui.norm_font as XNFontSet, - // thus that has been removed. Hopefully the default works... - # ifdef FEAT_XFONTSET - if (gui.fontset != NOFONTSET) - { - preedit_list = XVaCreateNestedList(0, - XNSpotLocation, &over_spot, - XNForeground, (Pixel)gui.def_norm_pixel, - XNBackground, (Pixel)gui.def_back_pixel, - XNFontSet, (XFontSet)gui.fontset, - NULL); - status_list = XVaCreateNestedList(0, - XNForeground, (Pixel)gui.def_norm_pixel, - XNBackground, (Pixel)gui.def_back_pixel, - XNFontSet, (XFontSet)gui.fontset, - NULL); - } - else - # endif - { - preedit_list = XVaCreateNestedList(0, - XNSpotLocation, &over_spot, - XNForeground, (Pixel)gui.def_norm_pixel, - XNBackground, (Pixel)gui.def_back_pixel, - NULL); - status_list = XVaCreateNestedList(0, - XNForeground, (Pixel)gui.def_norm_pixel, - XNBackground, (Pixel)gui.def_back_pixel, - NULL); - } - - xic = XCreateIC(xim, - XNInputStyle, input_style, - XNClientWindow, x11_window, - XNFocusWindow, gui.wid, - XNPreeditAttributes, preedit_list, - XNStatusAttributes, status_list, - NULL); - XFree(status_list); - XFree(preedit_list); - if (xic != NULL) - { - if (input_style & XIMStatusArea) - { - xim_set_status_area(); - status_area_enabled = TRUE; - } - else - gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH); - } - else - { - if (!is_not_a_term()) - emsg(_(e_xim)); - XCloseIM(xim); - return FALSE; - } - - return TRUE; - } - - # endif // FEAT_GUI_X11 - - /* - * Get IM status. When IM is on, return TRUE. Else return FALSE. - * FIXME: This doesn't work correctly: Having focus doesn't always mean XIM is - * active, when not having focus XIM may still be active (e.g., when using a - * tear-off menu item). - */ - int - im_get_status(void) - { - # ifdef FEAT_EVAL - if (USE_IMSTATUSFUNC) - return call_imstatusfunc(); - # endif - return xim_has_focus; - } - - # endif // !FEAT_GUI_GTK - - # if !defined(FEAT_GUI_GTK) || defined(PROTO) - /* - * Set up the status area. - * - * This should use a separate Widget, but that seems not possible, because - * preedit_area and status_area should be set to the same window as for the - * text input. Unfortunately this means the status area pollutes the text - * window... - */ - void - xim_set_status_area(void) - { - XVaNestedList preedit_list = 0, status_list = 0, list = 0; - XRectangle pre_area, status_area; - - if (xic == NULL) - return; - - if (input_style & XIMStatusArea) - { - if (input_style & XIMPreeditArea) - { - XRectangle *needed_rect; - - // to get status_area width - status_list = XVaCreateNestedList(0, XNAreaNeeded, - &needed_rect, NULL); - XGetICValues(xic, XNStatusAttributes, status_list, NULL); - XFree(status_list); - - status_area.width = needed_rect->width; - } - else - status_area.width = gui.char_width * Columns; - - status_area.x = 0; - status_area.y = gui.char_height * Rows + gui.border_offset; - if (gui.which_scrollbars[SBAR_BOTTOM]) - status_area.y += gui.scrollbar_height; - #ifdef FEAT_MENU - if (gui.menu_is_active) - status_area.y += gui.menu_height; - #endif - status_area.height = gui.char_height; - status_list = XVaCreateNestedList(0, XNArea, &status_area, NULL); - } - else - { - status_area.x = 0; - status_area.y = gui.char_height * Rows + gui.border_offset; - if (gui.which_scrollbars[SBAR_BOTTOM]) - status_area.y += gui.scrollbar_height; - #ifdef FEAT_MENU - if (gui.menu_is_active) - status_area.y += gui.menu_height; - #endif - status_area.width = 0; - status_area.height = gui.char_height; - } - - if (input_style & XIMPreeditArea) // off-the-spot - { - pre_area.x = status_area.x + status_area.width; - pre_area.y = gui.char_height * Rows + gui.border_offset; - pre_area.width = gui.char_width * Columns - pre_area.x; - if (gui.which_scrollbars[SBAR_BOTTOM]) - pre_area.y += gui.scrollbar_height; - #ifdef FEAT_MENU - if (gui.menu_is_active) - pre_area.y += gui.menu_height; - #endif - pre_area.height = gui.char_height; - preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); - } - else if (input_style & XIMPreeditPosition) // over-the-spot - { - pre_area.x = 0; - pre_area.y = 0; - pre_area.height = gui.char_height * Rows; - pre_area.width = gui.char_width * Columns; - preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL); - } - - if (preedit_list && status_list) - list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, - XNStatusAttributes, status_list, NULL); - else if (preedit_list) - list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list, - NULL); - else if (status_list) - list = XVaCreateNestedList(0, XNStatusAttributes, status_list, - NULL); - else - list = NULL; - - if (list) - { - XSetICValues(xic, XNVaNestedList, list, NULL); - XFree(list); - } - if (status_list) - XFree(status_list); - if (preedit_list) - XFree(preedit_list); - } - - int - xim_get_status_area_height(void) - { - if (status_area_enabled) - return gui.char_height; - return 0; - } - # endif - - #else // !defined(FEAT_XIM) - - # if defined(IME_WITHOUT_XIM) || defined(VIMDLL) - static int im_was_set_active = FALSE; - - int - # ifdef VIMDLL - mbyte_im_get_status(void) - # else - im_get_status(void) - # endif - { - # if defined(FEAT_EVAL) - if (USE_IMSTATUSFUNC) - return call_imstatusfunc(); - # endif - return im_was_set_active; - } - - void - # ifdef VIMDLL - mbyte_im_set_active(int active_arg) - # else - im_set_active(int active_arg) - # endif - { - # if defined(FEAT_EVAL) - int active = !p_imdisable && active_arg; - - if (USE_IMACTIVATEFUNC && active != im_get_status()) - { - call_imactivatefunc(active); - im_was_set_active = active; - } - # endif - } - - # if defined(FEAT_GUI) && !defined(FEAT_GUI_HAIKU) && !defined(VIMDLL) - void - im_set_position(int row UNUSED, int col UNUSED) - { - } - # endif - # endif - - #endif // FEAT_XIM - #if defined(FEAT_EVAL) || defined(PROTO) /* * "getimstatus()" function --- 4734,4739 ---- *** ../vim-8.2.0871/src/proto.h 2020-05-30 18:14:37.828521058 +0200 --- src/proto.h 2020-06-01 14:18:57.758948897 +0200 *************** *** 92,97 **** --- 92,98 ---- # include "findfile.pro" # include "fold.pro" # include "getchar.pro" + # include "gui_xim.pro" # include "hardcopy.pro" # include "hashtab.pro" # include "highlight.pro" *** ../vim-8.2.0871/src/proto/gui_xim.pro 2020-06-01 14:33:33.411649297 +0200 --- src/proto/gui_xim.pro 2020-06-01 14:18:57.758948897 +0200 *************** *** 0 **** --- 1,17 ---- + /* gui_xim.c */ + void im_set_active(int active); + void xim_set_focus(int focus); + void im_set_position(int row, int col); + void xim_set_preedit(void); + int im_get_feedback_attr(int col); + void xim_init(void); + void im_shutdown(void); + int im_xim_isvalid_imactivate(void); + void xim_reset(void); + int xim_queue_key_press_event(GdkEventKey *event, int down); + int im_get_status(void); + int preedit_get_status(void); + int im_is_preediting(void); + void xim_set_status_area(void); + int xim_get_status_area_height(void); + /* vim: set ft=c : */ *** ../vim-8.2.0871/src/proto/mbyte.pro 2019-12-12 12:55:26.000000000 +0100 --- src/proto/mbyte.pro 2020-06-01 14:18:57.758948897 +0200 *************** *** 74,94 **** void *my_iconv_open(char_u *to, char_u *from); int iconv_enabled(int verbose); void iconv_end(void); - void im_set_active(int active); - void xim_set_focus(int focus); - void im_set_position(int row, int col); - void xim_set_preedit(void); - int im_get_feedback_attr(int col); - void xim_init(void); - void im_shutdown(void); - int im_xim_isvalid_imactivate(void); - void xim_reset(void); - int xim_queue_key_press_event(GdkEventKey *event, int down); - int im_get_status(void); - int preedit_get_status(void); - int im_is_preediting(void); - void xim_set_status_area(void); - int xim_get_status_area_height(void); void f_getimstatus(typval_T *argvars, typval_T *rettv); int convert_setup(vimconv_T *vcp, char_u *from, char_u *to); int convert_setup_ext(vimconv_T *vcp, char_u *from, int from_unicode_is_utf8, char_u *to, int to_unicode_is_utf8); --- 74,79 ---- *** ../vim-8.2.0871/src/version.c 2020-06-01 14:14:40.691899742 +0200 --- src/version.c 2020-06-01 14:20:12.406676209 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 872, /**/ -- hundred-and-one symptoms of being an internet addict: 242. You turn down a better-paying job because it doesn't come with a free e-mail account. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///