]> git.eshelyaron.com Git - emacs.git/commitdiff
Support input method ``text conversion'' on X Windows
authorPo Lu <luangruo@yahoo.com>
Sun, 12 Feb 2023 11:55:28 +0000 (19:55 +0800)
committerPo Lu <luangruo@yahoo.com>
Sun, 12 Feb 2023 11:56:36 +0000 (19:56 +0800)
* 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.

configure.ac
etc/NEWS
src/emacs.c
src/frame.c
src/insdel.c
src/lisp.h
src/window.c
src/xfns.c
src/xterm.c

index fc17dbd83188cf6e83932591a228428ce5e0fb88..7bb0df88cb3de75f53d5fd2095e08598145d899e 100644 (file)
@@ -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])
index e0175bacfdfce517720c64e8f1b0f4e55c14d75e..d3eafaadf1923b161061ed1055af5a4ba4c70e0d 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -59,6 +59,13 @@ This allows the user to customize the prompt that is appended by
 \f
 * 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
index 214e2e2a2969c0286eabf31109790df13fb114d2..282e2f48100bb3e6f60b315f6a78db44e9bb8838 100644 (file)
@@ -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
 
index 983424b0bee7a7603ba9f47d271761c91299ef15..2cea96d4a328770d4e3a123952e0517a28ba14e4 100644 (file)
@@ -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)))
index e459d0cfa1774b0b234c09b3c3e2dfde7bdc63f0..b65a3fbd805b9304114ba5c91867fa17bb73a50f 100644 (file)
@@ -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. */
 
index 0bc400ba78f8fa60912b647b2c82804961c6d6df..cacd318c26f9d26e49525eaf32f0615c4310e3b1 100644 (file)
@@ -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)
index 6201a6f4a3672b82da8e269e2ad426ec198306d8..9334f922f89bb28a6389d1e7e4d8f173aec33451 100644 (file)
@@ -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.  */
index 3a129211463d0f021554813050cab0079957cd7f..9e004f6a6785a783ca8d15f6016f18845ad1299a 100644 (file)
@@ -37,6 +37,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "termhooks.h"
 #include "font.h"
 
+#ifdef HAVE_X_I18N
+#include "textconv.h"
+#endif
+
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -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;
 }
 
+\f
+
+/* 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
+
 \f
 /***********************************************************************
                            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);
index 1325d923be9fb50bd1de0ce3217d43c26d0324a3..5feaa4aef0fbe313d6c95942195eb19cd0782c59 100644 (file)
@@ -636,6 +636,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "xterm.h"
 #include <X11/cursorfont.h>
 
+#ifdef HAVE_X_I18N
+#include "textconv.h"
+#endif
+
 #ifdef USE_XCB
 #include <xcb/xproto.h>
 #include <xcb/xcb.h>
@@ -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)