/** * Tab binding. * * @param \Hoa\Console\Readline $self Self. * @return int */ public function _bindTab(Readline $self) { $autocompleter = $self->getAutocompleter(); $state = static::STATE_CONTINUE | static::STATE_NO_ECHO; if (null === $autocompleter) { return $state; } $current = $self->getLineCurrent(); $line = $self->getLine(); if (0 === $current) { return $state; } $matches = preg_match_all('#' . $autocompleter->getWordDefinition() . '#u', $line, $words, PREG_OFFSET_CAPTURE); if (0 === $matches) { return $state; } for ($i = 0, $max = count($words[0]); $i < $max && $current > $words[0][$i][1]; ++$i) { } $word = $words[0][$i - 1]; if ('' === trim($word[0])) { return $state; } $prefix = mb_substr($word[0], 0, $current - $word[1]); $solution = $autocompleter->complete($prefix); $length = mb_strlen($prefix); if (null === $solution) { return $state; } if (is_array($solution)) { $_solution = $solution; $count = count($_solution) - 1; $cWidth = 0; $window = Console\Window::getSize(); $wWidth = $window['x']; $cursor = Console\Cursor::getPosition(); array_walk($_solution, function (&$value) use(&$cWidth) { $handle = mb_strlen($value); if ($handle > $cWidth) { $cWidth = $handle; } return; }); array_walk($_solution, function (&$value) use(&$cWidth) { $handle = mb_strlen($value); if ($handle >= $cWidth) { return; } $value .= str_repeat(' ', $cWidth - $handle); return; }); $mColumns = (int) floor($wWidth / ($cWidth + 2)); $mLines = (int) ceil(($count + 1) / $mColumns); --$mColumns; $i = 0; if (0 > $window['y'] - $cursor['y'] - $mLines) { Console\Window::scroll('↑', $mLines); Console\Cursor::move('↑', $mLines); } Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); foreach ($_solution as $j => $s) { echo "[0m", $s, "[0m"; if ($i++ < $mColumns) { echo ' '; } else { $i = 0; if (isset($_solution[$j + 1])) { echo "\n"; } } } Console\Cursor::restore(); Console\Cursor::show(); ++$mColumns; $read = [STDIN]; $mColumn = -1; $mLine = -1; $coord = -1; $unselect = function () use(&$mColumn, &$mLine, &$coord, &$_solution, &$cWidth) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); echo "[0m" . $_solution[$coord] . "[0m"; Console\Cursor::restore(); Console\Cursor::show(); return; }; $select = function () use(&$mColumn, &$mLine, &$coord, &$_solution, &$cWidth) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); echo "[7m" . $_solution[$coord] . "[0m"; Console\Cursor::restore(); Console\Cursor::show(); return; }; $init = function () use(&$mColumn, &$mLine, &$coord, &$select) { $mColumn = 0; $mLine = 0; $coord = 0; $select(); return; }; while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = [STDIN]; continue; } switch ($char = $self->_read()) { case "[A": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "[B": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\t": case "[C": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "[D": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\n": if (-1 !== $mColumn && -1 !== $mLine) { $tail = mb_substr($line, $current); $current -= $length; $self->setLine(mb_substr($line, 0, $current) . $solution[$coord] . $tail); $self->setLineCurrent($current + mb_strlen($solution[$coord])); Console\Cursor::move('←', $length); echo $solution[$coord]; Console\Cursor::clear('→'); echo $tail; Console\Cursor::move('←', mb_strlen($tail)); } default: $mColumn = -1; $mLine = -1; $coord = -1; Console\Cursor::save(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); Console\Cursor::restore(); if ("" !== $char && "\n" !== $char) { $self->setBuffer($char); return $self->_readLine($char); } break 2; } } return $state; } $tail = mb_substr($line, $current); $current -= $length; $self->setLine(mb_substr($line, 0, $current) . $solution . $tail); $self->setLineCurrent($current + mb_strlen($solution)); Console\Cursor::move('←', $length); echo $solution; Console\Cursor::clear('→'); echo $tail; Console\Cursor::move('←', mb_strlen($tail)); return $state; }
/** * Tab binding. * * @access public * @param Readline $self Self. * @return int */ public function _bindTab(Readline $self) { $autocompleter = $self->getAutocompleter(); $state = static::STATE_CONTINUE | static::STATE_NO_ECHO; if (null === $autocompleter) { return $state; } $current = $self->getLineCurrent(); $line = $self->getLine(); // we need at least 1 char to work with // and if it's the start of a line, we'll echo it. if (0 === $current || trim($line) === '') { $this->appendLine("\t"); $this->_buffer .= "\t"; return static::STATE_CONTINUE; } $matches = preg_match_all('#' . $autocompleter->getWordDefinition() . '#u', $line, $words, PREG_OFFSET_CAPTURE); if (0 === $matches) { return $state; } for ($i = 0, $max = count($words[1]); $i < $max && $current > $words[1][$i][1]; ++$i) { } $word = $words[1][$i - 1]; if ('' === trim($word[0])) { return $state; } $prefix = mb_substr($word[0], 0, $current - $word[1]); //Debug::log('prefix', compact('line','current','words','word','prefix')); $solution = $autocompleter->complete($prefix); #Debug::log('solution', $solution); if (null === $solution || empty($solution)) { return $state; } if (preg_match('/[\\S]+([\\W]+)([\\S]*)/', $prefix, $m, PREG_OFFSET_CAPTURE)) { #Debug::log('matchPrefix', compact('prefix', 'm')); $suffixLength = mb_strlen($m[2][0]); $prefix = mb_substr($prefix, -$suffixLength); $tail = mb_substr($line, $current); $head = mb_substr($line, 0, $current - $suffixLength); $line = $head . $tail; $current -= $suffixLength; $length = $suffixLength; } else { $length = mb_strlen($prefix); $tail = mb_substr($line, $current); $head = mb_substr($line, 0, $current - $length); $line = $head . $tail; $current -= $length; } #Debug::log('completionSetup', compact('prefix','line','current','head','tail','length')); if (is_array($solution)) { if (count($solution) === 1) { $line = $head . $solution[0] . $tail; $self->setLine($line); $self->setLineCurrent($current + mb_strlen($solution[0])); $self->setBuffer($line); Cursor::move('left', $length); echo $solution[0]; Cursor::clear('right'); echo $tail; Cursor::move('left', mb_strlen($tail)); #Debug::log('completion', array( # 'line' =>$self->getLine(), # 'current' =>$self->getLineCurrent(), # 'buffer' =>$self->getBuffer(), #)); return $state; } $_solution = $solution; $window = Window::getSize(); $cursor = Cursor::getPosition(); $wWidth = $window['x']; while (1) { $count = count($_solution) - 1; $cWidth = 0; array_walk($_solution, function ($value) use(&$cWidth) { $handle = mb_strlen($value); if ($handle > $cWidth) { $cWidth = $handle; } }); array_walk($_solution, function (&$value) use($cWidth) { $handle = mb_strlen($value); if ($handle < $cWidth) { $value .= str_repeat(' ', $cWidth - $handle); } }); $mColumns = (int) floor($wWidth / ($cWidth + 2)); $mLines = (int) ceil(($count + 1) / $mColumns); --$mColumns; if ($mLines >= $window['y']) { $toRemove = ($mLines - $window['y']) * $mColumns + 1; for ($i = 0; $i < $toRemove; $i++) { array_pop($_solution); } } else { break; } } $pos = Cursor::getPosition(); if ($window['y'] - $cursor['y'] - $mLines < 0) { echo str_repeat("\n", $mLines + 1); Cursor::move('up', $mLines + 1); Cursor::clear('LEFT'); echo $this->getPrefix() . $this->getLine() . "\n"; Cursor::move('up LEFT'); Cursor::move('right', $pos['x'] - 1); } Cursor::save(); Cursor::hide(); Cursor::move('down LEFT'); Cursor::clear('down'); $i = 0; foreach ($_solution as $j => $s) { echo "[0m", $s, "[0m"; if ($i++ < $mColumns) { echo ' '; } else { $i = 0; if (isset($_solution[$j + 1])) { echo "\n"; } } } Cursor::restore(); Cursor::show(); ++$mColumns; $read = array(STDIN); $mColumn = -1; $mLine = -1; $coord = -1; $unselect = function () use(&$mColumn, &$mLine, &$coord, &$_solution, &$cWidth) { Cursor::save(); Cursor::hide(); Cursor::move('down LEFT'); Cursor::move('right', $mColumn * ($cWidth + 2)); Cursor::move('down', $mLine); echo "[0m" . $_solution[$coord] . "[0m"; Cursor::restore(); Cursor::show(); }; $select = function () use(&$mColumn, &$mLine, &$coord, &$_solution, &$cWidth) { Cursor::save(); Cursor::hide(); Cursor::move('down LEFT'); Cursor::move('right', $mColumn * ($cWidth + 2)); Cursor::move('down', $mLine); echo "[7m" . $_solution[$coord] . "[0m"; Cursor::restore(); Cursor::show(); }; $init = function () use(&$mColumn, &$mLine, &$coord, &$select) { $mColumn = 0; $mLine = 0; $coord = 0; $select(); }; while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = array(STDIN); continue; } switch ($char = $self->_read()) { case "[A": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "[B": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\t": case "[C": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "[D": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\n": if (-1 !== $mColumn && -1 !== $mLine) { $self->setLine($head . $solution[$coord] . $tail); $self->setLineCurrent($current + mb_strlen($solution[$coord])); Cursor::move('left', $length); echo $solution[$coord]; Cursor::clear('right'); echo $tail; Cursor::move('left', mb_strlen($tail)); } default: $mColumn = -1; $mLine = -1; $coord = -1; Cursor::save(); Cursor::move('down LEFT'); Cursor::clear('down'); Cursor::restore(); if ("" !== $char && "\n" !== $char) { $self->setBuffer($char); return $self->_readLine($char); } break 2; } } return $state; } }