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 * displayed but are correctely captured
*/ */
private $nonWriteableChar = array (1, 2, 4, 6, 8, 9, 16, 18, 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); 127);
/* The history list in an array /* 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, /** Wait one key pressed by the user. If the key pressed is an ESC sequence,
* return this sequence * return this sequence
* The non printable chars are not displayed, but are correctely returned * The non printable chars are not displayed, but are correctely returned
* The UTF8 chars are return as multiple length chars
* @return the pressed char * @return the pressed char
*/ */
public function getKey () public function getKey ()
@@ -130,25 +131,69 @@ class console
if ($char === chr (27)) if ($char === chr (27))
{ {
$sequence = $char; $sequence = $char;
// Start an ESC CSI sequence. Do not display it, just return it, complete. $char2 = fgetc (STDIN);
// ESC [ is followed by any number (including none) of "parameter bytes" if (ord ($char2) === 27)
// 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)
{ {
$sequence .= $char; // Sequence of ESC ESC
$char = fgetc (STDIN); }
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; 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)) if ($this->echoMode && ! in_array (ord ($char), $this->nonWriteableChar))
echo $char; echo $char;
return $char; return $char;
@@ -165,9 +210,13 @@ class console
{ {
// Gets can not delete chars before the call. Keep the prompt (if exists) // Gets can not delete chars before the call. Keep the prompt (if exists)
$prompt = $this->lineContent; $prompt = $this->lineContent;
$minLength = strlen ($this->lineContent); $minLength = mb_strlen ($this->lineContent);
// The cursor position from first char of line. // The cursor position from first char of line.
$cursorPos = $minLength; $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 = ""; $string = "";
while (1) while (1)
{ {
@@ -178,16 +227,18 @@ class console
$this->lineContent = ""; $this->lineContent = "";
break; 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 // End of process with stopperChars
$this->lineContent = ""; $this->lineContent = "";
break; break;
} }
if ($this->completionKeys !== false && 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); $completeArr = call_user_func ($this->completionFunction, $string);
if (count ($completeArr) === 1) if (count ($completeArr) === 1)
{ {
@@ -199,12 +250,12 @@ class console
else else
{ {
// Continuous word is unique : replace the last word by the proposal // Continuous word is unique : replace the last word by the proposal
$pos = strrpos ($string, " "); $pos = mb_strrpos ($string, " ");
if ($pos === false) if ($pos === false)
$string = reset ($completeArr); $string = reset ($completeArr);
else else
{ {
$string = substr ($string, 0, $pos +1); $string = mb_substr ($string, 0, $pos +1);
$string .= reset ($completeArr); $string .= reset ($completeArr);
} }
} }
@@ -214,65 +265,51 @@ class console
// Multiple answers : display them // Multiple answers : display them
echo "\n".implode ("\n", $completeArr)."\n"; echo "\n".implode ("\n", $completeArr)."\n";
} }
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; $cursorPos = mb_strlen ($prompt.$string);
$this->lineContent = $prompt.$string; $this->rewriteLine ($prompt.$string);
echo $this->lineContent; $this->moveCursor ($cursorPos);
$cursorPos = mb_strlen ($this->lineContent);
} }
// }}}
elseif (ord($char) === 21) elseif (ord($char) === 21)
// Empty line from prompt to cursor (Ctrl+U)
// {{{
{ {
// Empty line next to prompt (Ctrl+U) $string = mb_substr ($string, $cursorPos - $minLength);
// {{{
$string = substr ($string, $cursorPos - $minLength);
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r";
$this->lineContent = $prompt.$string;
echo $this->lineContent;
$cursorPos = $minLength; $cursorPos = $minLength;
// Move the cursor on the correct position $this->rewriteLine ($prompt.$string);
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos); $this->moveCursor ($cursorPos);
// }}}
} }
// }}}
elseif (ord($char) === 23) elseif (ord($char) === 23)
// Remove the last word (Ctrl+W)
// {{{
{ {
// Remove the last word (Ctrl+W) $tmp = mb_substr ($string, 0, $cursorPos - $minLength);
// TODO : Cursor management !! $end = mb_substr ($string, $cursorPos - $minLength);
$tmp = substr ($string, 0, $cursorPos - $minLength);
$end = substr ($string, $cursorPos - $minLength);
$tmp = rtrim ($tmp); $tmp = rtrim ($tmp);
$pos = strrpos ($tmp, " "); $pos = mb_strrpos ($tmp, " ");
$string = substr ($string, 0, $pos).$end; if ($pos !== false)
$pos++;
$string = mb_substr ($string, 0, $pos).$end;
$cursorPos = $minLength + $pos; $cursorPos = $minLength + $pos;
if ($this->echoMode) $this->rewriteLine ($prompt.$string);
{ $this->moveCursor ($cursorPos);
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);
}
} }
// }}}
elseif (ord($char) === 127) elseif (ord($char) === 127)
// Remove the previous char (Backspace)
// {{{
{ {
// Remove the previous char (Backspace) if ($cursorPos <= $minLength)
// {{{
if (strlen ($this->lineContent) < $minLength + 1)
continue;
if ($cursorPos < $minLength +1)
continue; continue;
$cursorPos--; $cursorPos--;
$strArr = str_split ($string); $strArr = $this->mb_str_split ($string);
unset ($strArr[$cursorPos - $minLength]); unset ($strArr[$cursorPos - $minLength]);
$string = implode ($strArr); $string = implode ($strArr);
if ($this->echoMode) $this->rewriteLine ($prompt.$string);
{ $this->moveCursor ($cursorPos);
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);
}
// }}}
} }
// }}}
elseif (ord ($char{0}) === 27) elseif (ord ($char{0}) === 27)
{ {
// ESC SEQUENCE // ESC SEQUENCE
@@ -280,96 +317,175 @@ class console
foreach (str_split ($char) as $key) foreach (str_split ($char) as $key)
file_put_contents ("/tmp/debug", ord ($key)." ", FILE_APPEND); file_put_contents ("/tmp/debug", ord ($key)." ", FILE_APPEND);
file_put_contents ("/tmp/debug", "\n", 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 $tmp = mb_substr ($string, $cursorPos - $minLength);
// {{{ $tmp = ltrim ($tmp);
if ($cursorPos < strlen ($this->lineContent)) $pos = strpos ($tmp, " ");
{ if ($pos !== false)
echo $char; $cursorPos += $pos +1 ;
$cursorPos++; 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) if ($cursorPos > $minLength)
{ {
echo $char;
$cursorPos--; $cursorPos--;
$this->moveCursor ($cursorPos);
} }
// }}}
} }
// }}}
elseif ($char === chr (27).chr (91).chr (70)) elseif ($char === chr (27).chr (91).chr (70))
// End key
// {{{
{ {
// End key $cursorPos = $minLength + mb_strlen ($string);
// {{{ $this->moveCursor ($cursorPos);
$cursorPos = $minLength + strlen ($string);
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
} }
// }}}
elseif ($char === chr (27).chr (91).chr (72)) elseif ($char === chr (27).chr (91).chr (72))
// Home key
// {{{
{ {
// Home key
// {{{
$cursorPos = $minLength; $cursorPos = $minLength;
// Move the cursor on the correct position $this->moveCursor ($cursorPos);
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
} }
// }}}
elseif ($char === chr (27).chr (91).chr (51).chr (126)) 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 >= mb_strlen ($prompt.$string))
// {{{
if ($cursorPos >= strlen ($this->lineContent))
continue; continue;
$cursorPos; $strArr = $this->mb_str_split ($string);
$strArr = str_split ($string);
unset ($strArr[$cursorPos - $minLength]); unset ($strArr[$cursorPos - $minLength]);
$string = implode ($strArr); $string = implode ($strArr);
if ($this->echoMode) $this->rewriteLine ($prompt.$string);
{ $this->moveCursor ($cursorPos);
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);
}
// }}}
} }
// }}}
} }
else else
// Normal char : Add it to the string
// {{{
{ {
// Normal char : Add it to the string $strArr = $this->mb_str_split ($string);
// {{{
$strArr = str_split ($string);
$firstArr = array_slice ($strArr, 0, $cursorPos - $minLength); $firstArr = array_slice ($strArr, 0, $cursorPos - $minLength);
$lastArr = array_slice ($strArr, $cursorPos - $minLength); $lastArr = array_slice ($strArr, $cursorPos - $minLength);
$insertArr = array ($char); $insertArr = array ($char);
$strArr = array_merge ($firstArr, $insertArr, $lastArr); $strArr = array_merge ($firstArr, $insertArr, $lastArr);
$string = implode ($strArr); $string = implode ($strArr);
$cursorPos++; $cursorPos++;
echo "\r"; $this->rewriteLine ($prompt.$string);
$this->lineContent = $prompt.$string; $this->moveCursor ($cursorPos);
echo $this->lineContent;
// Move the cursor on the correct position
echo "\r".str_repeat (chr (27).chr (91).chr (67), $cursorPos);
// }}}
} }
// }}}
} }
return $string; 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 () public function clearLine ()
// {{{ // {{{
{ {
echo "\r".str_repeat (" ", strlen ($this->lineContent))."\r"; echo "\r".str_repeat (" ", mb_strlen ($this->lineContent))."\r";
$this->lineContent = ""; $this->lineContent = "";
} }
// }}} // }}}
@@ -397,6 +513,15 @@ class console
} }
// }}} // }}}
/** Get the actual history in memory
*/
public function getHistory ()
// {{{
{
return $this->history;
}
// }}}
/** Clear the history /** Clear the history
* Do NOT write the empty history on disk * Do NOT write the empty history on disk
*/ */
@@ -404,6 +529,7 @@ class console
// {{{ // {{{
{ {
$this->history = array (); $this->history = array ();
return $this;
} }
// }}} // }}}
@@ -433,8 +559,9 @@ class console
$this->consoleException ("History file '$historyFile' ". $this->consoleException ("History file '$historyFile' ".
"can not be created: parent directory is not a directory"); "can not be created: parent directory is not a directory");
file_put_contents ($historyFile, "__HISTORY__\n"); 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); FILE_APPEND);
return $this;
} }
// }}} // }}}
@@ -466,6 +593,7 @@ class console
// }}} // }}}
/** Add a new entry in history. /** 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. * Do NOT write the history on disk.
* @param string The new entry to add in history * @param string The new entry to add in history
*/ */
@@ -475,8 +603,11 @@ class console
if (! is_string ($line)) if (! is_string ($line))
$this->consoleException ("Can not add line to history : ". $this->consoleException ("Can not add line to history : ".
"it is not a string"); "it is not a string");
if (trim ($line) === "")
return $this;
$this->history[] = $line; $this->history[] = $line;
$this->history = array_slice ($this->history, -$this->historyMaxSize); $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;
}
// }}}
} }