-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathlineedit.c
520 lines (458 loc) · 15.9 KB
/
lineedit.c
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
/*
* Implementation of local line editing. Used during username and
* password input at login time, and also by ldisc during the main
* session (if the session's virtual terminal is in that mode).
*
* Because we're tied into a real GUI terminal (and not a completely
* standalone line-discipline module that deals purely with byte
* streams), we can support a slightly richer input interface than
* plain bytes.
*
* In particular, the 'dedicated' flag sent along with every byte is
* used to distinguish control codes input via Ctrl+letter from the
* same code input by a dedicated key like Return or Backspace. This
* allows us to interpret the Ctrl+letter key combination as inputting
* a literal control character to go into the line buffer, and the
* dedicated-key version as performing an editing function.
*/
#include "putty.h"
#include "terminal.h"
typedef struct BufChar BufChar;
struct TermLineEditor {
Terminal *term;
BufChar *head, *tail;
unsigned flags;
bool quote_next_char;
TermLineEditorCallbackReceiver *receiver;
};
struct BufChar {
BufChar *prev, *next;
/* The bytes of the character, to be sent on the wire */
char wire[6];
uint8_t nwire;
/* Whether this character is considered complete */
bool complete;
/* Width of the character when it was displayed, in terminal cells */
uint8_t width;
/* Whether this character counts as whitespace, for ^W purposes */
bool space;
};
TermLineEditor *lineedit_new(Terminal *term, unsigned flags,
TermLineEditorCallbackReceiver *receiver)
{
TermLineEditor *le = snew(TermLineEditor);
le->term = term;
le->head = le->tail = NULL;
le->flags = flags;
le->quote_next_char = false;
le->receiver = receiver;
return le;
}
static void bufchar_free(BufChar *bc)
{
smemclr(bc, sizeof(*bc));
sfree(bc);
}
static void lineedit_free_buffer(TermLineEditor *le)
{
while (le->head) {
BufChar *bc = le->head;
le->head = bc->next;
bufchar_free(bc);
}
le->tail = NULL;
}
void lineedit_free(TermLineEditor *le)
{
lineedit_free_buffer(le);
sfree(le);
}
void lineedit_modify_flags(TermLineEditor *le, unsigned clr, unsigned flip)
{
le->flags &= ~clr;
le->flags ^= flip;
}
static void lineedit_term_write(TermLineEditor *le, ptrlen data)
{
le->receiver->vt->to_terminal(le->receiver, data);
}
static void lineedit_term_newline(TermLineEditor *le)
{
lineedit_term_write(le, PTRLEN_LITERAL("\x0D\x0A"));
}
static inline void lineedit_send_data(TermLineEditor *le, ptrlen data)
{
le->receiver->vt->to_backend(le->receiver, data);
}
static inline void lineedit_special(TermLineEditor *le,
SessionSpecialCode code, int arg)
{
le->receiver->vt->special(le->receiver, code, arg);
}
static inline void lineedit_send_newline(TermLineEditor *le)
{
le->receiver->vt->newline(le->receiver);
}
static void lineedit_delete_char(TermLineEditor *le)
{
if (le->tail) {
BufChar *bc = le->tail;
le->tail = bc->prev;
if (!le->tail)
le->head = NULL;
else
le->tail->next = NULL;
for (unsigned i = 0; i < bc->width; i++)
lineedit_term_write(le, PTRLEN_LITERAL("\x08 \x08"));
bufchar_free(bc);
}
}
static void lineedit_delete_word(TermLineEditor *le)
{
/*
* Deleting a word stops at the _start_ of a word, i.e. at any
* boundary with a space on the left and a non-space on the right.
*/
if (!le->tail)
return;
while (true) {
bool deleted_char_is_space = le->tail->space;
lineedit_delete_char(le);
if (!le->tail)
break; /* we've cleared the whole line */
if (le->tail->space && !deleted_char_is_space)
break; /* we've just reached a word boundary */
}
}
static void lineedit_delete_line(TermLineEditor *le)
{
while (le->tail)
lineedit_delete_char(le);
lineedit_special(le, SS_EL, 0);
}
void lineedit_send_line(TermLineEditor *le)
{
for (BufChar *bc = le->head; bc; bc = bc->next)
lineedit_send_data(le, make_ptrlen(bc->wire, bc->nwire));
lineedit_free_buffer(le);
le->quote_next_char = false;
}
static void lineedit_complete_line(TermLineEditor *le)
{
lineedit_term_newline(le);
lineedit_send_line(le);
lineedit_send_newline(le);
}
/*
* Send data to the terminal to display a BufChar. As a side effect,
* update bc->width to indicate how many character cells we think were
* taken up by what we just wrote. No other change to bc is made.
*/
static void lineedit_display_bufchar(TermLineEditor *le, BufChar *bc,
unsigned chr)
{
char buf[6];
buffer_sink bs[1];
buffer_sink_init(bs, buf, lenof(buf));
/* Handle single-byte character set translation. */
if (!in_utf(le->term) && DIRECT_CHAR(chr)) {
/*
* If we're not in UTF-8, i.e. we're in a single-byte
* character set, then first we must feed the input byte
* through term_translate, which will tell us whether it's a
* control character or not. (That varies with the charset:
* e.g. ISO 8859-1 and Win1252 disagree on a lot of
* 0x80-0x9F).
*
* In principle, we could pass NULL as our term_utf8_decode
* pointer, on the grounds that since the terminal isn't in
* UTF-8 mode term_translate shouldn't access it. But that
* seems needlessly reckless; we'll make up an empty one.
*/
term_utf8_decode dummy_utf8 = { .state = 0, .chr = 0, .size = 0 };
chr = term_translate(
le->term, &dummy_utf8, (unsigned char)chr);
/*
* After that, chr will be either a control-character value
* (00-1F, 7F, 80-9F), or a byte value ORed with one of the
* CSET_FOO character set indicators. The latter indicates
* that it's a printing character in this charset, in which
* case it takes up one character cell.
*/
if (chr & CSET_MASK) {
put_byte(bs, chr);
bc->width = 1;
goto got_char;
}
}
/*
* If we got here without taking the 'goto' above, then we're now
* holding an actual Unicode character.
*/
assert(!IS_SURROGATE(chr)); /* and it should be an _actual_ one */
/*
* Deal with symbolic representations of control characters.
*/
if (chr < 0x20 || chr == 0x7F) {
/*
* Represent C0 controls as '^C' or similar, and 7F as ^?.
*/
put_byte(bs, '^');
put_byte(bs, chr ^ 0x40);
bc->width = 2;
goto got_char;
}
if (chr >= 0x80 && chr < 0xA0) {
/*
* Represent C1 controls as <9B> or similar.
*/
put_fmt(bs, "<%02X>", chr);
bc->width = 4;
goto got_char;
}
/*
* And if we get _here_, we're holding a printing (or at least not
* _control_, even if zero-width) Unicode character, which _must_
* mean that the terminal is currently in UTF-8 mode (since if it
* were not then printing characters would have gone through the
* term_translate case above). So we can just write the UTF-8 for
* the character - but we must also pay attention to its width in
* character cells, which might be 0, 1 or 2.
*/
assert(in_utf(le->term));
put_utf8_char(bs, chr);
bc->width = term_char_width(le->term, chr);
got_char:
lineedit_term_write(le, make_ptrlen(buf, bs->out - buf));
}
/* Called when we've just added a byte to a UTF-8 character and want
* to see if it's complete */
static void lineedit_check_utf8_complete(TermLineEditor *le, BufChar *bc)
{
BinarySource src[1];
BinarySource_BARE_INIT(src, bc->wire, bc->nwire);
DecodeUTF8Failure err;
unsigned chr = decode_utf8(src, &err);
if (err == DUTF8_E_OUT_OF_DATA)
return; /* not complete yet */
/* Any other error code is regarded as complete, and we just
* display the character as the U+FFFD that decode_utf8 will have
* returned anyway */
bc->complete = true;
bc->space = (chr == ' ');
lineedit_display_bufchar(le, bc, chr);
}
static void lineedit_input_printing_char(TermLineEditor *le, char ch);
static void lineedit_redraw_line(TermLineEditor *le)
{
/* FIXME: I'm not 100% sure this is the behaviour I really want in
* this situation, but it's at least very simple to implement */
BufChar *prevhead = le->head;
le->head = le->tail = NULL;
while (prevhead) {
BufChar *bc = prevhead;
prevhead = prevhead->next;
for (unsigned i = 0; i < bc->nwire; i++)
lineedit_input_printing_char(le, bc->wire[i]);
bufchar_free(bc);
}
}
#define CTRL(c) ((char) (0x40 ^ (unsigned char)c))
void lineedit_input(TermLineEditor *le, char ch, bool dedicated)
{
if (le->quote_next_char) {
/*
* If the previous keypress was ^V, 'quoting' the next
* character to be treated literally, then skip all the
* editing-control processing, and clear that flag.
*/
le->quote_next_char = false;
} else {
/*
* Input events that are only valid with the 'dedicated' flag.
* These are limited to the control codes that _have_
* dedicated keys.
*
* Any case we actually handle here ends with a 'return'
* statement, so that if we fall out of the end of this switch
* at all, it's because the byte hasn't been handled here and
* will fall into the next switch dealing with ordinary input.
*/
if (dedicated) {
switch (ch) {
/*
* The Backspace key.
*
* Since our terminal can be configured to send either
* ^H or 7F (aka ^?) via the backspace key, we accept
* both.
*
* (We could query the Terminal's configuration here
* and accept only the one of those codes that the
* terminal is currently set to. But it's pointless,
* because whichever one the terminal isn't set to,
* the front end won't be sending it with
* dedicated=true anyway.)
*/
case CTRL('H'):
case 0x7F:
lineedit_delete_char(le);
return;
/*
* The Return key.
*/
case CTRL('M'):
lineedit_complete_line(le);
return;
}
}
/*
* Editing and special functions in response to ordinary keys
* or Ctrl+key combinations. Many editing functions have to be
* supported in this mode, like ^W and ^U, because there are
* no dedicated keys that generate the same control codes
* anyway.
*
* Again, we return if the key was handled. The final
* processing of ordinary data to go into the input buffer
* happens if we break from this switch.
*/
switch (ch) {
case CTRL('W'):
lineedit_delete_word(le);
return;
case CTRL('U'):
lineedit_delete_line(le);
return;
case CTRL('['):
if (!(le->flags & LE_ESC_ERASES))
break; /* treat as normal input */
lineedit_delete_line(le);
return;
case CTRL('R'):
lineedit_term_write(le, PTRLEN_LITERAL("^R"));
lineedit_term_newline(le);
lineedit_redraw_line(le);
return;
case CTRL('V'):
le->quote_next_char = true;
return;
case CTRL('C'):
lineedit_delete_line(le);
if (!(le->flags & LE_INTERRUPT))
break; /* treat as normal input */
lineedit_special(le, SS_IP, 0);
return;
case CTRL('Z'):
lineedit_delete_line(le);
if (!(le->flags & LE_SUSPEND))
break; /* treat as normal input */
lineedit_special(le, SS_SUSP, 0);
return;
case CTRL('\\'):
lineedit_delete_line(le);
if (!(le->flags & LE_ABORT))
break; /* treat as normal input */
lineedit_special(le, SS_ABORT, 0);
return;
case CTRL('D'):
if (le->flags & LE_EOF_ALWAYS) {
/* Our client wants to treat ^D / EOF as a special
* character in their own way. Just send an EOF
* special. */
lineedit_special(le, SS_EOF, 0);
return;
}
/*
* Otherwise, ^D has the same behaviour as in Unix tty
* line editing: if the edit buffer is non-empty then it's
* sent immediately without a newline, and if it is empty
* then an EOF is sent.
*/
if (le->head) {
lineedit_send_line(le);
return;
}
lineedit_special(le, SS_EOF, 0);
return;
case CTRL('J'):
if (le->flags & LE_CRLF_NEWLINE) {
/*
* If the previous character in the buffer is a
* literal Ctrl-M, and now the user sends Ctrl-J, then
* we amalgamate both into a newline event.
*/
if (le->tail && le->tail->nwire == 1 &&
le->tail->wire[0] == CTRL('M')) {
lineedit_delete_char(le); /* erase ^J from buffer */
lineedit_complete_line(le);
return;
}
}
}
}
/*
* If we get to here, we've exhausted the options for treating our
* character as an editing or special function of any kind. Treat
* it as a printing character, or part of one.
*/
lineedit_input_printing_char(le, ch);
}
static void lineedit_input_printing_char(TermLineEditor *le, char ch)
{
/*
* Append ch to the line buffer, either as a new BufChar or by
* adding it to a previous one containing an as yet incomplete
* UTF-8 encoding.
*/
if (le->tail && !le->tail->complete) {
BufChar *bc = le->tail;
/*
* If we're in UTF-8 mode, and ch is a UTF-8 continuation
* byte, then we can append it to bc, which we've just checked
* is missing at least one of those.
*/
if (in_utf(le->term) && (unsigned char)ch - 0x80U < 0x40) {
assert(bc->nwire < lenof(bc->wire));
bc->wire[bc->nwire++] = ch;
lineedit_check_utf8_complete(le, bc);
return;
}
/*
* Otherwise, the previous incomplete character can't be
* extended. Mark it as complete, and if possible, display it
* as a replacement character indicating that something weird
* happened.
*/
bc->complete = true;
if (in_utf(le->term))
lineedit_display_bufchar(le, bc, 0xFFFD);
/*
* But we still haven't processed the byte we're holding. Fall
* through to the next step, where we make a fresh BufChar for
* it.
*/
}
/*
* Make a fresh BufChar.
*/
BufChar *bc = snew(BufChar);
bc->prev = le->tail;
le->tail = bc;
if (bc->prev)
bc->prev->next = bc;
else
le->head = bc;
bc->next = NULL;
bc->complete = false;
bc->space = false;
bc->nwire = 1;
bc->wire[0] = ch;
if (in_utf(le->term)) {
lineedit_check_utf8_complete(le, bc);
} else {
bc->complete = true; /* always, in a single-byte charset */
bc->space = (bc->wire[0] == ' ');
lineedit_display_bufchar(le, bc, CSET_ASCII | (unsigned char)ch);
}
}