Update Android port

* doc/emacs/input.texi (On-Screen Keyboards):
* doc/lispref/commands.texi (Misc Events): Improve documentation
of text conversion stuff.
* java/org/gnu/emacs/EmacsInputConnection.java (beginBatchEdit)
(endBatchEdit, commitCompletion, commitText, deleteSurroundingText)
(finishComposingText, getSelectedText, getTextAfterCursor)
(EmacsInputConnection, setComposingRegion, performEditorAction)
(getExtractedText): Condition debug code on DEBUG_IC.
* java/org/gnu/emacs/EmacsService.java (EmacsService, updateIC):
Likewise.
* lisp/bindings.el (global-map):
* lisp/electric.el (global-map): Make `text-conversion'
`analyze-text-conversion'.
* lisp/progmodes/prog-mode.el (prog-mode): Enable text
conversion in input methods.
* lisp/simple.el (analyze-text-conversion): New function.
* lisp/textmodes/text-mode.el (text-conversion-style)
(text-mode): Likewise.
* src/androidterm.c (android_handle_ime_event): Handle
set_point_and_mark.
(android_sync_edit): Give Emacs 100 ms instead.
(android_perform_conversion_query): Skip the active region, not
the conversion region.
(getSelectedText): Implement properly.
(android_update_selection): Expose mark to input methods.
(android_reset_conversion): Handle `text-conversion-style'.
* src/buffer.c (init_buffer_once, syms_of_buffer): Add buffer
local variable `text-conversion-style'.
* src/buffer.h (struct buffer, bset_text_conversion_style): New
fields.
* src/emacs.c (android_emacs_init): Call syms_of_textconv.
* src/frame.h (enum text_conversion_operation): Rename
TEXTCONV_SET_POINT.
* src/lisp.h: Export syms_of_textconv.

* src/marker.c (set_marker_internal): Force redisplay when the
mark is set and the buffer is visible on builds that use text
conversion.  Explain why.

* src/textconv.c (copy_buffer): Fix copying past gap.
(get_mark): New function.
(textconv_query): Implement new flag.
(sync_overlay): New function.  Display conversion text in an
overlay.
(record_buffer_change, really_commit_text)
(really_set_composing_text, really_set_composing_region)
(really_delete_surrounding_text, really_set_point)
(handle_pending_conversion_events_1, decrement_inside)
(handle_pending_conversion_events, textconv_set_point)
(get_extracted_text, register_textconv_interface): Various fixes
and improvements.

* src/textconv.h (struct textconv_interface): Update
documentation.
* src/window.h (GCALIGNED_STRUCT): New field `prev_mark'.
* src/xdisp.c (mark_window_display_accurate_1): Handle
prev_mark.
This commit is contained in:
Po Lu 2023-02-15 22:51:44 +08:00
parent dd7066901f
commit cf24b61985
20 changed files with 607 additions and 81 deletions

View file

@ -121,6 +121,9 @@ screen, and send the appropriate key events to Emacs after completion.
However, on screen keyboard input methods directly perform edits to
the selected window of each frame; this is known as ``text
conversion'', or ``string conversion'' under the X Window System.
Emacs enables these input methods whenever the buffer local value of
@code{text-conversion-style} is non-@code{nil}, normally inside
derivatives of @code{text-mode} and @code{prog-mode}.
Text conversion is performed asynchronously whenever Emacs receives
a request to perform the conversion from the input method. After the

View file

@ -2205,9 +2205,51 @@ A few other event types represent occurrences within the system.
This kind of event is sent @strong{after} a system-wide input method
performs an edit to one or more buffers.
Once the event is sent, the input method may already have made changes
to multiple frames. @c TODO: allow querying which frames to which
@c changes have been made.
@vindex text-conversion-edits
Once the event is sent, the input method may already have made
changes to multiple buffers inside many different frames. To
determine which buffers have been changed, and what edits have
been made to them, use the variable
@code{text-conversion-edits}, which is set prior to each
@code{text-conversion} event being sent; it is a list of the
form:
@indentedblock
@w{@code{(@var{buffer} @var{beg} @var{end} @var{ephemeral})}}
@end indentedblock
Where @var{ephemeral} is the buffer which was modified,
@var{beg} and @var{end} are the positions of the edit at the
time it was completed, and @var{ephemeral} is either a string,
containing any text which was inserted, @code{t}, meaning that
the edit is a temporary edit made by the input method, and
@code{nil}, meaning that some text was deleted.
@vindex text-conversion-style
Whether or not this event is sent depends on the value of the
buffer-local variable @code{text-conversion-style}, which determines
how an input method that wishes to make edits to buffer contents will
behave.
This variable can have one three values:
@table @code
@item nil
This means that the input method will be disabled entirely, and key
events will be sent instead of text conversion events.
@item action
This means that the input method will be enabled, but @key{RET} will
be sent wherever the input method wanted to insert a new line.
@item t
This, or any other value, means that the input method will be enabled
and make edits terminated by @code{text-conversion} events.
@end itemize
Changes to the value of this variable will only take effect upon
the next redisplay after the buffer becomes the selected buffer
of a frame.
@cindex @code{delete-frame} event
@item (delete-frame (@var{frame}))

View file

@ -55,7 +55,9 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
beginBatchEdit ()
{
Log.d (TAG, "beginBatchEdit");
if (EmacsService.DEBUG_IC)
Log.d (TAG, "beginBatchEdit");
EmacsNative.beginBatchEdit (windowHandle);
return true;
}
@ -64,7 +66,9 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
endBatchEdit ()
{
Log.d (TAG, "endBatchEdit");
if (EmacsService.DEBUG_IC)
Log.d (TAG, "endBatchEdit");
EmacsNative.endBatchEdit (windowHandle);
return true;
}
@ -73,7 +77,9 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
commitCompletion (CompletionInfo info)
{
Log.d (TAG, "commitCompletion: " + info);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitCompletion: " + info);
EmacsNative.commitCompletion (windowHandle,
info.getText ().toString (),
info.getPosition ());
@ -84,7 +90,9 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
commitText (CharSequence text, int newCursorPosition)
{
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "commitText: " + text + " " + newCursorPosition);
EmacsNative.commitText (windowHandle, text.toString (),
newCursorPosition);
return true;
@ -94,8 +102,10 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
deleteSurroundingText (int leftLength, int rightLength)
{
Log.d (TAG, ("deleteSurroundingText: "
+ leftLength + " " + rightLength));
if (EmacsService.DEBUG_IC)
Log.d (TAG, ("deleteSurroundingText: "
+ leftLength + " " + rightLength));
EmacsNative.deleteSurroundingText (windowHandle, leftLength,
rightLength);
return true;
@ -105,7 +115,8 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
finishComposingText ()
{
Log.d (TAG, "finishComposingText");
if (EmacsService.DEBUG_IC)
Log.d (TAG, "finishComposingText");
EmacsNative.finishComposingText (windowHandle);
return true;
@ -115,7 +126,8 @@ public class EmacsInputConnection extends BaseInputConnection
public String
getSelectedText (int flags)
{
Log.d (TAG, "getSelectedText: " + flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getSelectedText: " + flags);
return EmacsNative.getSelectedText (windowHandle, flags);
}
@ -124,27 +136,44 @@ public class EmacsInputConnection extends BaseInputConnection
public String
getTextAfterCursor (int length, int flags)
{
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
String string;
return EmacsNative.getTextAfterCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
string = EmacsNative.getTextAfterCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public String
getTextBeforeCursor (int length, int flags)
{
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
String string;
return EmacsNative.getTextBeforeCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getTextBeforeCursor: " + length + " " + flags);
string = EmacsNative.getTextBeforeCursor (windowHandle, length,
flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, " --> " + string);
return string;
}
@Override
public boolean
setComposingText (CharSequence text, int newCursorPosition)
{
Log.d (TAG, "setComposingText: " + newCursorPosition);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setComposingText: " + newCursorPosition);
EmacsNative.setComposingText (windowHandle, text.toString (),
newCursorPosition);
@ -155,7 +184,8 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
setComposingRegion (int start, int end)
{
Log.d (TAG, "setComposingRegion: " + start + " " + end);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "setComposingRegion: " + start + " " + end);
EmacsNative.setComposingRegion (windowHandle, start, end);
return true;
@ -165,7 +195,8 @@ public class EmacsInputConnection extends BaseInputConnection
public boolean
performEditorAction (int editorAction)
{
Log.d (TAG, "performEditorAction: " + editorAction);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "performEditorAction: " + editorAction);
EmacsNative.performEditorAction (windowHandle, editorAction);
return true;
@ -175,7 +206,8 @@ public class EmacsInputConnection extends BaseInputConnection
public ExtractedText
getExtractedText (ExtractedTextRequest request, int flags)
{
Log.d (TAG, "getExtractedText: " + request + " " + flags);
if (EmacsService.DEBUG_IC)
Log.d (TAG, "getExtractedText: " + request + " " + flags);
return EmacsNative.getExtractedText (windowHandle, request,
flags);

View file

@ -88,6 +88,8 @@ public class EmacsService extends Service
/* Display metrics used by font backends. */
public DisplayMetrics metrics;
public static final boolean DEBUG_IC = false;
@Override
public int
onStartCommand (Intent intent, int flags, int startId)
@ -612,10 +614,11 @@ invocation of app_process (through android-emacs) can
int newSelectionEnd, int composingRegionStart,
int composingRegionEnd)
{
Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
+ " " + newSelectionEnd + " "
+ composingRegionStart + " "
+ composingRegionEnd));
if (DEBUG_IC)
Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
+ " " + newSelectionEnd + " "
+ composingRegionStart + " "
+ composingRegionEnd));
window.view.imManager.updateSelection (window.view,
newSelectionStart,
newSelectionEnd,
@ -626,7 +629,8 @@ invocation of app_process (through android-emacs) can
public void
resetIC (EmacsWindow window, int icMode)
{
Log.d (TAG, "resetIC: " + window);
if (DEBUG_IC)
Log.d (TAG, "resetIC: " + window);
window.view.setICMode (icMode);
window.view.imManager.restartInput (window.view);

View file

@ -1522,7 +1522,7 @@ if `inhibit-field-text-motion' is non-nil."
(define-key special-event-map [sigusr2] 'ignore)
;; Text conversion
(define-key global-map [text-conversion] 'ignore)
(define-key global-map [text-conversion] 'analyze-text-conversion)
;; Don't look for autoload cookies in this file.
;; Local Variables:

View file

@ -294,6 +294,7 @@ or comment."
;;;###autoload
(define-key global-map "\C-j" 'electric-newline-and-maybe-indent)
;;;###autoload
(defun electric-newline-and-maybe-indent ()
"Insert a newline.

View file

@ -336,6 +336,8 @@ support it."
(setq-local require-final-newline mode-require-final-newline)
(setq-local parse-sexp-ignore-comments t)
(add-hook 'context-menu-functions 'prog-context-menu 10 t)
;; Enable text conversion in this buffer.
(setq-local text-conversion-style t)
;; Any programming language is always written left to right.
(setq bidi-paragraph-direction 'left-to-right))

View file

@ -10864,6 +10864,58 @@ If the buffer doesn't exist, create it first."
"Change value in PLIST of PROP to VAL, comparing with `equal'."
(declare (obsolete plist-put "29.1"))
(plist-put plist prop val #'equal))
;; Text conversion support. See textconv.c for more details about
;; what this is.
;; Actually in textconv.c.
(defvar text-conversion-edits)
(defun analyze-text-conversion ()
"Analyze the results of the previous text conversion event.
For each insertion:
- Look for the insertion of a string starting or ending with a
character inside `auto-fill-chars', and fill the text around
it if `auto-fill-mode' is enabled.
- Look for the insertion of a new line, and cause automatic
line breaking of the previous line when `auto-fill-mode' is
enabled.
- Look for the insertion of a new line, and indent this new
line if `electric-indent-mode' is enabled."
(interactive)
(dolist (edit text-conversion-edits)
;; Filter out ephemeral edits and deletions.
(when (and (not (eq (nth 1 edit) (nth 2 edit)))
(stringp (nth 3 edit)))
(with-current-buffer (car edit)
(let* ((inserted (nth 3 edit))
;; Get the first and last characters.
(start (aref inserted 0))
(end (aref inserted (1- (length inserted))))
;; Figure out whether or not to auto-fill.
(auto-fill-p (or (aref auto-fill-chars start)
(aref auto-fill-chars end)))
;; Figure out whether or not a newline was inserted.
(newline-p (string-search "\n" inserted)))
(save-excursion
(if (and auto-fill-function newline-p)
(progn (goto-char (nth 2 edit))
(previous-logical-line)
(funcall auto-fill-function))
(when (and auto-fill-function auto-fill-p)
(progn (goto-char (nth 2 edit))
(funcall auto-fill-function)))))
(when (and electric-indent-mode newline-p)
(goto-char (nth 2 edit))
(indent-according-to-mode)))))))
(provide 'simple)

View file

@ -41,6 +41,9 @@
"Non-nil if this buffer's major mode is a variant of Text mode.")
(make-obsolete-variable 'text-mode-variant 'derived-mode-p "27.1")
;; Actually defined in textconv.c.
(defvar text-conversion-style)
(defvar text-mode-syntax-table
(let ((st (make-syntax-table)))
(modify-syntax-entry ?\" ". " st)
@ -125,6 +128,9 @@ You can thus get the full benefit of adaptive filling
Turning on Text mode runs the normal hook `text-mode-hook'."
(setq-local text-mode-variant t)
(setq-local require-final-newline mode-require-final-newline)
;; Enable text conversion in this buffer.
(setq-local text-conversion-style t)
(add-hook 'context-menu-functions 'text-mode-context-menu 10 t))
(define-derived-mode paragraph-indent-text-mode text-mode "Parindent"

View file

@ -621,8 +621,9 @@ android_handle_ime_event (union android_event *event, struct frame *f)
break;
case ANDROID_IME_SET_POINT:
textconv_set_point (f, event->ime.position,
event->ime.counter);
textconv_set_point_and_mark (f, event->ime.start,
event->ime.end,
event->ime.counter);
break;
case ANDROID_IME_START_BATCH_EDIT:
@ -4305,7 +4306,7 @@ static sem_t edit_sem;
Every time one of the text retrieval functions is called and an
editing request is made, Emacs gives the main thread approximately
50 ms to process it, in order to mostly keep the input method in
100 ms to process it, in order to mostly keep the input method in
sync with the buffer contents. */
static void
@ -4319,7 +4320,7 @@ android_sync_edit (void)
return;
start = current_timespec ();
end = timespec_add (start, make_timespec (0, 50000000));
end = timespec_add (start, make_timespec (0, 100000000));
while (true)
{
@ -4550,8 +4551,7 @@ android_perform_conversion_query (void *data)
if (!f)
return;
textconv_query (f, &context->query,
TEXTCONV_SKIP_CONVERSION_REGION);
textconv_query (f, &context->query, TEXTCONV_SKIP_ACTIVE_REGION);
/* context->query.text will have been set even if textconv_query
returns 1. */
@ -4648,13 +4648,6 @@ android_text_to_string (JNIEnv *env, char *buffer, ptrdiff_t n,
return string;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
jshort window)
{
return NULL;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getTextAfterCursor) (JNIEnv *env, jobject object, jshort window,
jint length, jint flags)
@ -4805,8 +4798,8 @@ NATIVE_NAME (setSelection) (JNIEnv *env, jobject object, jshort window,
event.ime.serial = ++event_serial;
event.ime.window = window;
event.ime.operation = ANDROID_IME_SET_POINT;
event.ime.start = 0;
event.ime.end = 0;
event.ime.start = start;
event.ime.end = end;
event.ime.length = 0;
event.ime.position = start;
event.ime.text = NULL;
@ -5068,6 +5061,34 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
return object;
}
JNIEXPORT jstring JNICALL
NATIVE_NAME (getSelectedText) (JNIEnv *env, jobject object,
jshort window)
{
struct android_get_extracted_text_context context;
jstring string;
context.hint_max_chars = -1;
context.token = 0;
context.text = NULL;
context.window = window;
android_sync_edit ();
if (android_run_in_emacs_thread (android_get_extracted_text,
&context))
return NULL;
if (!context.text)
return NULL;
/* Encode the returned text. */
string = android_text_to_string (env, context.text, context.length,
context.bytes);
free (context.text);
return string;
}
#ifdef __clang__
#pragma clang diagnostic pop
#else
@ -5083,7 +5104,8 @@ NATIVE_NAME (getExtractedText) (JNIEnv *env, jobject ignored_object,
static void
android_update_selection (struct frame *f, struct window *w)
{
ptrdiff_t start, end, point;
ptrdiff_t start, end, point, mark;
struct buffer *b;
if (MARKERP (f->conversion.compose_region_start))
{
@ -5103,12 +5125,20 @@ android_update_selection (struct frame *f, struct window *w)
if (!w)
w = XWINDOW (f->selected_window);
/* Figure out where the point is. */
/* Figure out where the point and mark are. If the mark is not
active, then point is set to equal mark. */
b = XBUFFER (w->contents);
point = min (w->last_point, TYPE_MAXIMUM (jint));
mark = ((!NILP (BVAR (b, mark_active))
&& w->last_mark != -1)
? min (w->last_mark, TYPE_MAXIMUM (jint))
: point);
/* Send the update. */
android_update_ic (FRAME_ANDROID_WINDOW (f), point, point,
start, end);
/* Send the update. Android doesn't have a concept of ``point'' and
``mark''; instead, it only has a selection, where the start of
the selection is less than or equal to the end. */
android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark),
max (point, mark), start, end);
}
/* Notice that the input method connection to F should be reset as a
@ -5117,16 +5147,32 @@ android_update_selection (struct frame *f, struct window *w)
static void
android_reset_conversion (struct frame *f)
{
enum android_ic_mode mode;
struct window *w;
struct buffer *buffer;
/* Reset the input method.
Pick an appropriate ``input mode'' based on whether or not the
minibuffer window is selected; this controls whether or not
``RET'' inserts a newline or sends an actual key event. */
w = XWINDOW (f->selected_window);
buffer = XBUFFER (WINDOW_BUFFER (w));
if (NILP (BVAR (buffer, text_conversion_style)))
mode = ANDROID_IC_MODE_NULL;
else if (EQ (BVAR (buffer, text_conversion_style),
Qaction))
mode = ANDROID_IC_MODE_ACTION;
else
mode = ANDROID_IC_MODE_TEXT;
android_reset_ic (FRAME_ANDROID_WINDOW (f),
(EQ (f->selected_window,
f->minibuffer_window)
? ANDROID_IC_MODE_ACTION
: ANDROID_IC_MODE_TEXT));
: mode));
/* Move its selection to the specified position. */
android_update_selection (f, NULL);

View file

@ -4710,6 +4710,7 @@ init_buffer_once (void)
#ifdef HAVE_TREE_SITTER
XSETFASTINT (BVAR (&buffer_local_flags, ts_parser_list), idx); ++idx;
#endif
XSETFASTINT (BVAR (&buffer_local_flags, text_conversion_style), idx); ++idx;
XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), idx); ++idx;
/* buffer_local_flags contains no pointers, so it's safe to treat it
@ -4780,6 +4781,9 @@ init_buffer_once (void)
bset_extra_line_spacing (&buffer_defaults, Qnil);
#ifdef HAVE_TREE_SITTER
bset_ts_parser_list (&buffer_defaults, Qnil);
#endif
#ifdef HAVE_TEXT_CONVERSION
bset_text_conversion_style (&buffer_defaults, Qnil);
#endif
bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);
@ -5852,6 +5856,22 @@ If t, displays a cursor related to the usual cursor type
You can also specify the cursor type as in the `cursor-type' variable.
Use Custom to set this variable and update the display. */);
/* While this is defined here, each *term.c module must implement
the logic itself. */
DEFVAR_PER_BUFFER ("text-conversion-style", &BVAR (current_buffer,
text_conversion_style),
Qnil,
"How the on screen keyboard's input method should insert in this buffer.\n\
When nil, the input method will be disabled and an ordinary keyboard\n\
will be displayed in its place.\n\
When the symbol `action', the input method will insert text directly, but\n\
will send `return' key events instead of inserting new line characters.\n\
Any other value means that the input method will insert text directly.\n\
\n\
This variable does not take immediate effect when set; rather, it takes\n\
effect upon the next redisplay after the selected window or buffer changes.");
DEFVAR_LISP ("kill-buffer-query-functions", Vkill_buffer_query_functions,
doc: /* List of functions called with no args to query before killing a buffer.
The buffer being killed will be current while the functions are running.

View file

@ -566,6 +566,11 @@ struct buffer
/* A list of tree-sitter parsers for this buffer. */
Lisp_Object ts_parser_list_;
#endif
/* What type of text conversion the input method should apply to
this buffer. */
Lisp_Object text_conversion_style_;
/* Cursor type to display in non-selected windows.
t means to use hollow box cursor.
See `cursor-type' for other values. */
@ -842,6 +847,12 @@ bset_width_table (struct buffer *b, Lisp_Object val)
b->width_table_ = val;
}
INLINE void
bset_text_conversion_style (struct buffer *b, Lisp_Object val)
{
b->text_conversion_style_ = val;
}
/* BUFFER_CEILING_OF (resp. BUFFER_FLOOR_OF), when applied to n, return
the max (resp. min) p such that

View file

@ -2000,6 +2000,9 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
#ifdef HAVE_WINDOW_SYSTEM
init_fringe_once (); /* Swap bitmaps if necessary. */
#endif /* HAVE_WINDOW_SYSTEM */
#ifdef HAVE_TEXT_CONVERSION
syms_of_textconv ();
#endif
}
init_alloc ();

View file

@ -86,7 +86,7 @@ enum text_conversion_operation
TEXTCONV_FINISH_COMPOSING_TEXT,
TEXTCONV_SET_COMPOSING_TEXT,
TEXTCONV_SET_COMPOSING_REGION,
TEXTCONV_SET_POINT,
TEXTCONV_SET_POINT_AND_MARK,
TEXTCONV_DELETE_SURROUNDING_TEXT,
};

View file

@ -5234,6 +5234,7 @@ extern void reset_frame_state (struct frame *);
extern void report_selected_window_change (struct frame *);
extern void report_point_change (struct frame *, struct window *,
struct buffer *);
extern void syms_of_textconv (void);
#endif
#ifdef HAVE_NATIVE_COMP

View file

@ -23,6 +23,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
#include "lisp.h"
#include "character.h"
#include "buffer.h"
#include "window.h"
/* Record one cached position found recently by
buf_charpos_to_bytepos or buf_bytepos_to_charpos. */
@ -566,6 +567,31 @@ set_marker_internal (Lisp_Object marker, Lisp_Object position,
attach_marker (m, b, charpos, bytepos);
}
#ifdef HAVE_TEXT_CONVERSION
/* If B is the buffer's mark and there is a window displaying B, and
text conversion is enabled while the mark is active, redisplay
the buffer.
propagate_window_redisplay will propagate this redisplay to the
window, which will eventually reach
mark_window_display_accurate_1. At that point,
report_point_change will be told to update the mark as seen by
the input method.
This is done all the way in (the seemingly irrelevant) redisplay
because the selection reported to the input method is actually what
is visible on screen, namely w->last_point. */
if (m->buffer
&& EQ (marker, BVAR (m->buffer, mark))
&& !NILP (BVAR (m->buffer, mark_active))
&& buffer_window_count (m->buffer))
bset_redisplay (m->buffer);
#endif
return marker;
}

View file

@ -89,13 +89,27 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
size = end0 - beg0;
memcpy (buffer, BYTE_POS_ADDR (beg0), size);
if (beg1 != -1)
memcpy (buffer, BEG_ADDR + beg1, end1 - beg1);
memcpy (buffer + size, BEG_ADDR + beg1, end1 - beg1);
}
/* Conversion query. */
/* Return the position of the active mark, or -1 if there is no mark
or it is not active. */
static ptrdiff_t
get_mark (void)
{
if (!NILP (BVAR (current_buffer, mark_active))
&& XMARKER (BVAR (current_buffer, mark))->buffer)
return marker_position (BVAR (current_buffer,
mark));
return -1;
}
/* Perform the text conversion operation specified in QUERY and return
the results.
@ -109,6 +123,9 @@ copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
If FLAGS & TEXTCONV_SKIP_CONVERSION_REGION, then first move PT past
the conversion region in the specified direction if it is inside.
If FLAGS & TEXTCONV_SKIP_ACTIVE_REGION, then also move PT past the
region if the mark is active.
Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION
or if deleting the text was successful, and 1 otherwise. */
@ -169,6 +186,39 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query,
}
}
/* Likewise for the region if the mark is active. */
if (flags & TEXTCONV_SKIP_ACTIVE_REGION)
{
temp = get_mark ();
if (temp == -1)
goto escape;
start = min (temp, PT);
end = max (temp, PT);
if (pos >= start && pos < end)
{
switch (query->direction)
{
case TEXTCONV_FORWARD_CHAR:
case TEXTCONV_FORWARD_WORD:
case TEXTCONV_CARET_DOWN:
case TEXTCONV_NEXT_LINE:
case TEXTCONV_LINE_START:
pos = end;
break;
default:
pos = max (BEGV, start);
break;
}
}
}
escape:
/* If pos is outside the accessible part of the buffer or if it
overflows, move back to point or to the extremes of the
accessible region. */
@ -335,6 +385,55 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query,
return 0;
}
/* Update the overlay displaying the conversion area on F after a
change to the conversion region. */
static void
sync_overlay (struct frame *f)
{
if (MARKERP (f->conversion.compose_region_start))
{
if (NILP (f->conversion.compose_region_overlay))
{
f->conversion.compose_region_overlay
= Fmake_overlay (f->conversion.compose_region_start,
f->conversion.compose_region_end, Qnil,
Qt, Qnil);
Foverlay_put (f->conversion.compose_region_overlay,
Qface, Qunderline);
}
Fmove_overlay (f->conversion.compose_region_overlay,
f->conversion.compose_region_start,
f->conversion.compose_region_end, Qnil);
}
else if (!NILP (f->conversion.compose_region_overlay))
{
Fdelete_overlay (f->conversion.compose_region_overlay);
f->conversion.compose_region_overlay = Qnil;
}
}
/* Record a change to the current buffer as a result of an
asynchronous text conversion operation on F.
Consult the doc string of `text-conversion-edits' for the meaning
of BEG, END, and EPHEMERAL. */
static void
record_buffer_change (ptrdiff_t beg, ptrdiff_t end,
Lisp_Object ephemeral)
{
Lisp_Object buffer;
XSETBUFFER (buffer, current_buffer);
Vtext_conversion_edits
= Fcons (list4 (buffer, make_fixnum (beg),
make_fixnum (end), ephemeral),
Vtext_conversion_edits);
}
/* Reset F's text conversion state. Delete any overlays or
markers inside. */
@ -438,8 +537,10 @@ really_commit_text (struct frame *f, EMACS_INT position,
/* Replace its contents. */
start = marker_position (f->conversion.compose_region_start);
end = marker_position (f->conversion.compose_region_end);
safe_del_range (start, end);
del_range (start, end);
record_buffer_change (start, start, Qnil);
Finsert (1, &text);
record_buffer_change (start, PT, text);
/* Move to a the position specified in POSITION. */
@ -493,6 +594,7 @@ really_commit_text (struct frame *f, EMACS_INT position,
location. */
wanted = PT;
Finsert (1, &text);
record_buffer_change (wanted, PT, text);
if (position < 0)
{
@ -520,6 +622,8 @@ really_commit_text (struct frame *f, EMACS_INT position,
}
}
/* This should deactivate the mark. */
call0 (Qdeactivate_mark);
unbind_to (count, Qnil);
}
@ -575,12 +679,11 @@ really_set_composing_text (struct frame *f, ptrdiff_t position,
if (!MARKERP (f->conversion.compose_region_start))
{
f->conversion.compose_region_start = Fmake_marker ();
f->conversion.compose_region_end = Fmake_marker ();
Fset_marker (f->conversion.compose_region_start,
Fpoint (), Qnil);
Fset_marker (f->conversion.compose_region_end,
Fpoint (), Qnil);
f->conversion.compose_region_start
= build_marker (current_buffer, PT, PT_BYTE);
f->conversion.compose_region_end
= build_marker (current_buffer, PT, PT_BYTE);
Fset_marker_insertion_type (f->conversion.compose_region_end,
Qt);
@ -595,13 +698,19 @@ really_set_composing_text (struct frame *f, ptrdiff_t position,
Fwiden ();
start = marker_position (f->conversion.compose_region_start);
end = marker_position (f->conversion.compose_region_end);
safe_del_range (start, end);
del_range (start, end);
set_point (start);
if (start != end)
record_buffer_change (start, start, Qnil);
}
/* Insert the new text. */
Finsert (1, &text);
if (start != PT)
record_buffer_change (start, PT, Qnil);
/* Now move point to an appropriate location. */
if (position < 0)
{
@ -632,6 +741,12 @@ really_set_composing_text (struct frame *f, ptrdiff_t position,
set_point (wanted);
/* This should deactivate the mark. */
call0 (Qdeactivate_mark);
/* Move the composition overlay. */
sync_overlay (f);
/* If PT hasn't changed, the conversion region definitely has.
Otherwise, redisplay will update the input method instead. */
@ -693,18 +808,21 @@ really_set_composing_region (struct frame *f, ptrdiff_t start,
make_fixnum (start), Qnil);
Fset_marker (f->conversion.compose_region_end,
make_fixnum (end), Qnil);
sync_overlay (f);
unbind_to (count, Qnil);
}
/* Delete LEFT and RIGHT chars around point. */
/* Delete LEFT and RIGHT chars around point or the active mark,
whichever is larger, avoiding the composing region if
necessary. */
static void
really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
ptrdiff_t right)
{
specpdl_ref count;
ptrdiff_t start, end;
ptrdiff_t start, end, a, b, a1, b1, lstart, rstart;
/* If F's old selected window is no longer live, fail. */
@ -719,21 +837,71 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
redisplay. */
Fselect_window (f->old_selected_window, Qt);
start = max (BEGV, PT - left);
end = min (ZV, PT + right);
/* Figure out where to start deleting from. */
a = get_mark ();
if (a != -1 && a != PT)
lstart = rstart = max (a, PT);
else
lstart = rstart = PT;
/* Avoid the composing text. This behavior is identical to how
Android's BaseInputConnection actually implements avoiding the
composing span. */
if (MARKERP (f->conversion.compose_region_start))
{
a = marker_position (f->conversion.compose_region_start);
b = marker_position (f->conversion.compose_region_end);
a1 = min (a, b);
b1 = max (a, b);
lstart = min (lstart, min (PT, a1));
rstart = max (rstart, max (PT, b1));
}
if (lstart == rstart)
{
start = max (BEGV, lstart - left);
end = min (ZV, rstart + right);
del_range (start, end);
record_buffer_change (start, start, Qnil);
}
else
{
start = max (BEGV, lstart - left);
end = lstart;
del_range (start, end);
record_buffer_change (start, start, Qnil);
start = rstart;
end = min (ZV, rstart + right);
del_range (start, end);
record_buffer_change (start, start, Qnil);
}
/* if the mark is now equal to start, deactivate it. */
if (get_mark () == PT)
call0 (Qdeactivate_mark);
safe_del_range (start, end);
unbind_to (count, Qnil);
}
/* Set point in F to POSITION.
/* Set point in F to POSITION. If MARK is not POSITION, activate the
mark and set MARK to that as well.
If it has not changed, signal an update through the text input
interface, which is necessary for the IME to acknowledge that the
change has completed. */
static void
really_set_point (struct frame *f, ptrdiff_t point)
really_set_point_and_mark (struct frame *f, ptrdiff_t point,
ptrdiff_t mark)
{
specpdl_ref count;
@ -763,6 +931,11 @@ really_set_point (struct frame *f, ptrdiff_t point)
/* Set the point. */
Fgoto_char (make_fixnum (point));
if (mark == point && BVAR (current_buffer, mark_active))
call0 (Qdeactivate_mark);
else
call1 (Qpush_mark, make_fixnum (mark));
unbind_to (count, Qnil);
}
@ -845,8 +1018,9 @@ handle_pending_conversion_events_1 (struct frame *f,
XFIXNUM (XCDR (data)));
break;
case TEXTCONV_SET_POINT:
really_set_point (f, XFIXNUM (data));
case TEXTCONV_SET_POINT_AND_MARK:
really_set_point_and_mark (f, XFIXNUM (XCAR (data)),
XFIXNUM (XCDR (data)));
break;
case TEXTCONV_DELETE_SURROUNDING_TEXT:
@ -858,6 +1032,17 @@ handle_pending_conversion_events_1 (struct frame *f,
unbind_to (count, Qnil);
}
/* Decrement the variable pointed to by *PTR. */
static void
decrement_inside (void *ptr)
{
int *i;
i = ptr;
(*i)--;
}
/* Process any outstanding text conversion events.
This may run Lisp or signal. */
@ -868,9 +1053,22 @@ handle_pending_conversion_events (void)
Lisp_Object tail, frame;
struct text_conversion_action *action, *next;
bool handled;
static int inside;
specpdl_ref count;
handled = false;
/* Reset Vtext_conversion_edits. Do not do this if called
reentrantly. */
if (!inside)
Vtext_conversion_edits = Qnil;
inside++;
count = SPECPDL_INDEX ();
record_unwind_protect_ptr (decrement_inside, &inside);
FOR_EACH_FRAME (tail, frame)
{
f = XFRAME (frame);
@ -905,6 +1103,8 @@ handle_pending_conversion_events (void)
handled = true;
}
}
unbind_to (count, Qnil);
}
/* Start a ``batch edit'' in F. During a batch edit, point_changed
@ -1057,13 +1257,13 @@ set_composing_region (struct frame *f, ptrdiff_t start,
input_pending = true;
}
/* Move point in F's selected buffer to POINT.
/* Move point in F's selected buffer to POINT and maybe push MARK.
COUNTER means the same as in `start_batch_edit'. */
void
textconv_set_point (struct frame *f, ptrdiff_t point,
unsigned long counter)
textconv_set_point_and_mark (struct frame *f, ptrdiff_t point,
ptrdiff_t mark, unsigned long counter)
{
struct text_conversion_action *action, **last;
@ -1071,8 +1271,9 @@ textconv_set_point (struct frame *f, ptrdiff_t point,
point = MOST_POSITIVE_FIXNUM;
action = xmalloc (sizeof *action);
action->operation = TEXTCONV_SET_POINT;
action->data = make_fixnum (point);
action->operation = TEXTCONV_SET_POINT_AND_MARK;
action->data = Fcons (make_fixnum (point),
make_fixnum (mark));
action->next = NULL;
action->counter = counter;
for (last = &f->conversion.actions; *last; last = &(*last)->next)
@ -1105,6 +1306,9 @@ delete_surrounding_text (struct frame *f, ptrdiff_t left,
/* Return N characters of text around point in F's old selected
window.
If N is -1, return the text between point and mark instead, given
that the mark is active.
Set *N to the actual number of characters returned, *START_RETURN
to the position of the first character returned, *OFFSET to the
offset of point within that text, *LENGTH to the actual number of
@ -1137,13 +1341,38 @@ get_extracted_text (struct frame *f, ptrdiff_t n,
/* Temporarily switch to F's selected window at the time of the last
redisplay. */
Fselect_window (f->old_selected_window, Qt);
buffer = NULL;
/* Figure out the bounds of the text to return. */
start = PT - n / 2;
end = PT + n - n / 2;
if (n != -1)
{
start = PT - n / 2;
end = PT + n - n / 2;
}
else
{
if (!NILP (BVAR (current_buffer, mark_active))
&& XMARKER (BVAR (current_buffer, mark))->buffer)
{
start = marker_position (BVAR (current_buffer, mark));
end = PT;
/* Sort start and end. start_byte is used to hold a
temporary value. */
if (start > end)
{
start_byte = end;
end = start;
start = start_byte;
}
}
else
goto finish;
}
start = max (start, BEGV);
end = min (end, ZV);
buffer = NULL;
/* Detect overflow. */
@ -1218,3 +1447,37 @@ register_textconv_interface (struct textconv_interface *interface)
{
text_interface = interface;
}
void
syms_of_textconv (void)
{
DEFSYM (Qaction, "action");
DEFSYM (Qtext_conversion, "text-conversion");
DEFSYM (Qpush_mark, "push-mark");
DEFSYM (Qunderline, "underline");
DEFVAR_LISP ("text-conversion-edits", Vtext_conversion_edits,
doc: /* List of buffers that were last edited as a result of text conversion.
This list can be used while handling a `text-conversion' event to
determine the changes which have taken place.
Each element of the list describes a single edit in a buffer, of the
form:
(BUFFER BEG END EPHEMERAL)
If an insertion or a change occured, then BEG and END are buffer
positions denote the bounds of the text that was changed or inserted.
If EPHEMERAL is t, then the input method will shortly make more
changes to the text, so any actions that would otherwise be taken
(such as indenting or automatically filling text) should not take
place; otherwise, it is a string describing the text which was
inserted.
If a deletion occured, then BEG and END are the same, and EPHEMERAL is
nil. */);
Vtext_conversion_edits = Qnil;
}

View file

@ -35,9 +35,10 @@ struct textconv_interface
unexpected buffer change occurs.) */
void (*reset) (struct frame *);
/* Notice that point has moved in the specified frame's selected
window's selected buffer. The second argument is the window
whose point changed, and the third argument is the buffer. */
/* Notice that point or mark has moved in the specified frame's
selected window's selected buffer. The second argument is the
window whose point changed, and the third argument is the
buffer. */
void (*point_changed) (struct frame *, struct window *,
struct buffer *);
@ -118,6 +119,7 @@ struct textconv_callback_struct
};
#define TEXTCONV_SKIP_CONVERSION_REGION (1 << 0)
#define TEXTCONV_SKIP_ACTIVE_REGION (1 << 1)
extern int textconv_query (struct frame *, struct textconv_callback_struct *,
int);
@ -132,7 +134,8 @@ extern void set_composing_text (struct frame *, Lisp_Object,
ptrdiff_t, unsigned long);
extern void set_composing_region (struct frame *, ptrdiff_t, ptrdiff_t,
unsigned long);
extern void textconv_set_point (struct frame *, ptrdiff_t, unsigned long);
extern void textconv_set_point_and_mark (struct frame *, ptrdiff_t,
ptrdiff_t, unsigned long);
extern void delete_surrounding_text (struct frame *, ptrdiff_t,
ptrdiff_t, unsigned long);
extern char *get_extracted_text (struct frame *, ptrdiff_t, ptrdiff_t *,

View file

@ -286,6 +286,10 @@ struct window
it should be positive. */
ptrdiff_t last_point;
/* Value of mark in the selected window at the time of the last
redisplay. */
ptrdiff_t last_mark;
/* Line number and position of a line somewhere above the top of the
screen. If this field is zero, it means we don't have a base line. */
ptrdiff_t base_line_number;

View file

@ -17268,7 +17268,7 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p)
{
struct buffer *b = XBUFFER (w->contents);
#ifdef HAVE_TEXT_CONVERSION
ptrdiff_t prev_point;
ptrdiff_t prev_point, prev_mark;
#endif
w->last_modified = accurate_p ? BUF_MODIFF (b) : 0;
@ -17301,6 +17301,7 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p)
#ifdef HAVE_TEXT_CONVERSION
prev_point = w->last_point;
prev_mark = w->last_mark;
#endif
if (w == XWINDOW (selected_window))
@ -17308,6 +17309,11 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p)
else
w->last_point = marker_position (w->pointm);
if (XMARKER (BVAR (b, mark))->buffer == b)
w->last_mark = marker_position (BVAR (b, mark));
else
w->last_mark = -1;
#ifdef HAVE_TEXT_CONVERSION
/* Point motion is only propagated to the input method for use
in text conversion during a redisplay. While this can lead
@ -17323,7 +17329,8 @@ mark_window_display_accurate_1 (struct window *w, bool accurate_p)
during such a change, so the consequences are not that
severe. */
if (prev_point != w->last_point
if ((prev_point != w->last_point
|| prev_mark != w->last_mark)
&& FRAME_WINDOW_P (WINDOW_XFRAME (w))
&& w == XWINDOW (WINDOW_XFRAME (w)->selected_window))
report_point_change (WINDOW_XFRAME (w), w, b);