mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-21 12:03:55 -08:00
313 lines
7.6 KiB
C
313 lines
7.6 KiB
C
/* String conversion support for graphics terminals.
|
||
|
||
Copyright (C) 2023 Free Software Foundation, Inc.
|
||
|
||
This file is part of GNU Emacs.
|
||
|
||
GNU Emacs is free software: you can redistribute it and/or modify
|
||
it under the terms of the GNU General Public License as published by
|
||
the Free Software Foundation, either version 3 of the License, or (at
|
||
your option) any later version.
|
||
|
||
GNU Emacs is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
GNU General Public License for more details.
|
||
|
||
You should have received a copy of the GNU General Public License
|
||
along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
|
||
|
||
/* String conversion support.
|
||
|
||
Many input methods require access to text surrounding the cursor.
|
||
They may then request that the text editor remove or substitute
|
||
that text for something else, for example when providing the
|
||
ability to ``undo'' or ``edit'' previously composed text. This is
|
||
most commonly seen in input methods for CJK laguages for X Windows,
|
||
and is extensively used throughout Android by input methods for all
|
||
kinds of scripts. */
|
||
|
||
#include <config.h>
|
||
|
||
#include "textconv.h"
|
||
#include "buffer.h"
|
||
#include "syntax.h"
|
||
|
||
|
||
|
||
/* The window system's text conversion interface.
|
||
NULL when the window system has not set up text conversion.
|
||
|
||
This interface will later be heavily extended on the
|
||
feature/android branch to deal with Android's much less
|
||
straightforward text conversion protocols. */
|
||
|
||
static struct textconv_interface *text_interface;
|
||
|
||
|
||
|
||
/* Copy the portion of the current buffer described by BEG, BEG_BYTE,
|
||
END, END_BYTE to the buffer BUFFER, which is END_BYTE - BEG_BYTEs
|
||
long. */
|
||
|
||
static void
|
||
copy_buffer (ptrdiff_t beg, ptrdiff_t beg_byte,
|
||
ptrdiff_t end, ptrdiff_t end_byte,
|
||
char *buffer)
|
||
{
|
||
ptrdiff_t beg0, end0, beg1, end1, size;
|
||
|
||
if (beg_byte < GPT_BYTE && GPT_BYTE < end_byte)
|
||
{
|
||
/* Two regions, before and after the gap. */
|
||
beg0 = beg_byte;
|
||
end0 = GPT_BYTE;
|
||
beg1 = GPT_BYTE + GAP_SIZE - BEG_BYTE;
|
||
end1 = end_byte + GAP_SIZE - BEG_BYTE;
|
||
}
|
||
else
|
||
{
|
||
/* The only region. */
|
||
beg0 = beg_byte;
|
||
end0 = end_byte;
|
||
beg1 = -1;
|
||
end1 = -1;
|
||
}
|
||
|
||
size = end0 - beg0;
|
||
memcpy (buffer, BYTE_POS_ADDR (beg0), size);
|
||
if (beg1 != -1)
|
||
memcpy (buffer, BEG_ADDR + beg1, end1 - beg1);
|
||
}
|
||
|
||
|
||
|
||
/* Conversion query. */
|
||
|
||
/* Perform the text conversion operation specified in QUERY and return
|
||
the results.
|
||
|
||
Find the text between QUERY->position from point on F's selected
|
||
window and QUERY->factor times QUERY->direction from that
|
||
position. Return it in QUERY->text.
|
||
|
||
Then, either delete that text from the buffer if QUERY->operation
|
||
is TEXTCONV_SUBSTITUTION, or return 0.
|
||
|
||
Value is 0 if QUERY->operation was not TEXTCONV_SUBSTITUTION
|
||
or if deleting the text was successful, and 1 otherwise. */
|
||
|
||
int
|
||
textconv_query (struct frame *f, struct textconv_callback_struct *query)
|
||
{
|
||
specpdl_ref count;
|
||
ptrdiff_t pos, pos_byte, end, end_byte;
|
||
ptrdiff_t temp, temp1;
|
||
char *buffer;
|
||
|
||
/* Save the excursion, as there will be extensive changes to the
|
||
selected window. */
|
||
count = SPECPDL_INDEX ();
|
||
record_unwind_protect_excursion ();
|
||
|
||
/* Inhibit quitting. */
|
||
specbind (Qinhibit_quit, Qt);
|
||
|
||
/* Temporarily switch to F's selected window. */
|
||
Fselect_window (f->selected_window, Qt);
|
||
|
||
/* Now find the appropriate text bounds for QUERY. First, move
|
||
point QUERY->position steps forward or backwards. */
|
||
|
||
pos = PT;
|
||
|
||
/* 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. */
|
||
|
||
if (INT_ADD_WRAPV (pos, query->position, &pos))
|
||
pos = PT;
|
||
|
||
if (pos < BEGV)
|
||
pos = BEGV;
|
||
|
||
if (pos > ZV)
|
||
pos = ZV;
|
||
|
||
/* Move to pos. */
|
||
set_point (pos);
|
||
pos = PT;
|
||
pos_byte = PT_BYTE;
|
||
|
||
/* Now scan forward or backwards according to what is in QUERY. */
|
||
|
||
switch (query->direction)
|
||
{
|
||
case TEXTCONV_FORWARD_CHAR:
|
||
/* Move forward by query->factor characters. */
|
||
if (INT_ADD_WRAPV (pos, query->factor, &end) || end > ZV)
|
||
end = ZV;
|
||
|
||
end_byte = CHAR_TO_BYTE (end);
|
||
break;
|
||
|
||
case TEXTCONV_BACKWARD_CHAR:
|
||
/* Move backward by query->factor characters. */
|
||
if (INT_SUBTRACT_WRAPV (pos, query->factor, &end) || end < BEGV)
|
||
end = BEGV;
|
||
|
||
end_byte = CHAR_TO_BYTE (end);
|
||
break;
|
||
|
||
case TEXTCONV_FORWARD_WORD:
|
||
/* Move forward by query->factor word. */
|
||
end = scan_words (pos, (EMACS_INT) query->factor);
|
||
|
||
if (!end)
|
||
{
|
||
end = ZV;
|
||
end_byte = ZV_BYTE;
|
||
}
|
||
else
|
||
end_byte = CHAR_TO_BYTE (end);
|
||
|
||
break;
|
||
|
||
case TEXTCONV_BACKWARD_WORD:
|
||
/* Move backwards by query->factor word. */
|
||
end = scan_words (pos, 0 - (EMACS_INT) query->factor);
|
||
|
||
if (!end)
|
||
{
|
||
end = BEGV;
|
||
end_byte = BEGV_BYTE;
|
||
}
|
||
else
|
||
end_byte = CHAR_TO_BYTE (end);
|
||
|
||
break;
|
||
|
||
case TEXTCONV_CARET_UP:
|
||
/* Move upwards one visual line, keeping the column intact. */
|
||
Fvertical_motion (Fcons (Fcurrent_column (), make_fixnum (-1)),
|
||
Qnil, Qnil);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_CARET_DOWN:
|
||
/* Move downwards one visual line, keeping the column
|
||
intact. */
|
||
Fvertical_motion (Fcons (Fcurrent_column (), make_fixnum (1)),
|
||
Qnil, Qnil);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_NEXT_LINE:
|
||
/* Move one line forward. */
|
||
scan_newline (pos, pos_byte, ZV, ZV_BYTE,
|
||
query->factor, false);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_PREVIOUS_LINE:
|
||
/* Move one line backwards. */
|
||
scan_newline (pos, pos_byte, BEGV, BEGV_BYTE,
|
||
0 - (EMACS_INT) query->factor, false);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_LINE_START:
|
||
/* Move to the beginning of the line. */
|
||
Fbeginning_of_line (Qnil);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_LINE_END:
|
||
/* Move to the end of the line. */
|
||
Fend_of_line (Qnil);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
case TEXTCONV_ABSOLUTE_POSITION:
|
||
/* How to implement this is unclear. */
|
||
SET_PT (query->factor);
|
||
end = PT;
|
||
end_byte = PT_BYTE;
|
||
break;
|
||
|
||
default:
|
||
unbind_to (count, Qnil);
|
||
return 1;
|
||
}
|
||
|
||
/* Sort end and pos. */
|
||
|
||
if (end < pos)
|
||
{
|
||
eassert (end_byte < pos_byte);
|
||
temp = pos_byte;
|
||
temp1 = pos;
|
||
pos_byte = end_byte;
|
||
pos = end;
|
||
end = temp1;
|
||
end_byte = temp;
|
||
}
|
||
|
||
/* Return the string first. */
|
||
buffer = xmalloc (end_byte - pos_byte);
|
||
copy_buffer (pos, pos_byte, end, end_byte, buffer);
|
||
query->text.text = buffer;
|
||
query->text.length = end - pos;
|
||
query->text.bytes = end_byte - pos_byte;
|
||
|
||
/* Next, perform any operation specified. */
|
||
|
||
switch (query->operation)
|
||
{
|
||
case TEXTCONV_SUBSTITUTION:
|
||
if (safe_del_range (pos, end))
|
||
{
|
||
/* Undo any changes to the excursion. */
|
||
unbind_to (count, Qnil);
|
||
return 1;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
/* Undo any changes to the excursion. */
|
||
unbind_to (count, Qnil);
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
/* Window system interface. These are called from the rest of
|
||
Emacs. */
|
||
|
||
/* Notice that F's selected window has been set from redisplay.
|
||
Reset F's input method state. */
|
||
|
||
void
|
||
report_selected_window_change (struct frame *f)
|
||
{
|
||
if (!text_interface)
|
||
return;
|
||
|
||
text_interface->reset (f);
|
||
}
|
||
|
||
/* Register INTERFACE as the text conversion interface. */
|
||
|
||
void
|
||
register_texconv_interface (struct textconv_interface *interface)
|
||
{
|
||
text_interface = interface;
|
||
}
|