summaryrefslogtreecommitdiff
path: root/src/textconv.c
blob: 7ed8ede35443b009435d66a357f1e999dc92554d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/* 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 (ckd_add (&pos, pos, query->position))
    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 (ckd_add (&end, pos, query->factor) || end > ZV)
	end = ZV;

      end_byte = CHAR_TO_BYTE (end);
      break;

    case TEXTCONV_BACKWARD_CHAR:
      /* Move backward by query->factor characters.  */
      if (ckd_sub (&end, pos, query->factor) || 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;
}