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:
2018-06-12 14:42:14 +00:00
parent 062465ac52
commit 0f7ef786e8

View File

@@ -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 0x300x3F (ASCII 09:;<=>?), then by any number of
// "intermediate bytes" in the range 0x200x2F (ASCII space and
// !"#$%&'()*+,-./), then finally by a single "final byte" in the range
// 0x400x7E (ASCII @AZ[\]^_`az{|}~).[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 0x300x3F (ASCII 09:;<=>?), then by any number of
// "intermediate bytes" in the range 0x200x2F (ASCII space and
// !"#$%&'()*+,-./), then finally by a single "final byte" in the range
// 0x400x7E (ASCII @AZ[\]^_`az{|}~).[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;
}
// }}}
}