Manage the historical
Manage the UTF-8 chars Allow to move cursor with Ctrl+Arrow git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@4234 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
374
console.php
374
console.php
@@ -29,7 +29,7 @@ class console
|
||||
* displayed but are correctely captured
|
||||
*/
|
||||
private $nonWriteableChar = array (1, 2, 4, 6, 8, 9, 16, 18,
|
||||
20, 21, 22, 23, 24, 25,
|
||||
20, 21, 22, 23, 24, 25, 27,
|
||||
127);
|
||||
/* The history list in an array
|
||||
*/
|
||||
@@ -121,6 +121,7 @@ class console
|
||||
/** Wait one key pressed by the user. If the key pressed is an ESC sequence,
|
||||
* return this sequence
|
||||
* The non printable chars are not displayed, but are correctely returned
|
||||
* The UTF8 chars are return as multiple length chars
|
||||
* @return the pressed char
|
||||
*/
|
||||
public function getKey ()
|
||||
@@ -130,25 +131,69 @@ class console
|
||||
if ($char === chr (27))
|
||||
{
|
||||
$sequence = $char;
|
||||
// Start an ESC CSI sequence. Do not display it, just return it, complete.
|
||||
// ESC [ is followed by any number (including none) of "parameter bytes"
|
||||
// in the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of
|
||||
// "intermediate bytes" in the range 0x20–0x2F (ASCII space and
|
||||
// !"#$%&'()*+,-./), then finally by a single "final byte" in the range
|
||||
// 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).[14]:5.4
|
||||
$char = fgetc (STDIN);
|
||||
if ($char !== chr (91))
|
||||
$this->consoleException ("Invalid ESC CSI sequence provided");
|
||||
$sequence .= $char;
|
||||
$char = fgetc (STDIN);
|
||||
while (ord ($char) < 64 || ord ($char) > 126)
|
||||
$char2 = fgetc (STDIN);
|
||||
if (ord ($char2) === 27)
|
||||
{
|
||||
$sequence .= $char;
|
||||
$char = fgetc (STDIN);
|
||||
// Sequence of ESC ESC
|
||||
}
|
||||
elseif (ord ($char2) === 79)
|
||||
{
|
||||
// Start an ESC SS3 sequence
|
||||
// Like F2 Key
|
||||
$sequence .= $char2;
|
||||
$char = fgetc (STDIN);
|
||||
$sequence .= $char;
|
||||
}
|
||||
elseif (ord ($char2) === 91)
|
||||
{
|
||||
// Start an ESC CSI sequence. Do not display it, just return it.
|
||||
// The ESC squences are used to communicate the cursor keys, associated
|
||||
// with the Ctrl key, by example
|
||||
// ESC [ is followed by any number (including none) of "parameter bytes"
|
||||
// in the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of
|
||||
// "intermediate bytes" in the range 0x20–0x2F (ASCII space and
|
||||
// !"#$%&'()*+,-./), then finally by a single "final byte" in the range
|
||||
// 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).[14]:5.4
|
||||
$sequence .= $char2;
|
||||
$char = fgetc (STDIN);
|
||||
while (ord ($char) < 64 || ord ($char) > 126)
|
||||
{
|
||||
$sequence .= $char;
|
||||
$char = fgetc (STDIN);
|
||||
}
|
||||
$sequence .= $char;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->consoleException ("Invalid ESC seq : ".ord($char2));
|
||||
}
|
||||
$sequence .= $char;
|
||||
return $sequence;
|
||||
}
|
||||
|
||||
// UTF Sequence :
|
||||
// - char1 < 128 : One char (like ascii)
|
||||
// - char1 >= 194 && char1 <= 223 : Two chars
|
||||
// - char1 >= 224 && char1 <= 239 : Three chars
|
||||
// - char1 >= 240 && char1 <= 244 : Four chars
|
||||
if (ord ($char) < 128)
|
||||
{
|
||||
// One Char like ascii
|
||||
}
|
||||
elseif (ord ($char) >= 194 && ord ($char) <= 223)
|
||||
{
|
||||
// Two chars
|
||||
$char .= fgetc (STDIN);
|
||||
}
|
||||
elseif (ord ($char) >= 224 && ord ($char) <= 239)
|
||||
{
|
||||
// Three chars
|
||||
$char .= fgetc (STDIN).fgetc (STDIN);
|
||||
}
|
||||
elseif (ord ($char) >= 240 && ord ($char) <= 244)
|
||||
{
|
||||
// Four chars
|
||||
$char .= fgetc (STDIN).fgetc (STDIN).fgetc (STDIN);
|
||||
}
|
||||
if ($this->echoMode && ! in_array (ord ($char), $this->nonWriteableChar))
|
||||
echo $char;
|
||||
return $char;
|
||||
@@ -165,9 +210,13 @@ class console
|
||||
{
|
||||
// Gets can not delete chars before the call. Keep the prompt (if exists)
|
||||
$prompt = $this->lineContent;
|
||||
$minLength = strlen ($this->lineContent);
|
||||
$minLength = mb_strlen ($this->lineContent);
|
||||
// The cursor position from first char of line.
|
||||
$cursorPos = $minLength;
|
||||
// Manage the history and a temporary buffer if the user has already type
|
||||
// something before calling the history
|
||||
$historyTmp = "";
|
||||
$historyPos = count ($this->history);
|
||||
$string = "";
|
||||
while (1)
|
||||
{
|
||||
@@ -178,16 +227,18 @@ class console
|
||||
$this->lineContent = "";
|
||||
break;
|
||||
}
|
||||
if ($stopperChar !== false && in_array ($char, str_split ($stopperChar)))
|
||||
if ($stopperChar !== false &&
|
||||
in_array ($char, $this->mb_str_split ($stopperChar)))
|
||||
{
|
||||
// End of process with stopperChars
|
||||
$this->lineContent = "";
|
||||
break;
|
||||
}
|
||||
if ($this->completionKeys !== false &&
|
||||
in_array ($char, str_split ($this->completionKeys)))
|
||||
in_array ($char, $this->mb_str_split ($this->completionKeys)))
|
||||
// Manage autocompletion
|
||||
// {{{
|
||||
{
|
||||
// Manage autocompletion
|
||||
$completeArr = call_user_func ($this->completionFunction, $string);
|
||||
if (count ($completeArr) === 1)
|
||||
{
|
||||
@@ -199,12 +250,12 @@ class console
|
||||
else
|
||||
{
|
||||
// Continuous word is unique : replace the last word by the proposal
|
||||
$pos = strrpos ($string, " ");
|
||||
$pos = mb_strrpos ($string, " ");
|
||||
if ($pos === false)
|
||||
$string = reset ($completeArr);
|
||||
else
|
||||
{
|
||||
$string = substr ($string, 0, $pos +1);
|
||||
$string = mb_substr ($string, 0, $pos +1);
|
||||
$string .= reset ($completeArr);
|
||||
}
|
||||
}
|
||||
@@ -214,65 +265,51 @@ class console
|
||||
// Multiple answers : display them
|
||||
echo "\n".implode ("\n", $completeArr)."\n";
|
||||
}
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
$cursorPos = mb_strlen ($this->lineContent);
|
||||
$cursorPos = mb_strlen ($prompt.$string);
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif (ord($char) === 21)
|
||||
// Empty line from prompt to cursor (Ctrl+U)
|
||||
// {{{
|
||||
{
|
||||
// Empty line next to prompt (Ctrl+U)
|
||||
// {{{
|
||||
$string = substr ($string, $cursorPos - $minLength);
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
$string = mb_substr ($string, $cursorPos - $minLength);
|
||||
$cursorPos = $minLength;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
// }}}
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif (ord($char) === 23)
|
||||
// Remove the last word (Ctrl+W)
|
||||
// {{{
|
||||
{
|
||||
// Remove the last word (Ctrl+W)
|
||||
// TODO : Cursor management !!
|
||||
$tmp = substr ($string, 0, $cursorPos - $minLength);
|
||||
$end = substr ($string, $cursorPos - $minLength);
|
||||
$tmp = mb_substr ($string, 0, $cursorPos - $minLength);
|
||||
$end = mb_substr ($string, $cursorPos - $minLength);
|
||||
$tmp = rtrim ($tmp);
|
||||
$pos = strrpos ($tmp, " ");
|
||||
$string = substr ($string, 0, $pos).$end;
|
||||
$pos = mb_strrpos ($tmp, " ");
|
||||
if ($pos !== false)
|
||||
$pos++;
|
||||
$string = mb_substr ($string, 0, $pos).$end;
|
||||
$cursorPos = $minLength + $pos;
|
||||
if ($this->echoMode)
|
||||
{
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
}
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif (ord($char) === 127)
|
||||
// Remove the previous char (Backspace)
|
||||
// {{{
|
||||
{
|
||||
// Remove the previous char (Backspace)
|
||||
// {{{
|
||||
if (strlen ($this->lineContent) < $minLength + 1)
|
||||
continue;
|
||||
if ($cursorPos < $minLength +1)
|
||||
if ($cursorPos <= $minLength)
|
||||
continue;
|
||||
$cursorPos--;
|
||||
$strArr = str_split ($string);
|
||||
$strArr = $this->mb_str_split ($string);
|
||||
unset ($strArr[$cursorPos - $minLength]);
|
||||
$string = implode ($strArr);
|
||||
if ($this->echoMode)
|
||||
{
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
}
|
||||
// }}}
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif (ord ($char{0}) === 27)
|
||||
{
|
||||
// ESC SEQUENCE
|
||||
@@ -280,96 +317,175 @@ class console
|
||||
foreach (str_split ($char) as $key)
|
||||
file_put_contents ("/tmp/debug", ord ($key)." ", FILE_APPEND);
|
||||
file_put_contents ("/tmp/debug", "\n", FILE_APPEND);*/
|
||||
if ($char === chr (27).chr (91).chr (67))
|
||||
if ($char === chr (27).chr (91).chr (49).chr (59).chr (53).chr (67))
|
||||
// Cursor right + Ctrl : cursor jump by word
|
||||
// {{{
|
||||
{
|
||||
// Cursor right
|
||||
// {{{
|
||||
if ($cursorPos < strlen ($this->lineContent))
|
||||
{
|
||||
echo $char;
|
||||
$cursorPos++;
|
||||
}
|
||||
// }}}
|
||||
$tmp = mb_substr ($string, $cursorPos - $minLength);
|
||||
$tmp = ltrim ($tmp);
|
||||
$pos = strpos ($tmp, " ");
|
||||
if ($pos !== false)
|
||||
$cursorPos += $pos +1 ;
|
||||
else
|
||||
$cursorPos = mb_strlen ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
elseif ($char === chr (27).chr (91).chr (68))
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (49).chr (59).chr (53).chr (68))
|
||||
// Cursor left + Ctrl : cursor jump by word
|
||||
// {{{
|
||||
{
|
||||
$tmp = mb_substr ($string, 0, $cursorPos - $minLength);
|
||||
$tmp = rtrim ($tmp);
|
||||
$pos = strrpos ($tmp, " ");
|
||||
if ($pos !== false) $pos++;
|
||||
$cursorPos = $minLength + $pos;
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (65))
|
||||
// Cursor up : display the previous history if defined
|
||||
// {{{
|
||||
{
|
||||
if ($string !== "" && $historyTmp == "")
|
||||
{
|
||||
$historyTmp = $string;
|
||||
}
|
||||
if ($historyPos > 0)
|
||||
{
|
||||
$historyPos--;
|
||||
$string = $this->history[$historyPos];
|
||||
$cursorPos = mb_strlen ($prompt.$string);
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (66))
|
||||
// Cursor down : display the next history if defined
|
||||
// {{{
|
||||
{
|
||||
if ($historyPos < count ($this->history) - 1)
|
||||
{
|
||||
$historyPos++;
|
||||
$string = $this->history[$historyPos];
|
||||
}
|
||||
elseif ($historyTmp !== "")
|
||||
{
|
||||
$string = $historyTmp;
|
||||
}
|
||||
$cursorPos = mb_strlen ($prompt.$string);
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (67))
|
||||
// Cursor right
|
||||
// {{{
|
||||
{
|
||||
if ($cursorPos < mb_strlen ($this->lineContent))
|
||||
{
|
||||
$cursorPos++;
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (68))
|
||||
// Cursor left
|
||||
// {{{
|
||||
{
|
||||
// Cursor left
|
||||
// {{{
|
||||
if ($cursorPos > $minLength)
|
||||
{
|
||||
echo $char;
|
||||
$cursorPos--;
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (70))
|
||||
// End key
|
||||
// {{{
|
||||
{
|
||||
// End key
|
||||
// {{{
|
||||
$cursorPos = $minLength + strlen ($string);
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
// }}}
|
||||
$cursorPos = $minLength + mb_strlen ($string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (72))
|
||||
// Home key
|
||||
// {{{
|
||||
{
|
||||
// Home key
|
||||
// {{{
|
||||
$cursorPos = $minLength;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
// }}}
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
elseif ($char === chr (27).chr (91).chr (51).chr (126))
|
||||
// Remove the char under the cursor (Delete)
|
||||
// {{{
|
||||
{
|
||||
// Remove the char under the cursor (Delete)
|
||||
// {{{
|
||||
if ($cursorPos >= strlen ($this->lineContent))
|
||||
if ($cursorPos >= mb_strlen ($prompt.$string))
|
||||
continue;
|
||||
$cursorPos;
|
||||
$strArr = str_split ($string);
|
||||
$strArr = $this->mb_str_split ($string);
|
||||
unset ($strArr[$cursorPos - $minLength]);
|
||||
$string = implode ($strArr);
|
||||
if ($this->echoMode)
|
||||
{
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
}
|
||||
// }}}
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
else
|
||||
// Normal char : Add it to the string
|
||||
// {{{
|
||||
{
|
||||
// Normal char : Add it to the string
|
||||
// {{{
|
||||
$strArr = str_split ($string);
|
||||
$strArr = $this->mb_str_split ($string);
|
||||
$firstArr = array_slice ($strArr, 0, $cursorPos - $minLength);
|
||||
$lastArr = array_slice ($strArr, $cursorPos - $minLength);
|
||||
$insertArr = array ($char);
|
||||
$strArr = array_merge ($firstArr, $insertArr, $lastArr);
|
||||
$string = implode ($strArr);
|
||||
$cursorPos++;
|
||||
echo "\r";
|
||||
$this->lineContent = $prompt.$string;
|
||||
echo $this->lineContent;
|
||||
// Move the cursor on the correct position
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
// }}}
|
||||
$this->rewriteLine ($prompt.$string);
|
||||
$this->moveCursor ($cursorPos);
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** Clear the existing line
|
||||
/** Rewrite the line with the provided $text.
|
||||
* Delete all the old data
|
||||
* @param string $text The new text to use on line
|
||||
*/
|
||||
private function rewriteLine ($text)
|
||||
// {{{
|
||||
{
|
||||
if ($this->echoMode)
|
||||
{
|
||||
echo "\r".str_repeat (" ", mb_strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = $text;
|
||||
echo $this->lineContent;
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** Move the cursor on position $position
|
||||
* @param integer $cursorPos The new position on line
|
||||
*/
|
||||
private function moveCursor ($cursorPos)
|
||||
// {{{
|
||||
{
|
||||
if ($this->echoMode)
|
||||
{
|
||||
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** Clear the existing line.
|
||||
*/
|
||||
public function clearLine ()
|
||||
// {{{
|
||||
{
|
||||
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
|
||||
echo "\r".str_repeat (" ", mb_strlen ($this->lineContent))."\r";
|
||||
$this->lineContent = "";
|
||||
}
|
||||
// }}}
|
||||
@@ -397,6 +513,15 @@ class console
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** Get the actual history in memory
|
||||
*/
|
||||
public function getHistory ()
|
||||
// {{{
|
||||
{
|
||||
return $this->history;
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** Clear the history
|
||||
* Do NOT write the empty history on disk
|
||||
*/
|
||||
@@ -404,6 +529,7 @@ class console
|
||||
// {{{
|
||||
{
|
||||
$this->history = array ();
|
||||
return $this;
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -433,8 +559,9 @@ class console
|
||||
$this->consoleException ("History file '$historyFile' ".
|
||||
"can not be created: parent directory is not a directory");
|
||||
file_put_contents ($historyFile, "__HISTORY__\n");
|
||||
return !! file_put_contents ($historyFile, implode ("\n", $this->history),
|
||||
file_put_contents ($historyFile, implode ("\n", $this->history),
|
||||
FILE_APPEND);
|
||||
return $this;
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -466,6 +593,7 @@ class console
|
||||
// }}}
|
||||
|
||||
/** Add a new entry in history.
|
||||
* The new line can not be empty : it is not stored, but without error
|
||||
* Do NOT write the history on disk.
|
||||
* @param string The new entry to add in history
|
||||
*/
|
||||
@@ -475,8 +603,11 @@ class console
|
||||
if (! is_string ($line))
|
||||
$this->consoleException ("Can not add line to history : ".
|
||||
"it is not a string");
|
||||
if (trim ($line) === "")
|
||||
return $this;
|
||||
$this->history[] = $line;
|
||||
$this->history = array_slice ($this->history, -$this->historyMaxSize);
|
||||
return $this;
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -506,4 +637,17 @@ class console
|
||||
}
|
||||
// }}}
|
||||
|
||||
/** This function return an array with each char, but supports UTF-8
|
||||
* @param string $string The string to explode
|
||||
* @return array
|
||||
*/
|
||||
private function mb_str_split ($string)
|
||||
// {{{
|
||||
{
|
||||
$res = array();
|
||||
for ($i = 0; $i < mb_strlen ($string); $i++)
|
||||
$res[] = mb_substr ($string, $i, 1);
|
||||
return $res;
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user