/* $Id: edit.c,v 1.1 2008-03-27 00:41:33 vsc Exp $ Part of SWI-Prolog Author: Jan Wielemaker E-mail: jan@swi.psy.uva.nl WWW: http://www.swi-prolog.org Copyright (C): 1985-2002, University of Amsterdam This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _MAKE_DLL 1 #undef _export #include #include #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; } } }