php-src/sapi/cli/php_cli_readline.c
2007-12-31 07:12:20 +00:00

509 lines
12 KiB
C

/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2008 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 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_01.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"
#if (HAVE_LIBREADLINE || HAVE_LIBEDIT) && !defined(COMPILE_DL_READLINE)
#ifndef HAVE_RL_COMPLETION_MATCHES
#define rl_completion_matches completion_matches
#endif
#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
#include <readline/readline.h>
#if !HAVE_LIBEDIT
#include <readline/history.h>
#endif
#include "zend_compile.h"
#include "zend_execute.h"
#include "zend_highlight.h"
#include "zend_indent.h"
/* {{{ 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, last_valid_end;
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 '\r':
case '\n':
case '\t':
break;
case '\'':
code_type = sstring;
break;
case '"':
code_type = dstring;
break;
case '#':
code_type = comment_line;
break;
case '/':
if (code[i+1] == '/') {
i++;
code_type = comment_line;
break;
}
if (code[i+1] == '*') {
last_valid_end = valid_end;
valid_end = 0;
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;
valid_end = last_valid_end;
}
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[i] == '\n') {
code_type = body;
} else if (code[i - (heredoc_len + 2)] == '\n' && !strncmp(code + i - heredoc_len - 1, heredoc_tag, heredoc_len) && code[i-1] == ';' && code[i] == '\n') {
code_type = body;
valid_end = 1;
}
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) /* {{{ */
{
zstr 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 || (UG(unicode) ? !zend_cmp_unicode_and_string(name.u, (char *)text, textlen) : !strncmp(name.s, text, textlen))) {
if (pData) {
zend_hash_get_current_data(ht, pData);
}
zend_hash_move_forward(ht);
return name.s;
}
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) {
if (UG(unicode)) {
int32_t tmp_len, len;
UErrorCode status = U_ZERO_ERROR;
len = u_strlen((UChar *)retval);
zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
(UChar *)retval, len, &status);
retval = malloc(tmp_len + 2);
retval[0] = '$';
strcpy(&retval[1], tmp);
rl_completion_append_character = '\0';
efree(tmp);
} else {
retval = malloc(strlen(tmp) + 2);
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;
retval = cli_completion_generator_ht(text, textlen, state, ht, (void**)&func TSRMLS_CC);
if (retval) {
rl_completion_append_character = '(';
if (UG(unicode)) {
char *tmp;
int32_t tmp_len, len;
UErrorCode status = U_ZERO_ERROR;
len = u_strlen((UChar *)func->common.function_name.u);
zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
(UChar *)func->common.function_name.u, len, &status);
retval = strdup(tmp);
efree(tmp);
} else {
retval = strdup(func->common.function_name.s);
}
}
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';
if (UG(unicode)) {
char *tmp;
int32_t tmp_len, len;
UErrorCode status = U_ZERO_ERROR;
len = u_strlen((UChar *)(*pce)->name.u);
zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
(UChar *)(*pce)->name.u, len, &status);
retval = strdup(tmp);
efree(tmp);
} else {
retval = strdup((*pce)->name.s);
}
}
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';
if (UG(unicode)) {
char *tmp;
int32_t tmp_len, len;
UErrorCode status = U_ZERO_ERROR;
len = u_strlen((UChar *)retval);
zend_unicode_to_string_ex(ZEND_U_CONVERTER(UG(output_encoding_conv)), &tmp, &tmp_len,
(UChar *)retval, len, &status);
retval = strdup(tmp);
efree(tmp);
} else {
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 = NULL;
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(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.s, 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
*/