From 123b77436e187c6254d4585d08135a44077528d1 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Thu, 5 Oct 2023 14:23:20 +0800 Subject: [PATCH] Introduce an input method callback required by Android 34 * java/org/gnu/emacs/EmacsInputConnection.java (replaceText): New function. * java/org/gnu/emacs/EmacsNative.java (replaceText): Declare native function. * src/androidgui.h (enum android_ime_operation): New operation ANDROID_IME_REPLACE_TEXT. * src/androidterm.c (android_handle_ime_event): Decode text when encountering an ANDROID_IME_REPLACE_TEXT operation. Return if decoding overflowed rather than presenting Qnil to textconv functions. (replaceText): New JNI function. * src/frame.h (enum text_conversion_operation): New operation TEXTCONV_REPLACE_TEXT. * src/textconv.c (really_commit_text): Move point to start if the composing region is set. (really_replace_text): New function. (handle_pending_conversion_events_1) : New case. (replace_text): New function. * src/textconv.h: Update prototypes. --- java/org/gnu/emacs/EmacsInputConnection.java | 15 ++ java/org/gnu/emacs/EmacsNative.java | 4 + src/androidgui.h | 1 + src/androidterm.c | 47 ++++++ src/frame.h | 1 + src/textconv.c | 168 ++++++++++++++++++- src/textconv.h | 3 + 7 files changed, 238 insertions(+), 1 deletion(-) diff --git a/java/org/gnu/emacs/EmacsInputConnection.java b/java/org/gnu/emacs/EmacsInputConnection.java index c3764a7b29f..7f6331205cb 100644 --- a/java/org/gnu/emacs/EmacsInputConnection.java +++ b/java/org/gnu/emacs/EmacsInputConnection.java @@ -628,6 +628,21 @@ public final class EmacsInputConnection implements InputConnection batchEditCount = 0; } + @Override + public boolean + replaceText (int start, int end, CharSequence text, + int newCursorPosition, TextAttribute attributes) + { + if (EmacsService.DEBUG_IC) + Log.d (TAG, ("replaceText: " + text + ":: " + start + "," + + end + "," + newCursorPosition)); + + EmacsNative.replaceText (windowHandle, start, end, + text.toString (), newCursorPosition, + attributes); + return true; + } + public void diff --git a/java/org/gnu/emacs/EmacsNative.java b/java/org/gnu/emacs/EmacsNative.java index a4b45aafbc1..d8524d92130 100644 --- a/java/org/gnu/emacs/EmacsNative.java +++ b/java/org/gnu/emacs/EmacsNative.java @@ -26,6 +26,7 @@ import android.graphics.Bitmap; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.SurroundingText; +import android.view.inputmethod.TextAttribute; import android.view.inputmethod.TextSnapshot; public final class EmacsNative @@ -219,6 +220,9 @@ public final class EmacsNative int leftLength, int rightLength); public static native void finishComposingText (short window); + public static native void replaceText (short window, int start, int end, + String text, int newCursorPosition, + TextAttribute attributes); public static native String getSelectedText (short window, int flags); public static native String getTextAfterCursor (short window, int length, int flags); diff --git a/src/androidgui.h b/src/androidgui.h index 14225f7bf80..936706b092e 100644 --- a/src/androidgui.h +++ b/src/androidgui.h @@ -463,6 +463,7 @@ enum android_ime_operation ANDROID_IME_END_BATCH_EDIT, ANDROID_IME_REQUEST_SELECTION_UPDATE, ANDROID_IME_REQUEST_CURSOR_UPDATES, + ANDROID_IME_REPLACE_TEXT, }; enum diff --git a/src/androidterm.c b/src/androidterm.c index 438f8ce1fbb..9b00ad85642 100644 --- a/src/androidterm.c +++ b/src/androidterm.c @@ -687,9 +687,17 @@ android_handle_ime_event (union android_event *event, struct frame *f) { case ANDROID_IME_COMMIT_TEXT: case ANDROID_IME_SET_COMPOSING_TEXT: + case ANDROID_IME_REPLACE_TEXT: text = android_decode_utf16 (event->ime.text, event->ime.length); xfree (event->ime.text); + + /* Return should text be long enough that it overflows ptrdiff_t. + Such circumstances are detected within android_decode_utf16. */ + + if (NILP (text)) + return; + break; default: @@ -773,6 +781,12 @@ android_handle_ime_event (union android_event *event, struct frame *f) case ANDROID_IME_REQUEST_CURSOR_UPDATES: android_request_cursor_updates (f, event->ime.length); break; + + case ANDROID_IME_REPLACE_TEXT: + replace_text (f, event->ime.start, event->ime.end, + text, event->ime.position, + event->ime.counter); + break; } } @@ -4856,6 +4870,39 @@ NATIVE_NAME (finishComposingText) (JNIEnv *env, jobject object, android_write_event (&event); } +JNIEXPORT void JNICALL +NATIVE_NAME (replaceText) (JNIEnv *env, jobject object, jshort window, + jint start, jint end, jobject text, + int new_cursor_position, jobject attribute) +{ + JNI_STACK_ALIGNMENT_PROLOGUE; + + union android_event event; + size_t length; + + /* First, obtain a copy of the Java string. */ + text = android_copy_java_string (env, text, &length); + + if (!text) + return; + + /* Next, populate the event with the information in this function's + arguments. */ + + event.ime.type = ANDROID_INPUT_METHOD; + event.ime.serial = ++event_serial; + event.ime.window = window; + event.ime.operation = ANDROID_IME_REPLACE_TEXT; + event.ime.start = start + 1; + event.ime.end = end + 1; + event.ime.length = length; + event.ime.position = new_cursor_position; + event.ime.text = text; + event.ime.counter = ++edit_counter; + + android_write_event (&event); +} + /* Structure describing the context used for a text query. */ struct android_conversion_query_context diff --git a/src/frame.h b/src/frame.h index f4726f1c0e5..d826ae56e8b 100644 --- a/src/frame.h +++ b/src/frame.h @@ -90,6 +90,7 @@ enum text_conversion_operation TEXTCONV_DELETE_SURROUNDING_TEXT, TEXTCONV_REQUEST_POINT_UPDATE, TEXTCONV_BARRIER, + TEXTCONV_REPLACE_TEXT, }; /* Structure describing a single edit being performed by the input diff --git a/src/textconv.c b/src/textconv.c index 57daa7e53b6..bd72562317f 100644 --- a/src/textconv.c +++ b/src/textconv.c @@ -616,6 +616,12 @@ really_commit_text (struct frame *f, EMACS_INT position, end = max (mark, PT); } + /* If it transpires that the start of the compose region is not + point, move point there. */ + + if (start != PT) + set_point (start); + /* Now delete whatever needs to go. */ del_range_1 (start, end, true, false); @@ -635,7 +641,7 @@ really_commit_text (struct frame *f, EMACS_INT position, record_buffer_change (start, PT, text); } - /* Move to a the position specified in POSITION. */ + /* Move to the position specified in POSITION. */ if (position <= 0) { @@ -1154,6 +1160,135 @@ really_set_point_and_mark (struct frame *f, ptrdiff_t point, unbind_to (count, Qnil); } +/* Remove the composing region. Replace the text between START and + END in F's selected window with TEXT, then set point to POSITION + relative to it. If the mark is active, deactivate it. */ + +static void +really_replace_text (struct frame *f, ptrdiff_t start, ptrdiff_t end, + Lisp_Object text, ptrdiff_t position) +{ + specpdl_ref count; + ptrdiff_t new_start, new_end, wanted; + struct window *w; + + /* If F's old selected window is no longer alive, fail. */ + + if (!WINDOW_LIVE_P (f->old_selected_window)) + return; + + count = SPECPDL_INDEX (); + record_unwind_protect (restore_selected_window, + selected_window); + + /* Make the composition region markers point elsewhere. */ + + if (!NILP (f->conversion.compose_region_start)) + { + Fset_marker (f->conversion.compose_region_start, Qnil, Qnil); + Fset_marker (f->conversion.compose_region_end, Qnil, Qnil); + f->conversion.compose_region_start = Qnil; + f->conversion.compose_region_end = Qnil; + + /* Notify the IME of an update to the composition region, + inasmuch as the point might not change if START and END are + identical and TEXT is empty, among other circumstances. */ + + if (text_interface + && text_interface->compose_region_changed) + (*text_interface->compose_region_changed) (f); + } + + /* Delete the composition region overlay. */ + + if (!NILP (f->conversion.compose_region_overlay)) + Fdelete_overlay (f->conversion.compose_region_overlay); + + /* Temporarily switch to F's selected window at the time of the last + redisplay. */ + select_window (f->old_selected_window, Qt); + + /* Sort START and END by magnitude. */ + new_start = min (start, end); + new_end = max (start, end); + + /* Now constrain both to the accessible region. */ + + if (new_start < BEGV) + new_start = BEGV; + else if (new_start > ZV) + new_start = ZV; + + if (new_end < BEGV) + new_end = BEGV; + else if (new_end > ZV) + new_end = ZV; + + start = new_start; + end = new_end; + + /* This should deactivate the mark. */ + call0 (Qdeactivate_mark); + + /* Go to start. */ + set_point (start); + + /* Now delete the text in between, and save PT before TEXT is + inserted. */ + del_range_1 (start, end, true, false); + record_buffer_change (start, start, Qt); + wanted = PT; + + /* So long as TEXT isn't empty, insert it now. */ + + if (SCHARS (text)) + { + /* Insert the new text. Make sure to inherit text properties + from the surroundings: if this doesn't happen, CC Mode + fontification might grow confused and become very slow. */ + + insert_from_string (text, 0, 0, SCHARS (text), + SBYTES (text), true); + record_buffer_change (start, PT, text); + } + + /* Now, move point to the position designated by POSITION. */ + + if (position <= 0) + { + if (INT_ADD_WRAPV (wanted, position, &wanted) + || wanted < BEGV) + wanted = BEGV; + + if (wanted > ZV) + wanted = ZV; + + set_point (wanted); + } + else + { + wanted = PT; + + if (INT_ADD_WRAPV (wanted, position - 1, &wanted) + || wanted > ZV) + wanted = ZV; + + if (wanted < BEGV) + wanted = BEGV; + + set_point (wanted); + } + + /* Print some debugging information. */ + TEXTCONV_DEBUG ("text inserted: %s, point now: %zd", + SSDATA (text), PT); + + /* Update the ephemeral last point. */ + w = XWINDOW (selected_window); + w->ephemeral_last_point = PT; + unbind_to (count, Qnil); +} + /* Complete the edit specified by the counter value inside *TOKEN. */ static void @@ -1325,6 +1460,13 @@ handle_pending_conversion_events_1 (struct frame *f, if (w) w->ephemeral_last_point = window_point (w); break; + + case TEXTCONV_REPLACE_TEXT: + really_replace_text (f, XFIXNUM (XCAR (data)), + XFIXNUM (XCAR (XCDR (data))), + XCAR (XCDR (XCDR (data))), + XFIXNUM (XCAR (XCDR (XCDR (XCDR (data)))))); + break; } /* Signal success. */ @@ -1679,6 +1821,30 @@ textconv_barrier (struct frame *f, unsigned long counter) input_pending = true; } +/* Remove the composing region. Replace the text between START and + END within F's selected window with TEXT; deactivate the mark if it + is active. Subsequently, set point to POSITION relative to TEXT, + much as `commit_text' would. */ + +void +replace_text (struct frame *f, ptrdiff_t start, ptrdiff_t end, + Lisp_Object text, ptrdiff_t position, + unsigned long counter) +{ + struct text_conversion_action *action, **last; + + action = xmalloc (sizeof *action); + action->operation = TEXTCONV_REPLACE_TEXT; + action->data = list4 (make_fixnum (start), make_fixnum (end), + text, make_fixnum (position)); + action->next = NULL; + action->counter = counter; + for (last = &f->conversion.actions; *last; last = &(*last)->next) + ;; + *last = action; + input_pending = true; +} + /* Return N characters of text around point in frame F's old selected window. diff --git a/src/textconv.h b/src/textconv.h index feac5b805af..c677c07e9aa 100644 --- a/src/textconv.h +++ b/src/textconv.h @@ -142,6 +142,9 @@ extern void delete_surrounding_text (struct frame *, ptrdiff_t, ptrdiff_t, unsigned long); extern void request_point_update (struct frame *, unsigned long); extern void textconv_barrier (struct frame *, unsigned long); +extern void replace_text (struct frame *, ptrdiff_t, ptrdiff_t, + Lisp_Object, ptrdiff_t, unsigned long); + extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, ptrdiff_t *, bool *); -- 2.39.2