@@ -1,10 +1,10 @@ /* ================================================================== FILE: "/home/joze/src/tclreadline/tclreadline.c" - LAST MODIFICATION: "Sat Aug 21 17:44:13 1999 (joze)" + LAST MODIFICATION: "Sun Aug 22 19:06:26 1999 (joze)" (C) 1998, 1999 by Johannes Zellner, $Id$ --- tclreadline -- gnu readline for tcl @@ -38,74 +38,114 @@ #include #include #define MALLOC(size) Tcl_Alloc((int) size) -#define FREE(ptr) if (ptr) Tcl_Free((char *) ptr) +#define FREE(ptr) if (ptr) { Tcl_Free((char*) ptr); ptr = 0; } enum { _CMD_SET = (1 << 0), - _CMD_GET = (1 << 1), - _CMD_SUB_GET = (1 << 2) + _CMD_GET = (1 << 1) }; typedef struct cmds_t { - struct cmds_t *prev; - char **cmd; - struct cmds_t *next; + struct cmds_t* prev; + char** cmd; + struct cmds_t* next; } cmds_t; -#define STRIPLEFT(ptr) \ -do { \ - char *tmp = ptr; \ - while (*tmp && *tmp <= ' ') \ - tmp ++; \ - strcpy(ptr, tmp); \ -} while (0) - -#define STRIPRIGHT(ptr) \ -do { \ - char *__tmp__; \ - for (__tmp__ = strchr(ptr, '\0') - 1; \ - __tmp__ >= ptr && *__tmp__ <= ' '; __tmp__--) \ - *__tmp__ = '\0'; \ -} while (0) - -#define STRIPWHITE(ptr) \ -do { \ - STRIPLEFT(ptr); \ - STRIPRIGHT(ptr); \ -} while (0) - +#define ISWHITE(c) ((' ' == c) || ('\t' == c) || ('\n' == c)) /* * forward declarations. */ -int TclReadlineCmd(ClientData clientData, Tcl_Interp *interp, - int argc, char **argv); +char* stripleft(char* in); +char* stripright(char* in); +char* stripwhite(char* in); +char* TclReadlineQuote(char* text, char* quotechars); +int TclReadlineCmd(ClientData clientData, Tcl_Interp* interp, + int argc, char** argv); void TclReadlineDataAvailableHandler(ClientData clientData, int mask); -void TclReadlineLineCompleteHandler(char *ptr); -int Tclreadline_SafeInit(Tcl_Interp *interp); -int Tclreadline_Init(Tcl_Interp *interp); -char* TclReadlineInitialize(char *historyfile); -char** TclReadlineCompletion(char *text, int start, int end); -char* TclReadline0generator(char *text, int state); -char* TclReadline1generator(char *text, int state); -char* TclReadlineKnownCommands(char *text, int state, int mode); -int TclReadlineParse(char **args, int maxargs, char *buf); +void TclReadlineLineCompleteHandler(char* ptr); +int Tclreadline_SafeInit(Tcl_Interp* interp); +int Tclreadline_Init(Tcl_Interp* interp); +char* TclReadlineInitialize(char* historyfile); +int blank_line(char* str); +char** TclReadlineCompletion(char* text, int start, int end); +char* TclReadline0generator(char* text, int state); +char* TclReadlineKnownCommands(char* text, int state, int mode); +int TclReadlineParse(char** args, int maxargs, char* buf); enum { LINE_PENDING, - LINE_CONTROL_D, + LINE_EOF, LINE_COMPLETE }; -static int line_complete = LINE_PENDING; -static char *line = (char *) NULL; +/** + * global variables + */ +static int tclrl_line_complete = LINE_PENDING; +static int tclrl_state = TCL_OK; +static char* tclrl_eof_string = (char*) NULL; +static char* tclrl_line = (char*) NULL; +static char* tclrl_custom_completer = (char*) NULL; +static int tclrl_use_builtin_completer = 1; +Tcl_Interp* tclrl_interp = (Tcl_Interp*) NULL; + + +char* +stripleft(char* in) +{ + char* ptr = in; + while (*ptr && *ptr <= ' ') + ptr++; + if (in != ptr) + memmove(in, ptr, strlen(ptr) + 1); + return in; +} + +char* +stripright(char* in) +{ + char* ptr; + for (ptr = strchr(in, '\0') - 1; ptr >= in && *ptr <= ' '; ptr--) + *ptr = '\0'; + return in; +} + +char* +stripwhite(char* in) +{ + stripleft(in); + stripright(in); + return in; +} + +char* +TclReadlineQuote(char* text, char* quotechars) +{ + char* ptr; + char* result_c; + int i, len = strlen(quotechars); + Tcl_DString result; + Tcl_DStringInit(&result); + for (ptr = text; ptr && *ptr; ptr++) { + for (i = 0; i < len; i++) { + if (quotechars[i] == *ptr) { + Tcl_DStringAppend(&result, "\\", 1); + break; + } + } + Tcl_DStringAppend(&result, ptr, 1); + } + result_c = strdup(Tcl_DStringValue(&result)); + return result_c; +} int TclReadlineCmd( ClientData clientData, /* Main window associated with interpreter */ Tcl_Interp* interp, /* Current interpreter */ @@ -113,99 +153,125 @@ char** argv /* Argument strings */ ) { int c, length; - if (argc < 2) { - interp->result = "wrong # args"; - for (c = 0; c < argc; c++) - fprintf(stderr, "argv[%d] = %s\n", c, argv[c]); - return TCL_ERROR; - } + if (argc < 2) + goto BAD_COMMAND; c = argv[1][0]; length = strlen(argv[1]); if (c == 'r' && strncmp(argv[1], "read", length) == 0) { - char *expansion = (char *) NULL; + char* expansion = (char*) NULL; int status; - line_complete = LINE_PENDING; + tclrl_line_complete = LINE_PENDING; + tclrl_state = TCL_OK; rl_callback_handler_install(argc == 3 ? argv[2] : "%", TclReadlineLineCompleteHandler); Tcl_CreateFileHandler(0, TCL_READABLE, TclReadlineDataAvailableHandler, (ClientData) NULL); - while (LINE_PENDING == line_complete) { + while (LINE_PENDING == tclrl_line_complete && TCL_OK == tclrl_state) { Tcl_DoOneEvent(0); } Tcl_DeleteFileHandler(0); - /* - if (LINE_CONTROL_D == line_complete) { - Tcl_Eval(interp, "puts {}; exit"); + if ((LINE_EOF == tclrl_line_complete) && tclrl_eof_string) { + Tcl_Eval(interp, tclrl_eof_string); + return tclrl_state; } - */ - status = history_expand(line, &expansion); + status = history_expand(tclrl_line, &expansion); if (status == 1) printf("%s\n", expansion); else if (status == -1) Tcl_AppendResult(interp, "error in history expansion\n", - (char *) NULL); + (char*) NULL); if (expansion && *expansion) add_history(expansion); - Tcl_AppendResult(interp, expansion, (char *) NULL); + Tcl_AppendResult(interp, expansion, (char*) NULL); - FREE(line); + FREE(tclrl_line); FREE(expansion); - return TCL_OK; - } - else if (c == 'i' && strncmp(argv[1], "initialize", length) == 0) { - if (argc != 3) + return tclrl_state; + } else if (c == 'i' && strncmp(argv[1], "initialize", length) == 0) { + if (3 != argc) goto BAD_COMMAND; else Tcl_AppendResult(interp, TclReadlineInitialize(argv[2]), - (char *) NULL); - } - else if (c == 'w' && strncmp(argv[1], "write", length) == 0) { - if (argc != 3) + (char*) NULL); + } else if (c == 'w' && strncmp(argv[1], "write", length) == 0) { + if (3 != argc) goto BAD_COMMAND; else if (write_history(argv[2])) Tcl_AppendResult(interp, "unable to write history to \"", - argv[2], "\"\n", (char *) NULL); - } - else if (c == 'a' && strncmp(argv[1], "add", length) == 0) { - if (argc != 3) + argv[2], "\"\n", (char*) NULL); + } else if (c == 'a' && strncmp(argv[1], "add", length) == 0) { + if (3 != argc) goto BAD_COMMAND; else if (TclReadlineKnownCommands(argv[2], (int) 0, _CMD_SET)) Tcl_AppendResult(interp, "unable to add command \"", - argv[2], "\"\n", (char *) NULL); - } - else if (c == 'c' && strncmp(argv[1], "complete", length) == 0) { - if (argc != 3) + argv[2], "\"\n", (char*) NULL); + } else if (c == 'c' && strncmp(argv[1], "complete", length) == 0) { + if (3 != argc) goto BAD_COMMAND; else if (Tcl_CommandComplete(argv[2])) - Tcl_AppendResult(interp, "1", (char *) NULL); + Tcl_AppendResult(interp, "1", (char*) NULL); else - Tcl_AppendResult(interp, "0", (char *) NULL); - } - else + Tcl_AppendResult(interp, "0", (char*) NULL); + } else if (c == 'c' && strncmp(argv[1], "customcompleter", length) == 0) { + if (3 != argc && 2 != argc) + goto BAD_COMMAND; + if (3 == argc) { + if (tclrl_custom_completer) + FREE(tclrl_custom_completer); + if (!blank_line(argv[2])) + tclrl_custom_completer = stripwhite(strdup(argv[2])); + } + Tcl_AppendResult (interp, tclrl_custom_completer, (char*) NULL); + } else if (c == 'b' && strncmp(argv[1], "builtincompleter", length) == 0) { + int bool = tclrl_use_builtin_completer; + if (3 != argc && 2 != argc) + goto BAD_COMMAND; + if (3 == argc) { + if (TCL_OK != Tcl_GetBoolean(interp, argv[2], &bool)) { + Tcl_AppendResult(interp, + "wrong # args: should be a boolean value.", (char*) NULL); + return TCL_ERROR; + } else { + tclrl_use_builtin_completer = bool; + } + } + Tcl_AppendResult(interp, tclrl_use_builtin_completer ? "1" : "0", + (char*) NULL); + } else if (c == 'e' && strncmp(argv[1], "eofchar", length) == 0) { + if (3 != argc && 2 != argc) + goto BAD_COMMAND; + if (3 == argc) { + if (tclrl_eof_string) + FREE(tclrl_eof_string); + if (!blank_line(argv[2])) + tclrl_eof_string = stripwhite(strdup(argv[2])); + } + Tcl_AppendResult(interp, tclrl_eof_string, (char*) NULL); + } else { goto BAD_COMMAND; - + } return TCL_OK; BAD_COMMAND: - Tcl_AppendResult (interp, - "wrong # args: should be \"readline option ?arg ...?\"", - (char *) NULL); + Tcl_AppendResult(interp, + "wrong # args: should be \"readline option ?arg ...?\"", + (char*) NULL); return TCL_ERROR; } void @@ -219,30 +285,28 @@ rl_callback_read_char(); } } void -TclReadlineLineCompleteHandler(char *ptr) +TclReadlineLineCompleteHandler(char* ptr) { -#if 0 - fprintf(stderr, "(TclReadlineLineCompleteHandler) ptr = %p\n", ptr); - if (ptr) - fprintf(stderr, "(TclReadlineLineCompleteHandler) *ptr = %p\n", *ptr); +#if 1 if (!ptr) { /* */ - line_complete = LINE_CONTROL_D; + tclrl_line_complete = LINE_EOF; rl_callback_handler_remove(); } else if (*ptr) { - line_complete = LINE_COMPLETE; + tclrl_line_complete = LINE_COMPLETE; rl_callback_handler_remove(); - line = ptr; + tclrl_line = ptr; } -#endif +#else if (ptr && *ptr) { - line_complete = 1; + tclrl_line_complete = 1; rl_callback_handler_remove (); - line = ptr; + tclrl_line = ptr; } +#endif } int Tclreadline_SafeInit(Tcl_Interp *interp) { @@ -252,19 +316,28 @@ int Tclreadline_Init(Tcl_Interp *interp) { Tcl_CreateCommand(interp, "::tclreadline::readline", TclReadlineCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); + tclrl_interp = interp; return Tcl_PkgProvide(interp, "tclreadline", TCLREADLINE_VERSION); } char* TclReadlineInitialize(char* historyfile) { rl_readline_name = "tclreadline"; + rl_special_prefixes = "${"; using_history(); + /** + * default is " \t\n\"\\'`@$><=;|&{(" + * removed '{'. + */ + rl_basic_word_break_characters = " \t\n\"\\'`@$><=;|&("; + if (!tclrl_eof_string) + tclrl_eof_string = strdup("puts {}; exit"); /* * try to read historyfile in home * directory. If this failes, this * is *not* an error. @@ -274,31 +347,95 @@ return "unable to read history file"; else return ""; } + +int +blank_line(char* str) +{ + char* ptr; + for (ptr = str; ptr && *ptr; ptr++) { + if (!ISWHITE(*ptr)) + return 0; + } + return 1; +} char** TclReadlineCompletion(char* text, int start, int end) { - char** matches = (char **) NULL; - static char local_line[BUFSIZ]; - - strcpy(local_line, rl_line_buffer); - - STRIPWHITE(local_line); - - /* + char** matches = (char**) NULL; + +#if 0 fprintf(stderr, "DEBUG> TclReadlineCompletion: text=|%s|\n", text); fprintf(stderr, "DEBUG> TclReadlineCompletion: start=|%d|\n", start); fprintf(stderr, "DEBUG> TclReadlineCompletion: end=|%d|\n", end); - */ +#endif + + if (tclrl_custom_completer) { + char start_s[BUFSIZ], end_s[BUFSIZ]; + Tcl_Obj* obj; + Tcl_Obj** objv; + int objc; + char* quoted_text = TclReadlineQuote(text, "$[]{}\""); + char* quoted_rl_line_buffer + = TclReadlineQuote(rl_line_buffer, "$[]{}\""); + /* + fprintf (stderr, "(TclReadlineCompletion) rl_line_buffer = |%s|\n", + rl_line_buffer); + fprintf (stderr, "(TclReadlineCompletion) text = |%s|\n", text); + fprintf (stderr, "(TclReadlineCompletion) quoted_text = |%s|\n", + quoted_text); + fprintf (stderr, "(TclReadlineCompletion) quoted_rl_line_buffer = |%s|\n", + quoted_rl_line_buffer); + */ + sprintf(start_s, "%d", start); + sprintf(end_s, "%d", end); + Tcl_ResetResult(tclrl_interp); /* clear result space */ + tclrl_state = Tcl_VarEval(tclrl_interp, tclrl_custom_completer, + " \"", quoted_text, "\" ", start_s, " ", end_s, + " \"", quoted_rl_line_buffer, "\"", (char*) NULL); + FREE(quoted_text); + FREE(quoted_rl_line_buffer); + if (TCL_OK != tclrl_state) { + fprintf (stderr, "%s\n", Tcl_GetStringResult(tclrl_interp)); + /* + Tcl_AppendResult (tclrl_interp, "`", tclrl_custom_completer, + " {", text, "} ", start_s, " ", end_s, + " {", rl_line_buffer, "}' failed.", (char*) NULL); + */ + return matches; + } + obj = Tcl_GetObjResult(tclrl_interp); + Tcl_ListObjGetElements(tclrl_interp, obj, &objc, &objv); + /* fprintf (stderr, "(TclReadlineCompletion) objc = %d\n", objc); */ + if (objc) { + int i, length /* not used */; + matches = (char**) MALLOC(sizeof(char*) * (objc + 1)); + for (i = 0; i < objc; i++) { + matches[i] = strdup(Tcl_GetStringFromObj(objv[i], &length)); + /* + fprintf (stderr, "(TclReadlineCompletion) matches[%d] = |%s|\n", + i, matches[i]); + */ + } + matches[i] = (char*) NULL; /* terminate */ + } + Tcl_ResetResult(tclrl_interp); /* clear result space */ + } - if (start == 0 || !strlen(local_line)) + if (!matches && tclrl_use_builtin_completer) { matches = completion_matches(text, TclReadline0generator); - else - matches = completion_matches(text, TclReadline1generator); + } +#if 0 + { + char **ptr; + for (ptr = matches; ptr && *ptr; ptr++) + fprintf (stderr, "(TclReadlineCompletion) |%s|\n", *ptr); + } +#endif return matches; } char* @@ -305,25 +442,22 @@ TclReadline0generator(char* text, int state) { return TclReadlineKnownCommands(text, state, _CMD_GET); } -char* -TclReadline1generator(char* text, int state) -{ - return TclReadlineKnownCommands(text, state, _CMD_SUB_GET); -} - char* TclReadlineKnownCommands(char* text, int state, int mode) { static int len; static cmds_t *cmds = (cmds_t *) NULL, *new; char* tmp; char* args[256]; int i, argc; char** name; + + char* local_line = (char*) NULL; + int sub; switch (mode) { case _CMD_SET: @@ -340,83 +474,128 @@ } tmp = strdup(text); argc = TclReadlineParse(args, sizeof(args), tmp); - new->cmd = (char **) MALLOC(sizeof(char *) * (argc + 1)); + new->cmd = (char**) MALLOC(sizeof(char*) * (argc + 1)); for (i = 0; i < argc; i++) new->cmd[i] = args[i]; - new->cmd[argc] = (char *) NULL; + new->cmd[argc] = (char*) NULL; - return (char *) NULL; + return (char*) NULL; break; case _CMD_GET: - - if (!state) { - new = cmds; - len = strlen(text); - } - - while (new && (name = new->cmd)) { - new = new->next; - if (!strncmp(name[0], text, len)) - return strdup(name[0]); - } - - return (char *) NULL; - break; - - case _CMD_SUB_GET: - - - if (!state) { - - int sub; - char *local_line = strdup(rl_line_buffer); - - len = strlen(local_line); - STRIPRIGHT(local_line); - - if (len != strlen(local_line)) - sub = TclReadlineParse(args, sizeof(args), local_line); - else - sub = TclReadlineParse(args, sizeof(args), local_line) - 1; - - new = cmds; - len = strlen(text); - - while (new && (name = new->cmd)) { - if (!strcmp(name[0], args[0])) - break; - new = new->next; - } - - if (!new) - return (char *) NULL; - - for (i = 0; new->cmd[i]; i++) /* EMPTY */; - - if (sub < i && !strncmp(new->cmd[sub], text, len)) - return strdup(new->cmd[sub]); - else - return (char *) NULL; - - } - else - return (char *) NULL; - - /* NOTREACHED */ - break; + local_line = strdup(rl_line_buffer); + sub = TclReadlineParse(args, sizeof(args), local_line); + // fprintf (stderr, "(TclReadlineKnownCommands) state=%d\n", state); + + if (0 == sub || (1 == sub && '\0' != text[0])) { + // if (0 == state) { + if (!state) { + new = cmds; + len = strlen(text); + } + while (new && (name = new->cmd)) { + new = new->next; + if (!strncmp(name[0], text, len)) + return strdup(name[0]); + } + return (char*) NULL; + } else { + + if (!state) { + +#if 0 + fprintf (stderr, + "(TclReadlineKnownCommands) _CMD_SUB_GET = |%s| %d %d\n", + text, state, mode); +#endif + + /* + len = strlen(local_line); + stripright(local_line); + */ + +#if 0 + if (len != strlen(local_line)) { +#endif +#if 0 + // fprintf (stderr, "(TclReadlineKnownCommands) len !=\n"); + } else { + sub = TclReadlineParse(args, sizeof(args), local_line) - 1; + // fprintf (stderr, "(TclReadlineKnownCommands) len ==\n"); + } +#endif +#if 0 + { + int i; + fprintf (stderr, "\n"); + for (i = 0; i < sub; i++) + fprintf (stderr, "|%s|\n", args[i]); + } + + fprintf (stderr, + "(TclReadlineKnownCommands) args[sub - 1] = |%s|\n", + args[sub - 1]); +#endif +#if 0 + if (sub > 0 && args[sub - 1] && *(args[sub - 1]) == '$') { + char* var = strdup(args[sub - 1] + 1); + char* ptr = var; + int varlen; + if (var) + varlen = strlen(var); + else + return (char*) NULL; + if (!varlen) { + FREE(var); + return (char* ) NULL; + } else if ('{' == var[0]) { + ptr++; + varlen--; + } + fprintf (stderr, "(TclReadlineKnownCommands) $\n"); + FREE(var); + return (char* ) NULL; + } +#endif + + new = cmds; + len = strlen(text); + + while (new && (name = new->cmd)) { + if (!strcmp(name[0], args[0])) + break; + new = new->next; + } + + if (!new) + return (char*) NULL; + + for (i = 0; new->cmd[i]; i++) /* EMPTY */; + + if (sub < i && !strncmp(new->cmd[sub], text, len)) + return strdup(new->cmd[sub]); + else + return (char*) NULL; + + } + else + return (char*) NULL; + + /* NOTREACHED */ + break; + } default: - return (char *) NULL; + return (char*) NULL; break; } /* NOTREACHED */ } @@ -430,21 +609,21 @@ /* * Strip whitespace. Use nulls, so * that the previous argument is terminated * automatically. */ - while ((*buf == ' ') || (*buf == '\t') || (*buf == '\n')) + while (ISWHITE(*buf)) *buf++ = '\0'; if (!(*buf)) /* don't count the terminating NULL */ break; *args++ = buf; nr++; - while ((*buf!='\0') && (*buf!=' ') && (*buf!='\t') && (*buf!='\n')) + while (('\0' != *buf) && !ISWHITE(*buf)) buf++; } *args = '\0'; return nr; }