File r38/lisp/csl/cslbase/termed.c artifact 8f983c8080 on branch master


/* 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 */


REDUCE Historical
REDUCE Sourceforge Project | Historical SVN Repository | GitHub Mirror | SourceHut Mirror | NotABug Mirror | Chisel Mirror | Chisel RSS ]