]> granicus.if.org Git - php/commitdiff
- Add command completion to CLI's -a mode
authorMarcus Boerger <helly@php.net>
Sat, 14 May 2005 19:33:18 +0000 (19:33 +0000)
committerMarcus Boerger <helly@php.net>
Sat, 14 May 2005 19:33:18 +0000 (19:33 +0000)
configure.in
sapi/cli/config.w32
sapi/cli/php_cli.c
sapi/cli/php_cli_readline.c [new file with mode: 0644]
sapi/cli/php_cli_readline.h [new file with mode: 0644]

index 04f6c3b5bba97c24dc8b2ff6f3e3178b235f296e..2e30c41d3d802347399f7100c1e70e029543dbc9 100644 (file)
@@ -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)
index 4f6ac8cc021255559003e4c29f8076ae64762ce9..83692489f5c927d3174addae7d4522822dba9dc8 100644 (file)
@@ -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');
 }
 
index 0eccab344d466565e906e4b006c8cc64556db3e1..e7a620980d7bfa908775a6e1015f1619dc75446f 100644 (file)
@@ -14,6 +14,7 @@
    +----------------------------------------------------------------------+
    | Author: Edin Kadribasic <edink@php.net>                              |
    |         Marcus Boerger <helly@php.net>                               |
+   |         Johannes Schlueter <johannes@php.net>                        |
    |         Parts based on CGI SAPI Module by                            |
    |         Rasmus Lerdorf, Stig Bakken and Zeev Suraski                 |
    +----------------------------------------------------------------------+
@@ -74,7 +75,7 @@
 #if !HAVE_LIBEDIT
 #include <readline/history.h>
 #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, "<?", 2))
-                               ||  (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
-                               ||  (i > 3 && !strncmp(code+i-4, "<?php", 5))
-                               ) {
-                                       code_type = body;
-                               }
-                               break;
-               }
-       }
-
-       switch (code_type) {
-               default:
-                       if (brace_count) {
-                               *prompt = "php ( ";
-                       } else if (brackets_count) {
-                               *prompt = "php { ";
-                       } else {
-                               *prompt = "php > ";
-                       }
-                       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 (file)
index 0000000..d10d664
--- /dev/null
@@ -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 <helly@php.net>                               |
+   |         Johannes Schlueter <johannes@php.net>                        |
+   +----------------------------------------------------------------------+
+*/
+
+/* $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 <locale.h>
+#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 <unixlib/local.h>
+#endif
+
+#if HAVE_LIBREADLINE || HAVE_LIBEDIT
+#include <readline/readline.h>
+#if !HAVE_LIBEDIT
+#include <readline/history.h>
+#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, "<?", 2))
+                               ||  (CG(asp_tags) && !strncmp(code+i-1, "<%", 2))
+                               ||  (i > 3 && !strncmp(code+i-4, "<?php", 5))
+                               ) {
+                                       code_type = body;
+                               }
+                               break;
+               }
+       }
+
+       switch (code_type) {
+               default:
+                       if (brace_count) {
+                               *prompt = "php ( ";
+                       } else if (brackets_count) {
+                               *prompt = "php { ";
+                       } else {
+                               *prompt = "php > ";
+                       }
+                       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 (file)
index 0000000..12a02d4
--- /dev/null
@@ -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 <helly@php.net>                               |
+   +----------------------------------------------------------------------+
+*/
+
+/* $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);