All,

I was recently involved in a project that relied heavily on readline to provide 
console text input capabilities. However I soon noticed that the current 
readline extension has a serious bug and is lacking some important 
functionality.

This patch applies only to PHP compiled with libreadline (and don't have an 
effect if compiled with libeditline). It adds/modifies/fixes the following:
- Fixes memory corruption caused by directly setting rl_line_buffer to new 
buffer and not using rl_replace_line().
- Fixes a bad combination of preprocessor #if that would cause 
readline_on_new_line() implementation not to compile when using libreadline 
only.
- Adds support for rl_bind_key function by exposing:
function readline_bind_key_function($key, $callback_func)
where:
$key: Key code to bind to.
$callback_func: A callback function in the form:
function callback($key, $count) where $key and $count are the same parameters 
passed from rl_bind_key()
Setting $callback_func to null will cause the binding to be removed for $key.
- Modifies the behavior of readline_info() to allow for setting the readline 
properties "point" and "end" (it used to only read them but not set them).

Patch below:

--- php-5.4.0/ext/readline/readline.c   2012-01-01 15:15:04.000000000 +0200
+++ php-5.4.0-patched/ext/readline/readline.c   2012-03-19 09:09:27.388174973 
+0200
@@ -58,9 +58,13 @@ PHP_FUNCTION(readline_callback_read_char
 PHP_FUNCTION(readline_callback_handler_remove);
 PHP_FUNCTION(readline_redisplay);
 PHP_FUNCTION(readline_on_new_line);
+PHP_FUNCTION(readline_bind_key_function);
 
 static zval *_prepped_callback = NULL;
+#endif
 
+#if HAVE_LIBREADLINE
+static HashTable _bind_key_functions_ht;
 #endif
 
 static zval *_readline_completion = NULL;
@@ -121,10 +125,19 @@ ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_INFO(arginfo_readline_redisplay, 0)
 ZEND_END_ARG_INFO()
+#endif
 
+#if HAVE_RL_ON_NEW_LINE | HAVE_LIBREADLINE
 ZEND_BEGIN_ARG_INFO(arginfo_readline_on_new_line, 0)
 ZEND_END_ARG_INFO()
 #endif
+
+#if HAVE_LIBREADLINE
+ZEND_BEGIN_ARG_INFO_EX(arginfo_readline_bind_key_function, 0, 0, 2)
+       ZEND_ARG_INFO(0, key)
+       ZEND_ARG_INFO(0, funcname)
+ZEND_END_ARG_INFO()
+#endif
 /* }}} */
 
 /* {{{ module stuff */
@@ -145,9 +158,12 @@ static const zend_function_entry php_rea
        PHP_FE(readline_callback_handler_remove,        
arginfo_readline_callback_handler_remove)
        PHP_FE(readline_redisplay, arginfo_readline_redisplay)
 #endif
-#if HAVE_RL_ON_NEW_LINE
+#if HAVE_RL_ON_NEW_LINE | HAVE_LIBREADLINE
        PHP_FE(readline_on_new_line, arginfo_readline_on_new_line)
 #endif
+#if HAVE_LIBREADLINE
+       PHP_FE(readline_bind_key_function, arginfo_readline_bind_key_function)
+#endif
        PHP_FE_END
 };
 
@@ -170,6 +186,9 @@ ZEND_GET_MODULE(readline)
 
 PHP_MINIT_FUNCTION(readline)
 {
+#if HAVE_LIBREADLINE
+       zend_hash_init(&_bind_key_functions_ht, 255, NULL, ZVAL_PTR_DTOR, 1);
+#endif
        using_history();
        return PHP_MINIT(cli_readline)(INIT_FUNC_ARGS_PASSTHRU);
 }
@@ -193,6 +212,9 @@ PHP_RSHUTDOWN_FUNCTION(readline)
        }
 #endif
 
+#if HAVE_LIBREADLINE
+       zend_hash_destroy(&_bind_key_functions_ht);
+#endif
        return SUCCESS;
 }
 
@@ -265,12 +287,20 @@ PHP_FUNCTION(readline_info)
                        if (value) {
                                /* XXX if (rl_line_buffer) 
free(rl_line_buffer); */
                                convert_to_string_ex(value);
-                               rl_line_buffer = strdup(Z_STRVAL_PP(value));
+                               rl_replace_line(Z_STRVAL_PP(value), 1);
                        }
                        RETVAL_STRING(SAFE_STRING(oldstr),1);
                } else if (!strcasecmp(what, "point")) {
+                       if (value) {
+                               convert_to_long_ex(value);
+                               rl_point = Z_LVAL_PP(value);
+                       }
                        RETVAL_LONG(rl_point);
                } else if (!strcasecmp(what, "end")) {
+                       if (value) {
+                               convert_to_long_ex(value);
+                               rl_end = Z_LVAL_PP(value);
+                       }
                        RETVAL_LONG(rl_end);
 #ifdef HAVE_LIBREADLINE
                } else if (!strcasecmp(what, "mark")) {
@@ -621,7 +651,7 @@ PHP_FUNCTION(readline_redisplay)
 
 #endif
 
-#if HAVE_RL_ON_NEW_LINE
+#if HAVE_RL_ON_NEW_LINE | HAVE_LIBREADLINE
 /* {{{ proto void readline_on_new_line(void)
    Inform readline that the cursor has moved to a new line */
 PHP_FUNCTION(readline_on_new_line)
@@ -632,6 +662,72 @@ PHP_FUNCTION(readline_on_new_line)
 
 #endif
 
+/* {{{ proto bool readline_bind_key_function(string funcname) 
+ Readline rl_bind_key */
+
+static int _readline_bind_key_cb(int count, int key)
+{ 
+       zval *params[2];
+       TSRMLS_FETCH();
+       
+       params[0]=_readline_long_zval(key);
+       params[1]=_readline_long_zval(count);
+       
+       zval **_callback_func = NULL;
+       long _key = key;
+       int r = zend_hash_find(&_bind_key_functions_ht, (char *) &_key, 
sizeof(_key), (void **)&_callback_func);
+       if (r == -1)
+               return -1;
+       
+       int _return_int = 0;
+       zval *_callback_return = NULL;
+       MAKE_STD_ZVAL(_callback_return);
+       
+       if (call_user_function(CG(function_table), NULL, *_callback_func, 
_callback_return, 2, params TSRMLS_CC) == SUCCESS) {
+               _return_int = _callback_return->value.lval;
+               zval_dtor(_callback_return);
+               FREE_ZVAL(_callback_return);
+       }
+       
+       return _return_int; 
+}
+
+PHP_FUNCTION(readline_bind_key_function)
+{
+       long key;
+       zval *arg = NULL;
+       char *name = NULL;
+       
+       if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l!z", 
&key, &arg)) {
+               RETURN_FALSE;
+       }
+       
+       if (Z_TYPE_P(arg) == IS_NULL) {
+               zend_hash_del(&_bind_key_functions_ht, (char *)&key, 
sizeof(key));
+               rl_bind_key(key, rl_insert);
+       }
+       else {
+               if (!zend_is_callable(arg, 0, &name TSRMLS_CC)) {
+                       php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s is not 
callable", name);
+                       efree(name);
+                       RETURN_FALSE;
+               }
+               efree(name);
+               
+               zval *_temp_callback = NULL;
+               MAKE_STD_ZVAL(_temp_callback);
+               *_temp_callback = *arg;
+               zval_copy_ctor(_temp_callback);
+               
+               zend_hash_add(&_bind_key_functions_ht, (char *)&key, 
sizeof(key), &_temp_callback, sizeof(zval *), NULL);
+               
+               rl_bind_key(key, &_readline_bind_key_cb);
+       }
+       
+       RETURN_TRUE;
+}
+
+/* }}} */
 
 #endif /* HAVE_LIBREADLINE */
 


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to