/* 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 . */ /* 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 #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; }