Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/bc/src/history.c
39507 views
1
/*
2
* *****************************************************************************
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*
6
* Copyright (c) 2018-2025 Gavin D. Howard and contributors.
7
*
8
* Redistribution and use in source and binary forms, with or without
9
* modification, are permitted provided that the following conditions are met:
10
*
11
* * Redistributions of source code must retain the above copyright notice, this
12
* list of conditions and the following disclaimer.
13
*
14
* * Redistributions in binary form must reproduce the above copyright notice,
15
* this list of conditions and the following disclaimer in the documentation
16
* and/or other materials provided with the distribution.
17
*
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
* POSSIBILITY OF SUCH DAMAGE.
29
*
30
* *****************************************************************************
31
*
32
* Adapted from the following:
33
*
34
* linenoise.c -- guerrilla line editing library against the idea that a
35
* line editing lib needs to be 20,000 lines of C code.
36
*
37
* You can find the original source code at:
38
* http://github.com/antirez/linenoise
39
*
40
* You can find the fork that this code is based on at:
41
* https://github.com/rain-1/linenoise-mob
42
*
43
* ------------------------------------------------------------------------
44
*
45
* This code is also under the following license:
46
*
47
* Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
48
* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
49
*
50
* Redistribution and use in source and binary forms, with or without
51
* modification, are permitted provided that the following conditions are
52
* met:
53
*
54
* * Redistributions of source code must retain the above copyright
55
* notice, this list of conditions and the following disclaimer.
56
*
57
* * Redistributions in binary form must reproduce the above copyright
58
* notice, this list of conditions and the following disclaimer in the
59
* documentation and/or other materials provided with the distribution.
60
*
61
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
62
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
63
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
64
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
65
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
66
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
67
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
68
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
69
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
70
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
71
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
72
*
73
* ------------------------------------------------------------------------
74
*
75
* Does a number of crazy assumptions that happen to be true in 99.9999% of
76
* the 2010 UNIX computers around.
77
*
78
* References:
79
* - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
80
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
81
*
82
* Todo list:
83
* - Filter bogus Ctrl+<char> combinations.
84
* - Win32 support
85
*
86
* Bloat:
87
* - History search like Ctrl+r in readline?
88
*
89
* List of escape sequences used by this program, we do everything just
90
* with three sequences. In order to be so cheap we may have some
91
* flickering effect with some slow terminal, but the lesser sequences
92
* the more compatible.
93
*
94
* EL (Erase Line)
95
* Sequence: ESC [ n K
96
* Effect: if n is 0 or missing, clear from cursor to end of line
97
* Effect: if n is 1, clear from beginning of line to cursor
98
* Effect: if n is 2, clear entire line
99
*
100
* CUF (CUrsor Forward)
101
* Sequence: ESC [ n C
102
* Effect: moves cursor forward n chars
103
*
104
* CUB (CUrsor Backward)
105
* Sequence: ESC [ n D
106
* Effect: moves cursor backward n chars
107
*
108
* The following is used to get the terminal width if getting
109
* the width with the TIOCGWINSZ ioctl fails
110
*
111
* DSR (Device Status Report)
112
* Sequence: ESC [ 6 n
113
* Effect: reports the current cusor position as ESC [ n ; m R
114
* where n is the row and m is the column
115
*
116
* When multi line mode is enabled, we also use two additional escape
117
* sequences. However multi line editing is disabled by default.
118
*
119
* CUU (CUrsor Up)
120
* Sequence: ESC [ n A
121
* Effect: moves cursor up of n chars.
122
*
123
* CUD (CUrsor Down)
124
* Sequence: ESC [ n B
125
* Effect: moves cursor down of n chars.
126
*
127
* When bc_history_clearScreen() is called, two additional escape sequences
128
* are used in order to clear the screen and position the cursor at home
129
* position.
130
*
131
* CUP (CUrsor Position)
132
* Sequence: ESC [ H
133
* Effect: moves the cursor to upper left corner
134
*
135
* ED (Erase Display)
136
* Sequence: ESC [ 2 J
137
* Effect: clear the whole screen
138
*
139
* *****************************************************************************
140
*
141
* Code for line history.
142
*
143
*/
144
145
#if BC_ENABLE_HISTORY
146
147
#if BC_ENABLE_EDITLINE
148
149
#include <string.h>
150
#include <errno.h>
151
#include <setjmp.h>
152
153
#include <history.h>
154
#include <vm.h>
155
156
sigjmp_buf bc_history_jmpbuf;
157
volatile sig_atomic_t bc_history_inlinelib;
158
159
static char* bc_history_prompt;
160
static char bc_history_no_prompt[] = "";
161
static HistEvent bc_history_event;
162
static bool bc_history_use_prompt;
163
164
static char*
165
bc_history_promptFunc(EditLine* el)
166
{
167
BC_UNUSED(el);
168
return BC_PROMPT && bc_history_use_prompt ? bc_history_prompt :
169
bc_history_no_prompt;
170
}
171
172
void
173
bc_history_init(BcHistory* h)
174
{
175
BcVec v;
176
char* home;
177
178
home = getenv("HOME");
179
180
// This will hold the true path to the editrc.
181
bc_vec_init(&v, 1, BC_DTOR_NONE);
182
183
// Initialize the path to the editrc. This is done manually because the
184
// libedit I used to test was failing with a NULL argument for the path,
185
// which was supposed to automatically do $HOME/.editrc. But it was failing,
186
// so I set it manually.
187
if (home == NULL)
188
{
189
bc_vec_string(&v, bc_history_editrc_len - 1, bc_history_editrc + 1);
190
}
191
else
192
{
193
bc_vec_string(&v, strlen(home), home);
194
bc_vec_concat(&v, bc_history_editrc);
195
}
196
197
h->hist = history_init();
198
if (BC_ERR(h->hist == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
199
200
h->el = el_init(vm->name, stdin, stdout, stderr);
201
if (BC_ERR(h->el == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);
202
el_set(h->el, EL_SIGNAL, 1);
203
204
// I want history and a prompt.
205
history(h->hist, &bc_history_event, H_SETSIZE, 100);
206
history(h->hist, &bc_history_event, H_SETUNIQUE, 1);
207
el_set(h->el, EL_EDITOR, "emacs");
208
el_set(h->el, EL_HIST, history, h->hist);
209
el_set(h->el, EL_PROMPT, bc_history_promptFunc);
210
211
// I also want to get the user's .editrc.
212
el_source(h->el, v.v);
213
214
bc_vec_free(&v);
215
216
h->badTerm = false;
217
bc_history_prompt = NULL;
218
}
219
220
void
221
bc_history_free(BcHistory* h)
222
{
223
if (BC_PROMPT && bc_history_prompt != NULL) free(bc_history_prompt);
224
el_end(h->el);
225
history_end(h->hist);
226
}
227
228
BcStatus
229
bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)
230
{
231
BcStatus s = BC_STATUS_SUCCESS;
232
const char* line;
233
int len;
234
235
BC_SIG_LOCK;
236
237
// If the jump happens here, then a SIGINT occurred.
238
if (sigsetjmp(bc_history_jmpbuf, 0))
239
{
240
bc_vec_string(vec, 1, "\n");
241
goto end;
242
}
243
244
// This is so the signal handler can handle line libraries properly.
245
bc_history_inlinelib = 1;
246
247
if (BC_PROMPT)
248
{
249
// Make sure to set the prompt.
250
if (bc_history_prompt != NULL)
251
{
252
if (strcmp(bc_history_prompt, prompt))
253
{
254
free(bc_history_prompt);
255
bc_history_prompt = bc_vm_strdup(prompt);
256
}
257
}
258
else bc_history_prompt = bc_vm_strdup(prompt);
259
}
260
261
bc_history_use_prompt = true;
262
263
line = NULL;
264
len = -1;
265
errno = EINTR;
266
267
// Get the line.
268
while (line == NULL && len == -1 && errno == EINTR)
269
{
270
line = el_gets(h->el, &len);
271
bc_history_use_prompt = false;
272
}
273
274
// If there is no line...
275
if (BC_ERR(line == NULL))
276
{
277
// If this is true, there was an error. Otherwise, it's just EOF.
278
if (len == -1)
279
{
280
if (errno == ENOMEM) bc_err(BC_ERR_FATAL_ALLOC_ERR);
281
bc_err(BC_ERR_FATAL_IO_ERR);
282
}
283
else
284
{
285
bc_file_printf(&vm->fout, "\n");
286
s = BC_STATUS_EOF;
287
}
288
}
289
// If there is a line...
290
else
291
{
292
bc_vec_string(vec, strlen(line), line);
293
294
if (strcmp(line, "") && strcmp(line, "\n"))
295
{
296
history(h->hist, &bc_history_event, H_ENTER, line);
297
}
298
299
s = BC_STATUS_SUCCESS;
300
}
301
302
end:
303
304
bc_history_inlinelib = 0;
305
306
BC_SIG_UNLOCK;
307
308
return s;
309
}
310
311
#else // BC_ENABLE_EDITLINE
312
313
#if BC_ENABLE_READLINE
314
315
#include <assert.h>
316
#include <setjmp.h>
317
#include <string.h>
318
319
#include <history.h>
320
#include <vm.h>
321
322
sigjmp_buf bc_history_jmpbuf;
323
volatile sig_atomic_t bc_history_inlinelib;
324
325
void
326
bc_history_init(BcHistory* h)
327
{
328
h->line = NULL;
329
h->badTerm = false;
330
331
// I want no tab completion.
332
rl_bind_key('\t', rl_insert);
333
}
334
335
void
336
bc_history_free(BcHistory* h)
337
{
338
if (h->line != NULL) free(h->line);
339
}
340
341
BcStatus
342
bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)
343
{
344
BcStatus s = BC_STATUS_SUCCESS;
345
size_t len;
346
347
BC_SIG_LOCK;
348
349
// If the jump happens here, then a SIGINT occurred.
350
if (sigsetjmp(bc_history_jmpbuf, 0))
351
{
352
bc_vec_string(vec, 1, "\n");
353
goto end;
354
}
355
356
// This is so the signal handler can handle line libraries properly.
357
bc_history_inlinelib = 1;
358
359
// Get rid of the last line.
360
if (h->line != NULL)
361
{
362
free(h->line);
363
h->line = NULL;
364
}
365
366
// Get the line.
367
h->line = readline(BC_PROMPT ? prompt : "");
368
369
// If there was a line, add it to the history. Otherwise, just return an
370
// empty line. Oh, and NULL actually means EOF.
371
if (h->line != NULL && h->line[0])
372
{
373
add_history(h->line);
374
375
len = strlen(h->line);
376
377
bc_vec_expand(vec, len + 2);
378
379
bc_vec_string(vec, len, h->line);
380
bc_vec_concat(vec, "\n");
381
}
382
else if (h->line == NULL)
383
{
384
bc_file_printf(&vm->fout, "%s\n", "^D");
385
s = BC_STATUS_EOF;
386
}
387
else bc_vec_string(vec, 1, "\n");
388
389
end:
390
391
bc_history_inlinelib = 0;
392
393
BC_SIG_UNLOCK;
394
395
return s;
396
}
397
398
#else // BC_ENABLE_READLINE
399
400
#include <assert.h>
401
#include <stdlib.h>
402
#include <errno.h>
403
#include <string.h>
404
#include <ctype.h>
405
406
#include <signal.h>
407
#include <sys/stat.h>
408
#include <sys/types.h>
409
410
#ifndef _WIN32
411
#include <strings.h>
412
#include <termios.h>
413
#include <unistd.h>
414
#include <sys/ioctl.h>
415
#include <sys/select.h>
416
#endif // _WIN32
417
418
#include <status.h>
419
#include <vector.h>
420
#include <history.h>
421
#include <read.h>
422
#include <file.h>
423
#include <vm.h>
424
425
#if BC_DEBUG_CODE
426
427
/// A file for outputting to when debugging.
428
BcFile bc_history_debug_fp;
429
430
/// A buffer for the above file.
431
char* bc_history_debug_buf;
432
433
#endif // BC_DEBUG_CODE
434
435
/**
436
* Checks if the code is a wide character.
437
* @param cp The codepoint to check.
438
* @return True if @a cp is a wide character, false otherwise.
439
*/
440
static bool
441
bc_history_wchar(uint32_t cp)
442
{
443
size_t i;
444
445
for (i = 0; i < bc_history_wchars_len; ++i)
446
{
447
// Ranges are listed in ascending order. Therefore, once the
448
// whole range is higher than the codepoint we're testing, the
449
// codepoint won't be found in any remaining range => bail early.
450
if (bc_history_wchars[i][0] > cp) return false;
451
452
// Test this range.
453
if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1])
454
{
455
return true;
456
}
457
}
458
459
return false;
460
}
461
462
/**
463
* Checks if the code is a combining character.
464
* @param cp The codepoint to check.
465
* @return True if @a cp is a combining character, false otherwise.
466
*/
467
static bool
468
bc_history_comboChar(uint32_t cp)
469
{
470
size_t i;
471
472
for (i = 0; i < bc_history_combo_chars_len; ++i)
473
{
474
// Combining chars are listed in ascending order, so once we pass
475
// the codepoint of interest, we know it's not a combining char.
476
if (bc_history_combo_chars[i] > cp) return false;
477
if (bc_history_combo_chars[i] == cp) return true;
478
}
479
480
return false;
481
}
482
483
/**
484
* Gets the length of previous UTF8 character.
485
* @param buf The buffer of characters.
486
* @param pos The index into the buffer.
487
*/
488
static size_t
489
bc_history_prevCharLen(const char* buf, size_t pos)
490
{
491
size_t end = pos;
492
for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos)
493
{
494
continue;
495
}
496
return end - (pos >= end ? 0 : pos);
497
}
498
499
/**
500
* Converts UTF-8 to a Unicode code point.
501
* @param s The string.
502
* @param len The length of the string.
503
* @param cp An out parameter for the codepoint.
504
* @return The number of bytes eaten by the codepoint.
505
*/
506
static size_t
507
bc_history_codePoint(const char* s, size_t len, uint32_t* cp)
508
{
509
if (len)
510
{
511
uchar byte = (uchar) s[0];
512
513
// This is literally the UTF-8 decoding algorithm. Look that up if you
514
// don't understand this.
515
516
if ((byte & 0x80) == 0)
517
{
518
*cp = byte;
519
return 1;
520
}
521
else if ((byte & 0xE0) == 0xC0)
522
{
523
if (len >= 2)
524
{
525
*cp = (((uint32_t) (s[0] & 0x1F)) << 6) |
526
((uint32_t) (s[1] & 0x3F));
527
return 2;
528
}
529
}
530
else if ((byte & 0xF0) == 0xE0)
531
{
532
if (len >= 3)
533
{
534
*cp = (((uint32_t) (s[0] & 0x0F)) << 12) |
535
(((uint32_t) (s[1] & 0x3F)) << 6) |
536
((uint32_t) (s[2] & 0x3F));
537
return 3;
538
}
539
}
540
else if ((byte & 0xF8) == 0xF0)
541
{
542
if (len >= 4)
543
{
544
*cp = (((uint32_t) (s[0] & 0x07)) << 18) |
545
(((uint32_t) (s[1] & 0x3F)) << 12) |
546
(((uint32_t) (s[2] & 0x3F)) << 6) |
547
((uint32_t) (s[3] & 0x3F));
548
return 4;
549
}
550
}
551
else
552
{
553
*cp = 0xFFFD;
554
return 1;
555
}
556
}
557
558
*cp = 0;
559
560
return 1;
561
}
562
563
/**
564
* Gets the length of next grapheme.
565
* @param buf The buffer.
566
* @param buf_len The length of the buffer.
567
* @param pos The index into the buffer.
568
* @param col_len An out parameter for the length of the grapheme on screen.
569
* @return The number of bytes in the grapheme.
570
*/
571
static size_t
572
bc_history_nextLen(const char* buf, size_t buf_len, size_t pos, size_t* col_len)
573
{
574
uint32_t cp;
575
size_t beg = pos;
576
size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);
577
578
if (bc_history_comboChar(cp))
579
{
580
BC_UNREACHABLE
581
582
#if !BC_CLANG
583
if (col_len != NULL) *col_len = 0;
584
585
return 0;
586
#endif // !BC_CLANG
587
}
588
589
// Store the width of the character on screen.
590
if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1;
591
592
pos += len;
593
594
// Find the first non-combining character.
595
while (pos < buf_len)
596
{
597
len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);
598
599
if (!bc_history_comboChar(cp)) return pos - beg;
600
601
pos += len;
602
}
603
604
return pos - beg;
605
}
606
607
/**
608
* Gets the length of previous grapheme.
609
* @param buf The buffer.
610
* @param pos The index into the buffer.
611
* @return The number of bytes in the grapheme.
612
*/
613
static size_t
614
bc_history_prevLen(const char* buf, size_t pos)
615
{
616
size_t end = pos;
617
618
// Find the first non-combining character.
619
while (pos > 0)
620
{
621
uint32_t cp;
622
size_t len = bc_history_prevCharLen(buf, pos);
623
624
pos -= len;
625
bc_history_codePoint(buf + pos, len, &cp);
626
627
// The original linenoise-mob had an extra parameter col_len, like
628
// bc_history_nextLen(), which, if not NULL, was set in this if
629
// statement. However, we always passed NULL, so just skip that.
630
if (!bc_history_comboChar(cp)) return end - pos;
631
}
632
633
BC_UNREACHABLE
634
635
#if !BC_CLANG
636
return 0;
637
#endif // BC_CLANG
638
}
639
640
/**
641
* Reads @a n characters from stdin.
642
* @param buf The buffer to read into. The caller is responsible for making
643
* sure this is big enough for @a n.
644
* @param n The number of characters to read.
645
* @return The number of characters read or less than 0 on error.
646
*/
647
static ssize_t
648
bc_history_read(char* buf, size_t n)
649
{
650
ssize_t ret;
651
652
BC_SIG_ASSERT_LOCKED;
653
654
#ifndef _WIN32
655
656
do
657
{
658
// We don't care about being interrupted.
659
ret = read(STDIN_FILENO, buf, n);
660
}
661
while (ret == EINTR);
662
663
#else // _WIN32
664
665
bool good;
666
DWORD read;
667
HANDLE hn = GetStdHandle(STD_INPUT_HANDLE);
668
669
good = ReadConsole(hn, buf, (DWORD) n, &read, NULL);
670
671
ret = (read != n || !good) ? -1 : 1;
672
673
#endif // _WIN32
674
675
return ret;
676
}
677
678
/**
679
* Reads a Unicode code point into a buffer.
680
* @param buf The buffer to read into.
681
* @param buf_len The length of the buffer.
682
* @param cp An out parameter for the codepoint.
683
* @param nread An out parameter for the number of bytes read.
684
* @return BC_STATUS_EOF or BC_STATUS_SUCCESS.
685
*/
686
static BcStatus
687
bc_history_readCode(char* buf, size_t buf_len, uint32_t* cp, size_t* nread)
688
{
689
ssize_t n;
690
uchar byte;
691
692
assert(buf_len >= 1);
693
694
BC_SIG_LOCK;
695
696
// Read a byte.
697
n = bc_history_read(buf, 1);
698
699
BC_SIG_UNLOCK;
700
701
if (BC_ERR(n <= 0)) goto err;
702
703
// Get the byte.
704
byte = ((uchar*) buf)[0];
705
706
// Once again, this is the UTF-8 decoding algorithm, but it has reads
707
// instead of actual decoding.
708
if ((byte & 0x80) != 0)
709
{
710
if ((byte & 0xE0) == 0xC0)
711
{
712
assert(buf_len >= 2);
713
714
BC_SIG_LOCK;
715
716
n = bc_history_read(buf + 1, 1);
717
718
BC_SIG_UNLOCK;
719
720
if (BC_ERR(n <= 0)) goto err;
721
}
722
else if ((byte & 0xF0) == 0xE0)
723
{
724
assert(buf_len >= 3);
725
726
BC_SIG_LOCK;
727
728
n = bc_history_read(buf + 1, 2);
729
730
BC_SIG_UNLOCK;
731
732
if (BC_ERR(n <= 0)) goto err;
733
}
734
else if ((byte & 0xF8) == 0xF0)
735
{
736
assert(buf_len >= 3);
737
738
BC_SIG_LOCK;
739
740
n = bc_history_read(buf + 1, 3);
741
742
BC_SIG_UNLOCK;
743
744
if (BC_ERR(n <= 0)) goto err;
745
}
746
else
747
{
748
n = -1;
749
goto err;
750
}
751
}
752
753
// Convert to the codepoint.
754
*nread = bc_history_codePoint(buf, buf_len, cp);
755
756
return BC_STATUS_SUCCESS;
757
758
err:
759
// If we get here, we either had a fatal error of EOF.
760
if (BC_ERR(n < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
761
else *nread = (size_t) n;
762
return BC_STATUS_EOF;
763
}
764
765
/**
766
* Gets the column length from beginning of buffer to current byte position.
767
* @param buf The buffer.
768
* @param buf_len The length of the buffer.
769
* @param pos The index into the buffer.
770
* @return The number of columns between the beginning of @a buffer to
771
* @a pos.
772
*/
773
static size_t
774
bc_history_colPos(const char* buf, size_t buf_len, size_t pos)
775
{
776
size_t ret = 0, off = 0;
777
778
// While we haven't reached the offset, get the length of the next grapheme.
779
while (off < pos && off < buf_len)
780
{
781
size_t col_len, len;
782
783
len = bc_history_nextLen(buf, buf_len, off, &col_len);
784
785
off += len;
786
ret += col_len;
787
}
788
789
return ret;
790
}
791
792
/**
793
* Returns true if the terminal name is in the list of terminals we know are
794
* not able to understand basic escape sequences.
795
* @return True if the terminal is a bad terminal.
796
*/
797
static inline bool
798
bc_history_isBadTerm(void)
799
{
800
size_t i;
801
bool ret = false;
802
char* term = bc_vm_getenv("TERM");
803
804
if (term == NULL) return false;
805
806
for (i = 0; !ret && bc_history_bad_terms[i]; ++i)
807
{
808
ret = (!strcasecmp(term, bc_history_bad_terms[i]));
809
}
810
811
bc_vm_getenvFree(term);
812
813
return ret;
814
}
815
816
/**
817
* Enables raw mode (1960's black magic).
818
* @param h The history data.
819
*/
820
static void
821
bc_history_enableRaw(BcHistory* h)
822
{
823
// I don't do anything for Windows because in Windows, you set their
824
// equivalent of raw mode and leave it, so I do it in bc_history_init().
825
826
#ifndef _WIN32
827
struct termios raw;
828
int err;
829
830
assert(BC_TTYIN);
831
832
if (h->rawMode) return;
833
834
BC_SIG_LOCK;
835
836
if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1))
837
{
838
bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
839
}
840
841
BC_SIG_UNLOCK;
842
843
// Modify the original mode.
844
raw = h->orig_termios;
845
846
// Input modes: no break, no CR to NL, no parity check, no strip char,
847
// no start/stop output control.
848
raw.c_iflag &= (unsigned int) (~(BRKINT | ICRNL | INPCK | ISTRIP | IXON));
849
850
// Control modes: set 8 bit chars.
851
raw.c_cflag |= (CS8);
852
853
// Local modes - choing off, canonical off, no extended functions,
854
// no signal chars (^Z,^C).
855
raw.c_lflag &= (unsigned int) (~(ECHO | ICANON | IEXTEN | ISIG));
856
857
// Control chars - set return condition: min number of bytes and timer.
858
// We want read to give every single byte, w/o timeout (1 byte, no timer).
859
raw.c_cc[VMIN] = 1;
860
raw.c_cc[VTIME] = 0;
861
862
BC_SIG_LOCK;
863
864
// Put terminal in raw mode after flushing.
865
do
866
{
867
err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
868
}
869
while (BC_ERR(err < 0) && errno == EINTR);
870
871
BC_SIG_UNLOCK;
872
873
if (BC_ERR(err < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
874
#endif // _WIN32
875
876
h->rawMode = true;
877
}
878
879
/**
880
* Disables raw mode.
881
* @param h The history data.
882
*/
883
static void
884
bc_history_disableRaw(BcHistory* h)
885
{
886
sig_atomic_t lock;
887
888
if (!h->rawMode) return;
889
890
BC_SIG_TRYLOCK(lock);
891
892
#ifndef _WIN32
893
if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1))
894
{
895
h->rawMode = false;
896
}
897
#endif // _WIN32
898
899
BC_SIG_TRYUNLOCK(lock);
900
}
901
902
/**
903
* Uses the ESC [6n escape sequence to query the horizontal cursor position
904
* and return it. On error -1 is returned, on success the position of the
905
* cursor.
906
* @return The horizontal cursor position.
907
*/
908
static size_t
909
bc_history_cursorPos(void)
910
{
911
char buf[BC_HIST_SEQ_SIZE];
912
char* ptr;
913
char* ptr2;
914
size_t cols, rows, i;
915
916
BC_SIG_ASSERT_LOCKED;
917
918
// Report cursor location.
919
bc_file_write(&vm->fout, bc_flush_none, "\x1b[6n", 4);
920
bc_file_flush(&vm->fout, bc_flush_none);
921
922
// Read the response: ESC [ rows ; cols R.
923
for (i = 0; i < sizeof(buf) - 1; ++i)
924
{
925
if (bc_history_read(buf + i, 1) != 1 || buf[i] == 'R') break;
926
}
927
928
buf[i] = '\0';
929
930
// This is basically an error; we didn't get what we were expecting.
931
if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX;
932
933
// Parse the rows.
934
ptr = buf + 2;
935
rows = strtoul(ptr, &ptr2, 10);
936
937
// Here we also didn't get what we were expecting.
938
if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX;
939
940
// Parse the columns.
941
ptr = ptr2 + 1;
942
cols = strtoul(ptr, NULL, 10);
943
944
if (BC_ERR(!cols)) return SIZE_MAX;
945
946
return cols <= UINT16_MAX ? cols : 0;
947
}
948
949
/**
950
* Tries to get the number of columns in the current terminal, or assume 80
951
* if it fails.
952
* @return The number of columns in the terminal.
953
*/
954
static size_t
955
bc_history_columns(void)
956
{
957
958
#ifndef _WIN32
959
960
struct winsize ws;
961
int ret;
962
963
ret = ioctl(vm->fout.fd, TIOCGWINSZ, &ws);
964
965
if (BC_ERR(ret == -1 || !ws.ws_col))
966
{
967
// Calling ioctl() failed. Try to query the terminal itself.
968
size_t start, cols;
969
970
// Get the initial position so we can restore it later.
971
start = bc_history_cursorPos();
972
if (BC_ERR(start == SIZE_MAX)) return BC_HIST_DEF_COLS;
973
974
// Go to right margin and get position.
975
bc_file_write(&vm->fout, bc_flush_none, "\x1b[999C", 6);
976
bc_file_flush(&vm->fout, bc_flush_none);
977
cols = bc_history_cursorPos();
978
if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS;
979
980
// Restore position.
981
if (cols > start)
982
{
983
bc_file_printf(&vm->fout, "\x1b[%zuD", cols - start);
984
bc_file_flush(&vm->fout, bc_flush_none);
985
}
986
987
return cols;
988
}
989
990
return ws.ws_col;
991
992
#else // _WIN32
993
994
CONSOLE_SCREEN_BUFFER_INFO csbi;
995
996
if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
997
{
998
return 80;
999
}
1000
1001
return ((size_t) (csbi.srWindow.Right)) - csbi.srWindow.Left + 1;
1002
1003
#endif // _WIN32
1004
}
1005
1006
/**
1007
* Gets the column length of prompt text. This is probably unnecessary because
1008
* the prompts that I use are ASCII, but I kept it just in case.
1009
* @param prompt The prompt.
1010
* @param plen The length of the prompt.
1011
* @return The column length of the prompt.
1012
*/
1013
static size_t
1014
bc_history_promptColLen(const char* prompt, size_t plen)
1015
{
1016
char buf[BC_HIST_MAX_LINE + 1];
1017
size_t buf_len = 0, off = 0;
1018
1019
// The original linenoise-mob checked for ANSI escapes here on the prompt. I
1020
// know the prompts do not have ANSI escapes. I deleted the code.
1021
while (off < plen)
1022
{
1023
buf[buf_len++] = prompt[off++];
1024
}
1025
1026
return bc_history_colPos(buf, buf_len, buf_len);
1027
}
1028
1029
/**
1030
* Rewrites the currently edited line accordingly to the buffer content,
1031
* cursor position, and number of columns of the terminal.
1032
* @param h The history data.
1033
*/
1034
static void
1035
bc_history_refresh(BcHistory* h)
1036
{
1037
char* buf = h->buf.v;
1038
size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos, extras_len = 0;
1039
1040
BC_SIG_ASSERT_LOCKED;
1041
1042
bc_file_flush(&vm->fout, bc_flush_none);
1043
1044
// Get to the prompt column position from the left.
1045
while (h->pcol + bc_history_colPos(buf, len, pos) >= h->cols)
1046
{
1047
size_t chlen = bc_history_nextLen(buf, len, 0, NULL);
1048
1049
buf += chlen;
1050
len -= chlen;
1051
pos -= chlen;
1052
}
1053
1054
// Get to the prompt column position from the right.
1055
while (h->pcol + bc_history_colPos(buf, len, len) > h->cols)
1056
{
1057
len -= bc_history_prevLen(buf, len);
1058
}
1059
1060
// Cursor to left edge.
1061
bc_file_write(&vm->fout, bc_flush_none, "\r", 1);
1062
1063
// Take the extra stuff into account. This is where history makes sure to
1064
// preserve stuff that was printed without a newline.
1065
if (h->extras.len > 1)
1066
{
1067
extras_len = h->extras.len - 1;
1068
1069
bc_vec_grow(&h->buf, extras_len);
1070
1071
len += extras_len;
1072
pos += extras_len;
1073
1074
bc_file_write(&vm->fout, bc_flush_none, h->extras.v, extras_len);
1075
}
1076
1077
// Write the prompt, if desired.
1078
if (BC_PROMPT) bc_file_write(&vm->fout, bc_flush_none, h->prompt, h->plen);
1079
1080
bc_file_write(&vm->fout, bc_flush_none, h->buf.v, len - extras_len);
1081
1082
// Erase to right.
1083
bc_file_write(&vm->fout, bc_flush_none, "\x1b[0K", 4);
1084
1085
// We need to be sure to grow this.
1086
if (pos >= h->buf.len - extras_len) bc_vec_grow(&h->buf, pos + extras_len);
1087
1088
// Move cursor to original position. Do NOT move the putchar of '\r' to the
1089
// printf with colpos. That causes a bug where the cursor will go to the end
1090
// of the line when there is no prompt.
1091
bc_file_putchar(&vm->fout, bc_flush_none, '\r');
1092
colpos = bc_history_colPos(h->buf.v, len - extras_len, pos) + h->pcol;
1093
1094
// Set the cursor position again.
1095
if (colpos) bc_file_printf(&vm->fout, "\x1b[%zuC", colpos);
1096
1097
bc_file_flush(&vm->fout, bc_flush_none);
1098
}
1099
1100
/**
1101
* Inserts the character(s) 'c' at cursor current position.
1102
* @param h The history data.
1103
* @param cbuf The character buffer to copy from.
1104
* @param clen The number of characters to copy.
1105
*/
1106
static void
1107
bc_history_edit_insert(BcHistory* h, const char* cbuf, size_t clen)
1108
{
1109
BC_SIG_ASSERT_LOCKED;
1110
1111
bc_vec_grow(&h->buf, clen);
1112
1113
// If we are at the end of the line...
1114
if (h->pos == BC_HIST_BUF_LEN(h))
1115
{
1116
size_t colpos = 0, len;
1117
1118
// Copy into the buffer.
1119
memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen);
1120
1121
// Adjust the buffer.
1122
h->pos += clen;
1123
h->buf.len += clen - 1;
1124
bc_vec_pushByte(&h->buf, '\0');
1125
1126
// Set the length and column position.
1127
len = BC_HIST_BUF_LEN(h) + h->extras.len - 1;
1128
colpos = bc_history_promptColLen(h->prompt, h->plen);
1129
colpos += bc_history_colPos(h->buf.v, len, len);
1130
1131
// Do we have the trivial case?
1132
if (colpos < h->cols)
1133
{
1134
// Avoid a full update of the line in the trivial case.
1135
bc_file_write(&vm->fout, bc_flush_none, cbuf, clen);
1136
bc_file_flush(&vm->fout, bc_flush_none);
1137
}
1138
else bc_history_refresh(h);
1139
}
1140
else
1141
{
1142
// Amount that we need to move.
1143
size_t amt = BC_HIST_BUF_LEN(h) - h->pos;
1144
1145
// Move the stuff.
1146
memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt);
1147
memcpy(h->buf.v + h->pos, cbuf, clen);
1148
1149
// Adjust the buffer.
1150
h->pos += clen;
1151
h->buf.len += clen;
1152
h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
1153
1154
bc_history_refresh(h);
1155
}
1156
}
1157
1158
/**
1159
* Moves the cursor to the left.
1160
* @param h The history data.
1161
*/
1162
static void
1163
bc_history_edit_left(BcHistory* h)
1164
{
1165
BC_SIG_ASSERT_LOCKED;
1166
1167
// Stop at the left end.
1168
if (h->pos <= 0) return;
1169
1170
h->pos -= bc_history_prevLen(h->buf.v, h->pos);
1171
1172
bc_history_refresh(h);
1173
}
1174
1175
/**
1176
* Moves the cursor to the right.
1177
* @param h The history data.
1178
*/
1179
static void
1180
bc_history_edit_right(BcHistory* h)
1181
{
1182
BC_SIG_ASSERT_LOCKED;
1183
1184
// Stop at the right end.
1185
if (h->pos == BC_HIST_BUF_LEN(h)) return;
1186
1187
h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);
1188
1189
bc_history_refresh(h);
1190
}
1191
1192
/**
1193
* Moves the cursor to the end of the current word.
1194
* @param h The history data.
1195
*/
1196
static void
1197
bc_history_edit_wordEnd(BcHistory* h)
1198
{
1199
size_t len = BC_HIST_BUF_LEN(h);
1200
1201
BC_SIG_ASSERT_LOCKED;
1202
1203
// Don't overflow.
1204
if (!len || h->pos >= len) return;
1205
1206
// Find the word, then find the end of it.
1207
while (h->pos < len && isspace(h->buf.v[h->pos]))
1208
{
1209
h->pos += 1;
1210
}
1211
while (h->pos < len && !isspace(h->buf.v[h->pos]))
1212
{
1213
h->pos += 1;
1214
}
1215
1216
bc_history_refresh(h);
1217
}
1218
1219
/**
1220
* Moves the cursor to the start of the current word.
1221
* @param h The history data.
1222
*/
1223
static void
1224
bc_history_edit_wordStart(BcHistory* h)
1225
{
1226
size_t len = BC_HIST_BUF_LEN(h);
1227
1228
BC_SIG_ASSERT_LOCKED;
1229
1230
// Stop with no data.
1231
if (!len) return;
1232
1233
// Find the word, the find the beginning of the word.
1234
while (h->pos > 0 && isspace(h->buf.v[h->pos - 1]))
1235
{
1236
h->pos -= 1;
1237
}
1238
while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1]))
1239
{
1240
h->pos -= 1;
1241
}
1242
1243
bc_history_refresh(h);
1244
}
1245
1246
/**
1247
* Moves the cursor to the start of the line.
1248
* @param h The history data.
1249
*/
1250
static void
1251
bc_history_edit_home(BcHistory* h)
1252
{
1253
BC_SIG_ASSERT_LOCKED;
1254
1255
// Stop at the beginning.
1256
if (!h->pos) return;
1257
1258
h->pos = 0;
1259
1260
bc_history_refresh(h);
1261
}
1262
1263
/**
1264
* Moves the cursor to the end of the line.
1265
* @param h The history data.
1266
*/
1267
static void
1268
bc_history_edit_end(BcHistory* h)
1269
{
1270
BC_SIG_ASSERT_LOCKED;
1271
1272
// Stop at the end of the line.
1273
if (h->pos == BC_HIST_BUF_LEN(h)) return;
1274
1275
h->pos = BC_HIST_BUF_LEN(h);
1276
1277
bc_history_refresh(h);
1278
}
1279
1280
/**
1281
* Substitutes the currently edited line with the next or previous history
1282
* entry as specified by 'dir' (direction).
1283
* @param h The history data.
1284
* @param dir The direction to substitute; true means previous, false next.
1285
*/
1286
static void
1287
bc_history_edit_next(BcHistory* h, bool dir)
1288
{
1289
const char* dup;
1290
const char* str;
1291
1292
BC_SIG_ASSERT_LOCKED;
1293
1294
// Stop if there is no history.
1295
if (h->history.len <= 1) return;
1296
1297
// Duplicate the buffer.
1298
if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v);
1299
else dup = "";
1300
1301
// Update the current history entry before overwriting it with the next one.
1302
bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup);
1303
1304
// Show the new entry.
1305
h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX);
1306
1307
// Se the index appropriately at the ends.
1308
if (h->idx == SIZE_MAX)
1309
{
1310
h->idx = 0;
1311
return;
1312
}
1313
else if (h->idx >= h->history.len)
1314
{
1315
h->idx = h->history.len - 1;
1316
return;
1317
}
1318
1319
// Get the string.
1320
str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx));
1321
bc_vec_string(&h->buf, strlen(str), str);
1322
1323
assert(h->buf.len > 0);
1324
1325
// Set the position at the end.
1326
h->pos = BC_HIST_BUF_LEN(h);
1327
1328
bc_history_refresh(h);
1329
}
1330
1331
/**
1332
* Deletes the character at the right of the cursor without altering the cursor
1333
* position. Basically, this is what happens with the "Delete" keyboard key.
1334
* @param h The history data.
1335
*/
1336
static void
1337
bc_history_edit_delete(BcHistory* h)
1338
{
1339
size_t chlen, len = BC_HIST_BUF_LEN(h);
1340
1341
BC_SIG_ASSERT_LOCKED;
1342
1343
// If there is no character, skip.
1344
if (!len || h->pos >= len) return;
1345
1346
// Get the length of the character.
1347
chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL);
1348
1349
// Move characters after it into its place.
1350
memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen);
1351
1352
// Make the buffer valid again.
1353
h->buf.len -= chlen;
1354
h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
1355
1356
bc_history_refresh(h);
1357
}
1358
1359
/**
1360
* Deletes the character to the left of the cursor and moves the cursor back one
1361
* space. Basically, this is what happens with the "Backspace" keyboard key.
1362
* @param h The history data.
1363
*/
1364
static void
1365
bc_history_edit_backspace(BcHistory* h)
1366
{
1367
size_t chlen, len = BC_HIST_BUF_LEN(h);
1368
1369
BC_SIG_ASSERT_LOCKED;
1370
1371
// If there are no characters, skip.
1372
if (!h->pos || !len) return;
1373
1374
// Get the length of the previous character.
1375
chlen = bc_history_prevLen(h->buf.v, h->pos);
1376
1377
// Move everything back one.
1378
memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos);
1379
1380
// Make the buffer valid again.
1381
h->pos -= chlen;
1382
h->buf.len -= chlen;
1383
h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
1384
1385
bc_history_refresh(h);
1386
}
1387
1388
/**
1389
* Deletes the previous word, maintaining the cursor at the start of the
1390
* current word.
1391
* @param h The history data.
1392
*/
1393
static void
1394
bc_history_edit_deletePrevWord(BcHistory* h)
1395
{
1396
size_t diff, old_pos = h->pos;
1397
1398
BC_SIG_ASSERT_LOCKED;
1399
1400
// If at the beginning of the line, skip.
1401
if (!old_pos) return;
1402
1403
// Find the word, then the beginning of the word.
1404
while (h->pos > 0 && isspace(h->buf.v[h->pos - 1]))
1405
{
1406
h->pos -= 1;
1407
}
1408
while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1]))
1409
{
1410
h->pos -= 1;
1411
}
1412
1413
// Get the difference in position.
1414
diff = old_pos - h->pos;
1415
1416
// Move the data back.
1417
memmove(h->buf.v + h->pos, h->buf.v + old_pos,
1418
BC_HIST_BUF_LEN(h) - old_pos + 1);
1419
1420
// Make the buffer valid again.
1421
h->buf.len -= diff;
1422
1423
bc_history_refresh(h);
1424
}
1425
1426
/**
1427
* Deletes the next word, maintaining the cursor at the same position.
1428
* @param h The history data.
1429
*/
1430
static void
1431
bc_history_edit_deleteNextWord(BcHistory* h)
1432
{
1433
size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h);
1434
1435
BC_SIG_ASSERT_LOCKED;
1436
1437
// If at the end of the line, skip.
1438
if (next_end == len) return;
1439
1440
// Find the word, then the end of the word.
1441
while (next_end < len && isspace(h->buf.v[next_end]))
1442
{
1443
next_end += 1;
1444
}
1445
while (next_end < len && !isspace(h->buf.v[next_end]))
1446
{
1447
next_end += 1;
1448
}
1449
1450
// Move the stuff into position.
1451
memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end);
1452
1453
// Make the buffer valid again.
1454
h->buf.len -= next_end - h->pos;
1455
1456
bc_history_refresh(h);
1457
}
1458
1459
/**
1460
* Swaps two characters, the one under the cursor and the one to the left.
1461
* @param h The history data.
1462
*/
1463
static void
1464
bc_history_swap(BcHistory* h)
1465
{
1466
size_t pcl, ncl;
1467
char auxb[5];
1468
1469
BC_SIG_ASSERT_LOCKED;
1470
1471
// If there are no characters, skip.
1472
if (!h->pos) return;
1473
1474
// Get the length of the previous and next characters.
1475
pcl = bc_history_prevLen(h->buf.v, h->pos);
1476
ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);
1477
1478
// To perform a swap we need:
1479
// * Nonzero char length to the left.
1480
// * To not be at the end of the line.
1481
if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5)
1482
{
1483
// Swap.
1484
memcpy(auxb, h->buf.v + h->pos - pcl, pcl);
1485
memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl);
1486
memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl);
1487
1488
// Reset the position.
1489
h->pos += ((~pcl) + 1) + ncl;
1490
1491
bc_history_refresh(h);
1492
}
1493
}
1494
1495
/**
1496
* Raises the specified signal. This is a convenience function.
1497
* @param h The history data.
1498
* @param sig The signal to raise.
1499
*/
1500
static void
1501
bc_history_raise(BcHistory* h, int sig)
1502
{
1503
// We really don't want to be in raw mode when longjmp()'s are flying.
1504
bc_history_disableRaw(h);
1505
raise(sig);
1506
}
1507
1508
/**
1509
* Handles escape sequences. This function will make sense if you know VT100
1510
* escape codes; otherwise, it will be confusing.
1511
* @param h The history data.
1512
*/
1513
static void
1514
bc_history_escape(BcHistory* h)
1515
{
1516
char c, seq[3];
1517
1518
BC_SIG_ASSERT_LOCKED;
1519
1520
// Read a character into seq.
1521
if (BC_ERR(BC_HIST_READ(seq, 1))) return;
1522
1523
c = seq[0];
1524
1525
// ESC ? sequences.
1526
if (c != '[' && c != 'O')
1527
{
1528
if (c == 'f') bc_history_edit_wordEnd(h);
1529
else if (c == 'b') bc_history_edit_wordStart(h);
1530
else if (c == 'd') bc_history_edit_deleteNextWord(h);
1531
}
1532
else
1533
{
1534
// Read a character into seq.
1535
if (BC_ERR(BC_HIST_READ(seq + 1, 1)))
1536
{
1537
bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
1538
}
1539
1540
// ESC [ sequences.
1541
if (c == '[')
1542
{
1543
c = seq[1];
1544
1545
if (c >= '0' && c <= '9')
1546
{
1547
// Extended escape, read additional byte.
1548
if (BC_ERR(BC_HIST_READ(seq + 2, 1)))
1549
{
1550
bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
1551
}
1552
1553
if (seq[2] == '~')
1554
{
1555
switch (c)
1556
{
1557
case '1':
1558
{
1559
bc_history_edit_home(h);
1560
break;
1561
}
1562
1563
case '3':
1564
{
1565
bc_history_edit_delete(h);
1566
break;
1567
}
1568
1569
case '4':
1570
{
1571
bc_history_edit_end(h);
1572
break;
1573
}
1574
1575
default:
1576
{
1577
break;
1578
}
1579
}
1580
}
1581
else if (seq[2] == ';')
1582
{
1583
// Read two characters into seq.
1584
if (BC_ERR(BC_HIST_READ(seq, 2)))
1585
{
1586
bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);
1587
}
1588
1589
if (seq[0] != '5') return;
1590
else if (seq[1] == 'C') bc_history_edit_wordEnd(h);
1591
else if (seq[1] == 'D') bc_history_edit_wordStart(h);
1592
}
1593
}
1594
else
1595
{
1596
switch (c)
1597
{
1598
// Up.
1599
case 'A':
1600
{
1601
bc_history_edit_next(h, BC_HIST_PREV);
1602
break;
1603
}
1604
1605
// Down.
1606
case 'B':
1607
{
1608
bc_history_edit_next(h, BC_HIST_NEXT);
1609
break;
1610
}
1611
1612
// Right.
1613
case 'C':
1614
{
1615
bc_history_edit_right(h);
1616
break;
1617
}
1618
1619
// Left.
1620
case 'D':
1621
{
1622
bc_history_edit_left(h);
1623
break;
1624
}
1625
1626
// Home.
1627
case 'H':
1628
case '1':
1629
{
1630
bc_history_edit_home(h);
1631
break;
1632
}
1633
1634
// End.
1635
case 'F':
1636
case '4':
1637
{
1638
bc_history_edit_end(h);
1639
break;
1640
}
1641
1642
case 'd':
1643
{
1644
bc_history_edit_deleteNextWord(h);
1645
break;
1646
}
1647
}
1648
}
1649
}
1650
// ESC O sequences.
1651
else
1652
{
1653
switch (seq[1])
1654
{
1655
case 'A':
1656
{
1657
bc_history_edit_next(h, BC_HIST_PREV);
1658
break;
1659
}
1660
1661
case 'B':
1662
{
1663
bc_history_edit_next(h, BC_HIST_NEXT);
1664
break;
1665
}
1666
1667
case 'C':
1668
{
1669
bc_history_edit_right(h);
1670
break;
1671
}
1672
1673
case 'D':
1674
{
1675
bc_history_edit_left(h);
1676
break;
1677
}
1678
1679
case 'F':
1680
{
1681
bc_history_edit_end(h);
1682
break;
1683
}
1684
1685
case 'H':
1686
{
1687
bc_history_edit_home(h);
1688
break;
1689
}
1690
}
1691
}
1692
}
1693
}
1694
1695
/**
1696
* Adds a line to the history.
1697
* @param h The history data.
1698
* @param line The line to add.
1699
*/
1700
static void
1701
bc_history_add(BcHistory* h, char* line)
1702
{
1703
BC_SIG_ASSERT_LOCKED;
1704
1705
// If there is something already there...
1706
if (h->history.len)
1707
{
1708
// Get the previous.
1709
char* s = *((char**) bc_vec_item_rev(&h->history, 0));
1710
1711
// Check for, and discard, duplicates.
1712
if (!strcmp(s, line))
1713
{
1714
free(line);
1715
return;
1716
}
1717
}
1718
1719
bc_vec_push(&h->history, &line);
1720
}
1721
1722
/**
1723
* Adds an empty line to the history. This is separate from bc_history_add()
1724
* because we don't want it allocating.
1725
* @param h The history data.
1726
*/
1727
static void
1728
bc_history_add_empty(BcHistory* h)
1729
{
1730
const char* line = "";
1731
1732
BC_SIG_ASSERT_LOCKED;
1733
1734
// If there is something already there...
1735
if (h->history.len)
1736
{
1737
// Get the previous.
1738
char* s = *((char**) bc_vec_item_rev(&h->history, 0));
1739
1740
// Check for, and discard, duplicates.
1741
if (!s[0]) return;
1742
}
1743
1744
bc_vec_push(&h->history, &line);
1745
}
1746
1747
/**
1748
* Resets the history state to nothing.
1749
* @param h The history data.
1750
*/
1751
static void
1752
bc_history_reset(BcHistory* h)
1753
{
1754
BC_SIG_ASSERT_LOCKED;
1755
1756
h->oldcolpos = h->pos = h->idx = 0;
1757
h->cols = bc_history_columns();
1758
1759
// The latest history entry is always our current buffer, that
1760
// initially is just an empty string.
1761
bc_history_add_empty(h);
1762
1763
// Buffer starts empty.
1764
bc_vec_empty(&h->buf);
1765
}
1766
1767
/**
1768
* Prints a control character.
1769
* @param h The history data.
1770
* @param c The control character to print.
1771
*/
1772
static void
1773
bc_history_printCtrl(BcHistory* h, unsigned int c)
1774
{
1775
char str[3] = { '^', 'A', '\0' };
1776
const char newline[2] = { '\n', '\0' };
1777
1778
BC_SIG_ASSERT_LOCKED;
1779
1780
// Set the correct character.
1781
str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A);
1782
1783
// Concatenate the string.
1784
bc_vec_concat(&h->buf, str);
1785
1786
h->pos = BC_HIST_BUF_LEN(h);
1787
bc_history_refresh(h);
1788
1789
// Pop the string.
1790
bc_vec_npop(&h->buf, sizeof(str));
1791
bc_vec_pushByte(&h->buf, '\0');
1792
h->pos = 0;
1793
1794
if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D)
1795
{
1796
// We sometimes want to print a newline; for the times we don't; it's
1797
// because newlines are taken care of elsewhere.
1798
bc_file_write(&vm->fout, bc_flush_none, newline, sizeof(newline) - 1);
1799
bc_history_refresh(h);
1800
}
1801
}
1802
1803
/**
1804
* Edits a line of history. This function is the core of the line editing
1805
* capability of bc history. It expects 'fd' to be already in "raw mode" so that
1806
* every key pressed will be returned ASAP to read().
1807
* @param h The history data.
1808
* @param prompt The prompt.
1809
* @return BC_STATUS_SUCCESS or BC_STATUS_EOF.
1810
*/
1811
static BcStatus
1812
bc_history_edit(BcHistory* h, const char* prompt)
1813
{
1814
BC_SIG_LOCK;
1815
1816
bc_history_reset(h);
1817
1818
// Don't write the saved output the first time. This is because it has
1819
// already been written to output. In other words, don't uncomment the
1820
// line below or add anything like it.
1821
// bc_file_write(&vm->fout, bc_flush_none, h->extras.v, h->extras.len - 1);
1822
1823
// Write the prompt if desired.
1824
if (BC_PROMPT)
1825
{
1826
h->prompt = prompt;
1827
h->plen = strlen(prompt);
1828
h->pcol = bc_history_promptColLen(prompt, h->plen);
1829
1830
bc_file_write(&vm->fout, bc_flush_none, prompt, h->plen);
1831
bc_file_flush(&vm->fout, bc_flush_none);
1832
}
1833
1834
// This is the input loop.
1835
for (;;)
1836
{
1837
BcStatus s;
1838
char cbuf[32];
1839
unsigned int c = 0;
1840
size_t nread = 0;
1841
1842
BC_SIG_UNLOCK;
1843
1844
// Read a code.
1845
s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread);
1846
if (BC_ERR(s)) return s;
1847
1848
BC_SIG_LOCK;
1849
1850
switch (c)
1851
{
1852
case BC_ACTION_LINE_FEED:
1853
case BC_ACTION_ENTER:
1854
{
1855
// Return the line.
1856
bc_vec_pop(&h->history);
1857
BC_SIG_UNLOCK;
1858
return s;
1859
}
1860
1861
case BC_ACTION_TAB:
1862
{
1863
// My tab handling is dumb; it just prints 8 spaces every time.
1864
memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1);
1865
bc_history_edit_insert(h, cbuf, bc_history_tab_len);
1866
break;
1867
}
1868
1869
case BC_ACTION_CTRL_C:
1870
{
1871
bc_history_printCtrl(h, c);
1872
1873
// Quit if the user wants it.
1874
if (!BC_SIGINT)
1875
{
1876
vm->status = BC_STATUS_QUIT;
1877
BC_SIG_UNLOCK;
1878
BC_JMP;
1879
}
1880
1881
// Print the ready message.
1882
bc_file_write(&vm->fout, bc_flush_none, vm->sigmsg, vm->siglen);
1883
bc_file_write(&vm->fout, bc_flush_none, bc_program_ready_msg,
1884
bc_program_ready_msg_len);
1885
bc_history_reset(h);
1886
bc_history_refresh(h);
1887
1888
break;
1889
}
1890
1891
case BC_ACTION_BACKSPACE:
1892
case BC_ACTION_CTRL_H:
1893
{
1894
bc_history_edit_backspace(h);
1895
break;
1896
}
1897
1898
// Act as end-of-file or delete-forward-char.
1899
case BC_ACTION_CTRL_D:
1900
{
1901
// Act as EOF if there's no chacters, otherwise emulate Emacs
1902
// delete next character to match historical gnu bc behavior.
1903
if (BC_HIST_BUF_LEN(h) == 0)
1904
{
1905
bc_history_printCtrl(h, c);
1906
BC_SIG_UNLOCK;
1907
return BC_STATUS_EOF;
1908
}
1909
1910
bc_history_edit_delete(h);
1911
1912
break;
1913
}
1914
1915
// Swaps current character with previous.
1916
case BC_ACTION_CTRL_T:
1917
{
1918
bc_history_swap(h);
1919
break;
1920
}
1921
1922
case BC_ACTION_CTRL_B:
1923
{
1924
bc_history_edit_left(h);
1925
break;
1926
}
1927
1928
case BC_ACTION_CTRL_F:
1929
{
1930
bc_history_edit_right(h);
1931
break;
1932
}
1933
1934
case BC_ACTION_CTRL_P:
1935
{
1936
bc_history_edit_next(h, BC_HIST_PREV);
1937
break;
1938
}
1939
1940
case BC_ACTION_CTRL_N:
1941
{
1942
bc_history_edit_next(h, BC_HIST_NEXT);
1943
break;
1944
}
1945
1946
case BC_ACTION_ESC:
1947
{
1948
bc_history_escape(h);
1949
break;
1950
}
1951
1952
// Delete the whole line.
1953
case BC_ACTION_CTRL_U:
1954
{
1955
bc_vec_string(&h->buf, 0, "");
1956
h->pos = 0;
1957
1958
bc_history_refresh(h);
1959
1960
break;
1961
}
1962
1963
// Delete from current to end of line.
1964
case BC_ACTION_CTRL_K:
1965
{
1966
bc_vec_npop(&h->buf, h->buf.len - h->pos);
1967
bc_vec_pushByte(&h->buf, '\0');
1968
bc_history_refresh(h);
1969
break;
1970
}
1971
1972
// Go to the start of the line.
1973
case BC_ACTION_CTRL_A:
1974
{
1975
bc_history_edit_home(h);
1976
break;
1977
}
1978
1979
// Go to the end of the line.
1980
case BC_ACTION_CTRL_E:
1981
{
1982
bc_history_edit_end(h);
1983
break;
1984
}
1985
1986
// Clear screen.
1987
case BC_ACTION_CTRL_L:
1988
{
1989
bc_file_write(&vm->fout, bc_flush_none, "\x1b[H\x1b[2J", 7);
1990
bc_history_refresh(h);
1991
break;
1992
}
1993
1994
// Delete previous word.
1995
case BC_ACTION_CTRL_W:
1996
{
1997
bc_history_edit_deletePrevWord(h);
1998
break;
1999
}
2000
2001
default:
2002
{
2003
// If we have a control character, print it and raise signals as
2004
// needed.
2005
if ((c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) ||
2006
c == BC_ACTION_CTRL_BSLASH)
2007
{
2008
bc_history_printCtrl(h, c);
2009
#ifndef _WIN32
2010
if (c == BC_ACTION_CTRL_Z) bc_history_raise(h, SIGTSTP);
2011
if (c == BC_ACTION_CTRL_S) bc_history_raise(h, SIGSTOP);
2012
if (c == BC_ACTION_CTRL_BSLASH)
2013
{
2014
bc_history_raise(h, SIGQUIT);
2015
}
2016
#else // _WIN32
2017
vm->status = BC_STATUS_QUIT;
2018
BC_SIG_UNLOCK;
2019
BC_JMP;
2020
#endif // _WIN32
2021
}
2022
// Otherwise, just insert.
2023
else bc_history_edit_insert(h, cbuf, nread);
2024
break;
2025
}
2026
}
2027
}
2028
2029
BC_SIG_UNLOCK;
2030
2031
return BC_STATUS_SUCCESS;
2032
}
2033
2034
/**
2035
* Returns true if stdin has more data. This is for multi-line pasting, and it
2036
* does not work on Windows.
2037
* @param h The history data.
2038
*/
2039
static inline bool
2040
bc_history_stdinHasData(BcHistory* h)
2041
{
2042
#ifndef _WIN32
2043
int n;
2044
return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 ||
2045
(ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0);
2046
#else // _WIN32
2047
return false;
2048
#endif // _WIN32
2049
}
2050
2051
BcStatus
2052
bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)
2053
{
2054
BcStatus s;
2055
char* line;
2056
2057
assert(vm->fout.len == 0);
2058
2059
bc_history_enableRaw(h);
2060
2061
do
2062
{
2063
// Do the edit.
2064
s = bc_history_edit(h, prompt);
2065
2066
// Print a newline and flush.
2067
bc_file_write(&vm->fout, bc_flush_none, "\n", 1);
2068
bc_file_flush(&vm->fout, bc_flush_none);
2069
2070
BC_SIG_LOCK;
2071
2072
// If we actually have data...
2073
if (h->buf.v[0])
2074
{
2075
// Duplicate it.
2076
line = bc_vm_strdup(h->buf.v);
2077
2078
// Store it.
2079
bc_history_add(h, line);
2080
}
2081
// Add an empty string.
2082
else bc_history_add_empty(h);
2083
2084
BC_SIG_UNLOCK;
2085
2086
// Concatenate the line to the return vector.
2087
bc_vec_concat(vec, h->buf.v);
2088
bc_vec_concat(vec, "\n");
2089
}
2090
while (!s && bc_history_stdinHasData(h));
2091
2092
assert(!s || s == BC_STATUS_EOF);
2093
2094
bc_history_disableRaw(h);
2095
2096
return s;
2097
}
2098
2099
void
2100
bc_history_string_free(void* str)
2101
{
2102
char* s = *((char**) str);
2103
BC_SIG_ASSERT_LOCKED;
2104
if (s[0]) free(s);
2105
}
2106
2107
void
2108
bc_history_init(BcHistory* h)
2109
{
2110
2111
#ifdef _WIN32
2112
HANDLE out, in;
2113
#endif // _WIN32
2114
2115
BC_SIG_ASSERT_LOCKED;
2116
2117
h->rawMode = false;
2118
h->badTerm = bc_history_isBadTerm();
2119
2120
// Just don't initialize with a bad terminal.
2121
if (h->badTerm) return;
2122
2123
#ifdef _WIN32
2124
2125
h->orig_in = 0;
2126
h->orig_out = 0;
2127
2128
in = GetStdHandle(STD_INPUT_HANDLE);
2129
out = GetStdHandle(STD_OUTPUT_HANDLE);
2130
2131
// Set the code pages.
2132
SetConsoleCP(CP_UTF8);
2133
SetConsoleOutputCP(CP_UTF8);
2134
2135
// Get the original modes.
2136
if (!GetConsoleMode(in, &h->orig_in) || !GetConsoleMode(out, &h->orig_out))
2137
{
2138
// Just mark it as a bad terminal on error.
2139
h->badTerm = true;
2140
return;
2141
}
2142
else
2143
{
2144
// Set the new modes.
2145
DWORD reqOut = h->orig_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
2146
DWORD reqIn = h->orig_in | ENABLE_VIRTUAL_TERMINAL_INPUT;
2147
2148
// The input handle requires turning *off* some modes. That's why
2149
// history didn't work before; I didn't read the documentation
2150
// closely enough to see that most modes were automaticall enabled,
2151
// and they need to be turned off.
2152
reqOut |= DISABLE_NEWLINE_AUTO_RETURN | ENABLE_PROCESSED_OUTPUT;
2153
reqIn &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
2154
reqIn &= ~(ENABLE_PROCESSED_INPUT);
2155
2156
// Set the modes; if there was an error, assume a bad terminal and
2157
// quit.
2158
if (!SetConsoleMode(in, reqIn) || !SetConsoleMode(out, reqOut))
2159
{
2160
h->badTerm = true;
2161
return;
2162
}
2163
}
2164
#endif // _WIN32
2165
2166
bc_vec_init(&h->buf, sizeof(char), BC_DTOR_NONE);
2167
bc_vec_init(&h->history, sizeof(char*), BC_DTOR_HISTORY_STRING);
2168
bc_vec_init(&h->extras, sizeof(char), BC_DTOR_NONE);
2169
2170
#ifndef _WIN32
2171
FD_ZERO(&h->rdset);
2172
FD_SET(STDIN_FILENO, &h->rdset);
2173
h->ts.tv_sec = 0;
2174
h->ts.tv_nsec = 0;
2175
2176
sigemptyset(&h->sigmask);
2177
sigaddset(&h->sigmask, SIGINT);
2178
#endif // _WIN32
2179
}
2180
2181
void
2182
bc_history_free(BcHistory* h)
2183
{
2184
BC_SIG_ASSERT_LOCKED;
2185
#ifndef _WIN32
2186
bc_history_disableRaw(h);
2187
#else // _WIN32
2188
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), h->orig_in);
2189
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), h->orig_out);
2190
#endif // _WIN32
2191
#if BC_DEBUG
2192
bc_vec_free(&h->buf);
2193
bc_vec_free(&h->history);
2194
bc_vec_free(&h->extras);
2195
#endif // BC_DEBUG
2196
}
2197
2198
#if BC_DEBUG_CODE
2199
2200
/**
2201
* Prints scan codes. This special mode is used by bc history in order to print
2202
* scan codes on screen for debugging / development purposes.
2203
* @param h The history data.
2204
*/
2205
void
2206
bc_history_printKeyCodes(BcHistory* h)
2207
{
2208
char quit[4];
2209
2210
bc_vm_printf("Linenoise key codes debugging mode.\n"
2211
"Press keys to see scan codes. "
2212
"Type 'quit' at any time to exit.\n");
2213
2214
bc_history_enableRaw(h);
2215
memset(quit, ' ', 4);
2216
2217
while (true)
2218
{
2219
char c;
2220
ssize_t nread;
2221
2222
nread = bc_history_read(&c, 1);
2223
if (nread <= 0) continue;
2224
2225
// Shift string to left.
2226
memmove(quit, quit + 1, sizeof(quit) - 1);
2227
2228
// Insert current char on the right.
2229
quit[sizeof(quit) - 1] = c;
2230
if (!memcmp(quit, "quit", sizeof(quit))) break;
2231
2232
bc_vm_printf("'%c' %lu (type quit to exit)\n", isprint(c) ? c : '?',
2233
(unsigned long) c);
2234
2235
// Go left edge manually, we are in raw mode.
2236
bc_vm_putchar('\r', bc_flush_none);
2237
bc_file_flush(&vm->fout, bc_flush_none);
2238
}
2239
2240
bc_history_disableRaw(h);
2241
}
2242
#endif // BC_DEBUG_CODE
2243
2244
#endif // BC_ENABLE_HISTORY
2245
2246
#endif // BC_ENABLE_READLINE
2247
2248
#endif // BC_ENABLE_EDITLINE
2249
2250