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
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}))
public boolean
beginBatchEdit ()
{
- Log.d (TAG, "beginBatchEdit");
+ if (EmacsService.DEBUG_IC)
+ Log.d (TAG, "beginBatchEdit");
+
EmacsNative.beginBatchEdit (windowHandle);
return true;
}
public boolean
endBatchEdit ()
{
- Log.d (TAG, "endBatchEdit");
+ if (EmacsService.DEBUG_IC)
+ Log.d (TAG, "endBatchEdit");
+
EmacsNative.endBatchEdit (windowHandle);
return true;
}
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 ());
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;
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;
public boolean
finishComposingText ()
{
- Log.d (TAG, "finishComposingText");
+ if (EmacsService.DEBUG_IC)
+ Log.d (TAG, "finishComposingText");
EmacsNative.finishComposingText (windowHandle);
return true;
public String
getSelectedText (int flags)
{
- Log.d (TAG, "getSelectedText: " + flags);
+ if (EmacsService.DEBUG_IC)
+ Log.d (TAG, "getSelectedText: " + flags);
return EmacsNative.getSelectedText (windowHandle, flags);
}
public String
getTextAfterCursor (int length, int flags)
{
- Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
+ String string;
+
+ if (EmacsService.DEBUG_IC)
+ Log.d (TAG, "getTextAfterCursor: " + length + " " + flags);
- return EmacsNative.getTextAfterCursor (windowHandle, 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;
+
+ 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 EmacsNative.getTextBeforeCursor (windowHandle, length,
- flags);
+ 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);
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;
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;
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);
/* 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)
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,
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);
(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:
;;;###autoload
(define-key global-map "\C-j" 'electric-newline-and-maybe-indent)
+
;;;###autoload
(defun electric-newline-and-maybe-indent ()
"Insert a newline.
(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))
"Change value in PLIST of PROP to VAL, comparing with `equal'."
(declare (obsolete plist-put "29.1"))
(plist-put plist prop val #'equal))
+
+\f
+
+;; 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)))))))
+
\f
(provide 'simple)
"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)
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"
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:
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
return;
start = current_timespec ();
- end = timespec_add (start, make_timespec (0, 50000000));
+ end = timespec_add (start, make_timespec (0, 100000000));
while (true)
{
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. */
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)
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;
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
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))
{
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
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);
#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
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);
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.
/* 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. */
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
#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 ();
TEXTCONV_FINISH_COMPOSING_TEXT,
TEXTCONV_SET_COMPOSING_TEXT,
TEXTCONV_SET_COMPOSING_REGION,
- TEXTCONV_SET_POINT,
+ TEXTCONV_SET_POINT_AND_MARK,
TEXTCONV_DELETE_SURROUNDING_TEXT,
};
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
#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. */
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;
}
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);
}
\f
/* 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.
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. */
}
}
+ /* 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. */
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. */
/* 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. */
location. */
wanted = PT;
Finsert (1, &text);
+ record_buffer_change (wanted, PT, text);
if (position < 0)
{
}
}
+ /* This should deactivate the mark. */
+ call0 (Qdeactivate_mark);
unbind_to (count, Qnil);
}
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);
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)
{
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. */
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. */
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;
/* 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);
}
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:
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. */
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);
handled = true;
}
}
+
+ unbind_to (count, Qnil);
}
/* Start a ``batch edit'' in F. During a batch edit, point_changed
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;
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)
/* 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
/* 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. */
{
text_interface = interface;
}
+
+\f
+
+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;
+}
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 *);
};
#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);
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 *,
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;
{
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;
#ifdef HAVE_TEXT_CONVERSION
prev_point = w->last_point;
+ prev_mark = w->last_mark;
#endif
if (w == XWINDOW (selected_window))
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
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);