/* termed.c Copyright (C) 2004-2007 Codemist Ltd */
/*
* Copyright (c) 2004 A C Norman, Codemist Ltd
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject
* to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
/* Signature: 22c291e3 09-Feb-2008 */
/*
* This supports modest line-editing and history for terminal-mode
* use of "fwin" applications.
*
* Major amounts of mess here are to try to keep things supported in both
* a Windows and a Unix-like world. For Windows I use the Console API to
* get direct contol of a screen. For Unix-like systems I demand that
* a "curses" package be available. But rather than using the high-level
* parts of "curses" I only use the low level functions - that is because the
* high level ones are generlly based on needing to clear the screen at
* that start and I do not want that. Also my cursor activity is really pretty
* simple and clever optimisation for it seems unnecessary.
*/
#include "config.h"
#ifdef WIN32
#include <windows.h>
#else /* WIN32 */
#ifdef HAVE_NCURSES_H
#include <ncurses.h>
#else
#ifdef HAVE_CURSES_H
#include <curses.h>
#else
#define DISABLE 1
#endif
#endif
#ifdef HAVE_NCURSES_TERM_H
#include <ncurses/term.h>
#else
#ifdef HAVE_TERM_H
#include <term.h>
#else
#ifdef HAVE_TGETENT
#define SIMULATE_TERM_H 1
#else
#ifndef DISABLE
#define DISABLE 1
#endif
#endif
#endif
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#endif /* WIN32 */
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#ifndef UNDER_CE
#include <signal.h>
#endif
#include "termed.h"
/*
* These are colors used when in curses/term/termcap mode
*/
static const char *term_colour;
/*
* The following are the colour codes supported by curses/term.
*
* 0 black B, K, k
* 1 red R, r
* 2 green G, g
* 3 yellow Y, y
* 4 blue b n.b. only lower case here!
* 5 magenta M, m
* 6 cyan C, c
* 7 white W, w
*/
static int map_colour(int ch)
{
switch (ch)
{
case 'k': case 'K':
case 'B': return 0;
case 'r': case 'R': return 1;
case 'g': case 'G': return 2;
case 'y': case 'Y': return 3;
case 'b': return 4;
case 'm': case 'M': return 5;
case 'c': case 'C': return 6;
case 'w': case 'W': return 7;
default: return -1;
}
}
/*
* The default values set here can be changed as a result of the colour
* option passed to term_setup.
*/
static int promptColour = 4; /* Blue */
static int inputColour = 1; /* Red */
static int outputColour = -1; /* whatever user had been using */
#ifndef DEBUG
#define LOG(a)
#else
#include <stdarg.h>
static FILE *logfile = NULL;
static void write_log(char *s, ...)
{
va_list x;
if (logfile == NULL) logfile = fopen("log.log", "w");
va_start(x, s);
vfprintf(logfile, s, x);
va_end(x);
}
#define LOG(a) write_log a;
#endif
/*
* My support for a history mechanism here is really primitive and simple.
* I have a fixed size history buffer and just dump strings into it.
*/
char *input_history[INPUT_HISTORY_SIZE];
int input_history_next = 0,
input_history_current = 0,
longest_history_line = 0;
void input_history_init(void)
{
int i;
input_history_next = longest_history_line = 0;
for (i=0; i<INPUT_HISTORY_SIZE; i++)
input_history[i] = NULL;
}
void input_history_end(void)
{
int i;
for (i=0; i<INPUT_HISTORY_SIZE; i++)
{ if (input_history[i] != NULL) free(input_history[i]);
}
}
void input_history_add(const char *s)
{
/* Make a copy of the input string... */
char *scopy = (char *)malloc(strlen(s) + 1);
int p = input_history_next % INPUT_HISTORY_SIZE;
/* If malloc returns NULL I just store an empty history entry. */
if (scopy != NULL) strcpy(scopy, s);
/*
* I can overwrite an old history item here... I will keep INPUT_HISTORY_SIZE
* entries.
*/
if (input_history[p] != NULL) free(input_history[p]);
input_history[p] = scopy;
input_history_next++;
if (scopy != NULL)
{ p = strlen(scopy);
if (p > longest_history_line) longest_history_line = p;
}
}
const char *input_history_get(int n)
{
const char *s;
/*
* Filter our values that are out of range and in those cases return NULL
* as a flag to report the error.
*/
if (n == input_history_next) return "";
if (n < 0 ||
n >= input_history_next ||
n < input_history_next-INPUT_HISTORY_SIZE) return NULL;
s = input_history[n % INPUT_HISTORY_SIZE];
/* The NULL here would be if malloc had failed earlier. */
if (s == NULL) return "";
else return s;
}
/*
* Sort of beware! I have fields in the class FXTerminal with the same
* names as these (static) variables and serving the same purpose (but for
* windowed applications). Do not get confused please. The two sets of
* values should never be active at the same time. Itf I were cleverer I
* would have found a good way to share more of the code and avoid
* this potential muddle.
*/
static int historyFirst, historyNumber, historyLast;
static int searchFlags;
static const char *prompt_string = ">";
static char *input_line, *display_line;
static int prompt_length = 1, input_line_size;
/*
* term_plain_getline() can be called when cursor addressing is not
* available. It just reads characters into a line-buffer (which it
* extends as necessary). The data returned includes the newline
* that terminated the line. If an error is detected (and in particular
* at end of file) the value NULL is raturned rather than a pointer to
* an array of characters.
*
* Nothing at all is done here to fuss about special characters, so
* things like ^C, backspace, ^Q etc etc etc are handled, if at all, by
* the system that underpins "getchar".
*/
static char *term_plain_getline(void)
{
int n, ch;
#ifdef TEST
fprintf(stderr, "plain_getline:");
fflush(stderr);
#endif
printf("%.*s", prompt_length, prompt_string);
fflush(stdout);
if (input_line_size == 0) return NULL;
input_line[0] = 0;
for (n=0, ch=getchar(); ch!=EOF && ch!='\n'; n++, ch=getchar())
{ if (n >= input_line_size-20)
{ input_line = (char *)realloc(input_line, 2*input_line_size);
if (input_line == NULL)
{ input_line_size = 0;
return NULL;
}
else input_line_size = 2*input_line_size;
}
input_line[n] = ch;
input_line[n+1] = 0;
}
if (n==0 && ch==EOF) return NULL;
input_line[n++] = ch;
input_line[n] = 0;
return input_line;
}
/*
* Before calling term_getline() or its variants use this to set the
* prompt string that will be displayed. You should leave the string that
* is passed unchanged until after input that uses it has been grabbed.
* A prompt string longer than the width of the terminal is liable to lead
* to bad confusion.
*/
#ifndef MAX_PROMPT_LENGTH
# define MAX_PROMPT_LENGTH 80
#endif
void term_setprompt(const char *s)
{
prompt_string = s;
prompt_length = strlen(s);
/*
* I truncate prompts if they are really ridiculous in length since otherwise
* it may look silly.
*/
if (prompt_length > MAX_PROMPT_LENGTH) prompt_length = MAX_PROMPT_LENGTH;
}
#ifdef DISABLE
/*
* In some cases this code can not be activated because not enough
* libraries are available. In that case I will provide stubs that do
* non-clever input. It may be that an external package or body of code
* will still be providing a good environment for the user.
*/
/*
* Start up input through this package. Returns 0 in this case because
* local editing is not supported on this platform. Hence the colour
* option is ignored.
*/
int term_setup(int flag, const char *colour)
{
input_line = (char *)malloc(200);
if (input_line == NULL) input_line_size = 0;
else input_line_size = 200;
return 0;
}
/*
* Before returning from your code it would be a really good idea to
* call "term_close" since that can re-set all sorts of terminal
* characteristics. In some cases use of "atexit" to ensure this will
* make sense.
*/
void term_close(void)
{
if (input_line != NULL)
{ free(input_line);
input_line = NULL;
}
}
char *term_getline(void)
{
return term_plain_getline();
}
#else /* DISABLED */
#ifndef WIN32
static int stdin_handle, stdout_handle;
#endif
#ifdef SIMULATE_TERM_H
/*
* if "term.h" is not available but the tgetent() function is then I will
* provide my own private term.h simulation here - just including the small
* number of features that I actually use.
*/
static int columns = 80;
static int lines = 25;
static char *cursor_up = NULL;
static char *cursor_down = NULL;
static char *cursor_left = NULL;
static char *cursor_right = NULL;
static char *carriage_return = NULL;
static char *enter_reverse_mode = NULL;
static char *exit_attribute_mode = NULL;
static char *bell = NULL;
static char *clear_screen = NULL;
static char *set_a_foregound = NULL;
static char *set_foreground = NULL;
static char *orig_pair = NULL;
static char *orig_colors = NULL;
static char termcap_entry[1024];
static char my_entries[1024];
static int setupterm(char *s, int h, int *errval)
{
char *tt = getenv("TERM");
char *p = my_entries;
columns = 80;
lines = 25;
/*
* Just to feel tidy I will explicitly set all these to NULL as
* I start up.
*/
cursor_up = NULL;
cursor_down = NULL;
cursor_left = NULL;
cursor_right = NULL;
carriage_return = NULL;
enter_reverse_mode = NULL;
exit_attribute_mode = NULL;
bell = NULL;
clear_screen = NULL;
if (tt == NULL) *errval = 0;
else *errval = tgetent(termcap_entry, tt);
if (*errval <= 0) return 0;
cursor_up = tgetstr("up", &p);
cursor_down = tgetstr("do", &p);
cursor_left = tgetstr("le", &p);
cursor_right = tgetstr("nd", &p);
carriage_return = tgetstr("cr", &p);
enter_reverse_mode = tgetstr("mr", &p);
exit_attribute_mode = tgetstr("me", &p);
bell = tgetstr("bl", &p);
clear_screen = tgetstr("cl", &p);
set_a_foregound = tgetstr("AF", &p);
set_foregound = tgetstr("Sf", &p);
orig_pair = tgetstr("op", &p);
orig_colors = tgetstr("oc", &p);
return OK;
}
static void putpc(int c)
{
putchar(c);
}
static void putp(char *s)
{
tputs(s, 1, putpc);
}
static struct termios shell_term, prog_term, shell_term_o, prog_term_o;
static void def_shell_mode(void)
{
fflush(stdout);
tcgetattr(stdin_handle, &shell_term);
tcgetattr(stdout_handle, &shell_term_o);
}
static void reset_shell_mode(void)
{
fflush(stdout);
tcsetattr(stdin_handle, TCSADRAIN, &shell_term);
tcsetattr(stdout_handle, TCSADRAIN, &shell_term_o);
fflush(stdout);
}
static void def_prog_mode(void)
{
fflush(stdout);
tcgetattr(stdin_handle, &prog_term);
tcgetattr(stdout_handle, &prog_term_o);
}
static void reset_prog_mode(void)
{
fflush(stdout);
tcsetattr(stdin_handle, TCSADRAIN, &prog_term);
tcsetattr(stdout_handle, TCSADRAIN, &prog_term_o);
}
#endif
#ifdef RAW_CYGWIN
static void reset_shell(void)
{
/* This still does not seem to do what I want... */
struct termios w;
reset_shell_mode();
tcgetattr(stdin_handle, &w);
w.c_oflag |= OPOST | ONLCR;
tcsetattr(stdin_handle, TCSADRAIN, &w);
tcgetattr(stdout_handle, &w);
w.c_oflag |= OPOST | ONLCR;
tcsetattr(stdout_handle, TCSADRAIN, &w);
}
#else
#define reset_shell() reset_shell_mode();
#endif
/*
* In case the system did not provide this I will supply the ANSI
* escape code and I will use that regardless...
*/
#ifndef set_a_foreground
#define set_a_foreground "\e[3%dm"
#endif
#ifndef set_foreground
#define set_foreground NULL
#endif
/*
* When the code is built it can still determine (dynamically) that it
* should not intervene, Eg when stdin/stdout have been redirected. When
* it is not enabled it can do simple getchar/putchar IO.
*/
static int term_enabled;
static int cursorx, cursory, final_cursorx, final_cursory, max_cursory;
static int insert_point;
static int term_can_invert, invert_start, invert_end;
#ifdef WIN32
/*
* The term/termios model provides "lines" and "columns" as macros.
* For Windows I manage them myself. By and large I will just not worry
* about "lines". The only case it ought to matter to me is if a user
* tries to enter a line of input that is so long that when wrapped
* it uses more lines of the screen than there are.
*
* To start with I will leave the behaviour in this case as a bug in my
* code. If I get keen I will revisit the situation later on.
*/
static int lines, columns;
static HANDLE stdin_handle, stdout_handle;
static WORD plainAttributes, revAttributes, promptAttributes, inputAttributes;
static DWORD stdin_attributes, stdout_attributes;
#endif
static void term_putchar(int c)
{
#ifdef WIN32
DWORD nbytes;
char buffer[1];
buffer[0] = c;
WriteFile(stdout_handle, buffer, 1, &nbytes, NULL);
#else
putchar(c);
#endif
}
/*
* If possible I would like to be able to retrieve the actual size of
* the current screen, rather than relying on shell variables, like COLUMNS,
* that could possibly get out of step with reality.
*/
static void measure_screen(void)
{
/*
* I try to measure the size of the current window or terminal.
* on some systems I may not be able to!
*/
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO csb;
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb)) return;
columns = csb.srWindow.Right - csb.srWindow.Left + 1;
lines = csb.srWindow.Bottom - csb.srWindow.Top + 1;
#else
#ifdef HAVE_SYS_IOCTL_H
#ifdef TIOCGWINSZ
struct winsize ws;
if (ioctl(stdin_handle, TIOCGWINSZ, &ws) != -1)
{ if (ws.ws_col) columns = ws.ws_col;
if (ws.ws_row) lines = ws.ws_row;
}
#else
#ifdef TIOCGSIZE
struct ttysize ws;
if (ioctl(stdin_handle, TIOCGSIZE, &ws) != -1)
{ if (ws.ts_cols) columns = ws.ts_cols;
if (ws.ts_lines) lines = ws.ts_lines;
}
#endif /* TIOCGSIZE */
#endif /* TIOCGWINSZ */
#endif /* HAVE_SYS_IOCTL_H */
#endif /* WIN32 */
LOG(("[screen:%dx%d]", columns, lines));
}
#ifdef WIN32
static const char *expanded_char_sequence = NULL;
#else
#ifdef RECORD_KEYS
/*
* This abomination is here to permit me to collect the key-sequences
* that a whole bunch of things generate for me. I do not guarantee that
* it will always work, but when it does it may be useful!
*/
static void record_keys(void)
{
static char *keys[] =
{ "left arrow",
"right arrow",
"up arrow",
"down arrow",
"delete",
"backspace-style delete key",
"home",
"end",
"insert",
"enter",
"ALT-left arrow",
"ALT-right arrow",
"ALT-up arrow",
"ALT-down arrow",
"ALT-delete",
"ALT-backspace-style delete key",
"ALT-home",
"ALT-end",
"ALT-insert",
"ALT-enter",
"Ctrl-left arrow",
"Ctrl-right arrow",
"Ctrl-up arrow",
"Ctrl-down arrow",
"Ctrl-delete",
"Ctrl-backspace-style delete key",
"Ctrl-home",
"Ctrl-end",
"Ctrl-insert",
"Ctrl-enter",
NULL
};
char **p = keys;
printf("\r\nFor each key listed here press the key and then an \"x\"\r\n");
while (*p != NULL)
{ int ch;
printf("%s: ", *p++);
fflush(stdout);
while ((ch = getchar()) != 'x')
{ if (ch < 0x20) printf("^%c ", ch | 0x40);
else if (0x20 < ch && ch < 0x7f) printf("%c ", ch);
else printf("[%x] ", ch);
}
printf("\r\n");
}
printf("Thank you\r\n");
}
#endif
#endif /* WIN32 */
int term_setup(int flag, const char *colour)
{
#ifdef WIN32
DWORD w;
CONSOLE_SCREEN_BUFFER_INFO csb;
term_enabled = 0;
term_colour = (colour == NULL ? "-" : colour);
expanded_char_sequence = NULL;
input_line = (char *)malloc(200);
display_line = (char *)malloc(200);
if (input_line == NULL || display_line == NULL)
{ input_line_size = 0;
return 1;
}
else input_line_size = 200;
if (!flag) return 1;
/*
* Standard input must be from a character device and must be accepted
* by the GetConsoleMode function
*/
stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
if (GetFileType(stdin_handle) != FILE_TYPE_CHAR) return 1;
if (!GetConsoleMode(stdin_handle, &w)) return 1;
/*
* Standard output must be a character device and a ConsoleScreenBuffer
*/
stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (GetFileType(stdout_handle) != FILE_TYPE_CHAR) return 1;
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb) ||
!GetConsoleMode(stdin_handle, &stdin_attributes) ||
!GetConsoleMode(stdout_handle, &stdout_attributes)) return 1;
plainAttributes = csb.wAttributes;
revAttributes = plainAttributes ^
(FOREGROUND_RED | BACKGROUND_RED |
FOREGROUND_GREEN | BACKGROUND_GREEN |
FOREGROUND_BLUE | BACKGROUND_BLUE |
FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);
promptAttributes = plainAttributes ^ FOREGROUND_BLUE;
inputAttributes = plainAttributes ^ FOREGROUND_RED;
if (!SetConsoleMode(stdout_handle, 0)) return 1;
if (!SetConsoleMode(stdin_handle,
ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT)) return 1;
columns = csb.srWindow.Right - csb.srWindow.Left + 1;
lines = csb.srWindow.Bottom - csb.srWindow.Top + 1;
SetConsoleMode(stdout_handle, stdout_attributes);
term_can_invert = 1;
#else /* WIN32 */
int errval, errcode;
char *s;
struct termios my_term;
term_enabled = 0;
term_colour = (colour == NULL ? "-" : colour);
{ const char *s = term_colour;
if (*s)
{ int c = map_colour(*s++);
if (c < 0) c = -1;
outputColour = c;
}
if (*s)
{ int c = map_colour(*s++);
if (c < 0) c = 1;
inputColour = c;
}
if (*s)
{ int c = map_colour(*s++);
if (c < 0) c = 4;
promptColour = c;
}
}
if (!flag) return 1;
input_line = (char *)malloc(200);
display_line = (char *)malloc(200);
if (input_line == NULL || display_line == NULL)
{ input_line_size = 0;
return 1;
}
else input_line_size = 200;
stdin_handle = fileno(stdin);
stdout_handle = fileno(stdout);
/*
* Check for redirected stdin/stdout.
*/
if (!isatty(stdin_handle) || !isatty(stdout_handle)) return 1;
/*
* Next check if the terminal is one that we know about...
*/
s = getenv("TERM");
if (s == NULL) s = "dumb";
errcode = setupterm(s, /* terminal type */
stdout_handle, /* ie to stdout */
&errval);
if (errcode != OK || errval != 1) return 1;
/*
* I really want the very basic units of cursor movement to be available,
* and if they are not I will just give up.
*/
if (cursor_up == NULL ||
cursor_down == NULL ||
cursor_left == NULL ||
cursor_right == NULL ||
carriage_return == NULL) return 1;
if (enter_reverse_mode != NULL &&
exit_attribute_mode != NULL) term_can_invert = 1;
else term_can_invert = 0;
/*
* def_shell_mode() remembers the configuration of my terminal in the
* state before I alter any parameters. It saves information so that
* reset_shell_mode() can put things back the way they were.
*/
def_shell_mode();
/*
* I guess I am going to suppose here that stdin and stdout are both
* associated with the SAME terminal. If the computer had two (or more)
* terminals and stdin/stdout were attached to different ones then
* the use of tcgetattr/tcsetattr would only do things to the stdin
* one here.
*/
tcgetattr(stdin_handle, &my_term);
#ifdef HAVE_CFMAKERAW
/*
* If I have cfmakeraw that is liable to be a definitive way of setting
* raw mode. Otherwise I will set the flags that I expect cfmakeraw to set.
* The one that it took be a while to spot was c_cc[VMIN] to ensure that
* reading characters still waits for at least one...
*/
cfmakeraw(&my_term);
#else
my_term.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON);
my_term.c_oflag &= ~OPOST;
my_term.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
my_term.c_cflag &= ~(CSIZE | PARENB);
my_term.c_cflag |= CS8;
my_term.c_cc[VMIN] = 1;
my_term.c_cc[VTIME] = 0;
#endif
tcsetattr(stdin_handle, TCSADRAIN, &my_term);
def_prog_mode();
#ifdef RECORD_KEYS
record_keys();
#endif
reset_shell();
#endif /* WIN32 */
term_enabled = 1;
input_history_init();
historyFirst = historyNumber = 0;
historyLast = -1;
searchFlags = 0;
invert_start = invert_end = -1;
return 0;
}
void term_close(void)
{
/*
* Note here and elsewhere in this file that I go "fflush(stdout)" before
* doing anything that may change styles or options for stream handling.
*/
fflush(stdout);
#ifdef WIN32
if (term_enabled)
{ SetConsoleMode(stdin_handle, stdin_attributes);
SetConsoleMode(stdout_handle, stdout_attributes);
}
#else
if (term_enabled)
{ reset_shell();
}
#endif
term_enabled = 0;
if (input_line != NULL)
{ free(input_line);
input_line = NULL;
}
if (display_line != NULL)
{ free(display_line);
display_line = NULL;
}
}
#ifdef WIN32
static INPUT_RECORD keyboard_buffer[1] = {{0}};
#endif
/*
* term_getchar() will block until the user has typed something. I will use
* this place as where I unravel various funny escape sequences that
* stand for uses of control keys. For all cases I will hand back an 8-bit
* "basic character", but I will then OR in 0x100 if ALT was pressed with it
* and 0x200 if it is not a simple key but a value returned to indicate a
* function key, arrow key etc etc. Regardless of the meaning of bits
* ORd in I will hand back EOF in some boundary cases. When using a Unix-
* style terminal (rather than Windows) I will just ignore invalid or
* unrecognized escape systems sent by the lowever level terminal drivers.
*/
#define TERM_UP 'A'
#define TERM_DOWN 'B'
#define TERM_RIGHT 'C'
#define TERM_LEFT 'D'
#define TERM_DELETE 'x'
#define TERM_HOME '1'
#define TERM_END '2'
#define TERM_INSERT 'y'
static int term_getchar(void)
{
#ifdef WIN32
DWORD n;
int down, key, ascii, ctrl;
for (;;)
{
/*
* I need to bother myself with repeat-counts. So in general there
* has been a call to inspect the keyboard before. If, after processing that
* I has a keyboard event with a residual repeat-count left over I will
* just have to handle that. Otherwise I need to call ReadConsoleInput to
* get some more. If that call fails I will return EOF as an error indication.
*/
if ((keyboard_buffer[0].EventType != KEY_EVENT ||
keyboard_buffer[0].Event.KeyEvent.wRepeatCount == 0) &&
!ReadConsoleInput(stdin_handle, keyboard_buffer, 1, &n))
{ return EOF;
}
/*
* By the time I get here keyboard_buffer will hold an event. It might be
* one of a range of sorts! At present I only do anything at all with KEY
* events, but I could potentially look for mouse activity.
*/
switch (keyboard_buffer[0].EventType)
{
default: /* Ignore non-keyboard event */
continue;
case KEY_EVENT:
keyboard_buffer[0].Event.KeyEvent.wRepeatCount--;
down = keyboard_buffer[0].Event.KeyEvent.bKeyDown;
if (!down) continue; /* discard KEY-UP events */
key = keyboard_buffer[0].Event.KeyEvent.wVirtualKeyCode;
ascii = keyboard_buffer[0].Event.KeyEvent.uChar.AsciiChar;
ctrl = keyboard_buffer[0].Event.KeyEvent.dwControlKeyState;
/*
* If Windows thinks that the key that has been hit corresponded to an
* ordinary character than I will just return it. No hassle here! Well
* not quite so easy after all. If ALT is held down at the same time as
* the character I will or in the 0x100 bit.
*
* Ha Ha! "ascii==0" would apply in the case of "^@". By experiment the
* notionally system independent key-code for "@" is 0xc0 (well I can worry
* in case that is really for "'") do I test for that. Anyway at least
* with my keyboard this lets "^@" get through!
*/
if (key != 0x11) LOG(("\nascii=%x VK=%x ctrl=%x\n", ascii, key, ctrl));
if (ascii != 0 || key == 0xc0)
{ if (ctrl & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED))
ascii |= 0x100;
return ascii;
}
/*
* Now use the variable "ascii" to record the state of the ALT key.
*/
if (ctrl & (LEFT_ALT_PRESSED|RIGHT_ALT_PRESSED)) ascii = 0x100;
else ascii = 0;
/*
* I map the Microsoft Key-Codes onto codes of my own. Observe that I do not
* support anything like all of the possible keys here, and that I do not
* detect SHIFT or CONTROL pressed in association with a function key. I will
* extend the tables here later if I feel moved to, but getting compatibility
* with the Unix-like case means I am unlikely to want to support every
* possible feature.
*/
switch (key)
{
default: continue; /* Ignore unknown keys */
case VK_LEFT:
return ascii | TERM_LEFT | 0x200;
case VK_RIGHT:
return ascii | TERM_RIGHT | 0x200;
case VK_UP:
return ascii | TERM_UP | 0x200;
case VK_DOWN:
return ascii | TERM_DOWN | 0x200;
case VK_HOME:
return ascii | TERM_HOME | 0x200;
case VK_END:
return ascii | TERM_END | 0x200;
case VK_DELETE:
return ascii | TERM_DELETE | 0x200;
}
}
}
#else
/*
* In the Unix-like case I run a state-machine to grab sequences of
* characters by way of escape codes. One consequence of this is that
* a single ESC character is generally not passed through to the user.
* So far as I can discover at present I can not guarantee to detect
* "^@" here even though I am in raw mode. I fear that to handle that I
* may need yet lower levels of protocol...
*/
#define BASE_STATE 0
#define ESC_STATE 1
#define ESC_BR_STATE 2
/*
* What I should almost certainly be doing here involves the following
* variables that I have either set up for myself or that setupterm()
* established for me.
*
* key_up = tgetstr("ku", &p);
* key_down = tgetstr("kd", &p);
* key_left = tgetstr("kl", &p);
* key_right = tgetstr("kr", &p);
* key_dc = tgetstr("kD", &p);
* key_home = tgetstr("kh", &p);
* key_end = tgetstr("@7", &p);
*
* So rather than trying to have a general state machine to parse things
* perhaps I could just check against the above strings (when they are
* non-NULL).
* HOWEVER when I look at the results from the above I find that in
* too many cases my HOME and END keys are not reported back to me, so I will
* go with ad-hoc decoding.
*
* BASE --esc--> ESC --[--> ESC_BR --0/9--> DIG --other--> done
* | |
* second ESC counted digits and ";" absorbed
*/
int state = BASE_STATE, esc_esc = 0, ch, numval1=0, numval2=0;
for (;;)
{ ch = getchar();
LOG(("RAW ch=%.2x : <%c>\n", ch, ch | 0x40));
if (ch == EOF) return EOF;
switch (state)
{
default:
case BASE_STATE:
/*
* This is where I start. If I get anything other than ESC I just return
* it, but if I get ESC I progress a state.
*/
if (ch == 0x1b)
{ state = ESC_STATE;
continue;
}
else return ch;
case ESC_STATE:
/*
* After ESC if the next character is another ESC I just count it, if
* the next character is [ (so I have had "ESC [" or "ESC ESC [") I
* progress, otherwise I treat the character as "escaped" and or in the
* "meta" bit 0x100.
*/
if (ch == 0x1b)
{ esc_esc ^= 0x100;
continue;
}
else if (ch == '[')
{ state = ESC_BR_STATE;
continue;
}
else return ch | 0x100;
case ESC_BR_STATE:
/*
* After "ESC [" I absorb digits and semicolons.
*/
if (isdigit(ch))
{ numval1 = 10*numval1 + ch - '0';
continue;
}
else if (ch == ';')
{ numval2 = numval1;
numval1 = 0;
continue;
}
/*
* Now the whole code can be put together as one item...
*/
ch = ch | (numval2 << 16) | (numval1 << 8);
/*
* The table here represents the codes that I have come across thus far:
*/
esc_esc |= 0x200;
switch (ch)
{
case 0x010500+'A':
case 0x000000+'A': return esc_esc | TERM_UP;
case 0x010500+'b':
case 0x000000+'B': return esc_esc | TERM_DOWN;
case 0x010500+'C':
case 0x000000+'C': return esc_esc | TERM_RIGHT;
case 0x010500+'D':
case 0x000000+'D': return esc_esc | TERM_LEFT;
case 0x030500+'~':
case 0x000300+'~': return esc_esc | TERM_DELETE;
case 0x000100+'~':
case 0x010500+'H':
case 0x000000+'H': return esc_esc | TERM_HOME;
case 0x000400+'~':
case 0x010500+'F':
case 0x000000+'F': return esc_esc | TERM_END;
case 0x020500+'~':
case 0x000200+'~': return esc_esc | TERM_INSERT;
case 0x010300+'A': return 0x300 | TERM_UP;
case 0x010300+'B': return 0x300 | TERM_DOWN;
case 0x010300+'C': return 0x300 | TERM_RIGHT;
case 0x010300+'D': return 0x300 | TERM_LEFT;
case 0x030300+'~': return 0x300 | TERM_DELETE;
case 0x010300+'H': return 0x300 | TERM_HOME;
case 0x010300+'F': return 0x300 | TERM_END;
case 0x020300+'~': return 0x300 | TERM_INSERT;
default: return 0x100 | ch;
}
}
}
#endif
}
/*
* Cursor movement functions are only wanted within this file and
* should only actually be called in the "enabled" case.
*/
static void term_move_down(int del)
{
#ifdef WIN32
/*
* Since the screen is on the same machine as the rest of my process, and
* I am in general interacting with a (slow) user here I will not try ANY
* optimisations at all!
*/
CONSOLE_SCREEN_BUFFER_INFO csb;
if (del == 0) return;
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb)) return;
csb.dwCursorPosition.Y += del;
SetConsoleCursorPosition(stdout_handle, csb.dwCursorPosition);
/*
* I do not quite know if the above is always what I want! When I move down
* and I start on the line that is the lowest one on the screen I do really
* want a new line to appear for me to move onto.
*/
cursory += del;
if (cursory > max_cursory) max_cursory = cursory;
#else
cursory += del;
if (cursory > max_cursory) max_cursory = cursory;
fflush(stdout);
while (del > 0)
{ putp(cursor_down);
del--;
}
while (del < 0)
{ putp(cursor_up);
del++;
}
#endif
}
static void term_move_right(int del)
{
#ifdef WIN32 /* OK */
CONSOLE_SCREEN_BUFFER_INFO csb;
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb)) return;
csb.dwCursorPosition.X += del;
SetConsoleCursorPosition(stdout_handle, csb.dwCursorPosition);
/*
* The above would be unsatisfactory if it ever happened that the
* movement was liable to take me beyond the width of the terminal, and
* there is a special worry about the bottom rightmost position on the
* screen, since it is not well supported by some terminals.
*/
cursorx += del;
return;
#else
/*
* As a small optimisation here if the target location is closer to the
* left margin than to the currect position (by at least 3) then
* issue a CR to move rapidly to the left margin and then sort out what
* else is needed.
*/
fflush(stdout);
if (del<0 && (cursorx+del+3) < (-del))
{ putp(carriage_return);
del = cursorx + del;
cursorx = 0;
}
cursorx += del;
/*
* ... otherwise I position the cursor by doing dull sequences trhat move the
* cursor one positiomn at a time.
*/
while (del > 0)
{ putp(cursor_right);
del--;
}
while (del < 0)
{ putp(cursor_left);
del++;
}
#endif
}
static void term_move_first_column(void)
{
#ifdef WIN32 /* OK */
CONSOLE_SCREEN_BUFFER_INFO csb;
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb)) return;
csb.dwCursorPosition.X = 0;
SetConsoleCursorPosition(stdout_handle, csb.dwCursorPosition);
cursorx = 0;
return;
#else
fflush(stdout);
putp(carriage_return);
cursorx = 0;
#endif
}
static void term_bell(void)
{
#ifdef WIN32
Beep(1000, 100);
#else
if (term_enabled && bell) putp(bell);
else putchar(0x07);
#endif
}
/*
* I call refresh_display() to get the screen up to date with what my
* internal model expects. The data I base this on will be:
* input_line a string showing what I want displayed
* !display_line the string I had last displayed
* insert_point the offset in input_line that I want the cursor
* to end up at
* !cursorx the current X coordinate on the cursor on the scren,
* where 0 indicated the leftmost column
* !cursory the current Y coordinate, where 0 is the line that
* the active input line's display starts (and positive
* coordinates indicated lines below that)
* !final_cursorx x-coordinate of rightmost visible character on the
* screen at present.
* !final_cursory y-coordinate ditto
* !max_cursory largest value of cursory used thus far
* columns width of the screen
* lines height of the screen
* term_can_invert display is capable of inverse video
* invert_start,
* invert_end region (if any) to highlight.
*
* [I have annotated items that are updated here with (!)]
*
* The main complications thar arise are
* (a) input_line may contain characters that should be rendered across
* several columns. The key case that forces this is TAB, but once
* that has been conceded it may make sense to render control characters
* so that eg '\04' is dispalyed as "^D" and so that other unprintable
* characters that may get into the buffer by accident are handled
* kindly.
* [as a late bit of imagination I might like to render an embedded
* newline character ('\n') as a line-break]
* (b) input_line (with or without TABs!) can stand for a line that is
* longer than the line-length shown by "columns". In that case the
* expected representation on the screen will involve wrapping it.
* proper treatment of wrapping and scrolling and the final character
* position on the screen all need attention.
*
* (c) In extreme cases wrapping could lead to a display using too many
* lines, and in that case further truncation is needed.
* (d) After a case where the user re-sizes the screen the issue of what is
* visible on the screen, where the cursor is and in general what to do
* to regain sanity seems pretty delicate!
*/
/*
* With my first version of the code here I will not handle either (c) or (d)
* a lot. Well maybe in fact (d) gets coped with by virtue of the total
* screen re-write..., and I check screen size (if I can) whenever I am
* about to refresh.
*
* The code here updates from the first changed position of a line right to
* the end. If the user has re-positioned the cursor so they are making edits
* towards the start of a long line this can lead to excessive screen
* update actvity. If this ever became a worry there are two things I could
* do:
* (1) check for and exploit delete and insert modes for my screen and use
* them where helpful
* (2) notice when the tail of a line does not need change and avoid
* re-writing in in that case.
* Actually I do not count these as high priority - mostly because screens
* etc etc are now usually very fast. Also inserts mid-line will be less
* common than just adding characters at the end.
*/
/*
* line_wrap() is a part of refresh_display(), and it prints a character
* taking care of line-wrapping.
*/
static int line_wrap(int ch, int tab_offset)
{
cursorx++;
if (cursorx >= columns)
{ tab_offset += cursorx;
/*
* When the line I am wrapping is the final one of my stuff I will be super
* careful and not write the character until it will no longer be at the
* final position on the screen. I do this because some (but perhaps not
* all) terminals force a scroll when that final character is filled in,
* and for that to happen and for me to be not quite certain just what the
* situation was would be terrible.
*/
/*
* I could imagine that if cursory==max_cursory here I could need to do
* something special to move down a row to ensure that if I am on the bottom
* row of the screen I scroll properly... What I do here with a hope that it
* counts as "on the safe side" is to switch back to cooked mode before
* outputting a newline character.
*/
if (cursory == max_cursory)
{ term_move_first_column();
fflush(stdout);
#ifdef WIN32
SetConsoleMode(stdout_handle, stdout_attributes);
#else
reset_shell();
#endif
term_putchar('\n');
fflush(stdout);
#ifdef WIN32
SetConsoleMode(stdout_handle, 0);
#else
reset_prog_mode();
#endif
cursory++;
max_cursory = cursory;
/*
* Now I have performed the scroll, so I will go back and insert the
* character. I very much hope that this time writing to the last position on
* a row does not do anything special at all about the cursor or scrolling.
*/
term_move_down(-1);
term_move_right(columns-1);
term_putchar(ch);
term_move_first_column();
term_move_down(1);
}
else
{ term_putchar(ch);
term_move_first_column();
term_move_down(1);
}
}
else term_putchar(ch);
return tab_offset;
}
static void refresh_display(void)
{
/*
* The version here right now is done as directly as I can without
* thought for optimisation. That is in the hope that I can get it
* right, and then put in performance upgrades later on.
*/
int i, ch, inverse = 0,
tab_offset = 0,
curx=columns, cury=lines,
finx, finy, window_size_changed;
/*
* Rather than believing in any SIGWINCH signal (or some such!) to alert
* me to screen size changes, I will (on systems that give me a way to)
* check the screen size every time I am about to do a screen update. This
* may feel like a cost, but it is incurred when the user has hit a key
* so may not be noticed too much. Well I can see that it can call for
* a handshake between the terminal and this program so on systems with
* remote access and slow lines it may well put in a noticable screen-update
* delay - if that proves the case I will need to review what I do.
*/
measure_screen();
/*
* Check if the screen-size has changed since last time.
* At present since I re-draw the whole input line every time changes
* in screen size make no difference to me and so I just ignore the
* information I collect here, but as I start to optimise my screen
* update I will need to look at it more carefully.
*/
window_size_changed = (curx!=columns || cury!=lines);
if (window_size_changed)
{
/*
* I want to force a fairly complete re-write here, so I explain that
* the screen now holds stuff that is totally different to what is
* desired, and that there may be residual stuff on the screen down as far
* as the almost-rightmost column of the bottom line that I have ever put
* anything on.
*/
display_line[0] = input_line[0] + 1;
display_line[1] = 0;
final_cursory = max_cursory;
final_cursorx = columns-1;
term_move_first_column();
}
finx = finy = 0;
i = 0;
while ((ch = input_line[i]) != 0 &&
display_line[i] == ch &&
(!term_can_invert || i!=invert_start))
{ if (i == insert_point)
{ curx = finx;
cury = finy;
}
if (ch == '\t')
{ do
{ finx++;
if (finx >= columns)
{ tab_offset += finx;
finx -= columns;
finy++;
}
} while ((tab_offset + finx)%8 != 0);
}
else if (ch == '\n')
{ tab_offset = 0;
finx = 0;
finy++;
}
else if (ch < 0x20) finx += 2;
else if (ch < 0x7f) finx += 1;
else finx += 4;
if (finx >= columns)
{ tab_offset += finx;
finx -= columns;
finy++;
}
i++;
}
/*
* Start my moving to the beginning of the where I must write stuff...
*/
term_move_right(finx - cursorx);
term_move_down(finy - cursory);
/*
* Re-display all of the current input line. In may cases this
* will do all the re-drawing needed, but beware if the existing displayed
* line is longer (because I have just deleted something). While drawing
* the current line when I reach the insert_point I will record the cursor
* position.
*/
for (; (ch = input_line[i])!=0; i++)
{ if (i == insert_point)
{ curx = cursorx;
cury = cursory;
}
if (term_can_invert && invert_start<invert_end && i==invert_start)
{
fflush(stdout);
#ifdef WIN32
SetConsoleTextAttribute(stdout_handle, revAttributes);
#else
putp(enter_reverse_mode);
#endif
inverse = 1;
}
if (term_can_invert && invert_start<invert_end && i==invert_end)
{
fflush(stdout);
#ifdef WIN32
SetConsoleTextAttribute(stdout_handle, plainAttributes);
#else
putp(exit_attribute_mode);
#endif
inverse = 0;
}
/*
* A horrid case arises here. If the cursor starts off close to the right
* hand side of the window and a TAB is issued than it might wrap. Well
* that in itself is OK. However if the screen width is not a multiple of
* the tab spacing then in effect tab-stops on the second and subsequent
* lines of the display of the wrapped line will need to allow for this.
* The variable "tab_offset" is allowing for the characters on previous as
* well as this line.
*/
if (ch == '\t')
{ do
{ tab_offset = line_wrap(' ', tab_offset);
} while ((tab_offset + cursorx)%8 != 0);
}
else if (ch == '\n')
{
/*
* Here I want a line-break in the "single line" I am displaying. I achieve
* the effect I want by writing blanks until cursorx gets back to zero by
* virtue of line-wrapping!
*/
while (cursorx != 0) line_wrap(' ', 0);
/*
* Tabs should now be relative to the new line-start.
*/
tab_offset = 0;
}
else if (ch < 0x20)
{ tab_offset = line_wrap('^', tab_offset);
/* Turn into @, A, B etc */
tab_offset = line_wrap(ch | 0x40, tab_offset);
}
else if (ch <= 0x7f) tab_offset = line_wrap(ch, tab_offset);
else
{ char b[5];
/*
* Characters from 0x7f to 0xff are displayed as "[dd]" with 2 hex digits.
* This may not be a universal standard notation and it does mean that any
* characters in that range that are desirable (eg maybe some accented
* letters) do not get displayed. But it avoids risk of
*/
int j;
sprintf(b, "[%.2x]", ch);
for (j=0; j<4; j++) tab_offset = line_wrap(b[j], tab_offset);
}
}
/*
* Clear inverse video mode.
*/
if (inverse)
{
fflush(stdout);
#ifdef WIN32
SetConsoleTextAttribute(stdout_handle, plainAttributes);
#else
putp(exit_attribute_mode);
#endif
}
if (invert_start >= invert_end) invert_start = invert_end = -1;
if (i == insert_point)
{ curx = cursorx;
cury = cursory;
}
/*
* Now the input line may have shrunk because of a deletion so there may be
* bits of the display line remaining. I need to replace them with blanks.
* Because I expect to be filling neatly up to the right hand end of the
* line and I will never be creating an extra row here I do not need to
* use line_wrap.
*/
finx = cursorx;
finy = cursory;
/*
* If the cursor is now positioned below where the previously-displayed line
* finished there is no joy at all in doing any padding to try to overwrite
* left-over bits of that previous line.
*/
if (cursory <= final_cursory)
{ while (cursory < final_cursory)
{ while (cursorx < columns)
{ term_putchar(' ');
cursorx++;
}
term_move_first_column();
term_move_down(1);
}
while (cursorx < final_cursorx)
{ term_putchar(' ');
cursorx++;
}
}
/*
* record where real output finished this time
*/
final_cursorx = finx;
final_cursory = finy;
/*
* Move the cursor to where it needs to appear.
*/
term_move_down(cury-cursory);
term_move_right(curx-cursorx);
fflush(stdout);
/*
* Now the display should be up to date, so record that situation.
*/
strcpy(display_line, input_line);
}
static void term_set_mark(void)
{
/*
* Actually I will not (at present) do anything here! And furthermore I
* have a degree of pain. When I try running on a Unix-like system even
* after setting "raw" mode I do not get all the chars that I want...
*/
term_bell();
refresh_display();
}
static void term_to_start(void)
{
insert_point = prompt_length;
refresh_display();
}
static void term_to_end(void)
{
while (input_line[insert_point] != 0) insert_point++;
refresh_display();
}
/*
* The next two just find where the position to left or right is, searching
* by words, with a FOX-like interpretation of the term "word", which I
* hope will also be an emacs-like interpretation.
*/
static int term_find_next_word_forwards(void)
{
int n = insert_point;
if (input_line[n] == 0) return n;
do
{ n++;
} while (input_line[n] != 0 &&
(isalnum(input_line[n]) || input_line[n] == '_'));
while (input_line[n] != 0 && isspace(input_line[n])) n++;
return n;
}
static int term_find_next_word_backwards(void)
{
int n = insert_point;
if (n == prompt_length) return n;
do
{ n--;
} while (n != prompt_length &&
(isalnum(input_line[n]) || input_line[n] == '_'));
while (n != prompt_length && isspace(input_line[n])) n--;
if (n == prompt_length || n == insert_point-1) return n;
else return n+1;
}
static void term_forwards_char(void)
{
if (input_line[insert_point] == 0) term_bell();
else insert_point++;
refresh_display();
}
static void term_forwards_word(void)
{
if (input_line[insert_point] == 0) term_bell();
else
{ insert_point = term_find_next_word_forwards();
refresh_display();
}
}
static void term_back_char(void)
{
if (insert_point == prompt_length) term_bell();
else insert_point--;
refresh_display();
}
static void term_back_word(void)
{
if (insert_point == prompt_length) term_bell();
else
{ insert_point = term_find_next_word_backwards();
refresh_display();
}
}
static void term_delete_forwards(void)
{
int i = insert_point;
if (input_line[i] == 0) term_bell();
else while (input_line[i] != 0)
{ input_line[i] = input_line[i+1];
i++;
}
refresh_display();
}
static void term_delete_backwards(void)
{
if (insert_point == prompt_length) term_bell();
else
{ int i = insert_point - 1;
insert_point = i;
while (input_line[i] != 0)
{ input_line[i] = input_line[i+1];
i++;
}
}
refresh_display();
}
static void term_delete_word_forwards(void)
{
if (input_line[insert_point] == 0) term_bell();
else
{ int i = insert_point;
int n = term_find_next_word_forwards() - insert_point;
while (input_line[i] != 0)
{ input_line[i] = input_line[i+n];
i++;
}
refresh_display();
}
}
static void term_delete_word_backwards(void)
{
if (insert_point == prompt_length) term_bell();
else
{ int i = term_find_next_word_backwards();
int n = insert_point - i;
insert_point = i;
while (input_line[i] != 0)
{ input_line[i] = input_line[i+n];
i++;
}
}
refresh_display();
}
static void term_kill_line(void)
{
insert_point = prompt_length;
input_line[insert_point] = 0;
refresh_display();
}
static void term_history_next(void)
{
const char *history_string;
if (historyLast == -1)
{ term_bell();
return;
}
if (historyNumber < historyLast) historyNumber++;
if ((history_string = input_history_get(historyNumber)) == NULL)
{ term_bell();
return;
}
insert_point = strlen(history_string);
strncpy(input_line+prompt_length, history_string, insert_point);
insert_point += prompt_length;
input_line[insert_point] = 0;
refresh_display();
}
static void term_previous_history(void)
{
const char *history_string;
if (historyLast == -1) /* no previous lines to retrieve */
{ term_bell();
return;
}
/*
* If I have not moved the history pointer at all yet move it into the
* range of valid history entries.
*/
if (historyNumber > historyFirst) historyNumber--;
history_string = input_history_get(historyNumber);
if (history_string == NULL)
{ term_bell();
return;
}
insert_point = strlen(history_string);
strncpy(input_line+prompt_length, history_string, insert_point);
insert_point += prompt_length;
input_line[insert_point] = 0;
refresh_display();
}
static int regular_line_end = 0;
static int search_saved_point = 0;
static int search_found = 0;
static char searchBuff[100];
static int searchStack[100];
static int searchLen;
static void term_search_next(void)
{
/*
* I remember where I was on the input line but then move to the end and
* append a message that indicates to the user that a search is in progress.
*/
search_found = search_saved_point = insert_point;
regular_line_end = strlen(input_line);
strcpy(&input_line[regular_line_end], "\nN-search: ");
insert_point = regular_line_end + 11;
searchLen = 0;
searchBuff[0] = 0;
searchFlags = 1;
refresh_display();
}
static void term_search_previous(void)
{
search_found = search_saved_point = insert_point;
regular_line_end = strlen(input_line);
strcpy(&input_line[regular_line_end], "\nP-search: ");
insert_point = regular_line_end + 11;
searchLen = 0;
searchBuff[0] = 0;
searchFlags = -1;
refresh_display();
}
static int matchString(const char *pat, int n, const char *text)
{
/*
* This is a crude and not especially efficient pattern match. I think
* it should be good enough for use here! I make it return the offset where
* a match first occurred (if one does) in case that will be useful to me
* later. I could put the cursor there, perhaps?
*/
int offset;
for (offset=0;*(text+offset)!=0;offset++)
{ const char *p = pat, *q = text+offset;
int i;
for (i=0; i<n; i++)
{ if (p[i] != q[i]) break;
}
if (i == n) return offset;
}
return -1;
}
static int trySearch(void)
{
int r = -1;
const char *history_string = input_history_get(historyNumber);
if (history_string == NULL) return -1;
while ((r = matchString(searchBuff, searchLen, history_string)) < 0)
{ if (searchFlags > 0)
{ if (historyNumber == historyLast) return -1;
historyNumber++;
}
else
{ if (historyNumber == historyFirst) return -1;
historyNumber--;
}
history_string = input_history_get(historyNumber);
if (history_string == NULL) return -1;
}
return r;
}
/*
* After a success in a search this sets the input line to the given
* text. The real mess here is that it has to stick the search text
* on the end. And a further worry might be buffer overflow... Suppose that
* the lines searched so far have all been just the same length as the search
* string, and that this is quite long. Now we have searched and found a
* line that is much longer. The line itself just about filled the
* input line buffer, and when the search string is stuck on its end it
* overflows. Oh woe. Well look later on and you will see that I keep track
* of the longest line I have stuck into the history and I measure against
* that when accepting search characters. So do not worry after all!!
*/
static void set_input(const char *s)
{
strcpy(&input_line[prompt_length], s);
insert_point = prompt_length + strlen(s);
regular_line_end = insert_point;
input_line[insert_point++] = '\n';
input_line[insert_point++] = searchFlags > 0 ? 'N' : 'P';
strcpy(&input_line[insert_point], "-search: ");
insert_point += 9;
strcpy(&input_line[insert_point], searchBuff);
insert_point += searchLen;
}
#ifdef CTRL
#undef CTRL
#endif
#define CTRL(n) ((n) & 0x1f)
/*
* This is called when I am in the process of searching and a character is
* typed.
* Returns true if searching must continue, or false if searchMode has been
* left. While in search mode I will keep insert_point at the end of the
* buffer.
*/
static int term_search_char(int ch)
{
switch (ch)
{
/*
* ALT-N and ALT-Down continue the search using the current search string
* but searching through the Next history item
*/
case CTRL('N') + 0x100: case 'N' + 0x100: case 'n' + 0x100:
case 0x200 + TERM_DOWN + 0x100:
searchFlags = 1; /* search downwards */
if (historyNumber >= historyLast) term_bell();
else
{ int r;
int save = historyNumber;
historyNumber++;
r = trySearch();
if (r == -1)
{ historyNumber = save;
term_bell();
return 1;
}
search_found = r;
set_input(input_history_get(historyNumber));
invert_start = prompt_length + search_found;
invert_end = invert_start + searchLen;
refresh_display();
}
return 1;
/*
* ALT-P and ALT-Up continue the search using the current search string
* but searching through the Previous history item
*/
case CTRL('P') + 0x100: case 'P' + 0x100: case 'p' + 0x100:
case 0x200 + TERM_UP + 0x100:
searchFlags = -1; /* search upwards */
if (historyNumber <= historyFirst) term_bell();
else
{ int r;
int save = historyNumber;
historyNumber--;
r = trySearch();
if (r == -1)
{ historyNumber = save;
term_bell();
return 1;
}
search_found = r;
set_input(input_history_get(historyNumber));
invert_start = prompt_length + search_found;
invert_end = invert_start + searchLen;
refresh_display();
}
return 1;
/*
* Ctrl-H deletes exist search mode if the search string is empty, otherwise
* it deletes a char from the search string and pops back to wherever the
* shorter string had matched.
*/
case CTRL('H'):
case 0x7f:
case 0xff:
if (searchLen != 0)
{ input_line[--insert_point] = 0;
searchBuff[--searchLen] = 0;
historyNumber = searchStack[searchLen];
search_found = trySearch(); /* ought to succeed */
set_input(input_history_get(historyNumber));
invert_start = prompt_length + search_found;
invert_end = invert_start + searchLen;
refresh_display();
return 1;
}
else
{ term_bell();
ch = CTRL('U'); /* Drop through to exit search mode */
}
/*
* Control characters (except TAB) and things tagged with ESC cause an exit
* from search mode.
*/
default:
if ((ch & 0x300) != 0 ||
(ch != '\t' && ch<0x20) ||
ch > 0x7f)
{
/*
* Exit search mode
*/
searchFlags = 0;
/*
* The approved means of switching some highlights off is as follows. This
* leads the next refresh_display to re-write the bit that had previously
* been in inverse video... ie leave invert_start at the start of where
* inverting had started, but set invert_end to the same value.
*/
invert_end = invert_start;
input_line[regular_line_end] = 0;
insert_point = prompt_length + search_found + searchLen;
refresh_display();
/*
* "^U" will exit search mode, and when it does that it does not do anything
* else. Even if there were ever a time that I had implemented an UNDO
* facility, which I think I am not abou to.
*/
return (ch & 0x7f) == CTRL('U');
}
}
/*
* So not accept search characters beyond some limit since I have a fixed-
* size buffer for the search string. Just beep and ignore the chararcter.
*/
if (searchLen > 98)
{ term_bell();
return 1;
}
searchStack[searchLen] = historyNumber;
searchBuff[searchLen++] = ch;
searchBuff[searchLen] = 0;
/*
* Now perform a search...
*/
{ int r;
int save = historyNumber;
if ((r = trySearch()) == -1)
{ historyNumber = save; /* search failed this time */
searchBuff[--searchLen] = 0;
term_bell();
return 1;
}
search_found = r;
set_input(input_history_get(historyNumber));
invert_start = prompt_length + search_found;
invert_end = invert_start + searchLen;
}
refresh_display();
return 1;
}
static int term_find_word_start(void)
{
/*
* If the insert point points at an alphemumeric character this ends up
* returning the address of the first letter of the "word". If the
* initial point is not on an alphanumeric it returns a pointer one to the
* right of the insert point.
*/
int n = insert_point;
while (n>=prompt_length &&
(isalnum(input_line[n]) || input_line[n]=='_')) n--;
return n+1;
}
static int term_find_word_end(void)
{
/*
* returns the address of the first character beyond the end of a current
* word. If the character at the insert point is not alphanumeric then its
* address is returned.
*/
int n = insert_point;
while (input_line[n]!=0 &&
(isalnum(input_line[n]) || input_line[n]=='_')) n++;
return n;
}
static void term_capitalize_word(void)
{
int a = term_find_word_start();
int b = term_find_word_end();
int i;
if (a < b) input_line[a] = toupper(input_line[a]);
for (i=a+1; i<b; i++) input_line[i] = tolower(input_line[i]);
refresh_display();
}
static void term_lowercase_word(void)
{
int a = term_find_word_start();
int b = term_find_word_end();
int i;
for (i=a; i<b; i++) input_line[i] = tolower(input_line[i]);
refresh_display();
}
static void term_uppercase_word(void)
{
int a = term_find_word_start();
int b = term_find_word_end();
int i;
for (i=a; i<b; i++) input_line[i] = toupper(input_line[i]);
refresh_display();
}
static void term_transpose_chars(void)
{
int c, d;
if (((c = input_line[insert_point]) == 0) ||
((d = input_line[insert_point+1]) == 0)) term_bell();
else
{ input_line[insert_point] = d;
input_line[insert_point+1] = c;
refresh_display();
}
}
static void term_undo(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^U>");
term_bell();
}
static void term_quoted_insert(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^V>");
term_bell();
}
static void term_copy_previous_word(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^W>");
term_bell();
}
static void term_copy_region(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&W>");
term_bell();
}
static void term_yank(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^Y>");
term_bell();
}
static void term_reinput(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^R>");
term_bell();
}
static void term_redisplay(void)
{
/*
* To get as much of the active area redrawn as I can I will reset the
* record of what is visible to suggest that everything is totally out of
* date.
*/
display_line[0] = input_line[0] + 1;
display_line[1] = 0;
refresh_display();
}
static void term_clear_screen(void)
{
/*
* This will then leave the input-line displayed at the top of your window...
*/
#ifdef WIN32
CONSOLE_SCREEN_BUFFER_INFO csb;
DWORD size, nbytes;
COORD topleft = {0, 0};
if (!GetConsoleScreenBufferInfo(stdout_handle, &csb)) return;
size = csb.dwSize.X * csb.dwSize.Y;
if (!FillConsoleOutputCharacter(stdout_handle,
(TCHAR)' ', size, topleft, &nbytes)) return;
if (!FillConsoleOutputAttribute(stdout_handle,
csb.wAttributes, size, topleft, &nbytes)) return;
SetConsoleCursorPosition(stdout_handle, topleft);
#else
if (clear_screen != NULL) putp(clear_screen);
#endif
display_line[0] = input_line[0] + 1;
display_line[1] = 0;
term_redisplay();
}
static void term_pause_output(void)
{
/* @@@@@ */
}
static void term_resume_output(void)
{
/* @@@@@ */
}
static void term_discard_output(void)
{
/* @@@@@ */
}
static void term_extended_command(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^X>");
term_bell();
term_redisplay();
}
static void term_obey_command(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&X>");
term_bell();
term_redisplay();
}
static void term_interrupt(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^C>");
term_redisplay();
}
static void term_noisy_interrupt(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^G>");
term_redisplay();
}
static void term_pause_execution(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<^Z>");
term_redisplay();
}
static void term_exit_program(void)
{
exit(0);
}
static void term_edit_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&E>");
term_redisplay();
}
static void term_file_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&I>");
term_redisplay();
}
static void term_module_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&M>");
term_redisplay();
}
static void term_font_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&O>");
term_redisplay();
}
static void term_break_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&B>");
term_redisplay();
}
static void term_switch_menu(void)
{
/* @@@@@ */
insert_point += sprintf(&input_line[insert_point], "<&S>");
term_redisplay();
}
/****************************************************************************/
#if defined SOLARIS && SIZEOF_VOID_P==8
/* This is pretty horrible! Sorry. */
#define PAD_TPARM ,0,0,0,0,0,0,0,0
#else
#define PAD_TPARM
#endif
static void set_fg(int n)
{
#ifdef WIN32
int k;
if (*term_colour == 0) return;
if (n == inputColour) k = inputAttributes;
else if (n == promptColour) k = promptAttributes;
else k = plainAttributes;
fflush(stdout);
SetConsoleTextAttribute(stdout_handle, k);
#else
if (*term_colour == 0) return;
fflush(stdout);
if (set_a_foreground) putp(tparm(set_a_foreground, n PAD_TPARM));
else if (set_foreground) putp(tparm(set_foreground, n PAD_TPARM));
#endif
}
static void set_normal(void)
{
#ifdef WIN32
fflush(stdout);
if (*term_colour != 0)
SetConsoleTextAttribute(stdout_handle, plainAttributes);
SetConsoleMode(stdout_handle, stdout_attributes);
#else
fflush(stdout);
if (*term_colour == 0) /* nothing */;
else if (orig_pair) putp(orig_pair);
else if (orig_colors) putp(orig_colors);
else if (set_a_foreground) putp(tparm(set_a_foreground, 0 PAD_TPARM));
fflush(stdout);
reset_shell();
#endif
}
static void set_shell(void)
{
#ifdef WIN32
fflush(stdout);
if (*term_colour != 0)
SetConsoleTextAttribute(stdout_handle, plainAttributes);
SetConsoleMode(stdout_handle, stdout_attributes);
#else
fflush(stdout);
if (*term_colour == 0) /* nothing */;
else if (orig_pair) putp(orig_pair);
else if (orig_colors) putp(orig_colors);
else if (set_a_foreground) putp(tparm(set_a_foreground, 0 PAD_TPARM));
fflush(stdout);
reset_shell();
#endif
}
static char *term_fancy_getline(void)
{
int ch;
#ifdef TEST
fprintf(stderr, "term_fancy_getline\n");
fflush(stderr);
#endif
#ifdef WIN32
SetConsoleMode(stdout_handle, 0);
#else
reset_prog_mode();
#endif
/*
* I am going to take strong action to ensure that the prompt appears
* at the left-hand side of the screen.
*/
term_move_first_column();
set_fg(promptColour);
printf("%.*s", prompt_length, prompt_string);
fflush(stdout);
if (input_line_size == 0)
{ set_normal();
return NULL;
}
set_fg(inputColour);
strncpy(input_line, prompt_string, prompt_length);
strncpy(display_line, prompt_string, prompt_length);
input_line[prompt_length] = 0;
display_line[prompt_length] = 0;
insert_point = final_cursorx = cursorx = prompt_length;
final_cursory = max_cursory = cursory = 0;
for (;;)
{ int n;
ch = term_getchar();
if (ch == EOF)
{ set_normal();
return NULL;
}
/*
* First ensure there is space in the buffer. In some cases maybe putting
* the test here is marginally over-keen, since the keystroke entered
* might not be one that was going to add a character. But it is harmless
* to be safe! If I need to extend the buffer and I fail then I just
* give up and return an error marker. Note that the insert_point may not
* be at the end of the line, so I use strlen() to find out how long the
* line actually is.
*/
n = strlen(input_line);
/*
* The curious activity here is to ensure that the buffer would not overflow
* even if the "regular" part of it was replaced by the longest line
* that can possibly get into the history-record.
*/
if (n > longest_history_line) longest_history_line = n;
if (searchFlags != 0)
n = n - regular_line_end + longest_history_line;
if (n >= input_line_size-20)
{ input_line = (char *)realloc(input_line, 2*input_line_size);
display_line = (char *)realloc(display_line, 2*input_line_size);
if (input_line == NULL || display_line == NULL)
{ input_line_size = 0;
set_normal();
return NULL;
}
else input_line_size = 2*input_line_size;
}
/*
* There are cases where I need to treat characters specially:
* (1) I am in the middle of a history search, and the
* character is thus to be treated as part of the incremental
* string, unless it terminates the search;
*/
if (searchFlags != 0 &&
term_search_char(ch)) continue;
/*
* term_search_char will return true if it has handled the character and thus
* searching continues. It returns false if it has exited search mode and the
* key must now be treated as if it was a "normal" non-search one.
*/
#ifdef TEST
fprintf(stderr, "process character %#x\n", ch);
fflush(stderr);
#endif
switch (ch)
{
case EOF:
break;
case CTRL('@'):
term_set_mark();
continue;
case CTRL('A'):
case 0x200 + TERM_HOME:
case 0x200 + TERM_HOME + 0x100:
term_to_start();
continue;
case CTRL('B'):
case 0x200+TERM_LEFT:
term_back_char();
continue;
case CTRL('C'):
term_interrupt();
continue;
case CTRL('D'):
case 0x200+TERM_DELETE:
term_delete_forwards();
continue;
case CTRL('E'):
case 0x200 + TERM_END:
case 0x200 + TERM_END + 0x100:
term_to_end();
continue;
case CTRL('F'):
case 0x200+TERM_RIGHT:
term_forwards_char();
continue;
case CTRL('G'):
term_noisy_interrupt();
continue;
case CTRL('H'):
case 0x7f: /* The "delete backwards" key, I hope */
case 0xff:
term_delete_backwards();
continue;
/*
* I treat tab as an "ordinary" character here, so it just gets inserted
* into the input line. This is achieved by letting it hit the "default" case
* in this switch block!
*
* case CTRL('I'):
* goto default;
*/
case CTRL('J'): /* line-feed */
break;
case CTRL('K'):
term_kill_line();
continue;
case CTRL('L'):
term_clear_screen();
continue;
case CTRL('M'): /* carriage return, ENTER */
break;
case CTRL('N'):
case 0x200+TERM_DOWN:
term_history_next();
continue;
case CTRL('O'):
term_discard_output();
continue;
case CTRL('P'):
case 0x200+TERM_UP:
term_previous_history();
continue;
case CTRL('Q'):
term_resume_output();
continue;
case CTRL('R'):
term_redisplay();
continue;
case CTRL('S'):
term_pause_output();
continue;
case CTRL('T'):
term_transpose_chars();
continue;
case CTRL('U'):
term_undo();
continue;
case CTRL('V'):
term_quoted_insert();
continue;
case CTRL('W'):
term_delete_word_backwards();
continue;
case CTRL('X'):
term_extended_command();
continue;
case CTRL('Y'):
term_yank();
continue;
case CTRL('Z'):
term_pause_execution();
continue;
/*
* (already dealt with)
* case CTRL('['): ESC
*/
case CTRL('\\'):
set_shell();
term_exit_program();
/* No return */
case CTRL(']'):
continue; /* Ignored */
case CTRL('_'):
continue; /* Ignored */
case CTRL('^'):
term_reinput();
continue;
case CTRL('@') + 0x100:
term_set_mark();
continue;
/*
* When a character is combined with ALT (or prefixed with ESC) I will in
* at least very nearly every case ignore whether it is upper or lower case
* or whether CTRL is pressed too. In a rather few cases I will let CTRL
* take precedence over ALT...
*/
case CTRL('A') + 0x100: case 'A' + 0x100: case 'a' + 0x100:
term_to_start();
continue;
case CTRL('B') + 0x100: case 'B' + 0x100: case 'b' + 0x100:
case 0x200 + TERM_LEFT + 0x100:
term_back_word();
continue;
case CTRL('C') + 0x100: case 'C' + 0x100: case 'c' + 0x100:
term_capitalize_word();
continue;
case CTRL('D') + 0x100: case 'D' + 0x100: case 'd' + 0x100:
case 0x200 + TERM_DELETE + 0x100:
term_delete_word_forwards();
continue;
case CTRL('E') + 0x100: case 'E' + 0x100: case 'e' + 0x100:
term_edit_menu();
continue;
case CTRL('F') + 0x100: case 'F' + 0x100: case 'f' + 0x100:
case 0x200 + TERM_RIGHT + 0x100:
term_forwards_word();
continue;
case CTRL('G') + 0x100: case 'G' + 0x100: case 'g' + 0x100:
term_noisy_interrupt();
continue;
case CTRL('H') + 0x100: case 'H' + 0x100: case 'h' + 0x100:
case 0x7f+0x100: /* Under X maybe the key above ENTER that I use to .. */
case 0xff+0x100: /* delete things will return 0x7f. */
term_delete_word_backwards();
continue;
case CTRL('I') + 0x100: case 'I' + 0x100: case 'i' + 0x100:
term_file_menu();
continue;
case CTRL('J') + 0x100: case 'J' + 0x100: case 'j' + 0x100: /* line-feed */
break;
case CTRL('K') + 0x100: case 'K' + 0x100: case 'k' + 0x100:
term_kill_line();
continue;
case CTRL('L') + 0x100: case 'L' + 0x100: case 'l' + 0x100:
term_lowercase_word();
continue;
case CTRL('M') + 0x100: /* carriage return, ENTER */
break;
case 'M' + 0x100: case 'm' + 0x100: /* ALT-^M differs from ALT-M, ALT-m */
term_module_menu();
continue;
case CTRL('N') + 0x100: case 'N' + 0x100: case 'n' + 0x100:
case 0x200 + TERM_DOWN + 0x100:
term_search_next();
continue;
case CTRL('O') + 0x100: case 'O' + 0x100: case 'o' + 0x100:
term_font_menu();
continue;
case CTRL('P') + 0x100: case 'P' + 0x100: case 'p' + 0x100:
case 0x200 + TERM_UP + 0x100:
term_search_previous();
continue;
case CTRL('Q') + 0x100: case 'Q' + 0x100: case 'q' + 0x100:
continue; /* Ignored */
case CTRL('R') + 0x100: case 'R' + 0x100: case 'r' + 0x100:
term_break_menu();
continue;
case CTRL('S') + 0x100: case 'S' + 0x100: case 's' + 0x100:
term_switch_menu();
continue;
case CTRL('T') + 0x100: case 'T' + 0x100: case 't' + 0x100:
term_transpose_chars();
continue;
case CTRL('U') + 0x100: case 'U' + 0x100: case 'u' + 0x100:
term_uppercase_word();
continue;
case CTRL('V') + 0x100: case 'V' + 0x100: case 'v' + 0x100:
term_quoted_insert();
continue;
case CTRL('W') + 0x100: case 'W' + 0x100: case 'w' + 0x100:
term_copy_region();
continue;
case CTRL('X') + 0x100: case 'X' + 0x100: case 'x' + 0x100:
term_obey_command();
continue;
case CTRL('Y') + 0x100: case 'Y' + 0x100: case 'y' + 0x100:
term_yank();
continue;
case CTRL('Z') + 0x100: case 'Z' + 0x100: case 'z' + 0x100:
term_pause_execution();
continue;
/*
* (already dealt with)
* case CTRL('[') + 0x100: ESC
*/
case CTRL('\\') + 0x100:
continue;
case CTRL(']') + 0x100:
continue;
case CTRL('_') + 0x100:
term_copy_previous_word();
continue;
case '@' + 0x100:
term_set_mark();
continue;
case '[' + 0x100: /* ESC-[ */
/* Should never be delivered here */
continue;
case '\\' + 0x100:
term_exit_program();
/* no return */
case ']' + 0x100:
continue;
case '_' + 0x100:
term_copy_previous_word();
continue;
case '{' + 0x100:
continue; /* Ignored */
case '^' + 0x100:
term_reinput();
continue;
case '}' + 0x100:
continue; /* Ignored */
default:
/*
* Here I need to insert a character into the buffer. I may not be inserting
* at the end, so I perhaps have to shuffle existing stuff upwards.
*/
{ int n = insert_point;
while (input_line[n] != 0) n++;
while (n >= insert_point)
{ input_line[n+1] = input_line[n];
n--;
}
}
input_line[insert_point] = ch;
insert_point++;
refresh_display();
continue;
}
break;
}
/*
* Put the cursor at the start of the final line of displayed (wrapped)
* input before moving back to normal screen mode.
*/
term_move_first_column();
term_move_down(final_cursory-cursory);
set_normal();
term_putchar('\n');
fflush(stdout);
insert_point = strlen(input_line);
if (insert_point==prompt_length && ch==EOF) return NULL;
/*
* Stick the line into my history record: WITHOUT any newline at its end.
*/
input_line[insert_point] = 0;
input_history_add(input_line+prompt_length);
/*
* Adding an entry could cause an old one to be discarded. So I now ensure
* that I know what the first and last recorded numbers are.
*/
historyLast = input_history_next - 1;
historyFirst = input_history_next - INPUT_HISTORY_SIZE;
if (historyFirst < 0) historyFirst = 0;
historyNumber = historyLast + 1; /* so that ALT-P moves to first entry */
/*
* Whether the user terminated the line with CR or LF I will always
* return "\n" to the program.
*/
input_line[insert_point++] = '\n';
input_line[insert_point] = 0;
return input_line + prompt_length;
}
char *term_getline(void)
{
if (!term_enabled) return term_plain_getline();
else return term_fancy_getline();
}
#endif /* DISABLE */
/* end of file termed.c */