From: Marcus Boerger Date: Sat, 14 May 2005 19:33:18 +0000 (+0000) Subject: - Add command completion to CLI's -a mode X-Git-Tag: php-5.0.1b1~236 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=51fe495ea1d6c08bf24ade02b1f250b5d0051d70;p=php - Add command completion to CLI's -a mode --- diff --git a/configure.in b/configure.in index 04f6c3b5bb..2e30c41d3d 100644 --- a/configure.in +++ b/configure.in @@ -1054,7 +1054,7 @@ AC_DEFINE_UNQUOTED(PHP_OS,"$PHP_OS",[uname output]) if test "$PHP_SAPI_CLI" != "no"; then PHP_CLI_TARGET="\$(SAPI_CLI_PATH)" PHP_INSTALL_CLI_TARGET="install-cli" - PHP_ADD_SOURCES(sapi/cli, php_cli.c getopt.c,, cli) + PHP_ADD_SOURCES(sapi/cli, php_cli.c php_cli_readline.c getopt.c,, cli) fi PHP_SUBST(PHP_CLI_TARGET) diff --git a/sapi/cli/config.w32 b/sapi/cli/config.w32 index 4f6ac8cc02..83692489f5 100644 --- a/sapi/cli/config.w32 +++ b/sapi/cli/config.w32 @@ -6,13 +6,13 @@ ARG_ENABLE('crt-debug', 'Extra CRT debugging', 'no'); ARG_ENABLE('cli-win32', 'Build console-less CLI version of PHP', 'no'); if (PHP_CLI == "yes") { - SAPI('cli', 'getopt.c php_cli.c', 'php.exe'); + SAPI('cli', 'getopt.c php_cli.c php_cli_readline.c', 'php.exe'); if (PHP_CRT_DEBUG == "yes") { ADD_FLAG("CFLAGS_CLI", "/D PHP_WIN32_DEBUG_HEAP"); } } if (PHP_CLI_WIN32 == "yes") { - SAPI('cli_win32', 'getopt.c cli_win32.c', 'php-win.exe'); + SAPI('cli_win32', 'getopt.c cli_win32.c php_cli_readline.c', 'php-win.exe'); } diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index 0eccab344d..e7a620980d 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -14,6 +14,7 @@ +----------------------------------------------------------------------+ | Author: Edin Kadribasic | | Marcus Boerger | + | Johannes Schlueter | | Parts based on CGI SAPI Module by | | Rasmus Lerdorf, Stig Bakken and Zeev Suraski | +----------------------------------------------------------------------+ @@ -74,7 +75,7 @@ #if !HAVE_LIBEDIT #include #endif -#endif +#endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */ #include "zend_compile.h" #include "zend_execute.h" @@ -83,6 +84,7 @@ #include "php_getopt.h" +#include "php_cli_readline.h" #ifndef O_BINARY #define O_BINARY 0 @@ -539,216 +541,6 @@ static int cli_seek_file_begin(zend_file_handle *file_handle, char *script_file, } /* }}} */ -#if HAVE_LIBREADLINE || HAVE_LIBEDIT - -/* {{{ cli_is_valid_code - */ -typedef enum { - body, - sstring, - dstring, - sstring_esc, - dstring_esc, - comment_line, - comment_block, - heredoc_start, - heredoc, - outside, -} php_code_type; - -static int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) -{ - int valid_end = 1; - int brackets_count = 0; - int brace_count = 0; - int i; - php_code_type code_type = body; - char *heredoc_tag; - int heredoc_len; - - for (i = 0; i < len; ++i) { - switch(code_type) { - default: - switch(code[i]) { - case '{': - brackets_count++; - valid_end = 0; - break; - case '}': - if (brackets_count > 0) { - brackets_count--; - } - valid_end = brackets_count ? 0 : 1; - break; - case '(': - brace_count++; - valid_end = 0; - break; - case ')': - if (brace_count > 0) { - brace_count--; - } - valid_end = 0; - break; - case ';': - valid_end = brace_count == 0 && brackets_count == 0; - break; - case ' ': - case '\n': - case '\t': - break; - case '\'': - code_type = sstring; - break; - case '"': - code_type = dstring; - break; - case '/': - if (code[i+1] == '/') { - i++; - code_type = comment_line; - break; - } - if (code[i+1] == '*') { - code_type = comment_block; - i++; - break; - } - valid_end = 0; - break; - case '%': - if (!CG(asp_tags)) { - valid_end = 0; - break; - } - /* no break */ - case '?': - if (code[i+1] == '>') { - i++; - code_type = outside; - break; - } - valid_end = 0; - break; - case '<': - valid_end = 0; - if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') { - i += 2; - code_type = heredoc_start; - heredoc_len = 0; - } - break; - default: - valid_end = 0; - break; - } - break; - case sstring: - if (code[i] == '\\') { - code_type = sstring_esc; - } else { - if (code[i] == '\'') { - code_type = body; - } - } - break; - case sstring_esc: - code_type = sstring; - break; - case dstring: - if (code[i] == '\\') { - code_type = dstring_esc; - } else { - if (code[i] == '"') { - code_type = body; - } - } - break; - case dstring_esc: - code_type = dstring; - break; - case comment_line: - if (code[i] == '\n') { - code_type = body; - } - break; - case comment_block: - if (code[i-1] == '*' && code[i] == '/') { - code_type = body; - } - break; - case heredoc_start: - switch(code[i]) { - case ' ': - case '\t': - break; - case '\r': - case '\n': - code_type = heredoc; - break; - default: - if (!heredoc_len) { - heredoc_tag = code+i; - } - heredoc_len++; - break; - } - break; - case heredoc: - if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len)) { - code_type = body; - } - break; - case outside: - if ((CG(short_tags) && !strncmp(code+i-1, " 3 && !strncmp(code+i-4, " "; - } - break; - case sstring: - case sstring_esc: - *prompt = "php ' "; - break; - case dstring: - case dstring_esc: - *prompt = "php \" "; - break; - case comment_block: - *prompt = "/* > "; - break; - case heredoc: - *prompt = "<<< > "; - break; - case outside: - *prompt = " > "; - break; - } - - if (!valid_end || brackets_count) { - return 0; - } else { - return 1; - } -} -/* }}} */ - -#endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */ - /* {{{ main */ #ifdef PHP_CLI_WIN32_NO_CONSOLE @@ -1185,11 +977,11 @@ int main(int argc, char *argv[]) char *history_file; history_file = tilde_expand("~/.php_history"); + rl_attempted_completion_function = cli_code_completion; + /*rl_completion_append_character = '(';*/ + rl_special_prefixes = "$"; read_history(history_file); - /* it would be nicer to implement this correct */ - rl_bind_key ('\t', rl_insert); - EG(exit_status) = 0; while ((line = readline(pos ? prompt : "php > ")) != NULL) { if (strcmp(line, "exit") == 0 || strcmp(line, "quit") == 0) { diff --git a/sapi/cli/php_cli_readline.c b/sapi/cli/php_cli_readline.c new file mode 100644 index 0000000000..d10d664a81 --- /dev/null +++ b/sapi/cli/php_cli_readline.c @@ -0,0 +1,436 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2005 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Marcus Boerger | + | Johannes Schlueter | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "php_globals.h" +#include "php_variables.h" +#include "zend_hash.h" +#include "zend_modules.h" + +#include "SAPI.h" + +#if HAVE_SETLOCALE +#include +#endif +#include "zend.h" +#include "zend_extensions.h" +#include "php_ini.h" +#include "php_globals.h" +#include "php_main.h" +#include "fopen_wrappers.h" +#include "ext/standard/php_standard.h" + +#ifdef __riscos__ +#include +#endif + +#if HAVE_LIBREADLINE || HAVE_LIBEDIT +#include +#if !HAVE_LIBEDIT +#include +#endif +#endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */ + +#include "zend_compile.h" +#include "zend_execute.h" +#include "zend_highlight.h" +#include "zend_indent.h" + +#if HAVE_LIBREADLINE || HAVE_LIBEDIT + +/* {{{ cli_is_valid_code + */ +typedef enum { + body, + sstring, + dstring, + sstring_esc, + dstring_esc, + comment_line, + comment_block, + heredoc_start, + heredoc, + outside, +} php_code_type; + +int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC) +{ + int valid_end = 1; + int brackets_count = 0; + int brace_count = 0; + int i; + php_code_type code_type = body; + char *heredoc_tag; + int heredoc_len; + + for (i = 0; i < len; ++i) { + switch(code_type) { + default: + switch(code[i]) { + case '{': + brackets_count++; + valid_end = 0; + break; + case '}': + if (brackets_count > 0) { + brackets_count--; + } + valid_end = brackets_count ? 0 : 1; + break; + case '(': + brace_count++; + valid_end = 0; + break; + case ')': + if (brace_count > 0) { + brace_count--; + } + valid_end = 0; + break; + case ';': + valid_end = brace_count == 0 && brackets_count == 0; + break; + case ' ': + case '\n': + case '\t': + break; + case '\'': + code_type = sstring; + break; + case '"': + code_type = dstring; + break; + case '/': + if (code[i+1] == '/') { + i++; + code_type = comment_line; + break; + } + if (code[i+1] == '*') { + code_type = comment_block; + i++; + break; + } + valid_end = 0; + break; + case '%': + if (!CG(asp_tags)) { + valid_end = 0; + break; + } + /* no break */ + case '?': + if (code[i+1] == '>') { + i++; + code_type = outside; + break; + } + valid_end = 0; + break; + case '<': + valid_end = 0; + if (i + 2 < len && code[i+1] == '<' && code[i+2] == '<') { + i += 2; + code_type = heredoc_start; + heredoc_len = 0; + } + break; + default: + valid_end = 0; + break; + } + break; + case sstring: + if (code[i] == '\\') { + code_type = sstring_esc; + } else { + if (code[i] == '\'') { + code_type = body; + } + } + break; + case sstring_esc: + code_type = sstring; + break; + case dstring: + if (code[i] == '\\') { + code_type = dstring_esc; + } else { + if (code[i] == '"') { + code_type = body; + } + } + break; + case dstring_esc: + code_type = dstring; + break; + case comment_line: + if (code[i] == '\n') { + code_type = body; + } + break; + case comment_block: + if (code[i-1] == '*' && code[i] == '/') { + code_type = body; + } + break; + case heredoc_start: + switch(code[i]) { + case ' ': + case '\t': + break; + case '\r': + case '\n': + code_type = heredoc; + break; + default: + if (!heredoc_len) { + heredoc_tag = code+i; + } + heredoc_len++; + break; + } + break; + case heredoc: + if (code[i - (heredoc_len + 1)] == '\n' && !strncmp(code + i - heredoc_len, heredoc_tag, heredoc_len)) { + code_type = body; + } + break; + case outside: + if ((CG(short_tags) && !strncmp(code+i-1, " 3 && !strncmp(code+i-4, " "; + } + break; + case sstring: + case sstring_esc: + *prompt = "php ' "; + break; + case dstring: + case dstring_esc: + *prompt = "php \" "; + break; + case comment_block: + *prompt = "/* > "; + break; + case heredoc: + *prompt = "<<< > "; + break; + case outside: + *prompt = " > "; + break; + } + + if (!valid_end || brackets_count) { + return 0; + } else { + return 1; + } +} +/* }}} */ + +static char *cli_completion_generator_ht(const char *text, int textlen, int *state, HashTable *ht, void **pData TSRMLS_DC) /* {{{ */ +{ + char *name; + ulong number; + + if (!(*state % 2)) { + zend_hash_internal_pointer_reset(ht); + (*state)++; + } + while(zend_hash_has_more_elements(ht) == SUCCESS) { + zend_hash_get_current_key(ht, &name, &number, 0); + if (!textlen || !strncmp(name, text, textlen)) { + if (pData) { + zend_hash_get_current_data(ht, pData); + } + zend_hash_move_forward(ht); + return name; + } + if (zend_hash_move_forward(ht) == FAILURE) { + break; + } + } + (*state)++; + return NULL; +} /* }}} */ + +static char *cli_completion_generator_var(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */ +{ + char *retval, *tmp; + + tmp = retval = cli_completion_generator_ht(text + 1, textlen - 1, state, EG(active_symbol_table), NULL TSRMLS_CC); + if (retval) { + retval = malloc(strlen(tmp) + 1); + retval[0] = '$'; + strcpy(&retval[1], tmp); + rl_completion_append_character = '\0'; + } + return retval; +} /* }}} */ + +static char *cli_completion_generator_func(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */ +{ + zend_function *func; + char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC); + if (retval) { + rl_completion_append_character = '('; + retval = strdup(func->common.function_name); + } + + return retval; +} /* }}} */ + +static char *cli_completion_generator_class(const char *text, int textlen, int *state TSRMLS_DC) /* {{{ */ +{ + zend_class_entry **pce; + char *retval = cli_completion_generator_ht(text, textlen, state, EG(class_table), (void**)&pce TSRMLS_CC); + if (retval) { + rl_completion_append_character = '\0'; + retval = strdup((*pce)->name); + } + + return retval; +} /* }}} */ + +static char *cli_completion_generator_define(const char *text, int textlen, int *state, HashTable *ht TSRMLS_DC) /* {{{ */ +{ + zend_class_entry **pce; + char *retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&pce TSRMLS_CC); + if (retval) { + rl_completion_append_character = '\0'; + retval = strdup(retval); + } + + return retval; +} /* }}} */ + +static int cli_completion_state; + +static char *cli_completion_generator(const char *text, int index) /* {{{ */ +{ +/* +TODO: +- constants +- maybe array keys +- language constructs and other things outside a hashtable (echo, try, function, class, ...) +- object/class members + +- future: respect scope ("php > function foo() { $[tab]" should only expand to local variables...) +*/ + char *retval; + int textlen = strlen(text); + TSRMLS_FETCH(); + + if (!index) { + cli_completion_state = 0; + } + if (text[0] == '$') { + retval = cli_completion_generator_var(text, textlen, &cli_completion_state TSRMLS_CC); + } else { + char *lc_text, *class_name, *class_name_end; + int class_name_len; + zend_class_entry **pce = NULL; + + class_name_end = strstr(text, "::"); + if (class_name_end) { + class_name_len = class_name_end - text; + class_name = zend_str_tolower_dup(text, class_name_len); + class_name[class_name_len] = '\0'; /* not done automatically */ + if (zend_lookup_class(class_name, class_name_len, &pce TSRMLS_CC)==FAILURE) { + efree(class_name); + return NULL; + } + lc_text = zend_str_tolower_dup(class_name_end + 2, textlen - 2 - class_name_len); + textlen -= (class_name_len + 2); + } else { + lc_text = zend_str_tolower_dup(text, textlen); + } + + switch (cli_completion_state) { + case 0: + case 1: + retval = cli_completion_generator_func(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->function_table : EG(function_table) TSRMLS_CC); + if (retval) { + break; + } + case 2: + case 3: + retval = cli_completion_generator_define(lc_text, textlen, &cli_completion_state, pce ? &(*pce)->constants_table : EG(zend_constants) TSRMLS_CC); + if (retval || pce) { + break; + } + case 4: + case 5: + retval = cli_completion_generator_class(lc_text, textlen, &cli_completion_state TSRMLS_CC); + break; + default: + break; + } + efree(lc_text); + if (class_name_end) { + efree(class_name); + } + if (pce && retval) { + char *tmp = malloc(class_name_len + 2 + strlen(retval) + 1); + + sprintf(tmp, "%s::%s", (*pce)->name, retval); + free(retval); + retval = tmp; + } + } + + return retval; +} /* }}} */ + +/* {{{ cli_code_completion + */ +char **cli_code_completion(const char *text, int start, int end) +{ + return rl_completion_matches(text, cli_completion_generator); +} +/* }}} */ + +#endif /* HAVE_LIBREADLINE || HAVE_LIBEDIT */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: sw=4 ts=4 fdm=marker + * vim<600: sw=4 ts=4 + */ diff --git a/sapi/cli/php_cli_readline.h b/sapi/cli/php_cli_readline.h new file mode 100644 index 0000000000..12a02d4b94 --- /dev/null +++ b/sapi/cli/php_cli_readline.h @@ -0,0 +1,25 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2004 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Marcus Boerger | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" + +int cli_is_valid_code(char *code, int len, char **prompt TSRMLS_DC); + +char **cli_code_completion(const char *text, int start, int end);