1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-06 06:20:55 -08:00

Take fields into account during text conversion

* lisp/cus-edit.el (Custom-mode): Enable text conversion, now
that fields are correctly treated.

* src/alloc.c (mark_frame): Mark f->conversion.field.

* src/androidterm.c (android_update_selection): Adjust
conversion region and selection position by the field start and
end.

* src/editfns.c (find_field): Export function.

* src/frame.c (make_frame): Clear f->conversion.field.

* src/frame.h (struct text_conversion_state) <field>: New field.

* src/lisp.h (find_fields, reset_frame_conversion): Export
functions.

* src/minibuf.c (Fread_from_minibuffer): Reset frame conversion
if Voverriding_text_conversion_style is set.

* src/textconv.c (textconv_query): Narrow to field.
(reset_frame_conversion): New function.
(reset_frame_state): Clear conversion field.
(really_delete_surrounding_text): Narrow to field.
(locate_and_save_position_in_field): New function.
(really_request_point_update, really_set_point_and_mark)
(complete_edit_check, handle_pending_conversion_events_1)
(handle_pending_conversion_events, get_conversion_field)
(set_composing_region, textconv_set_point_and_mark, replace_text)
(get_extracted_text, get_surrounding_text, report_point_change):
Compute, narrow to and offset by the currently active field
whenever point is updated or a command is received.
(syms_of_textconv): Revise doc strings.

* src/textconv.h (get_conversion_field): Export function.
This commit is contained in:
Po Lu 2024-04-21 21:51:09 +08:00
parent ee2e0031d8
commit 430088c9cc
10 changed files with 324 additions and 38 deletions

View file

@ -5400,6 +5400,7 @@ if that value is non-nil."
(setq-local custom--invocation-options nil
custom--hidden-state 'hidden)
(setq-local revert-buffer-function #'custom--revert-buffer)
(setq-local text-conversion-style 'action)
(make-local-variable 'custom-options)
(make-local-variable 'custom-local-buffer)
(custom--initialize-widget-variables)

View file

@ -7050,6 +7050,7 @@ mark_frame (struct Lisp_Vector *ptr)
mark_object (f->conversion.compose_region_start);
mark_object (f->conversion.compose_region_end);
mark_object (f->conversion.compose_region_overlay);
mark_object (f->conversion.field);
for (tem = f->conversion.actions; tem; tem = tem->next)
mark_object (tem->data);

View file

@ -6265,14 +6265,24 @@ android_update_selection (struct frame *f, struct window *w)
jobject extracted;
jstring string;
bool mark_active;
ptrdiff_t field_start, field_end;
/* Offset these values by the start offset of the field. */
get_conversion_field (f, &field_start, &field_end);
if (MARKERP (f->conversion.compose_region_start))
{
eassert (MARKERP (f->conversion.compose_region_end));
/* Indexing in android starts from 0 instead of 1. */
start = marker_position (f->conversion.compose_region_start) - 1;
end = marker_position (f->conversion.compose_region_end) - 1;
start = marker_position (f->conversion.compose_region_start);
end = marker_position (f->conversion.compose_region_end);
/* Offset and detect underflow. */
start = max (start, field_start) - field_start - 1;
end = min (end, field_end) - field_start - 1;
if (end < 0 || start < 0)
end = start = -1;
}
else
start = -1, end = -1;
@ -6288,24 +6298,27 @@ android_update_selection (struct frame *f, struct window *w)
/* 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->ephemeral_last_point,
point = min (min (max (w->ephemeral_last_point,
field_start),
field_end) - field_start,
TYPE_MAXIMUM (jint));
mark = ((!NILP (BVAR (b, mark_active))
&& w->last_mark != -1)
? min (w->last_mark, TYPE_MAXIMUM (jint))
? min (min (max (w->last_mark, field_start),
field_end) - field_start,
TYPE_MAXIMUM (jint))
: point);
/* Send the update. Android doesn't employ 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, and the region
is ``active'' when those two values differ. Also, convert the
indices from 1-based Emacs indices to 0-based Android ones. */
android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark) - 1,
max (point, mark) - 1, start, end);
/* Send the update. Android doesn't employ 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, and the region is
"active" when those two values differ. The indices will have been
converted from 1-based Emacs indices to 0-based Android ones. */
android_update_ic (FRAME_ANDROID_WINDOW (f), min (point, mark),
max (point, mark), start, end);
/* Update the extracted text as well, if the input method has asked
for updates. 1 is
InputConnection.GET_EXTRACTED_TEXT_MONITOR. */
for updates. 1 is InputConnection.GET_EXTRACTED_TEXT_MONITOR. */
if (FRAME_ANDROID_OUTPUT (f)->extracted_text_flags & 1)
{

View file

@ -370,7 +370,7 @@ at POSITION. */)
Either BEG or END may be 0, in which case the corresponding value
is not stored. */
static void
void
find_field (Lisp_Object pos, Lisp_Object merge_at_boundary,
Lisp_Object beg_limit,
ptrdiff_t *beg, Lisp_Object end_limit, ptrdiff_t *end)

View file

@ -1001,6 +1001,7 @@ make_frame (bool mini_p)
f->conversion.compose_region_start = Qnil;
f->conversion.compose_region_end = Qnil;
f->conversion.compose_region_overlay = Qnil;
f->conversion.field = Qnil;
f->conversion.batch_edit_count = 0;
f->conversion.batch_edit_flags = 0;
f->conversion.actions = NULL;

View file

@ -126,6 +126,10 @@ struct text_conversion_state
/* Overlay representing the composing region. */
Lisp_Object compose_region_overlay;
/* Cons of (START END . WINDOW) holding the field to which text
conversion should be confined, or nil if no such field exists. */
Lisp_Object field;
/* The number of ongoing ``batch edits'' that are causing point
reporting to be delayed. */
int batch_edit_count;

View file

@ -4933,6 +4933,8 @@ extern void unmark_main_thread (void);
/* Defined in editfns.c. */
extern void insert1 (Lisp_Object);
extern void find_field (Lisp_Object, Lisp_Object, Lisp_Object,
ptrdiff_t *, Lisp_Object, ptrdiff_t *);
extern void save_excursion_save (union specbinding *);
extern void save_excursion_restore (Lisp_Object, Lisp_Object);
extern Lisp_Object save_restriction_save (void);
@ -5496,6 +5498,7 @@ extern char *emacs_root_dir (void);
#ifdef HAVE_TEXT_CONVERSION
/* Defined in textconv.c. */
extern void reset_frame_state (struct frame *);
extern void reset_frame_conversion (struct frame *);
extern void report_selected_window_change (struct frame *);
extern void report_point_change (struct frame *, struct window *,
struct buffer *);

View file

@ -1367,6 +1367,20 @@ and some related functions, which use zero-indexing for POSITION. */)
if (NILP (histpos))
XSETFASTINT (histpos, 0);
#ifdef HAVE_TEXT_CONVERSION
/* If overriding-text-conversion-style is set, assume that it was
changed prior to this call and force text conversion to be reset,
since redisplay might conclude that the value was retained
unmodified from a previous call to Fread_from_minibuffer as the
selected window will not have changed. */
if (!EQ (Voverriding_text_conversion_style, Qlambda)
/* Separate minibuffer frames are not material here, since they
will already be selected if the situation that this is meant to
prevent is possible. */
&& FRAME_WINDOW_P (SELECTED_FRAME ()))
reset_frame_conversion (SELECTED_FRAME ());
#endif /* HAVE_TEXT_CONVERSION */
val = read_minibuf (keymap, initial_contents, prompt,
!NILP (read),
histvar, histpos, default_value,

View file

@ -195,6 +195,15 @@ textconv_query (struct frame *f, struct textconv_callback_struct *query,
: f->selected_window), Qt);
w = XWINDOW (selected_window);
/* Narrow to the field, if any. */
if (!NILP (f->conversion.field))
{
record_unwind_protect (save_restriction_restore,
save_restriction_save ());
Fnarrow_to_region (XCAR (f->conversion.field),
XCAR (XCDR (f->conversion.field)));
}
/* Now find the appropriate text bounds for QUERY. First, move
point QUERY->position steps forward or backwards. */
@ -488,6 +497,17 @@ record_buffer_change (ptrdiff_t beg, ptrdiff_t end,
Vtext_conversion_edits);
}
/* Reset text conversion state of frame F, and resume text conversion.
Delete any overlays or markers inside. */
void
reset_frame_conversion (struct frame *f)
{
reset_frame_state (f);
if (text_interface && FRAME_WINDOW_P (f) && FRAME_VISIBLE_P (f))
text_interface->reset (f);
}
/* Reset text conversion state of frame F. Delete any overlays or
markers inside. */
@ -530,6 +550,15 @@ reset_frame_state (struct frame *f)
/* Clear batch edit state. */
f->conversion.batch_edit_count = 0;
f->conversion.batch_edit_flags = 0;
/* Clear active field. */
if (!NILP (f->conversion.field))
{
Fset_marker (XCAR (f->conversion.field), Qnil, Qnil);
Fset_marker (XCAR (XCDR (f->conversion.field)), Qnil,
Qnil);
}
f->conversion.field = Qnil;
}
/* Return whether or not there are pending edits from an input method
@ -1012,6 +1041,15 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
redisplay. */
select_window (f->old_selected_window, Qt);
/* Narrow to the field, if any. */
if (!NILP (f->conversion.field))
{
record_unwind_protect (save_restriction_restore,
save_restriction_save ());
Fnarrow_to_region (XCAR (f->conversion.field),
XCAR (XCDR (f->conversion.field)));
}
/* Figure out where to start deleting from. */
a = get_mark ();
@ -1078,6 +1116,115 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
unbind_to (count, Qnil);
}
/* Save the confines of the field surrounding point in w into F's text
conversion state. If NOTIFY_COMPOSE, notify the input method of
changes to the composition region if they arise in this process. */
static void
locate_and_save_position_in_field (struct frame *f, struct window *w,
bool notify_compose)
{
Lisp_Object pos, window, c1, c2;
specpdl_ref count;
ptrdiff_t beg, end, cstart, cend, newstart, newend;
/* Set the current buffer to W's. */
count = SPECPDL_INDEX ();
record_unwind_protect (restore_selected_window, selected_window);
XSETWINDOW (window, w);
select_window (window, Qt);
/* Search for a field around the current editing position; this should
also serve to confine text conversion to the visible region. */
XSETFASTINT (pos, min (max (w->ephemeral_last_point, BEGV), ZV));
find_field (pos, Qnil, Qnil, &beg, Qnil, &end);
/* If beg is 1 and end is ZV, disable the active field entirely. */
if (beg == 1 && end == ZV)
{
f->conversion.field = Qnil;
goto exit;
}
/* Don't cons if a pair already exists. */
if (!NILP (f->conversion.field))
{
c1 = f->conversion.field;
c2 = XCDR (c1);
Fset_marker (XCAR (c1), make_fixed_natnum (beg), Qnil);
Fset_marker (XCAR (c2), make_fixed_natnum (end), Qnil);
XSETCDR (c2, window);
}
else
{
c1 = build_marker (current_buffer, beg, CHAR_TO_BYTE (beg));
c2 = build_marker (current_buffer, end, CHAR_TO_BYTE (end));
Fset_marker_insertion_type (c2, Qt);
f->conversion.field = Fcons (c1, Fcons (c2, window));
}
/* If the composition region is active and oversteps the active field,
restrict it to the same. */
if (!NILP (f->conversion.compose_region_start))
{
cstart = marker_position (f->conversion.compose_region_start);
cend = marker_position (f->conversion.compose_region_end);
if (cend < beg || cstart > end)
{
/* Remove the composition region in whole. */
/* 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;
}
/* Delete the composition region overlay. */
if (!NILP (f->conversion.compose_region_overlay))
Fdelete_overlay (f->conversion.compose_region_overlay);
TEXTCONV_DEBUG ("removing composing region outside active field");
}
else
{
newstart = max (beg, min (cstart, end));
newend = max (beg, min (cend, end));
if (newstart != cstart || newend != cend)
{
TEXTCONV_DEBUG ("confined composing region to %td, %td",
newstart, newend);
Fset_marker (f->conversion.compose_region_end,
make_fixed_natnum (newstart), Qnil);
Fset_marker (f->conversion.compose_region_end,
make_fixed_natnum (newend), Qnil);
}
else
notify_compose = false;
}
}
else
notify_compose = false;
if (notify_compose
&& text_interface->compose_region_changed)
{
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_COMPOSE_CHANGE;
else
text_interface->compose_region_changed (f);
}
exit:
unbind_to (count, Qnil);
}
/* Update the interface with frame F's new point and mark. If a batch
edit is in progress, schedule the update for when it finishes
instead. */
@ -1085,6 +1232,8 @@ really_delete_surrounding_text (struct frame *f, ptrdiff_t left,
static void
really_request_point_update (struct frame *f)
{
struct window *w;
/* If F's old selected window is no longer live, fail. */
if (!WINDOW_LIVE_P (f->old_selected_window))
@ -1093,9 +1242,11 @@ really_request_point_update (struct frame *f)
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
else if (text_interface && text_interface->point_changed)
text_interface->point_changed (f,
XWINDOW (f->old_selected_window),
current_buffer);
{
w = XWINDOW (f->old_selected_window);
locate_and_save_position_in_field (f, w, false);
text_interface->point_changed (f, w, current_buffer);
}
}
/* Set point in frame F's selected window to POSITION. If MARK is not
@ -1130,9 +1281,11 @@ really_set_point_and_mark (struct frame *f, ptrdiff_t point,
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
else if (text_interface && text_interface->point_changed)
text_interface->point_changed (f,
XWINDOW (f->old_selected_window),
current_buffer);
{
w = XWINDOW (f->old_selected_window);
locate_and_save_position_in_field (f, w, false);
text_interface->point_changed (f, w, current_buffer);
}
}
else
/* Set the point. */
@ -1331,9 +1484,12 @@ complete_edit_check (void *ptr)
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
else
{
locate_and_save_position_in_field (f, context->w, false);
text_interface->point_changed (f, context->w, NULL);
}
}
}
}
/* Process and free the text conversion ACTION. F must be the frame
@ -1400,7 +1556,10 @@ handle_pending_conversion_events_1 (struct frame *f,
break;
if (f->conversion.batch_edit_flags & PENDING_POINT_CHANGE)
{
locate_and_save_position_in_field (f, w, false);
text_interface->point_changed (f, w, buffer);
}
if (f->conversion.batch_edit_flags & PENDING_COMPOSE_CHANGE)
text_interface->compose_region_changed (f);
@ -1529,8 +1688,11 @@ handle_pending_conversion_events (void)
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
else
{
locate_and_save_position_in_field (f, w, false);
text_interface->point_changed (f, NULL, NULL);
}
}
last_point = w->ephemeral_last_point;
}
@ -1564,6 +1726,39 @@ handle_pending_conversion_events (void)
unbind_to (count, Qnil);
}
/* Return the confines of the field to which editing operations on frame
F should be constrained in *BEG and *END. Should no field be active,
set *END to MOST_POSITIVE_FIXNUM. */
void
get_conversion_field (struct frame *f, ptrdiff_t *beg, ptrdiff_t *end)
{
Lisp_Object c1, c2;
struct window *w;
if (!NILP (f->conversion.field))
{
c1 = f->conversion.field;
c2 = XCDR (c1);
if (!EQ (XCDR (c2), f->old_selected_window))
{
/* Update this outdated field location. */
w = XWINDOW (f->old_selected_window);
locate_and_save_position_in_field (f, w, true);
get_conversion_field (f, beg, end);
return;
}
*beg = marker_position (XCAR (c1));
*end = marker_position (XCAR (c2));
return;
}
*beg = 1;
*end = MOST_POSITIVE_FIXNUM;
}
/* Start a ``batch edit'' in frame F. During a batch edit,
point_changed will not be called until the batch edit ends.
@ -1694,7 +1889,8 @@ set_composing_text (struct frame *f, Lisp_Object object,
}
/* Make the region between START and END the currently active
``composing region'' on frame F.
``composing region'' on frame F. Which of START and END is the
larger value is not significant.
The ``composing region'' is a region of text in the buffer that is
about to undergo editing by the input method. */
@ -1704,14 +1900,22 @@ set_composing_region (struct frame *f, ptrdiff_t start,
ptrdiff_t end, unsigned long counter)
{
struct text_conversion_action *action, **last;
ptrdiff_t field_start, field_end, temp;
start = min (start, MOST_POSITIVE_FIXNUM);
end = min (end, MOST_POSITIVE_FIXNUM);
if (start > end)
{
temp = end;
end = start;
start = temp;
}
get_conversion_field (f, &field_start, &field_end);
start = min (start + field_start - 1, MOST_POSITIVE_FIXNUM);
end = max (start, min (end + field_start - 1, field_end));
action = xmalloc (sizeof *action);
action->operation = TEXTCONV_SET_COMPOSING_REGION;
action->data = Fcons (make_fixnum (start),
make_fixnum (end));
action->data = Fcons (make_fixnum (start), make_fixnum (end));
action->next = NULL;
action->counter = counter;
for (last = &f->conversion.actions; *last; last = &(*last)->next)
@ -1730,8 +1934,13 @@ textconv_set_point_and_mark (struct frame *f, ptrdiff_t point,
ptrdiff_t mark, unsigned long counter)
{
struct text_conversion_action *action, **last;
ptrdiff_t field_start, field_end;
point = min (point, MOST_POSITIVE_FIXNUM);
get_conversion_field (f, &field_start, &field_end);
point = min (max (point + field_start - 1, field_start),
field_end);
mark = min (max (mark + field_start - 1, field_start),
field_end);
action = xmalloc (sizeof *action);
action->operation = TEXTCONV_SET_POINT_AND_MARK;
@ -1809,10 +2018,11 @@ 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. */
/* Remove the composing region. Replace the text between START and END
(whose order, as in `set_composing_region', is not significant)
within F's selected window with TEXT; deactivate the mark if it is
active. Subsequently, set point to POSITION relative to TEXT, as
`commit_text' would. */
void
replace_text (struct frame *f, ptrdiff_t start, ptrdiff_t end,
@ -1820,6 +2030,18 @@ replace_text (struct frame *f, ptrdiff_t start, ptrdiff_t end,
unsigned long counter)
{
struct text_conversion_action *action, **last;
ptrdiff_t field_start, field_end, temp;
if (start > end)
{
temp = end;
end = start;
start = temp;
}
get_conversion_field (f, &field_start, &field_end);
start = min (start + field_start - 1, MOST_POSITIVE_FIXNUM);
end = max (start, min (end + field_start - 1, field_end));
action = xmalloc (sizeof *action);
action->operation = TEXTCONV_REPLACE_TEXT;
@ -1858,6 +2080,7 @@ get_extracted_text (struct frame *f, ptrdiff_t n,
specpdl_ref count;
ptrdiff_t start, end, start_byte, end_byte, mark;
char *buffer;
ptrdiff_t field_start, field_end;
if (!WINDOW_LIVE_P (f->old_selected_window))
return NULL;
@ -1907,6 +2130,15 @@ get_extracted_text (struct frame *f, ptrdiff_t n,
goto finish;
}
/* Narrow to the field, if any. */
if (!NILP (f->conversion.field))
{
record_unwind_protect (save_restriction_restore,
save_restriction_save ());
Fnarrow_to_region (XCAR (f->conversion.field),
XCAR (XCDR (f->conversion.field)));
}
start = max (start, BEGV);
end = min (end, ZV);
@ -1935,7 +2167,8 @@ get_extracted_text (struct frame *f, ptrdiff_t n,
}
/* Return the offsets. */
*start_return = start;
get_conversion_field (f, &field_start, &field_end);
*start_return = max (1, start - field_start + 1);
*start_offset = min (mark - start, PT - start);
*end_offset = max (mark - start, PT - start);
*length = end - start;
@ -1968,6 +2201,7 @@ get_surrounding_text (struct frame *f, ptrdiff_t left,
{
specpdl_ref count;
ptrdiff_t start, end, start_byte, end_byte, mark, temp;
ptrdiff_t field_start, field_end;
char *buffer;
if (!WINDOW_LIVE_P (f->old_selected_window))
@ -2012,6 +2246,15 @@ get_surrounding_text (struct frame *f, ptrdiff_t left,
|| ckd_add (&end, end, right))
goto finish;
/* Narrow to the field, if any. */
if (!NILP (f->conversion.field))
{
record_unwind_protect (save_restriction_restore,
save_restriction_save ());
Fnarrow_to_region (XCAR (f->conversion.field),
XCAR (XCDR (f->conversion.field)));
}
start = max (start, BEGV);
end = min (end, ZV);
@ -2038,7 +2281,8 @@ get_surrounding_text (struct frame *f, ptrdiff_t left,
/* Return the offsets. Unlike `get_extracted_text', this need not
sort mark and point. */
*offset = start;
get_conversion_field (f, &field_start, &field_end);
*offset = max (1, start - field_start + 1);
*start_return = mark - start;
*end_return = PT - start;
*length = end - start;
@ -2110,7 +2354,10 @@ report_point_change (struct frame *f, struct window *window,
if (f->conversion.batch_edit_count > 0)
f->conversion.batch_edit_flags |= PENDING_POINT_CHANGE;
else
{
locate_and_save_position_in_field (f, window, false);
text_interface->point_changed (f, window, buffer);
}
}
/* Temporarily disable text conversion. Must be paired with a
@ -2348,8 +2595,9 @@ as indenting or automatically filling text, should not take place.
Otherwise, it is either a string containing text that was inserted,
text deleted before point, or nil if text was deleted after point.
The list contents are ordered in the reverse order of editing, i.e.
the latest edit first, so you must iterate through the list in reverse. */);
The list contents are arranged in the reverse of the order of editing,
i.e. latest edit first, so you must iterate through the list in
reverse. */);
Vtext_conversion_edits = Qnil;
DEFVAR_LISP ("overriding-text-conversion-style",

View file

@ -155,6 +155,7 @@ extern char *get_surrounding_text (struct frame *, ptrdiff_t,
extern bool conversion_disabled_p (void);
extern void check_postponed_buffers (void);
extern void get_conversion_field (struct frame *, ptrdiff_t *, ptrdiff_t *);
extern void register_textconv_interface (struct textconv_interface *);
#endif /* _TEXTCONV_H_ */