diff options
Diffstat (limited to 'boxes.c')
-rw-r--r-- | boxes.c | 245 |
1 files changed, 141 insertions, 104 deletions
@@ -1741,15 +1741,57 @@ void nop(box *box, event *event) | |||
1741 | } | 1741 | } |
1742 | 1742 | ||
1743 | 1743 | ||
1744 | static struct keyCommand *lineLoop(long extra) | ||
1745 | { | ||
1746 | struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away. | ||
1747 | int y, len; | ||
1748 | |||
1749 | // Coz things might change out from under us, find the current view. | ||
1750 | if (commandMode) view = commandLine; | ||
1751 | else view = currentBox->view; | ||
1752 | // Draw the prompt and the current line. | ||
1753 | y = view->Y + (view->cY - view->offsetY); | ||
1754 | len = strlen(view->prompt); | ||
1755 | drawLine(y, view->X, view->X + view->W, "", " ", view->prompt, "", 0); | ||
1756 | drawContentLine(view, y, view->X + len, view->X + view->W, "", " ", view->line->line, "", 1); | ||
1757 | // Move the cursor. | ||
1758 | printf("\x1B[%d;%dH", y + 1, view->X + len + (view->cX - view->offsetX) + 1); | ||
1759 | fflush(stdout); | ||
1760 | |||
1761 | // This is using the currentBox instead of view, coz the command line keys are part of the box context now, not separate. | ||
1762 | // More importantly, the currentBox may change due to a command. | ||
1763 | return currentBox->view->content->context->modes[currentBox->view->mode].keys; | ||
1764 | } | ||
1765 | |||
1766 | static void lineChar(long extra, char *buffer) | ||
1767 | { | ||
1768 | struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away. | ||
1769 | |||
1770 | // Coz things might change out from under us, find the current view. | ||
1771 | if (commandMode) view = commandLine; | ||
1772 | else view = currentBox->view; | ||
1773 | // TODO - Should check for tabs to, and insert them. | ||
1774 | // Though better off having a function for that? | ||
1775 | // TODO - see if I can get these out of here. Some sort of pushCharacter(buffer, blob) that is passed in. | ||
1776 | mooshStrings(view->line, buffer, view->iX, 0, !TT.overWriteMode); | ||
1777 | view->oW = formatLine(view, view->line->line, &(view->output)); | ||
1778 | moveCursorRelative(view, strlen(buffer), 0, 0, 0); | ||
1779 | } | ||
1780 | |||
1781 | static void lineCommand(long extra, char *command, event *event) | ||
1782 | { | ||
1783 | struct _view *view = (struct _view *) extra; // Though we pretty much stomp on this straight away. | ||
1784 | |||
1785 | // Coz things might change out from under us, find the current view. | ||
1786 | if (commandMode) view = commandLine; | ||
1787 | else view = currentBox->view; | ||
1788 | doCommand(view->content->context->commands, command, view, event); | ||
1789 | } | ||
1790 | |||
1744 | // Basically this is the main loop. | 1791 | // Basically this is the main loop. |
1745 | 1792 | ||
1746 | // X and Y are screen coords. | 1793 | void editLine(long extra, struct keyCommand *(*lineLoop)(long extra), void (*lineChar)(long extra, char *buffer), void (*lineCommand)(long extra, char *command, event *event)) |
1747 | // W and H are the size of the editLine. EditLine will start one line high, and grow to a maximum of H if needed, then start to scroll. | ||
1748 | // H more than one means the editLine can grow upwards, unless it's at the top of the box / screen, then it has to grow downwards. | ||
1749 | // X, Y, W, and H can be -1, which means to grab suitable numbers from the views box. | ||
1750 | void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | ||
1751 | { | 1794 | { |
1752 | struct termios termio, oldtermio; | ||
1753 | struct pollfd pollfds[1]; | 1795 | struct pollfd pollfds[1]; |
1754 | char buffer[20]; | 1796 | char buffer[20]; |
1755 | char command[20]; | 1797 | char command[20]; |
@@ -1762,56 +1804,15 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1762 | buffer[0] = 0; | 1804 | buffer[0] = 0; |
1763 | command[0] = 0; | 1805 | command[0] = 0; |
1764 | 1806 | ||
1765 | if (view->box) | ||
1766 | sizeViewToBox(view->box, X, Y, W, H); | ||
1767 | // Assumes the view was already setup if it's not part of a box. | ||
1768 | |||
1769 | // All the mouse tracking methods suck one way or another. sigh | ||
1770 | // Enable mouse (VT200 normal tracking mode, UTF8 encoding). The limit is 2015. Seems to only be in later xterms. | ||
1771 | // printf("\x1B[?1005h"); | ||
1772 | // Enable mouse (DEC locator reporting mode). In theory has no limit. Wont actually work though. | ||
1773 | // On the other hand, only allows for four buttons, so only half a mouse wheel. | ||
1774 | // printf("\x1B[1;2'z\x1B[1;3'{"); | ||
1775 | // Enable mouse (VT200 normal tracking mode). Has a limit of 256 - 32 rows and columns. An xterm exclusive I think, but works in roxterm at least. | ||
1776 | printf("\x1B[?1000h"); | ||
1777 | fflush(stdout); | ||
1778 | // TODO - Should remember to turn off mouse reporting when we leave. | ||
1779 | |||
1780 | // Grab the old terminal settings and save it. | ||
1781 | tcgetattr(0, &oldtermio); | ||
1782 | tcflush(0, TCIFLUSH); | ||
1783 | termio = oldtermio; | ||
1784 | |||
1785 | // Mould the terminal to our will. | ||
1786 | termio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); | ||
1787 | termio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ICANON); | ||
1788 | termio.c_cc[VTIME]=0; // deciseconds. | ||
1789 | termio.c_cc[VMIN]=1; | ||
1790 | tcsetattr(0, TCSANOW, &termio); | ||
1791 | |||
1792 | calcBoxes(currentBox); | ||
1793 | drawBoxes(currentBox); | ||
1794 | |||
1795 | // TODO - OS buffered keys might be a problem, but we can't do the usual timestamp filter for now. | 1807 | // TODO - OS buffered keys might be a problem, but we can't do the usual timestamp filter for now. |
1808 | TT.stillRunning = 1; | ||
1796 | while (TT.stillRunning) | 1809 | while (TT.stillRunning) |
1797 | { | 1810 | { |
1798 | // TODO - We can reuse one or two of these to have less of them. | 1811 | int j, p; |
1799 | int j = 0, p, ret, y, len; | 1812 | char *found = NULL; |
1800 | 1813 | // We do this coz the command set might change out from under us in response to commands. | |
1801 | // This is using the currentBox instead of view, coz the command line keys are part of the box context now, not separate. | 1814 | // So lineLoop should return the current command set if nothing else. |
1802 | // More importantly, the currentBox may change due to a command. | 1815 | struct keyCommand *ourKeys = lineLoop(extra); |
1803 | struct keyCommand *ourKeys = currentBox->view->content->context->modes[currentBox->view->mode].keys; | ||
1804 | |||
1805 | // Coz things might change out from under us, find the current view. | ||
1806 | // TODO - see if I can get this lot out of here. | ||
1807 | if (commandMode) view = commandLine; | ||
1808 | else view = currentBox->view; | ||
1809 | y = view->Y + (view->cY - view->offsetY); | ||
1810 | len = strlen(view->prompt); | ||
1811 | drawLine(y, view->X, view->X + view->W, "\0", " ", view->prompt, '\0', 0); | ||
1812 | drawContentLine(view, y, view->X + len, view->X + view->W, "\0", " ", view->line->line, '\0', 1); | ||
1813 | printf("\x1B[%d;%dH", y + 1, view->X + len + (view->cX - view->offsetX) + 1); | ||
1814 | fflush(stdout); | ||
1815 | 1816 | ||
1816 | // Apparently it's more portable to reset this each time. | 1817 | // Apparently it's more portable to reset this each time. |
1817 | memset(pollfds, 0, pollcount * sizeof(struct pollfd)); | 1818 | memset(pollfds, 0, pollcount * sizeof(struct pollfd)); |
@@ -1832,7 +1833,7 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1832 | index = 0; | 1833 | index = 0; |
1833 | buffer[0] = 0; | 1834 | buffer[0] = 0; |
1834 | } | 1835 | } |
1835 | // TODO - Send a timer event somewhere. This wont be a precise timed event, but don't think we need one. | 1836 | // TODO - Send a timer event to lineCommand(). This wont be a precise timed event, but don't think we need one. |
1836 | } | 1837 | } |
1837 | 1838 | ||
1838 | while (0 < p) | 1839 | while (0 < p) |
@@ -1842,21 +1843,21 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1842 | { | 1843 | { |
1843 | // I am assuming that we get the input atomically, each multibyte key fits neatly into one read. | 1844 | // I am assuming that we get the input atomically, each multibyte key fits neatly into one read. |
1844 | // If that's not true (which is entirely likely), then we have to get complicated with circular buffers and stuff, or just one byte at a time. | 1845 | // If that's not true (which is entirely likely), then we have to get complicated with circular buffers and stuff, or just one byte at a time. |
1845 | ret = read(pollfds[p].fd, &buffer[index], sizeof(buffer) - (index + 1)); | 1846 | j = read(pollfds[p].fd, &buffer[index], sizeof(buffer) - (index + 1)); |
1846 | if (ret < 0) // An error happened. | 1847 | if (j < 0) // An error happened. |
1847 | { | 1848 | { |
1848 | // For now, just ignore errors. | 1849 | // For now, just ignore errors. |
1849 | fprintf(stderr, "input error on %d\n", p); | 1850 | fprintf(stderr, "input error on %d\n", p); |
1850 | fflush(stderr); | 1851 | fflush(stderr); |
1851 | } | 1852 | } |
1852 | else if (ret == 0) // End of file. | 1853 | else if (j == 0) // End of file. |
1853 | { | 1854 | { |
1854 | fprintf(stderr, "EOF\n"); | 1855 | fprintf(stderr, "EOF\n"); |
1855 | fflush(stderr); | 1856 | fflush(stderr); |
1856 | } | 1857 | } |
1857 | else | 1858 | else |
1858 | { | 1859 | { |
1859 | index += ret; | 1860 | index += j; |
1860 | if (sizeof(buffer) < (index + 1)) // Ran out of buffer. | 1861 | if (sizeof(buffer) < (index + 1)) // Ran out of buffer. |
1861 | { | 1862 | { |
1862 | fprintf(stderr, "Full buffer - %s -> %s\n", buffer, command); | 1863 | fprintf(stderr, "Full buffer - %s -> %s\n", buffer, command); |
@@ -1873,7 +1874,7 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1873 | 1874 | ||
1874 | // For a real timeout checked Esc, buffer is now empty, so this for loop wont find it anyway. | 1875 | // For a real timeout checked Esc, buffer is now empty, so this for loop wont find it anyway. |
1875 | // While it's true we could avoid it by checking, the user already had to wait for a time out, and this loop wont take THAT long. | 1876 | // While it's true we could avoid it by checking, the user already had to wait for a time out, and this loop wont take THAT long. |
1876 | for (j = 0; keys[j].code; j++) // Search for multibyte keys and some control keys. | 1877 | for (j = 0; keys[j].code; j++) // Search for multibyte keys and control keys. |
1877 | { | 1878 | { |
1878 | if (strcmp(keys[j].code, buffer) == 0) | 1879 | if (strcmp(keys[j].code, buffer) == 0) |
1879 | { | 1880 | { |
@@ -1887,42 +1888,26 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1887 | // See if it's an ordinary key, | 1888 | // See if it's an ordinary key, |
1888 | if ((1 == index) && isprint(buffer[0])) | 1889 | if ((1 == index) && isprint(buffer[0])) |
1889 | { | 1890 | { |
1890 | int visucks = 0; | 1891 | // Here we want to run it through the command finder first, and only "insert" it if it's not a command. |
1891 | |||
1892 | // Here we want to pass it to the command finder first, and only "insert" it if it's not a command. | ||
1893 | // Less and more have the "ZZ" command, but nothing else seems to have multi ordinary character commands. | ||
1894 | // Less and more also have some ordinary character commands, mostly vi like. | ||
1895 | for (j = 0; ourKeys[j].key; j++) | 1892 | for (j = 0; ourKeys[j].key; j++) |
1896 | { | 1893 | { |
1897 | // Yes, that's right, we are scanning ourKeys twice, coz vi. | ||
1898 | // TODO - We can wriggle out of this later by bumping visucks up a scope and storing a pointer to ourKeys[j].command. | ||
1899 | // In fact, visucks could be that pointer. | ||
1900 | if (strcmp(ourKeys[j].key, buffer) == 0) | 1894 | if (strcmp(ourKeys[j].key, buffer) == 0) |
1901 | { | 1895 | { |
1902 | strcpy(command, buffer); | 1896 | strcpy(command, buffer); |
1903 | visucks = 1; | 1897 | found = command; |
1904 | break; | 1898 | break; |
1905 | } | 1899 | } |
1906 | } | 1900 | } |
1907 | // If there's an outstanding command, add this to the end of it. | 1901 | if (NULL == found) |
1908 | if (!visucks) | ||
1909 | { | 1902 | { |
1903 | // If there's an outstanding command, add this to the end of it. | ||
1910 | if (command[0]) strcat(command, buffer); | 1904 | if (command[0]) strcat(command, buffer); |
1911 | else | 1905 | else lineChar(extra, buffer); |
1912 | { | ||
1913 | // TODO - Should check for tabs to, and insert them. | ||
1914 | // Though better off having a function for that? | ||
1915 | // TODO - see if I can get these out of here. Some sort of pushCharacter(buffer, blob) that is passed in. | ||
1916 | mooshStrings(view->line, buffer, view->iX, 0, !TT.overWriteMode); | ||
1917 | view->oW = formatLine(view, view->line->line, &(view->output)); | ||
1918 | moveCursorRelative(view, strlen(buffer), 0, 0, 0); | ||
1919 | } | ||
1920 | } | 1906 | } |
1921 | index = 0; | 1907 | index = 0; |
1922 | buffer[0] = 0; | 1908 | buffer[0] = 0; |
1923 | } | 1909 | } |
1924 | 1910 | ||
1925 | // TODO - If the view->context has an event handler, use it, otherwise look up the specific event handler in the context modes ourselves. | ||
1926 | if (command[0]) // Search for a bound key. | 1911 | if (command[0]) // Search for a bound key. |
1927 | { | 1912 | { |
1928 | if (sizeof(command) < (strlen(command) + 1)) | 1913 | if (sizeof(command) < (strlen(command) + 1)) |
@@ -1932,21 +1917,25 @@ void editLine(view *view, int16_t X, int16_t Y, int16_t W, int16_t H) | |||
1932 | command[0] = 0; | 1917 | command[0] = 0; |
1933 | } | 1918 | } |
1934 | 1919 | ||
1935 | for (j = 0; ourKeys[j].key; j++) | 1920 | if (NULL == found) |
1936 | { | 1921 | { |
1937 | if (strcmp(ourKeys[j].key, command) == 0) | 1922 | for (j = 0; ourKeys[j].key; j++) |
1938 | { | 1923 | { |
1939 | doCommand(view->content->context->commands, ourKeys[j].command, view, NULL); | 1924 | if (strcmp(ourKeys[j].key, command) == 0) |
1940 | command[0] = 0; | 1925 | { |
1941 | break; | 1926 | found = ourKeys[j].command; |
1927 | break; | ||
1928 | } | ||
1942 | } | 1929 | } |
1943 | } | 1930 | } |
1931 | if (found) | ||
1932 | { | ||
1933 | // That last argument, an event pointer, should be the original raw keystrokes, though by this time that's been cleared. | ||
1934 | lineCommand(extra, found, NULL); | ||
1935 | command[0] = 0; | ||
1936 | } | ||
1944 | } | 1937 | } |
1945 | |||
1946 | } | 1938 | } |
1947 | |||
1948 | // Restore the old terminal settings. | ||
1949 | tcsetattr(0, TCSANOW, &oldtermio); | ||
1950 | } | 1939 | } |
1951 | 1940 | ||
1952 | 1941 | ||
@@ -2591,22 +2580,10 @@ struct context simpleVi = | |||
2591 | void boxes_main(void) | 2580 | void boxes_main(void) |
2592 | { | 2581 | { |
2593 | struct context *context = &simpleMcedit; // The default is mcedit, coz that's what I use. | 2582 | struct context *context = &simpleMcedit; // The default is mcedit, coz that's what I use. |
2583 | struct termios termio, oldtermio; | ||
2594 | char *prompt = "Enter a command : "; | 2584 | char *prompt = "Enter a command : "; |
2595 | unsigned W = 80, H = 24; | 2585 | unsigned W = 80, H = 24; |
2596 | 2586 | ||
2597 | // TODO - Should do an isatty() here, though not sure about the usefullness of driving this from a script or redirected input, since it's supposed to be a UI for terminals. | ||
2598 | // It would STILL need the terminal size for output though. Perhaps just bitch and abort if it's not a tty? | ||
2599 | // On the other hand, sed don't need no stinkin' UI. And things like more or less should be usable on the end of a pipe. | ||
2600 | |||
2601 | // TODO - set up a handler for SIGWINCH to find out when the terminal has been resized. | ||
2602 | terminal_size(&W, &H); | ||
2603 | if (toys.optflags & FLAG_w) | ||
2604 | W = TT.w; | ||
2605 | if (toys.optflags & FLAG_h) | ||
2606 | H = TT.h; | ||
2607 | |||
2608 | TT.stillRunning = 1; | ||
2609 | |||
2610 | // For testing purposes, figure out which context we use. When this gets real, the toybox multiplexer will sort this out for us instead. | 2587 | // For testing purposes, figure out which context we use. When this gets real, the toybox multiplexer will sort this out for us instead. |
2611 | if (toys.optflags & FLAG_m) | 2588 | if (toys.optflags & FLAG_m) |
2612 | { | 2589 | { |
@@ -2626,8 +2603,48 @@ void boxes_main(void) | |||
2626 | context = &simpleVi; | 2603 | context = &simpleVi; |
2627 | } | 2604 | } |
2628 | 2605 | ||
2606 | // TODO - Should do an isatty() here, though not sure about the usefullness of driving this from a script or redirected input, since it's supposed to be a UI for terminals. | ||
2607 | // It would STILL need the terminal size for output though. Perhaps just bitch and abort if it's not a tty? | ||
2608 | // On the other hand, sed don't need no stinkin' UI. And things like more or less should be usable on the end of a pipe. | ||
2609 | |||
2610 | // Grab the old terminal settings and save it. | ||
2611 | tcgetattr(0, &oldtermio); | ||
2612 | tcflush(0, TCIFLUSH); | ||
2613 | termio = oldtermio; | ||
2614 | |||
2615 | // Mould the terminal to our will. | ||
2616 | /* | ||
2617 | IUCLC (not in POSIX) Map uppercase characters to lowercase on input. | ||
2618 | IXON Enable XON/XOFF flow control on output. | ||
2619 | IXOFF Enable XON/XOFF flow control on input. | ||
2620 | IXANY (not in POSIX.1; XSI) Enable any character to restart output. | ||
2621 | |||
2622 | ECHO Echo input characters. | ||
2623 | ECHOE If ICANON is also set, the ERASE character erases the preceding input character, and WERASE erases the preceding word. | ||
2624 | ECHOK If ICANON is also set, the KILL character erases the current line. | ||
2625 | ECHONL If ICANON is also set, echo the NL character even if ECHO is not set. | ||
2626 | TOSTOP Send the SIGTTOU signal to the process group of a background process which tries to write to its controlling terminal. | ||
2627 | ICANON Enable canonical mode. This enables the special characters EOF, EOL, EOL2, ERASE, KILL, LNEXT, REPRINT, STATUS, and WERASE, and buffers by lines. | ||
2628 | |||
2629 | VTIME Timeout in deciseconds for non-canonical read. | ||
2630 | VMIN Minimum number of characters for non-canonical read. | ||
2631 | */ | ||
2632 | termio.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); | ||
2633 | termio.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP|ICANON); | ||
2634 | termio.c_cc[VTIME]=0; // deciseconds. | ||
2635 | termio.c_cc[VMIN]=1; | ||
2636 | tcsetattr(0, TCSANOW, &termio); | ||
2637 | |||
2638 | // TODO - set up a handler for SIGWINCH to find out when the terminal has been resized. | ||
2639 | terminal_size(&W, &H); | ||
2640 | if (toys.optflags & FLAG_w) | ||
2641 | W = TT.w; | ||
2642 | if (toys.optflags & FLAG_h) | ||
2643 | H = TT.h; | ||
2644 | |||
2629 | // Create the main box. Right now the system needs one for wrapping around while switching. The H - 1 bit is to leave room for our example command line. | 2645 | // Create the main box. Right now the system needs one for wrapping around while switching. The H - 1 bit is to leave room for our example command line. |
2630 | rootBox = addBox("root", context, toys.optargs[0], 0, 0, W, H - 1); | 2646 | rootBox = addBox("root", context, toys.optargs[0], 0, 0, W, H - 1); |
2647 | currentBox = rootBox; | ||
2631 | 2648 | ||
2632 | // Create the command line view, sharing the same context as the root. It will differentiate based on the view mode of the current box. | 2649 | // Create the command line view, sharing the same context as the root. It will differentiate based on the view mode of the current box. |
2633 | // Also load the command line history as it's file. | 2650 | // Also load the command line history as it's file. |
@@ -2639,9 +2656,29 @@ void boxes_main(void) | |||
2639 | // Move to the end of the history. | 2656 | // Move to the end of the history. |
2640 | moveCursorAbsolute(commandLine, 0, commandLine->content->lines.length, 0, 0); | 2657 | moveCursorAbsolute(commandLine, 0, commandLine->content->lines.length, 0, 0); |
2641 | 2658 | ||
2659 | // All the mouse tracking methods suck one way or another. sigh | ||
2660 | // http://leonerds-code.blogspot.co.uk/2012/04/wide-mouse-support-in-libvterm.html is helpful. | ||
2661 | // Enable mouse (VT200 normal tracking mode, UTF8 encoding). The limit is 2015. Seems to only be in later xterms. | ||
2662 | // printf("\x1B[?1005h"); | ||
2663 | // Enable mouse (VT340 locator reporting mode). In theory has no limit. Wont actually work though. | ||
2664 | // On the other hand, only allows for four buttons, so only half a mouse wheel. | ||
2665 | // Responds with "\1B[e;p;r;c;p&w" where e is event type, p is a bitmap of buttons pressed, r and c are the mouse coords in decimal, and p is the "page number". | ||
2666 | // printf("\x1B[1;2'z\x1B[1;3'{"); | ||
2667 | // Enable mouse (VT200 normal tracking mode). Has a limit of 256 - 32 rows and columns. An xterm exclusive I think, but works in roxterm at least. No wheel reports. | ||
2668 | // Responds with "\x1B[Mbxy" where x and y are the mouse coords, and b is bit encoded buttons and modifiers - 0=MB1 pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release, 4=Shift, 8=Meta, 16=Control | ||
2669 | printf("\x1B[?1000h"); | ||
2670 | fflush(stdout); | ||
2671 | |||
2672 | calcBoxes(currentBox); | ||
2673 | drawBoxes(currentBox); | ||
2674 | |||
2642 | // Run the main loop. | 2675 | // Run the main loop. |
2643 | currentBox = rootBox; | 2676 | editLine((long) currentBox->view, lineLoop, lineChar, lineCommand); |
2644 | editLine(currentBox->view, -1, -1, -1, -1); | 2677 | |
2678 | // TODO - Should remember to turn off mouse reporting when we leave. | ||
2679 | |||
2680 | // Restore the old terminal settings. | ||
2681 | tcsetattr(0, TCSANOW, &oldtermio); | ||
2645 | 2682 | ||
2646 | puts("\n"); | 2683 | puts("\n"); |
2647 | fflush(stdout); | 2684 | fflush(stdout); |