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) <TEXTCONV_REPLACE_TEXT>:
New case.
(replace_text): New function.

* src/textconv.h: Update prototypes.
This commit is contained in:
Po Lu 2023-10-05 14:23:20 +08:00
parent 253f1aff1a
commit 123b77436e
7 changed files with 238 additions and 1 deletions

View file

@ -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

View file

@ -26,6 +26,7 @@
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 static native void deleteSurroundingText (short window,
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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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 *);