]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/cli/linenoise.m
4bfd7df954a238efc3467b051e68ba92af0360ec
[jubjub.git] / src / gui / cli / linenoise.m
1 /* linenoise.m -- guerrilla line editing library against the idea that a
2  * line editing lib needs to be 20,000 lines of C code.
3  *
4  * You can find the latest source code at:
5  *
6  *       http://github.com/antirez/linenoise
7  *
8  * Does a number of crazy assumptions that happen to be true in 99.9999% of
9  * the 2010 UNIX computers around.
10  *
11  * ------------------------------------------------------------------------
12  *
13  * Copyright (c) 2010-2013, Salvatore Sanfilippo <antirez at gmail dot com>
14  * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
15  *
16  * All rights reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions are
20  * met:
21  *
22  *      * Redistributions of source code must retain the above copyright
23  *        notice, this list of conditions and the following disclaimer.
24  *
25  *      * Redistributions in binary form must reproduce the above copyright
26  *        notice, this list of conditions and the following disclaimer in the
27  *        documentation and/or other materials provided with the distribution.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * ------------------------------------------------------------------------
42  *
43  * References:
44  * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
45  * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
46  *
47  * Todo list:
48  * - Filter bogus Ctrl+<char> combinations.
49  * - Win32 support
50  *
51  * Bloat:
52  * - History search like Ctrl+r in readline?
53  *
54  * List of escape sequences used by this program, we do everything just
55  * with three sequences. In order to be so cheap we may have some
56  * flickering effect with some slow terminal, but the lesser sequences
57  * the more compatible.
58  *
59  * CHA (Cursor Horizontal Absolute)
60  *      Sequence: ESC [ n G
61  *      Effect: moves cursor to column n
62  *
63  * EL (Erase Line)
64  *      Sequence: ESC [ n K
65  *      Effect: if n is 0 or missing, clear from cursor to end of line
66  *      Effect: if n is 1, clear from beginning of line to cursor
67  *      Effect: if n is 2, clear entire line
68  *
69  * CUF (CUrsor Forward)
70  *      Sequence: ESC [ n C
71  *      Effect: moves cursor forward of n chars
72  *
73  * When multi line mode is enabled, we also use an additional escape
74  * sequence. However multi line editing is disabled by default.
75  *
76  * CUU (Cursor Up)
77  *      Sequence: ESC [ n A
78  *      Effect: moves cursor up of n chars.
79  *
80  * CUD (Cursor Down)
81  *      Sequence: ESC [ n B
82  *      Effect: moves cursor down of n chars.
83  *
84  * The following are used to clear the screen: ESC [ H ESC [ 2 J
85  * This is actually composed of two sequences:
86  *
87  * cursorhome
88  *      Sequence: ESC [ H
89  *      Effect: moves the cursor to upper left corner
90  *
91  * ED2 (Clear entire screen)
92  *      Sequence: ESC [ 2 J
93  *      Effect: clear the whole screen
94  *
95  */
96
97 #include <stdlib.h>
98 #include <stdio.h>
99 #include <errno.h>
100 #include <string.h>
101 #include <stdlib.h>
102 #include <sys/types.h>
103 #include <sys/ioctl.h>
104
105 #import "linenoise.h"
106
107 #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
108 #define LINENOISE_MAX_LINE 4096
109
110 static Linenoise *instance = nil;
111
112 // At exit we'll try to fix the terminal to the initial conditions.
113 static void linenoiseAtExit(void)
114 {
115         [instance LN_disableRawModeForFD: STDIN_FILENO];
116 }
117
118
119 @implementation Linenoise
120 @synthesize multiline = _multiline;
121 @synthesize completionCallback = _completionCallback;
122
123 + (Linenoise*)sharedLinenoise
124 {
125         return instance;
126 }
127
128 + (void)initialize
129 {
130         static bool initialized = false;
131         if (!initialized) {
132                 initialized = true;
133                 instance = [[self alloc] init];
134                 atexit(linenoiseAtExit);
135         }
136 }
137
138 - init
139 {
140         self = [super init];
141
142         _maximalHistoryLength = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
143         _history =
144             [[OFMutableArray alloc] initWithCapacity: _maximalHistoryLength];
145
146         return self;
147 }
148
149 - (void)dealloc
150 {
151         [_history release];
152
153         [super dealloc];
154 }
155
156 /* =========================== Line editing ================================= */
157
158
159 /* Insert the character 'c' at cursor current position.
160  *
161  * On error writing to the terminal -1 is returned, otherwise 0. */
162 - (int)LN_editInsertCharacter: (int)c
163 {
164         static char tmp[7];
165         static int fill = 0;
166         tmp[fill++] = c;
167         tmp[fill] = '\0';
168         OFString *ins;
169
170         @try {
171                 ins = @(tmp);
172         }
173         @catch (id e) {
174                 return 0;
175         }
176
177         fill = 0;
178         [_buf insertString: ins
179                    atIndex: _pos++];
180         [self refreshLine];
181
182         return 0;
183 }
184
185 /* Move cursor on the left. */
186 - (void)LN_editMoveLeft
187 {
188         if (_pos > 0) {
189                 _pos--;
190                 [self refreshLine];
191         }
192 }
193
194 /* Move cursor on the right. */
195 - (void)LN_editMoveRight
196 {
197         if (_pos != [_buf length]) {
198                 _pos++;
199                 [self refreshLine];
200         }
201 }
202
203 /* Substitute the currently edited line with the next or previous history
204  * entry as specified by 'dir'. */
205 - (void)LN_editHistoryNextInDirection: (enum linenoiseDirection)dir
206 {
207         size_t count = [_history count];
208         if (count > 1) {
209                 /* Update the current history entry before to
210                  * overwrite it with the next one. */
211                 _history[count - 1 - _historyIndex] = _buf;
212                 // Show the new entry
213                 _historyIndex += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
214                 if (_historyIndex < 0) {
215                         _historyIndex = 0;
216                         return;
217                 } else if (_historyIndex >= count) {
218                         _historyIndex = count - 1;
219                         return;
220                 }
221                 [_buf release];
222                 _buf = [_history[count - 1 - _historyIndex] mutableCopy];
223                 _pos = [_buf length];
224                 [self refreshLine];
225         }
226 }
227
228 /* Delete the character at the right of the cursor without altering the cursor
229  * position. Basically this is what happens with the "Delete" keyboard key. */
230 - (void)LN_editDelete
231 {
232         size_t len = [_buf length];
233         if (len > 0 && _pos < len) {
234                 [_buf deleteCharactersInRange: of_range(_pos, 1)];
235                 [self refreshLine];
236         }
237 }
238
239 /* Backspace implementation. */
240 - (void)LN_editBackspace
241 {
242         size_t len = [_buf length];
243         if (_pos > 0 && len > 0) {
244                 _pos--;
245                 [_buf deleteCharactersInRange: of_range(_pos, 1)];
246                 [self refreshLine];
247         }
248 }
249
250 /* Delete the previosu word, maintaining the cursor at the start of the
251  * current word. */
252 - (void)LN_editDeletePreviousWord
253 {
254         size_t old_pos = _pos;
255
256         while (_pos > 0 && [_buf characterAtIndex: _pos - 1] == ' ')
257                 _pos--;
258         while (_pos > 0 && [_buf characterAtIndex: _pos - 1] != ' ')
259                 _pos--;
260
261         [_buf deleteCharactersInRange: of_range(_pos, old_pos - _pos)];
262         [self refreshLine];
263 }
264
265 /* This function is the core of the line editing capability of linenoise.
266  * It expects 'fd' to be already in "raw mode" so that every key pressed
267  * will be returned ASAP to read().
268  *
269  * The resulting string is put into 'buf' when the user type enter, or
270  * when ctrl+d is typed.
271  *
272  * The function returns the length of the current buffer. */
273 - (OFString*)LN_editWithFD: (int)fd
274                     prompt: (OFString*)prompt
275 {
276         /* Populate the linenoise state that we pass to functions implementing
277          * specific editing functionalities. */
278         _term = [OFFile fileWithFileDescriptor: fd];
279         _buf = [@"" mutableCopy];
280         _prompt = prompt;
281         _oldpos = _pos = 0;
282         _cols = [self LN_getColumns];
283         _maxrows = 0;
284         _historyIndex = 0;
285
286         /* The latest history entry is always our current buffer, that
287          * initially is just an empty string. */
288         [self addHistoryItem: @""];
289
290         [_term writeString: prompt];
291
292         while (1) {
293                 char c;
294                 size_t nread;
295                 char seq[2], seq2[2];
296
297                 nread = [_term readIntoBuffer: &c
298                                       length: 1];
299                 if (nread == 0) {
300                         return [_buf autorelease];
301                 }
302
303                 /* Only autocomplete when the callback is set.
304                  * It returns < 0 when there was an error reading from fd.
305                  * Otherwise it will return the character that should be
306                  * handled next. */
307                 if (c == 9 && _completionCallback != NULL) {
308                         c = [self LN_completeLine];
309                         // Return on errors
310                         if (c < 0) {
311                                 return [_buf autorelease];
312                         }
313                         // Read next character when 0
314                         if (c == 0)
315                                 continue;
316                 }
317
318                 switch (c) {
319                 case 13: // enter
320                         [_history removeLastObject];
321                         return [_buf autorelease];
322                 case 3: // ctrl-c
323                         errno = EAGAIN;
324                         return nil;
325                 case 8:   // ctrl-h
326                 case 127: // backspace
327                         [self LN_editBackspace];
328                         break;
329                 case 4: // ctrl-d
330                         /* remove char at right of cursor, or if the
331                          * line is empty, act as end-of-file. */
332                         if ([_buf length] > 0) {
333                                 [self LN_editDelete];
334                         } else {
335                                 [_history removeLastObject];
336                                 return nil;
337                         }
338                         break;
339                 case 20: { // ctrl-t, swaps current character with previous.
340                         size_t pos = _pos;
341                         if (pos <= 0 || pos >= [_buf length])
342                                 break;
343
344                         OFMutableString *reverse =
345                             [[_buf substringWithRange: of_range(pos - 1, 2)]
346                             mutableCopy];
347                         [reverse reverse];
348
349                         [_buf replaceCharactersInRange: of_range(pos - 1, 2)
350                                             withString: reverse];
351
352                         if (pos != [_buf length])
353                                 _pos++;
354                         [self refreshLine];
355                         break;
356                  }
357                 case 2: // ctrl-b
358                         [self LN_editMoveLeft];
359                         break;
360                 case 6: // ctrl-f
361                         [self LN_editMoveRight];
362                         break;
363                 case 16: // ctrl-p
364                         [self LN_editHistoryNextInDirection:
365                             LINENOISE_HISTORY_PREV];
366                         break;
367                 case 14: // ctrl-n
368                         [self LN_editHistoryNextInDirection:
369                             LINENOISE_HISTORY_NEXT];
370                         break;
371                 case 27: // escape sequence
372                         /* Read the next two bytes representing the
373                          * escape sequence. */
374                         if ([_term readIntoBuffer: seq
375                                           length: 2] != 2)
376                                 break;
377
378                         if (seq[0] == 91 && seq[1] == 68) {
379                                 /* Left arrow */
380                                 [self LN_editMoveLeft];
381                         } else if (seq[0] == 91 && seq[1] == 67) {
382                                 /* Right arrow */
383                                 [self LN_editMoveRight];
384                         } else if (seq[0] == 91 &&
385                             (seq[1] == 65 || seq[1] == 66)) {
386                                 /* Up and Down arrows */
387                                 [self LN_editHistoryNextInDirection:
388                                     (seq[1] == 65) ? LINENOISE_HISTORY_PREV
389                                                    : LINENOISE_HISTORY_NEXT];
390                         } else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
391                                 // extended escape, read additional two bytes.
392                                 if ([_term readIntoBuffer: seq2
393                                                    length: 2] < 1)
394                                         break;
395                                 if (seq[1] == 51 && seq2[0] == 126) {
396                                         /* Delete key. */
397                                         [self LN_editDelete];
398                                 }
399                         }
400                         break;
401                 default:
402                         if ([self LN_editInsertCharacter: c])
403                                 return nil;
404                         break;
405                 case 21: // Ctrl+u, delete the whole line.
406                         _buf = [@"" mutableCopy];
407                         _pos = 0;
408                         [self refreshLine];
409                         break;
410                 case 11: { // Ctrl+k, delete from current to end of line.
411                         size_t pos = _pos;
412                         size_t diff = [_buf length] - pos;
413                         [_buf deleteCharactersInRange:
414                             of_range(pos, diff)];
415                         [self refreshLine];
416                         break;
417                 }
418                 case 1: // Ctrl+a, go to the start of the line
419                         _pos = 0;
420                         [self refreshLine];
421                         break;
422                 case 5: // ctrl+e, go to the end of the line
423                         _pos = [_buf length];
424                         [self refreshLine];
425                         break;
426                 case 12: // ctrl+l, clear screen
427                         [self clearScreen];
428                         [self refreshLine];
429                         break;
430                 case 23: // ctrl+w, delete previous word
431                         [self LN_editDeletePreviousWord];
432                         break;
433                 }
434         }
435         return [_buf autorelease];
436 }
437
438 /* This function calls the line editing function linenoiseEdit() using
439  * the STDIN file descriptor set in raw mode. */
440 - (OFString*)LN_editRawWithPrompt: (OFString*)prompt
441 {
442         OFString *ret;
443         int fd = [of_stdin fileDescriptorForReading];
444
445         if (!isatty(fd))
446                 return [of_stdin readLine];
447
448         if ([self LN_enableRawModeForFD: fd] == -1)
449                 return nil;
450         ret = [self LN_editWithFD: fd
451                            prompt: prompt];
452         [self LN_disableRawModeForFD: fd];
453         [of_stdout writeString: @"\n"];
454
455         return ret;
456 }
457
458 /* The high level function that is the main API of the linenoise library.
459  * This function checks if the terminal has basic capabilities, just checking
460  * for a blacklist of stupid terminals, and later either calls the line
461  * editing function or uses dummy fgets() so that you will be able to type
462  * something even in the most desperate of the conditions. */
463 - (OFString*)readInputWithPrompt: (OFString*)prompt
464 {
465         if ([self LN_isUnsupportedTerm]) {
466                 OFString *ret;
467
468                 _prompt = [prompt retain];
469
470                 [of_stdout writeString: prompt];
471                 [of_stdout flushWriteBuffer];
472
473                 ret = [of_stdin readLine];
474
475                 [_prompt release];
476                 _prompt = nil;
477
478                 return ret;
479         } else
480                 return [self LN_editRawWithPrompt: prompt];
481 }
482
483
484 /* ============================== Completion ================================ */
485
486 /* This is an helper function for linenoiseEdit() and is called when the
487  * user types the <tab> key in order to complete the string currently in the
488  * input.
489  *
490  * The state of the editing is encapsulated into the pointed linenoiseState
491  * structure as described in the structure definition. */
492 - (int)LN_completeLine
493 {
494         OFList *lc = [OFList new];
495         int nread;
496         char c = 0;
497
498         _completionCallback(_buf, lc);
499         if ([lc count] == 0) {
500                 [self LN_beep];
501         } else {
502                 bool stop = false;
503                 size_t i = 0;
504                 of_list_object_t *completion = [lc firstListObject];
505                 size_t count = [lc count];
506
507                 while (!stop) {
508                         // Show completion or original buffer
509                         if (i < count) {
510                                 size_t saved_pos = _pos;
511                                 OFMutableString *saved_buf = _buf;
512
513                                 _buf = [completion->object mutableCopy];
514                                 _pos = [_buf length];
515                                 [self refreshLine];
516                                 _pos = saved_pos;
517                                 _buf = saved_buf;
518                         } else {
519                                 [self refreshLine];
520                         }
521
522                         nread = [_term readIntoBuffer: &c
523                                                length: 1];
524                         if (nread == 0) {
525                                 [lc release];
526                                 return -1;
527                         }
528
529                         switch (c) {
530                         case 9: // tab
531                                 i = (i + 1) % (count + 1);
532                                 if (i == count) {
533                                         [self LN_beep];
534                                         break;
535                                 }
536
537                                 completion = completion->next;
538                                 if (completion == NULL)
539                                         completion = [lc firstListObject];
540                                 break;
541                         case 27: // escape
542                                 // Re-show original buffer
543                                 if (i < count)
544                                         [self refreshLine];
545                                 stop = true;
546                                 break;
547                         default:
548                                 // Update buffer and return
549                                 if (i < count) {
550                                         [_buf release];
551                                         _buf =
552                                             [completion->object mutableCopy];
553                                         _pos =
554                                             [_buf length];
555                                 }
556                                 stop = true;
557                                 break;
558                         }
559                 }
560         }
561
562         [lc release];
563         return c; // Return last read character
564 }
565
566 /* ================================ History ================================= */
567
568 // Using a circular buffer is smarter, but a bit more complex to handle.
569 - (int)addHistoryItem: (OFString*)line
570 {
571         if (_maximalHistoryLength == 0)
572                 return 0;
573
574         size_t len = [_history count];
575
576         if (len == _maximalHistoryLength) {
577                 for (size_t i = 0; i < len - 1; i++)
578                         _history[i] = _history[i+1];
579                 [_history removeLastObject];
580         }
581
582         [_history addObject: line];
583         return 1;
584 }
585
586 /* Set the maximum length for the history. This function can be called even
587  * if there is already some history, the function will make sure to retain
588  * just the latest 'len' elements if the new history length value is smaller
589  * than the amount of items already inside the history. */
590 - (void)setMaximalHistoryLength: (size_t)len
591 {
592         if (len < 1)
593                 @throw [OFInvalidArgumentException
594                     exceptionWithClass: [self class]
595                               selector: _cmd];
596
597         OFMutableArray *old = _history, *new;
598         int tocopy = len < [old count] ? len : [old count];
599
600         new = [[OFMutableArray alloc] initWithCapacity: len];
601
602         for (int i = 0; i < tocopy; i++)
603                 [new addObject: old[i]];
604
605         _history = new;
606         [old release];
607
608         _maximalHistoryLength = len;
609 }
610
611 - (size_t)maximalHistoryLength
612 {
613         return _maximalHistoryLength;
614 }
615
616 /* Save the history in the specified file. On success 0 is returned
617  * otherwise -1 is returned. */
618 - (void)saveHistoryToFile: (OFString*)filename
619 {
620         OFFile *file = [OFFile fileWithPath: filename
621                                        mode: @"w"];
622         for (int j = 0; j < [_history count]; j++)
623                 [file writeLine: _history[j]];
624         [file close];
625 }
626
627 /* Load the history from the specified file. If the file does not exist
628  * zero is returned and no operation is performed.
629  *
630  * If the file exists and the operation succeeded 0 is returned, otherwise
631  * on error -1 is returned. */
632 - (void)loadHistoryFromFile: (OFString*)filename
633 {
634         OFFile *file = [OFFile fileWithPath: filename
635                                        mode: @"r"];
636         OFString *line;
637         while ((line = [file readLine]) != nil)
638                 [self addHistoryItem: line];
639         [file close];
640 }
641
642
643 /* =========================== Line editing ================================= */
644
645 // Clear the screen. Used to handle ctrl+l
646 - (void)clearScreen
647 {
648         [of_stdout writeString: @"\x1b[H\x1b[2J"];
649 }
650
651 /* Calls the two low level functions LN_refreshSingleLine or
652  * LN_refreshMultiLine according to the selected mode. */
653 - (void)refreshLine
654 {
655         if (_buf == nil)
656                 return;
657
658         if (_multiline)
659                 [self LN_refreshMultiLine];
660         else
661                 [self LN_refreshSingleLine];
662 }
663
664 /* Single line low level line refresh.
665  *
666  * Rewrite the currently edited line accordingly to the buffer content,
667  * cursor position, and number of columns of the terminal. */
668 - (void)LN_refreshSingleLine;
669 {
670         size_t plen = [_prompt length];
671         size_t pos = _pos;
672         OFString *buf = _buf;
673
674         if ((plen + pos) >= _cols) {
675                 size_t offset = (plen + pos) - _cols;
676                 size_t buflen = [buf length] - offset;
677                 buf = [buf substringWithRange: of_range(offset, buflen)];
678                 pos -= offset;
679         }
680
681         // Cursor to left edge
682         [_term writeString: @"\x1b[0G"];
683         // Write the prompt and the current buffer content
684         [_term writeString: _prompt];
685         [_term writeString: buf];
686         // Erase to right
687         [_term writeString: @"\x1b[0K"];
688         // Move cursor to original position
689         [_term writeFormat: @"\x1b[0G\x1b[%zdC", pos + plen];
690 }
691
692 /* Multi line low level line refresh.
693  *
694  * Rewrite the currently edited line accordingly to the buffer content,
695  * cursor position, and number of columns of the terminal. */
696 - (void)LN_refreshMultiLine
697 {
698         size_t plen = [_prompt length];
699
700         // rows used by current buf.
701         size_t rows = (plen + [_buf length] + _cols - 1) / _cols;
702         // cursor relative row.
703         size_t rpos = (plen + _oldpos + _cols) / _cols;
704         // rpos after refresh.
705         size_t rpos2;
706         ssize_t old_rows = _maxrows, j;
707
708         // Update maxrows if needed.
709         if (rows > _maxrows)
710                 _maxrows = rows;
711
712 #ifdef LN_DEBUG
713         OFFile *file = [OFFile fileWithPath: @"/tmp/debug.txt"
714                                        mode: @"a"];
715         [file writeFormat: @"[%zd %zd %zd] p: %zd, rows: %zd, rpos: %zd, "
716             @"max: %zd, oldmax: %zd", [_buf length], _pos, _oldpos, plen, rows,
717             rpos, _maxrows, old_rows];
718 #endif
719
720         /* First step: clear all the lines used before. To do so start by
721          * going to the last row. */
722         if (old_rows > rpos) {
723 #ifdef LN_DEBUG
724                 [file writeFormat: @", go down %zd", old_rows - rpos];
725 #endif
726                 [_term writeFormat: @"\x1b[%zdB", old_rows - rpos];
727         }
728
729         // Now for every row clear it, go up.
730         for (j = 0; j < old_rows - 1; j++) {
731 #ifdef LN_DEBUG
732                 [file writeString: @", clear+up"];
733 #endif
734                 [_term writeString: @"\x1b[0G\x1b[0K\x1b[1A"];
735         }
736
737         // Clean the top line.
738 #ifdef LN_DEBUG
739         [file writeString: @", clear"];
740 #endif
741         [_term writeString: @"\x1b[0G\x1b[0K"];
742
743         // Write the prompt and the current buffer content
744         [_term writeString: _prompt];
745         [_term writeString: _buf];
746
747         /* If we are at the very end of the screen with our prompt, we need to
748          * emit a newline and move the prompt to the first column. */
749         if (_pos && _pos == [_buf length] && (_pos + plen) % _cols == 0) {
750 #ifdef LN_DEBUG
751                 [file writeString: @", <newline>"];
752 #endif
753                 [_term writeString: @"\n\x1b[0G"];
754                 rows++;
755                 if (rows > _maxrows)
756                         _maxrows = rows;
757         }
758
759         // Move cursor to right position.
760         rpos2 = (plen + _pos + _cols) / _cols;
761         // current cursor relative row.
762 #ifdef LN_DEBUG
763         [file writeFormat: @", rpos2 %zd", rpos2];
764 #endif
765         // Go up till we reach the expected positon.
766         if (rows - rpos2 > 0) {
767 #ifdef LN_DEBUG
768                 [file writeFormat: @", go-up %zd", rows - rpos2];
769 #endif
770                 [_term writeFormat: @"\x1b[%zdA", rows - rpos2];
771         }
772         /* Set column. */
773 #ifdef LN_DEBUG
774         [file writeFormat: @", set col %zd", 1 + ((plen + _pos) % _cols)];
775 #endif
776         [_term writeFormat: @"\x1b[%zdG", 1 + ((plen + _pos) % _cols)];
777
778         _oldpos = _pos;
779
780 #ifdef LN_DEBUG
781         [file writeString: @"\n"];
782         [file close];
783 #endif
784 }
785
786 /* ======================= Low level terminal handling ====================== */
787
788 /* Return true if the terminal name is in the list of terminals we know are
789  * not able to understand basic escape sequences. */
790
791 - (bool)LN_isUnsupportedTerm
792 {
793         static char *unsupported_term[] = { "dumb", "cons25", NULL };
794         char *term = getenv("TERM");
795         int j;
796
797         if (term == NULL)
798                 return false;
799         for (j = 0; unsupported_term[j]; j++)
800                 if (!strcasecmp(term, unsupported_term[j]))
801                         return true;
802         return false;
803 }
804
805 /* Raw mode: 1960 magic shit. */
806 - (int)LN_enableRawModeForFD: (int)fd
807 {
808         struct termios raw;
809
810         if (!isatty(STDIN_FILENO))
811                 goto fatal;
812         if (tcgetattr(fd, &_orig_termios) == -1)
813                 goto fatal;
814
815         // modify the original mode
816         raw = _orig_termios;
817
818         /* input modes: no break, no CR to NL, no parity check, no strip char,
819          * no start/stop output control. */
820         raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
821         // output modes - disable post processing
822         raw.c_oflag &= ~(OPOST);
823         // control modes - set 8 bit chars
824         raw.c_cflag |= (CS8);
825         /* local modes - choing off, canonical off, no extended functions,
826          * no signal chars (^Z, ^C) */
827         raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
828         /* control chars - set return condition: min number of bytes and timer.
829          * We want read to return every single byte, without timeout. */
830         raw.c_cc[VMIN] = 1;
831         // 1 byte, no timer
832         raw.c_cc[VTIME] = 0;
833
834         // put terminal in raw mode after flushing
835         if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
836                 goto fatal;
837         _rawmode = true;
838         return 0;
839
840 fatal:
841         errno = ENOTTY;
842         return -1;
843 }
844
845 - (void)LN_disableRawModeForFD: (int)fd
846 {
847         if (_rawmode && tcsetattr(fd, TCSAFLUSH, &_orig_termios) != -1)
848                 _rawmode = false;
849 }
850
851 /* Try to get the number of columns in the current terminal, or assume 80
852  * if it fails. */
853 - (int)LN_getColumns
854 {
855         struct winsize ws;
856
857         if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0)
858                 return 80;
859         return ws.ws_col;
860 }
861
862 /* Beep, used for completion when there is nothing to complete or when all
863  * the choices were already shown. */
864 - (void)LN_beep
865 {
866         fprintf(stderr, "\x7");
867         fflush(stderr);
868 }
869 @end