/* Part of SWI-Prolog Author: Jan Wielemaker E-mail: J.Wielemaker@vu.nl WWW: http://www.swi-prolog.org Copyright (c) 1999-2011, University of Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #define _MAKE_DLL 1 #undef _export #include "console.h" #include "console_i.h" #include "common.h" #include #include #include #ifndef EOF #define EOF -1 #endif typedef void (*function)(Line ln, int chr); /* edit-function */ static function dispatch_table[256]; /* general dispatch-table */ static function dispatch_meta[256]; /* ESC-char dispatch */ static RlcCompleteFunc _rlc_complete_function = rlc_complete_file_function; static void init_line_package(RlcData b); static void bind_actions(void); #ifndef min #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) #endif #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif #ifndef EOS #define EOS 0 #endif #ifndef ESC #define ESC 27 #endif #define COMPLETE_NEWLINE 1 #define COMPLETE_EOF 2 #define ctrl(c) ((c) - '@') #define META_OFFSET 128 #define meta(c) ((c) + META_OFFSET) /******************************* * BUFFER * *******************************/ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - make_room(Line, int room) Make n-characters space after the point. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static void make_room(Line ln, size_t room) { while ( ln->size + room + 1 > ln->allocated ) { if ( !ln->data ) { ln->data = rlc_malloc(256 * sizeof(TCHAR)); ln->allocated = 256; } else { ln->allocated *= 2; ln->data = rlc_realloc(ln->data, ln->allocated * sizeof(TCHAR)); } } memmove(&ln->data[ln->point + room], &ln->data[ln->point], (ln->size - ln->point)*sizeof(TCHAR)); ln->size += room; if ( room > 0 ) ln->change_start = min(ln->change_start, ln->point); } static void set_line(Line ln, const TCHAR *s) { size_t len = _tcslen(s); ln->size = ln->point = 0; make_room(ln, len); _tcsncpy(ln->data, s, len); } static void terminate(Line ln) { if ( !ln->data ) { ln->data = rlc_malloc(sizeof(TCHAR)); ln->allocated = 1; } ln->data[ln->size] = EOS; } static void delete(Line ln, size_t from, size_t len) { if ( from < 0 || from > ln->size || len < 0 || from + len > ln->size ) return; _tcsncpy(&ln->data[from], &ln->data[from+len], ln->size - (from+len)); ln->size -= len; } /******************************* * POSITIONING * *******************************/ static size_t back_word(Line ln, size_t from) { from = min(from, ln->size); from = max(0, from); if ( ln->data ) { while(!rlc_is_word_char(ln->data[from-1]) && from > 0 ) from--; while(rlc_is_word_char(ln->data[from-1]) && from > 0 ) from--; } return from; } static size_t forw_word(Line ln, size_t from) { from = min(from, ln->size); from = max(0, from); if ( ln->data ) { while(!rlc_is_word_char(ln->data[from]) && from < ln->size ) from++; while(rlc_is_word_char(ln->data[from]) && from < ln->size ) from++; } return from; } /******************************* * EDITING FUNCTIONS * *******************************/ static __inline void changed(Line ln, size_t from) { ln->change_start = min(ln->change_start, from); } static void insert_self(Line ln, int chr) { make_room(ln, 1); ln->data[ln->point++] = chr; } static void backward_delete_character(Line ln, int chr) { if ( ln->point > 0 ) { memmove(&ln->data[ln->point-1], &ln->data[ln->point], (ln->size - ln->point)*sizeof(TCHAR)); ln->size--; ln->point--; } changed(ln, ln->point); } static void delete_character(Line ln, int chr) { if ( ln->point < ln->size ) { ln->point++; backward_delete_character(ln, chr); } } static void backward_character(Line ln, int chr) { if ( ln->point > 0 ) ln->point--; } static void forward_character(Line ln, int chr) { if ( ln->point < ln->size ) ln->point++; } static void backward_word(Line ln, int chr) { ln->point = back_word(ln, ln->point); } static void forward_word(Line ln, int chr) { ln->point = forw_word(ln, ln->point); } static void backward_delete_word(Line ln, int chr) { size_t from = back_word(ln, ln->point); memmove(&ln->data[from], &ln->data[ln->point], (ln->size - ln->point)*sizeof(TCHAR)); ln->size -= ln->point - from; ln->point = from; changed(ln, from); } static void forward_delete_word(Line ln, int chr) { size_t to = forw_word(ln, ln->point); memmove(&ln->data[ln->point], &ln->data[to], (ln->size - to)*sizeof(TCHAR)); ln->size -= to - ln->point; changed(ln, ln->point); } static void transpose_chars(Line ln, int chr) { if ( ln->point > 0 && ln->point < ln->size ) { int c0 = ln->data[ln->point-1]; ln->data[ln->point-1] = ln->data[ln->point]; ln->data[ln->point] = c0; changed(ln, ln->point-1); } } static void start_of_line(Line ln, int chr) { ln->point = 0; } static void end_of_line(Line ln, int chr) { ln->point = ln->size; } static void kill_line(Line ln, int chr) { ln->size = ln->point; changed(ln, ln->size); } static void empty_line(Line ln, int chr) { ln->size = ln->point = 0; changed(ln, 0); } static void enter(Line ln, int chr) { ln->point = ln->size; #ifdef DOS_CRNL make_room(ln, 2); ln->data[ln->point++] = '\r'; ln->data[ln->point++] = '\n'; #else make_room(ln, 1); ln->data[ln->point++] = '\n'; #endif terminate(ln); ln->complete = COMPLETE_NEWLINE; } static void eof(Line ln, int chr) { ln->point = ln->size; terminate(ln); ln->complete = COMPLETE_EOF; } static void delete_character_or_eof(Line ln, int chr) { if ( ln->size == 0 ) { ln->point = ln->size; terminate(ln); ln->complete = COMPLETE_EOF; } else delete_character(ln, chr); } static void undefined(Line ln, int chr) { } static void interrupt(Line ln, int chr) { raise(SIGINT); } /******************************* * HISTORY * *******************************/ static void add_history(rlc_console c, const TCHAR *data) { const TCHAR *s = data; while(*s && *s <= ' ') s++; if ( *s ) rlc_add_history(c, s); } static void backward_history(Line ln, int chr) { const TCHAR *h; if ( rlc_at_head_history(ln->console) && ln->size > 0 ) { terminate(ln); add_history(ln->console, ln->data); } if ( (h = rlc_bwd_history(ln->console)) ) { set_line(ln, h); ln->point = ln->size; } } static void forward_history(Line ln, int chr) { if ( !rlc_at_head_history(ln->console) ) { const TCHAR *h = rlc_fwd_history(ln->console); if ( h ) { set_line(ln, h); ln->point = ln->size; } } else empty_line(ln, chr); } /******************************* * COMPLETE * *******************************/ RlcCompleteFunc rlc_complete_hook(RlcCompleteFunc new) { RlcCompleteFunc old = _rlc_complete_function; _rlc_complete_function = new; return old; } static int common(const TCHAR *s1, const TCHAR *s2, int insensitive) { int n = 0; if ( !insensitive ) { while(*s1 && *s1 == *s2) { s1++, s2++; n++; } return n; } else { while(*s1) { if ( _totlower(*s1) == _totlower(*s2) ) { s1++, s2++; n++; } else break; } return n; } } static void complete(Line ln, int chr) { if ( _rlc_complete_function ) { rlc_complete_data dbuf; RlcCompleteData data = &dbuf; memset(data, 0, sizeof(dbuf)); data->line = ln; data->call_type = COMPLETE_INIT; if ( (*_rlc_complete_function)(data) ) { TCHAR match[COMPLETE_MAX_WORD_LEN]; int nmatches = 1; size_t ncommon = _tcslen(data->candidate); size_t patlen = ln->point - data->replace_from; _tcscpy(match, data->candidate); data->call_type = COMPLETE_ENUMERATE; while( (*data->function)(data) ) { ncommon = common(match, data->candidate, data->case_insensitive); match[ncommon] = EOS; nmatches++; } data->call_type = COMPLETE_CLOSE; (*data->function)(data); delete(ln, data->replace_from, patlen); ln->point = data->replace_from; make_room(ln, ncommon); _tcsncpy(&ln->data[data->replace_from], match, ncommon); ln->point += ncommon; if ( nmatches == 1 && data->quote ) insert_self(ln, data->quote); } } } #define MAX_LIST_COMPLETIONS 256 static void list_completions(Line ln, int chr) { if ( _rlc_complete_function ) { rlc_complete_data dbuf; RlcCompleteData data = &dbuf; memset(data, 0, sizeof(dbuf)); data->line = ln; data->call_type = COMPLETE_INIT; if ( (*_rlc_complete_function)(data) ) { TCHAR *buf[COMPLETE_MAX_MATCHES]; int n, nmatches = 0; size_t len = _tcslen(data->candidate) + 1; size_t longest = len; size_t cols; buf[nmatches] = rlc_malloc(len*sizeof(TCHAR)); _tcsncpy(buf[nmatches], data->candidate, len); nmatches++; data->call_type = COMPLETE_ENUMERATE; while( (*data->function)(data) ) { len = _tcslen(data->candidate) + 1; buf[nmatches] = rlc_malloc(len*sizeof(TCHAR)); _tcsncpy(buf[nmatches], data->candidate, len); nmatches++; longest = max(longest, len); if ( nmatches > COMPLETE_MAX_MATCHES ) { TCHAR *msg = _T("\r\n! Too many matches\r\n"); while(*msg) rlc_putchar(ln->console, *msg++); ln->reprompt = TRUE; data->call_type = COMPLETE_CLOSE; (*data->function)(data); return; } } data->call_type = COMPLETE_CLOSE; (*data->function)(data); cols = ScreenCols(ln->console) / longest; rlc_putchar(ln->console, '\r'); rlc_putchar(ln->console, '\n'); for(n=0; nconsole, *s++); } rlc_free(buf[n++]); if ( n % cols == 0 ) { rlc_putchar(ln->console, '\r'); rlc_putchar(ln->console, '\n'); } else { while( len++ < longest ) rlc_putchar(ln->console, ' '); } } if ( nmatches % cols != 0 ) { rlc_putchar(ln->console, '\r'); rlc_putchar(ln->console, '\n'); } ln->reprompt = TRUE; } } } /******************************* * REPAINT * *******************************/ static void output(rlc_console b, TCHAR *s, size_t len) { while(len-- > 0) { if ( *s == '\n' ) rlc_putchar(b, '\r'); rlc_putchar(b, *s++); } } static void update_display(Line ln) { if ( ln->reprompt ) { const TCHAR *prompt = rlc_prompt(ln->console, NULL); const TCHAR *s = prompt; rlc_putchar(ln->console, '\r'); while(*s) rlc_putchar(ln->console, *s++); rlc_get_mark(ln->console, &ln->origin); ln->change_start = 0; ln->reprompt = FALSE; } rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->change_start); output(ln->console, &ln->data[ln->change_start], ln->size - ln->change_start); rlc_erase_from_caret(ln->console); rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->point); rlc_update(ln->console); ln->change_start = ln->size; } /******************************* * TOPLEVEL * *******************************/ TCHAR * read_line(rlc_console b) { line ln; init_line_package(b); memset(&ln, 0, sizeof(line)); ln.console = b; rlc_get_mark(b, &ln.origin); while(!ln.complete) { int c; rlc_mark m0, m1; function func; rlc_get_mark(b, &m0); if ( (c = getch(b)) == IMODE_SWITCH_CHAR ) return RL_CANCELED_CHARP; if ( c == EOF ) { eof(&ln, c); update_display(&ln); break; } else if ( c == ESC ) { if ( (c = getch(b)) == IMODE_SWITCH_CHAR ) return RL_CANCELED_CHARP; if ( c > 256 ) func = undefined; else func = dispatch_meta[c&0xff]; } else { if ( c >= 256 ) func = insert_self; else func = dispatch_table[c&0xff]; } rlc_get_mark(b, &m1); (*func)(&ln, c); if ( m0.mark_x != m1.mark_x || m0.mark_y != m1.mark_y ) ln.reprompt = TRUE; update_display(&ln); } rlc_clearprompt(b); add_history(b, ln.data); return ln.data; } /******************************* * DISPATCH * *******************************/ static void init_dispatch_table() { static int done; if ( !done ) { int n; for(n=0; n<32; n++) dispatch_table[n] = undefined; for(n=32; n<256; n++) dispatch_table[n] = insert_self; for(n=0; n<256; n++) dispatch_meta[n] = undefined; bind_actions(); done = TRUE; } } static void init_line_package(RlcData b) { init_dispatch_table(); rlc_init_history(b, 50); } /******************************* * BIND * *******************************/ typedef struct _action { char *name; function function; unsigned char keys[4]; } action, *Action; #define ACTION(n, f, k) { n, f, k } static action actions[] = { ACTION("insert_self", insert_self, ""), ACTION("backward_delete_character", backward_delete_character, "\b"), ACTION("complete", complete, "\t"), ACTION("enter", enter, "\r\n"), ACTION("start_of_line", start_of_line, {ctrl('A')}), ACTION("backward_character", backward_character, {ctrl('B')}), ACTION("interrupt", interrupt, {ctrl('C')}), ACTION("end_of_line", end_of_line, {ctrl('E')}), ACTION("forward_character", forward_character, {ctrl('F')}), ACTION("transpose_chars", transpose_chars, {ctrl('T')}), ACTION("kill_line", kill_line, {ctrl('K')}), ACTION("backward_history", backward_history, {ctrl('P')}), ACTION("forward_history", forward_history, {ctrl('N')}), ACTION("empty_line", empty_line, {ctrl('U')}), ACTION("eof", eof, {ctrl('Z')}), ACTION("delete_character_or_eof", delete_character_or_eof, {ctrl('D')}), ACTION("delete_character", delete_character, {127}), { "forward_word", forward_word, {meta(ctrl('F')), meta('f')}}, { "backward_word", backward_word, {meta(ctrl('B')), meta('b')}}, { "forward_delete_word", forward_delete_word, {meta(127), meta('d')}}, ACTION("list_completions", list_completions, {meta('?')}), ACTION("backward_delete_word", backward_delete_word, {meta('\b')}), ACTION(NULL, NULL, "") }; int rlc_bind(int chr, const char *fname) { if ( chr >= 0 && chr <= 256 ) { Action a = actions; for( ; a->name; a++ ) { if ( strcmp(a->name, fname) == 0 ) { if ( chr > META_OFFSET ) dispatch_meta[chr-META_OFFSET] = a->function; else dispatch_table[chr] = a->function; return TRUE; } } } return FALSE; } static void bind_actions() { Action a = actions; for( ; a->name; a++ ) { unsigned char *k = a->keys; for( ; *k; k++ ) { int chr = *k & 0xff; if ( chr > META_OFFSET ) dispatch_meta[chr-META_OFFSET] = a->function; else dispatch_table[chr] = a->function; } } }