From: Po Lu Date: Sun, 12 Feb 2023 11:55:28 +0000 (+0800) Subject: Support input method ``text conversion'' on X Windows X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=ae4ff4f25fbf704446f8f38d8e818f223b79042b;p=emacs.git Support input method ``text conversion'' on X Windows * configure.ac (HAVE_TEXT_CONVERSION): Define on X. * etc/NEWS: Announce new change. * src/emacs.c (main): Always call init_xterm. * src/frame.c (do_switch_frame): Use `fset_selected_window'. * src/insdel.c (struct safe_del_range_context): New structure. (safe_del_range_1, safe_del_range_2, safe_del_range): New functions. * src/lisp.h: Export new functions. * src/window.c (run_window_change_functions): Report selected window and buffer changes so that the input method can be reset. * src/xfns.c (XICCallback, Xxic_preedit_caret_callback) (Xxic_preedit_done_callback, Xxic_preedit_start_callback) (Xxic_preedit_draw_callback): Fix coding style. (Xxic_string_conversion_callback): New callback. (create_frame_xic): Register string conversion callback. (struct x_xim_text_conversion_data): New field `size'. (x_encode_xim_text_1, x_encode_xim_text): New functions. (xic_string_conversion_callback): New function. * src/xterm.c (x_reset_conversion): New function. (text_conversion_interface): New variable. (init_xterm): Initialize text conversion interface. --- diff --git a/configure.ac b/configure.ac index fc17dbd8318..7bb0df88cb3 100644 --- a/configure.ac +++ b/configure.ac @@ -6502,6 +6502,12 @@ if test "$window_system" != "none"; then AC_DEFINE([POLL_FOR_INPUT], [1], [Define if you poll periodically to detect C-g.]) WINDOW_SYSTEM_OBJ="fontset.o fringe.o image.o" + + if test "$window_system" = "x11"; then + AC_DEFINE([HAVE_TEXT_CONVERSION], [1], + [Define if the window system has text conversion support.]) + WINDOW_SYSTEM_OBJ="$WINDOW_SYSTEM_OBJ textconv.o" + fi fi AC_SUBST([WINDOW_SYSTEM_OBJ]) diff --git a/etc/NEWS b/etc/NEWS index e0175bacfdf..d3eafaadf19 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -59,6 +59,13 @@ This allows the user to customize the prompt that is appended by * Editing Changes in Emacs 30.1 +--- +** On X, Emacs now supports input methods which perform "string conversion". +This means an input method can now ask Emacs to delete text +surrounding point and replace it with something else, as well as query +Emacs for surrounding text. If your input method allows you to "undo" +mistaken compositions, this will now work as well. + --- ** New command 'kill-matching-buffers-no-ask'. This works like 'kill-matching-buffers', but without asking for diff --git a/src/emacs.c b/src/emacs.c index 214e2e2a296..282e2f48100 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -2447,7 +2447,8 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #ifdef HAVE_DBUS init_dbusbind (); #endif -#if defined(USE_GTK) && !defined(HAVE_PGTK) + +#ifdef HAVE_X_WINDOWS init_xterm (); #endif diff --git a/src/frame.c b/src/frame.c index 983424b0bee..2cea96d4a32 100644 --- a/src/frame.c +++ b/src/frame.c @@ -1526,7 +1526,7 @@ do_switch_frame (Lisp_Object frame, int for_deletion, Lisp_Object norecord) if (f->select_mini_window_flag && !NILP (Fminibufferp (XWINDOW (f->minibuffer_window)->contents, Qt))) - f->selected_window = f->minibuffer_window; + fset_selected_window (f, f->minibuffer_window); f->select_mini_window_flag = false; if (! FRAME_MINIBUF_ONLY_P (XFRAME (selected_frame))) diff --git a/src/insdel.c b/src/insdel.c index e459d0cfa17..b65a3fbd805 100644 --- a/src/insdel.c +++ b/src/insdel.c @@ -1715,6 +1715,44 @@ del_range (ptrdiff_t from, ptrdiff_t to) del_range_1 (from, to, 1, 0); } +struct safe_del_range_context +{ + /* From and to positions. */ + ptrdiff_t from, to; +}; + +static Lisp_Object +safe_del_range_1 (void *ptr) +{ + struct safe_del_range_context *context; + + context = ptr; + del_range (context->from, context->to); + return Qnil; +} + +static Lisp_Object +safe_del_range_2 (enum nonlocal_exit type, Lisp_Object value) +{ + return Qt; +} + +/* Like del_range; however, catch all non-local exits. Value is 0 if + the buffer contents were really deleted. Otherwise, it is 1. */ + +int +safe_del_range (ptrdiff_t from, ptrdiff_t to) +{ + struct safe_del_range_context context; + + context.from = from; + context.to = to; + + return !NILP (internal_catch_all (safe_del_range_1, + &context, + safe_del_range_2)); +} + /* Like del_range; PREPARE says whether to call prepare_to_modify_buffer. RET_STRING says to return the deleted text. */ diff --git a/src/lisp.h b/src/lisp.h index 0bc400ba78f..cacd318c26f 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4116,6 +4116,7 @@ extern void del_range_byte (ptrdiff_t, ptrdiff_t); extern void del_range_both (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool); extern Lisp_Object del_range_2 (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool); +extern int safe_del_range (ptrdiff_t, ptrdiff_t); extern void modify_text (ptrdiff_t, ptrdiff_t); extern void prepare_to_modify_buffer (ptrdiff_t, ptrdiff_t, ptrdiff_t *); extern void prepare_to_modify_buffer_1 (ptrdiff_t, ptrdiff_t, ptrdiff_t *); @@ -5212,6 +5213,11 @@ extern void syms_of_profiler (void); extern char *emacs_root_dir (void); #endif /* DOS_NT */ +#ifdef HAVE_TEXT_CONVERSION +/* Defined in textconv.c. */ +extern void report_selected_window_change (struct frame *); +#endif + #ifdef HAVE_NATIVE_COMP INLINE bool SUBR_NATIVE_COMPILEDP (Lisp_Object a) diff --git a/src/window.c b/src/window.c index 6201a6f4a36..9334f922f89 100644 --- a/src/window.c +++ b/src/window.c @@ -3856,6 +3856,9 @@ run_window_change_functions_1 (Lisp_Object symbol, Lisp_Object buffer, * * This function does not save and restore match data. Any functions * it calls are responsible for doing that themselves. + * + * Additionally, report changes to each frame's selected window to the + * input method in textconv.c. */ void run_window_change_functions (void) @@ -4015,6 +4018,18 @@ run_window_change_functions (void) run_window_change_functions_1 (Qwindow_selection_change_functions, Qnil, frame); +#if defined HAVE_TEXT_CONVERSION + + /* If the buffer or selected window has changed, also reset the + input method composition state. */ + + if ((frame_selected_window_change || frame_buffer_change) + && FRAME_LIVE_P (f) + && FRAME_WINDOW_P (f)) + report_selected_window_change (f); + +#endif + /* A frame has changed state when a size or buffer change occurred, its selected window has changed, when it was (de-)selected or its window state change flag was set. */ diff --git a/src/xfns.c b/src/xfns.c index 3a129211463..9e004f6a678 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -37,6 +37,10 @@ along with GNU Emacs. If not, see . */ #include "termhooks.h" #include "font.h" +#ifdef HAVE_X_I18N +#include "textconv.h" +#endif + #include #include @@ -2671,24 +2675,50 @@ append_wm_protocols (struct x_display_info *dpyinfo, #ifdef HAVE_X_I18N -static void xic_preedit_draw_callback (XIC, XPointer, XIMPreeditDrawCallbackStruct *); -static void xic_preedit_caret_callback (XIC, XPointer, XIMPreeditCaretCallbackStruct *); +static void xic_preedit_draw_callback (XIC, XPointer, + XIMPreeditDrawCallbackStruct *); +static void xic_preedit_caret_callback (XIC, XPointer, + XIMPreeditCaretCallbackStruct *); static void xic_preedit_done_callback (XIC, XPointer, XPointer); static int xic_preedit_start_callback (XIC, XPointer, XPointer); +static void xic_string_conversion_callback (XIC, XPointer, + XIMStringConversionCallbackStruct *); #ifndef HAVE_XICCALLBACK_CALLBACK #define XICCallback XIMCallback #define XICProc XIMProc #endif -static XIMCallback Xxic_preedit_draw_callback = { NULL, - (XIMProc) xic_preedit_draw_callback }; -static XIMCallback Xxic_preedit_caret_callback = { NULL, - (XIMProc) xic_preedit_caret_callback }; -static XIMCallback Xxic_preedit_done_callback = { NULL, - (XIMProc) xic_preedit_done_callback }; -static XICCallback Xxic_preedit_start_callback = { NULL, - (XICProc) xic_preedit_start_callback }; +static XIMCallback Xxic_preedit_draw_callback = + { + NULL, + (XIMProc) xic_preedit_draw_callback, + }; + +static XIMCallback Xxic_preedit_caret_callback = + { + NULL, + (XIMProc) xic_preedit_caret_callback, + }; + +static XIMCallback Xxic_preedit_done_callback = + { + NULL, + (XIMProc) xic_preedit_done_callback, + }; + +static XICCallback Xxic_preedit_start_callback = + { + NULL, + (XICProc) xic_preedit_start_callback, + }; + +static XIMCallback Xxic_string_conversion_callback = + { + /* This is actually an XICCallback! */ + NULL, + (XIMProc) xic_string_conversion_callback, + }; #if defined HAVE_X_WINDOWS && defined USE_X_TOOLKIT /* Create an X fontset on frame F with base font name BASE_FONTNAME. */ @@ -3094,6 +3124,8 @@ create_frame_xic (struct frame *f) XNFocusWindow, FRAME_X_WINDOW (f), XNStatusAttributes, status_attr, XNPreeditAttributes, preedit_attr, + XNStringConversionCallback, + &Xxic_string_conversion_callback, NULL); else if (preedit_attr) xic = XCreateIC (xim, @@ -3101,6 +3133,8 @@ create_frame_xic (struct frame *f) XNClientWindow, FRAME_X_WINDOW (f), XNFocusWindow, FRAME_X_WINDOW (f), XNPreeditAttributes, preedit_attr, + XNStringConversionCallback, + &Xxic_string_conversion_callback, NULL); else if (status_attr) xic = XCreateIC (xim, @@ -3108,12 +3142,16 @@ create_frame_xic (struct frame *f) XNClientWindow, FRAME_X_WINDOW (f), XNFocusWindow, FRAME_X_WINDOW (f), XNStatusAttributes, status_attr, + XNStringConversionCallback, + &Xxic_string_conversion_callback, NULL); else xic = XCreateIC (xim, XNInputStyle, xic_style, XNClientWindow, FRAME_X_WINDOW (f), XNFocusWindow, FRAME_X_WINDOW (f), + XNStringConversionCallback, + &Xxic_string_conversion_callback, NULL); if (!xic) @@ -3377,6 +3415,7 @@ struct x_xim_text_conversion_data struct coding_system *coding; char *source; struct x_display_info *dpyinfo; + size_t size; }; static Lisp_Object @@ -3411,6 +3450,38 @@ x_xim_text_to_utf8_unix_1 (ptrdiff_t nargs, Lisp_Object *args) return Qnil; } +static Lisp_Object +x_encode_xim_text_1 (ptrdiff_t nargs, Lisp_Object *args) +{ + struct x_xim_text_conversion_data *data; + ptrdiff_t nbytes; + Lisp_Object coding_system; + + data = xmint_pointer (args[0]); + + if (SYMBOLP (Vx_input_coding_system)) + coding_system = Vx_input_coding_system; + else if (!NILP (data->dpyinfo->xim_coding)) + coding_system = data->dpyinfo->xim_coding; + else + coding_system = Vlocale_coding_system; + + nbytes = data->size; + + data->coding->destination = NULL; + + setup_coding_system (coding_system, data->coding); + data->coding->mode |= (CODING_MODE_LAST_BLOCK + | CODING_MODE_SAFE_ENCODING); + data->coding->source = (const unsigned char *) data->source; + data->coding->dst_bytes = 2048; + data->coding->destination = xmalloc (2048); + encode_coding_object (data->coding, Qnil, 0, 0, + nbytes, nbytes, Qnil); + + return Qnil; +} + static Lisp_Object x_xim_text_to_utf8_unix_2 (Lisp_Object val, ptrdiff_t nargs, Lisp_Object *args) @@ -3468,6 +3539,46 @@ x_xim_text_to_utf8_unix (struct x_display_info *dpyinfo, return (char *) coding.destination; } +/* Convert SIZE bytes of the specified text from Emacs's internal + coding system to the input method coding system. Return the + result, its byte length in *LENGTH, and its character length in + *CHARS, or NULL. + + The string returned is not NULL terminated. */ + +static char * +x_encode_xim_text (struct x_display_info *dpyinfo, char *text, + size_t size, ptrdiff_t *length, + ptrdiff_t *chars) +{ + struct coding_system coding; + struct x_xim_text_conversion_data data; + Lisp_Object arg; + bool was_waiting_for_input_p; + + data.coding = &coding; + data.source = text; + data.dpyinfo = dpyinfo; + data.size = size; + + was_waiting_for_input_p = waiting_for_input; + /* Otherwise Fsignal will crash. */ + waiting_for_input = false; + + arg = make_mint_ptr (&data); + internal_condition_case_n (x_encode_xim_text_1, 1, &arg, + Qt, x_xim_text_to_utf8_unix_2); + waiting_for_input = was_waiting_for_input_p; + + if (length) + *length = coding.produced; + + if (chars) + *chars = coding.produced_char; + + return (char *) coding.destination; +} + static void xic_preedit_draw_callback (XIC xic, XPointer client_data, XIMPreeditDrawCallbackStruct *call_data) @@ -3664,6 +3775,128 @@ xic_set_xfontset (struct frame *f, const char *base_fontname) FRAME_XIC_FONTSET (f) = xfs; } + + +/* String conversion support. See textconv.c for more details. */ + +static void +xic_string_conversion_callback (XIC ic, XPointer client_data, + XIMStringConversionCallbackStruct *call_data) +{ + struct textconv_callback_struct request; + ptrdiff_t length; + struct frame *f; + int rc; + + /* Find the frame associated with this IC. */ + f = x_xic_to_frame (ic); + + if (!f) + goto failure; + + /* Fill in CALL_DATA as early as possible. */ + call_data->text->feedback = NULL; + call_data->text->encoding_is_wchar = False; + + /* Now translate the conversion request to the format understood by + textconv.c. */ + request.position = call_data->position; + + switch (call_data->direction) + { + case XIMForwardChar: + request.direction = TEXTCONV_FORWARD_CHAR; + break; + + case XIMBackwardChar: + request.direction = TEXTCONV_BACKWARD_CHAR; + break; + + case XIMForwardWord: + request.direction = TEXTCONV_FORWARD_WORD; + break; + + case XIMBackwardWord: + request.direction = TEXTCONV_BACKWARD_WORD; + break; + + case XIMCaretUp: + request.direction = TEXTCONV_CARET_UP; + break; + + case XIMCaretDown: + request.direction = TEXTCONV_CARET_DOWN; + break; + + case XIMNextLine: + request.direction = TEXTCONV_NEXT_LINE; + break; + + case XIMPreviousLine: + request.direction = TEXTCONV_PREVIOUS_LINE; + break; + + case XIMLineStart: + request.direction = TEXTCONV_LINE_START; + break; + + case XIMLineEnd: + request.direction = TEXTCONV_LINE_END; + break; + + case XIMAbsolutePosition: + request.direction = TEXTCONV_ABSOLUTE_POSITION; + break; + + default: + goto failure; + } + + /* factor is signed in call_data but is actually a CARD16. */ + request.factor = call_data->factor; + + if (call_data->operation == XIMStringConversionSubstitution) + request.operation = TEXTCONV_SUBSTITUTION; + else + request.operation = TEXTCONV_RETRIEVAL; + + /* Now perform the string conversion. */ + rc = textconv_query (f, &request); + + if (rc) + { + xfree (request.text.text); + goto failure; + } + + /* Encode the text in the locale coding system and give it back to + the input method. */ + request.text.text = NULL; + call_data->text->string.mbs + = x_encode_xim_text (FRAME_DISPLAY_INFO (f), + request.text.text, + request.text.bytes, NULL, + &length); + call_data->text->length = length; + + /* Free the encoded text. This is always set to something + valid. */ + xfree (request.text.text); + + /* Detect failure. */ + if (!call_data->text->string.mbs) + goto failure; + + return; + + failure: + /* Return a string of length 0 using the C library malloc. This + assumes XFree is able to free data allocated with our malloc + wrapper. */ + call_data->text->length = 0; + call_data->text->string.mbs = malloc (0); +} + #endif /* HAVE_X_I18N */ @@ -9771,6 +10004,53 @@ This should be called from a variable watcher for `x-gtk-use-native-input'. */) return Qnil; } + +#if 0 + +DEFUN ("x-test-string-conversion", Fx_test_string_conversion, + Sx_test_string_conversion, 5, 5, 0, + doc: /* Perform tests on the XIM string conversion support. */) + (Lisp_Object frame, Lisp_Object position, + Lisp_Object direction, Lisp_Object operation, Lisp_Object factor) +{ + struct frame *f; + XIMStringConversionCallbackStruct call_data; + XIMStringConversionText text; + + f = decode_window_system_frame (frame); + + if (!FRAME_XIC (f)) + error ("No XIC on FRAME!"); + + CHECK_FIXNUM (position); + CHECK_FIXNUM (direction); + CHECK_FIXNUM (operation); + CHECK_FIXNUM (factor); + + /* xic_string_conversion_callback (XIC ic, XPointer client_data, + XIMStringConversionCallbackStruct *call_data) */ + + call_data.position = XFIXNUM (position); + call_data.direction = XFIXNUM (direction); + call_data.operation = XFIXNUM (operation); + call_data.factor = XFIXNUM (factor); + call_data.text = &text; + + block_input (); + xic_string_conversion_callback (FRAME_XIC (f), NULL, + &call_data); + unblock_input (); + + /* Place a breakpoint here to inspect TEXT! */ + + while (1) + maybe_quit (); + + return Qnil; +} + +#endif + /*********************************************************************** Initialization @@ -10217,6 +10497,9 @@ eliminated in future versions of Emacs. */); defsubr (&Sx_display_set_last_user_time); defsubr (&Sx_translate_coordinates); defsubr (&Sx_get_modifier_masks); +#if 0 + defsubr (&Sx_test_string_conversion); +#endif tip_timer = Qnil; staticpro (&tip_timer); diff --git a/src/xterm.c b/src/xterm.c index 1325d923be9..5feaa4aef0f 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -636,6 +636,10 @@ along with GNU Emacs. If not, see . */ #include "xterm.h" #include +#ifdef HAVE_X_I18N +#include "textconv.h" +#endif + #ifdef USE_XCB #include #include @@ -31506,7 +31510,37 @@ x_initialize (void) XSetIOErrorHandler (x_io_error_quitter); } -#ifdef USE_GTK +#ifdef HAVE_X_I18N + +/* Notice that a change has occured on F that requires its input + method state to be reset. */ + +static void +x_reset_conversion (struct frame *f) +{ + char *string; + + if (FRAME_XIC (f)) + { + string = XmbResetIC (FRAME_XIC (f)); + + /* string is actually any string that was being composed at the + time of the reset. */ + + if (string) + XFree (string); + } +} + +/* Interface used to control input method ``text conversion''. */ + +static struct textconv_interface text_conversion_interface = + { + x_reset_conversion, + }; + +#endif + void init_xterm (void) { @@ -31520,8 +31554,11 @@ init_xterm (void) gdk_disable_multidevice (); #endif #endif -} + +#ifdef HAVE_X_I18N + register_texconv_interface (&text_conversion_interface); #endif +} void mark_xterm (void)