comparison vendor/psy/psysh/src/Psy/Shell.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 7a779792577d
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 /*
4 * This file is part of Psy Shell.
5 *
6 * (c) 2012-2017 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Psy;
13
14 use Psy\CodeCleaner\NoReturnValue;
15 use Psy\Exception\BreakException;
16 use Psy\Exception\ErrorException;
17 use Psy\Exception\Exception as PsyException;
18 use Psy\Exception\ThrowUpException;
19 use Psy\Input\ShellInput;
20 use Psy\Input\SilentInput;
21 use Psy\Output\ShellOutput;
22 use Psy\TabCompletion\Matcher;
23 use Psy\VarDumper\PresenterAware;
24 use Symfony\Component\Console\Application;
25 use Symfony\Component\Console\Command\Command as BaseCommand;
26 use Symfony\Component\Console\Formatter\OutputFormatter;
27 use Symfony\Component\Console\Input\ArgvInput;
28 use Symfony\Component\Console\Input\InputArgument;
29 use Symfony\Component\Console\Input\InputDefinition;
30 use Symfony\Component\Console\Input\InputInterface;
31 use Symfony\Component\Console\Input\InputOption;
32 use Symfony\Component\Console\Input\StringInput;
33 use Symfony\Component\Console\Output\OutputInterface;
34
35 /**
36 * The Psy Shell application.
37 *
38 * Usage:
39 *
40 * $shell = new Shell;
41 * $shell->run();
42 *
43 * @author Justin Hileman <justin@justinhileman.info>
44 */
45 class Shell extends Application
46 {
47 const VERSION = 'v0.8.15';
48
49 const PROMPT = '>>> ';
50 const BUFF_PROMPT = '... ';
51 const REPLAY = '--> ';
52 const RETVAL = '=> ';
53
54 private $config;
55 private $cleaner;
56 private $output;
57 private $readline;
58 private $inputBuffer;
59 private $code;
60 private $codeBuffer;
61 private $codeBufferOpen;
62 private $context;
63 private $includes;
64 private $loop;
65 private $outputWantsNewline = false;
66 private $completion;
67 private $tabCompletionMatchers = array();
68 private $stdoutBuffer;
69 private $prompt;
70
71 /**
72 * Create a new Psy Shell.
73 *
74 * @param Configuration $config (default: null)
75 */
76 public function __construct(Configuration $config = null)
77 {
78 $this->config = $config ?: new Configuration();
79 $this->cleaner = $this->config->getCodeCleaner();
80 $this->loop = $this->config->getLoop();
81 $this->context = new Context();
82 $this->includes = array();
83 $this->readline = $this->config->getReadline();
84 $this->inputBuffer = array();
85 $this->stdoutBuffer = '';
86
87 parent::__construct('Psy Shell', self::VERSION);
88
89 $this->config->setShell($this);
90
91 // Register the current shell session's config with \Psy\info
92 \Psy\info($this->config);
93 }
94
95 /**
96 * Check whether the first thing in a backtrace is an include call.
97 *
98 * This is used by the psysh bin to decide whether to start a shell on boot,
99 * or to simply autoload the library.
100 */
101 public static function isIncluded(array $trace)
102 {
103 return isset($trace[0]['function']) &&
104 in_array($trace[0]['function'], array('require', 'include', 'require_once', 'include_once'));
105 }
106
107 /**
108 * Invoke a Psy Shell from the current context.
109 *
110 * @see Psy\debug
111 * @deprecated will be removed in 1.0. Use \Psy\debug instead
112 *
113 * @param array $vars Scope variables from the calling context (default: array())
114 * @param object $boundObject Bound object ($this) value for the shell
115 *
116 * @return array Scope variables from the debugger session
117 */
118 public static function debug(array $vars = array(), $boundObject = null)
119 {
120 return \Psy\debug($vars, $boundObject);
121 }
122
123 /**
124 * Adds a command object.
125 *
126 * {@inheritdoc}
127 *
128 * @param BaseCommand $command A Symfony Console Command object
129 *
130 * @return BaseCommand The registered command
131 */
132 public function add(BaseCommand $command)
133 {
134 if ($ret = parent::add($command)) {
135 if ($ret instanceof ContextAware) {
136 $ret->setContext($this->context);
137 }
138
139 if ($ret instanceof PresenterAware) {
140 $ret->setPresenter($this->config->getPresenter());
141 }
142 }
143
144 return $ret;
145 }
146
147 /**
148 * Gets the default input definition.
149 *
150 * @return InputDefinition An InputDefinition instance
151 */
152 protected function getDefaultInputDefinition()
153 {
154 return new InputDefinition(array(
155 new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
156 new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'),
157 ));
158 }
159
160 /**
161 * Gets the default commands that should always be available.
162 *
163 * @return array An array of default Command instances
164 */
165 protected function getDefaultCommands()
166 {
167 $sudo = new Command\SudoCommand();
168 $sudo->setReadline($this->readline);
169
170 $hist = new Command\HistoryCommand();
171 $hist->setReadline($this->readline);
172
173 return array(
174 new Command\HelpCommand(),
175 new Command\ListCommand(),
176 new Command\DumpCommand(),
177 new Command\DocCommand(),
178 new Command\ShowCommand($this->config->colorMode()),
179 new Command\WtfCommand($this->config->colorMode()),
180 new Command\WhereamiCommand($this->config->colorMode()),
181 new Command\ThrowUpCommand(),
182 new Command\TraceCommand(),
183 new Command\BufferCommand(),
184 new Command\ClearCommand(),
185 new Command\EditCommand($this->config->getRuntimeDir()),
186 // new Command\PsyVersionCommand(),
187 $sudo,
188 $hist,
189 new Command\ExitCommand(),
190 );
191 }
192
193 /**
194 * @return array
195 */
196 protected function getTabCompletionMatchers()
197 {
198 if (empty($this->tabCompletionMatchers)) {
199 $this->tabCompletionMatchers = array(
200 new Matcher\CommandsMatcher($this->all()),
201 new Matcher\KeywordsMatcher(),
202 new Matcher\VariablesMatcher(),
203 new Matcher\ConstantsMatcher(),
204 new Matcher\FunctionsMatcher(),
205 new Matcher\ClassNamesMatcher(),
206 new Matcher\ClassMethodsMatcher(),
207 new Matcher\ClassAttributesMatcher(),
208 new Matcher\ObjectMethodsMatcher(),
209 new Matcher\ObjectAttributesMatcher(),
210 new Matcher\ClassMethodDefaultParametersMatcher(),
211 new Matcher\ObjectMethodDefaultParametersMatcher(),
212 new Matcher\FunctionDefaultParametersMatcher(),
213 );
214 }
215
216 return $this->tabCompletionMatchers;
217 }
218
219 /**
220 * @param array $matchers
221 */
222 public function addTabCompletionMatchers(array $matchers)
223 {
224 $this->tabCompletionMatchers = array_merge($matchers, $this->getTabCompletionMatchers());
225 }
226
227 /**
228 * Set the Shell output.
229 *
230 * @param OutputInterface $output
231 */
232 public function setOutput(OutputInterface $output)
233 {
234 $this->output = $output;
235 }
236
237 /**
238 * Runs the current application.
239 *
240 * @param InputInterface $input An Input instance
241 * @param OutputInterface $output An Output instance
242 *
243 * @return int 0 if everything went fine, or an error code
244 */
245 public function run(InputInterface $input = null, OutputInterface $output = null)
246 {
247 $this->initializeTabCompletion();
248
249 if ($input === null && !isset($_SERVER['argv'])) {
250 $input = new ArgvInput(array());
251 }
252
253 if ($output === null) {
254 $output = $this->config->getOutput();
255 }
256
257 try {
258 return parent::run($input, $output);
259 } catch (\Exception $e) {
260 $this->writeException($e);
261 }
262 }
263
264 /**
265 * Runs the current application.
266 *
267 * @throws Exception if thrown via the `throw-up` command
268 *
269 * @param InputInterface $input An Input instance
270 * @param OutputInterface $output An Output instance
271 *
272 * @return int 0 if everything went fine, or an error code
273 */
274 public function doRun(InputInterface $input, OutputInterface $output)
275 {
276 $this->setOutput($output);
277
278 $this->resetCodeBuffer();
279
280 $this->setAutoExit(false);
281 $this->setCatchExceptions(false);
282
283 $this->readline->readHistory();
284
285 // if ($this->config->useReadline()) {
286 // readline_completion_function(array($this, 'autocomplete'));
287 // }
288
289 $this->output->writeln($this->getHeader());
290 $this->writeVersionInfo();
291 $this->writeStartupMessage();
292
293 try {
294 $this->loop->run($this);
295 } catch (ThrowUpException $e) {
296 throw $e->getPrevious();
297 }
298 }
299
300 /**
301 * Read user input.
302 *
303 * This will continue fetching user input until the code buffer contains
304 * valid code.
305 *
306 * @throws BreakException if user hits Ctrl+D
307 */
308 public function getInput()
309 {
310 $this->codeBufferOpen = false;
311
312 do {
313 // reset output verbosity (in case it was altered by a subcommand)
314 $this->output->setVerbosity(ShellOutput::VERBOSITY_VERBOSE);
315
316 $input = $this->readline();
317
318 /*
319 * Handle Ctrl+D. It behaves differently in different cases:
320 *
321 * 1) In an expression, like a function or "if" block, clear the input buffer
322 * 2) At top-level session, behave like the exit command
323 */
324 if ($input === false) {
325 $this->output->writeln('');
326
327 if ($this->hasCode()) {
328 $this->resetCodeBuffer();
329 } else {
330 throw new BreakException('Ctrl+D');
331 }
332 }
333
334 // handle empty input
335 if (trim($input) === '') {
336 continue;
337 }
338
339 if ($this->hasCommand($input)) {
340 $this->readline->addHistory($input);
341 $this->runCommand($input);
342
343 continue;
344 }
345
346 $this->addCode($input);
347 } while (!$this->hasValidCode());
348 }
349
350 /**
351 * Pass the beforeLoop callback through to the Loop instance.
352 *
353 * @see Loop::beforeLoop
354 */
355 public function beforeLoop()
356 {
357 $this->loop->beforeLoop();
358 }
359
360 /**
361 * Pass the afterLoop callback through to the Loop instance.
362 *
363 * @see Loop::afterLoop
364 */
365 public function afterLoop()
366 {
367 $this->loop->afterLoop();
368 }
369
370 /**
371 * Set the variables currently in scope.
372 *
373 * @param array $vars
374 */
375 public function setScopeVariables(array $vars)
376 {
377 $this->context->setAll($vars);
378 }
379
380 /**
381 * Return the set of variables currently in scope.
382 *
383 * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
384 * passing the scope variables to `extract`
385 * in PHP 7.1+, you _must_ exclude 'this'
386 *
387 * @return array Associative array of scope variables
388 */
389 public function getScopeVariables($includeBoundObject = true)
390 {
391 $vars = $this->context->getAll();
392
393 if (!$includeBoundObject) {
394 unset($vars['this']);
395 }
396
397 return $vars;
398 }
399
400 /**
401 * Return the set of magic variables currently in scope.
402 *
403 * @param bool $includeBoundObject Pass false to exclude 'this'. If you're
404 * passing the scope variables to `extract`
405 * in PHP 7.1+, you _must_ exclude 'this'
406 *
407 * @return array Associative array of magic scope variables
408 */
409 public function getSpecialScopeVariables($includeBoundObject = true)
410 {
411 $vars = $this->context->getSpecialVariables();
412
413 if (!$includeBoundObject) {
414 unset($vars['this']);
415 }
416
417 return $vars;
418 }
419
420 /**
421 * Get the set of unused command-scope variable names.
422 *
423 * @return array Array of unused variable names
424 */
425 public function getUnusedCommandScopeVariableNames()
426 {
427 return $this->context->getUnusedCommandScopeVariableNames();
428 }
429
430 /**
431 * Get the set of variable names currently in scope.
432 *
433 * @return array Array of variable names
434 */
435 public function getScopeVariableNames()
436 {
437 return array_keys($this->context->getAll());
438 }
439
440 /**
441 * Get a scope variable value by name.
442 *
443 * @param string $name
444 *
445 * @return mixed
446 */
447 public function getScopeVariable($name)
448 {
449 return $this->context->get($name);
450 }
451
452 /**
453 * Set the bound object ($this variable) for the interactive shell.
454 *
455 * @param object|null $boundObject
456 */
457 public function setBoundObject($boundObject)
458 {
459 $this->context->setBoundObject($boundObject);
460 }
461
462 /**
463 * Get the bound object ($this variable) for the interactive shell.
464 *
465 * @return object|null
466 */
467 public function getBoundObject()
468 {
469 return $this->context->getBoundObject();
470 }
471
472 /**
473 * Add includes, to be parsed and executed before running the interactive shell.
474 *
475 * @param array $includes
476 */
477 public function setIncludes(array $includes = array())
478 {
479 $this->includes = $includes;
480 }
481
482 /**
483 * Get PHP files to be parsed and executed before running the interactive shell.
484 *
485 * @return array
486 */
487 public function getIncludes()
488 {
489 return array_merge($this->config->getDefaultIncludes(), $this->includes);
490 }
491
492 /**
493 * Check whether this shell's code buffer contains code.
494 *
495 * @return bool True if the code buffer contains code
496 */
497 public function hasCode()
498 {
499 return !empty($this->codeBuffer);
500 }
501
502 /**
503 * Check whether the code in this shell's code buffer is valid.
504 *
505 * If the code is valid, the code buffer should be flushed and evaluated.
506 *
507 * @return bool True if the code buffer content is valid
508 */
509 protected function hasValidCode()
510 {
511 return !$this->codeBufferOpen && $this->code !== false;
512 }
513
514 /**
515 * Add code to the code buffer.
516 *
517 * @param string $code
518 */
519 public function addCode($code)
520 {
521 try {
522 // Code lines ending in \ keep the buffer open
523 if (substr(rtrim($code), -1) === '\\') {
524 $this->codeBufferOpen = true;
525 $code = substr(rtrim($code), 0, -1);
526 } else {
527 $this->codeBufferOpen = false;
528 }
529
530 $this->codeBuffer[] = $code;
531 $this->code = $this->cleaner->clean($this->codeBuffer, $this->config->requireSemicolons());
532 } catch (\Exception $e) {
533 // Add failed code blocks to the readline history.
534 $this->addCodeBufferToHistory();
535
536 throw $e;
537 }
538 }
539
540 /**
541 * Get the current code buffer.
542 *
543 * This is useful for commands which manipulate the buffer.
544 *
545 * @return array
546 */
547 public function getCodeBuffer()
548 {
549 return $this->codeBuffer;
550 }
551
552 /**
553 * Run a Psy Shell command given the user input.
554 *
555 * @throws InvalidArgumentException if the input is not a valid command
556 *
557 * @param string $input User input string
558 *
559 * @return mixed Who knows?
560 */
561 protected function runCommand($input)
562 {
563 $command = $this->getCommand($input);
564
565 if (empty($command)) {
566 throw new \InvalidArgumentException('Command not found: ' . $input);
567 }
568
569 $input = new ShellInput(str_replace('\\', '\\\\', rtrim($input, " \t\n\r\0\x0B;")));
570
571 if ($input->hasParameterOption(array('--help', '-h'))) {
572 $helpCommand = $this->get('help');
573 $helpCommand->setCommand($command);
574
575 return $helpCommand->run($input, $this->output);
576 }
577
578 return $command->run($input, $this->output);
579 }
580
581 /**
582 * Reset the current code buffer.
583 *
584 * This should be run after evaluating user input, catching exceptions, or
585 * on demand by commands such as BufferCommand.
586 */
587 public function resetCodeBuffer()
588 {
589 $this->codeBuffer = array();
590 $this->code = false;
591 }
592
593 /**
594 * Inject input into the input buffer.
595 *
596 * This is useful for commands which want to replay history.
597 *
598 * @param string|array $input
599 * @param bool $silent
600 */
601 public function addInput($input, $silent = false)
602 {
603 foreach ((array) $input as $line) {
604 $this->inputBuffer[] = $silent ? new SilentInput($line) : $line;
605 }
606 }
607
608 /**
609 * Flush the current (valid) code buffer.
610 *
611 * If the code buffer is valid, resets the code buffer and returns the
612 * current code.
613 *
614 * @return string PHP code buffer contents
615 */
616 public function flushCode()
617 {
618 if ($this->hasValidCode()) {
619 $this->addCodeBufferToHistory();
620 $code = $this->code;
621 $this->resetCodeBuffer();
622
623 return $code;
624 }
625 }
626
627 /**
628 * Filter silent input from code buffer, write the rest to readline history.
629 */
630 private function addCodeBufferToHistory()
631 {
632 $codeBuffer = array_filter($this->codeBuffer, function ($line) {
633 return !$line instanceof SilentInput;
634 });
635
636 $code = implode("\n", $codeBuffer);
637
638 if (trim($code) !== '') {
639 $this->readline->addHistory($code);
640 }
641 }
642
643 /**
644 * Get the current evaluation scope namespace.
645 *
646 * @see CodeCleaner::getNamespace
647 *
648 * @return string Current code namespace
649 */
650 public function getNamespace()
651 {
652 if ($namespace = $this->cleaner->getNamespace()) {
653 return implode('\\', $namespace);
654 }
655 }
656
657 /**
658 * Write a string to stdout.
659 *
660 * This is used by the shell loop for rendering output from evaluated code.
661 *
662 * @param string $out
663 * @param int $phase Output buffering phase
664 */
665 public function writeStdout($out, $phase = PHP_OUTPUT_HANDLER_END)
666 {
667 $isCleaning = false;
668 if (version_compare(PHP_VERSION, '5.4', '>=')) {
669 $isCleaning = $phase & PHP_OUTPUT_HANDLER_CLEAN;
670 }
671
672 // Incremental flush
673 if ($out !== '' && !$isCleaning) {
674 $this->output->write($out, false, ShellOutput::OUTPUT_RAW);
675 $this->outputWantsNewline = (substr($out, -1) !== "\n");
676 $this->stdoutBuffer .= $out;
677 }
678
679 // Output buffering is done!
680 if ($phase & PHP_OUTPUT_HANDLER_END) {
681 // Write an extra newline if stdout didn't end with one
682 if ($this->outputWantsNewline) {
683 $this->output->writeln(sprintf('<aside>%s</aside>', $this->config->useUnicode() ? '⏎' : '\\n'));
684 $this->outputWantsNewline = false;
685 }
686
687 // Save the stdout buffer as $__out
688 if ($this->stdoutBuffer !== '') {
689 $this->context->setLastStdout($this->stdoutBuffer);
690 $this->stdoutBuffer = '';
691 }
692 }
693 }
694
695 /**
696 * Write a return value to stdout.
697 *
698 * The return value is formatted or pretty-printed, and rendered in a
699 * visibly distinct manner (in this case, as cyan).
700 *
701 * @see self::presentValue
702 *
703 * @param mixed $ret
704 */
705 public function writeReturnValue($ret)
706 {
707 if ($ret instanceof NoReturnValue) {
708 return;
709 }
710
711 $this->context->setReturnValue($ret);
712 $ret = $this->presentValue($ret);
713 $indent = str_repeat(' ', strlen(static::RETVAL));
714
715 $this->output->writeln(static::RETVAL . str_replace(PHP_EOL, PHP_EOL . $indent, $ret));
716 }
717
718 /**
719 * Renders a caught Exception.
720 *
721 * Exceptions are formatted according to severity. ErrorExceptions which were
722 * warnings or Strict errors aren't rendered as harshly as real errors.
723 *
724 * Stores $e as the last Exception in the Shell Context.
725 *
726 * @param \Exception $e An exception instance
727 * @param OutputInterface $output An OutputInterface instance
728 */
729 public function writeException(\Exception $e)
730 {
731 $this->context->setLastException($e);
732 $this->output->writeln($this->formatException($e));
733 $this->resetCodeBuffer();
734 }
735
736 /**
737 * Helper for formatting an exception for writeException().
738 *
739 * @todo extract this to somewhere it makes more sense
740 *
741 * @param \Exception $e
742 *
743 * @return string
744 */
745 public function formatException(\Exception $e)
746 {
747 $message = $e->getMessage();
748 if (!$e instanceof PsyException) {
749 if ($message === '') {
750 $message = get_class($e);
751 } else {
752 $message = sprintf('%s with message \'%s\'', get_class($e), $message);
753 }
754 }
755
756 $severity = ($e instanceof \ErrorException) ? $this->getSeverity($e) : 'error';
757
758 return sprintf('<%s>%s</%s>', $severity, OutputFormatter::escape($message), $severity);
759 }
760
761 /**
762 * Helper for getting an output style for the given ErrorException's level.
763 *
764 * @param \ErrorException $e
765 *
766 * @return string
767 */
768 protected function getSeverity(\ErrorException $e)
769 {
770 $severity = $e->getSeverity();
771 if ($severity & error_reporting()) {
772 switch ($severity) {
773 case E_WARNING:
774 case E_NOTICE:
775 case E_CORE_WARNING:
776 case E_COMPILE_WARNING:
777 case E_USER_WARNING:
778 case E_USER_NOTICE:
779 case E_STRICT:
780 return 'warning';
781
782 default:
783 return 'error';
784 }
785 } else {
786 // Since this is below the user's reporting threshold, it's always going to be a warning.
787 return 'warning';
788 }
789 }
790
791 /**
792 * Helper for throwing an ErrorException.
793 *
794 * This allows us to:
795 *
796 * set_error_handler(array($psysh, 'handleError'));
797 *
798 * Unlike ErrorException::throwException, this error handler respects the
799 * current error_reporting level; i.e. it logs warnings and notices, but
800 * doesn't throw an exception unless it's above the current error_reporting
801 * threshold. This should probably only be used in the inner execution loop
802 * of the shell, as most of the time a thrown exception is much more useful.
803 *
804 * If the error type matches the `errorLoggingLevel` config, it will be
805 * logged as well, regardless of the `error_reporting` level.
806 *
807 * @see \Psy\Exception\ErrorException::throwException
808 * @see \Psy\Shell::writeException
809 *
810 * @throws \Psy\Exception\ErrorException depending on the current error_reporting level
811 *
812 * @param int $errno Error type
813 * @param string $errstr Message
814 * @param string $errfile Filename
815 * @param int $errline Line number
816 */
817 public function handleError($errno, $errstr, $errfile, $errline)
818 {
819 if ($errno & error_reporting()) {
820 ErrorException::throwException($errno, $errstr, $errfile, $errline);
821 } elseif ($errno & $this->config->errorLoggingLevel()) {
822 // log it and continue...
823 $this->writeException(new ErrorException($errstr, 0, $errno, $errfile, $errline));
824 }
825 }
826
827 /**
828 * Format a value for display.
829 *
830 * @see Presenter::present
831 *
832 * @param mixed $val
833 *
834 * @return string Formatted value
835 */
836 protected function presentValue($val)
837 {
838 return $this->config->getPresenter()->present($val);
839 }
840
841 /**
842 * Get a command (if one exists) for the current input string.
843 *
844 * @param string $input
845 *
846 * @return null|BaseCommand
847 */
848 protected function getCommand($input)
849 {
850 $input = new StringInput($input);
851 if ($name = $input->getFirstArgument()) {
852 return $this->get($name);
853 }
854 }
855
856 /**
857 * Check whether a command is set for the current input string.
858 *
859 * @param string $input
860 *
861 * @return bool True if the shell has a command for the given input
862 */
863 protected function hasCommand($input)
864 {
865 $input = new StringInput($input);
866 if ($name = $input->getFirstArgument()) {
867 return $this->has($name);
868 }
869
870 return false;
871 }
872
873 /**
874 * Get the current input prompt.
875 *
876 * @return string
877 */
878 protected function getPrompt()
879 {
880 if ($this->hasCode()) {
881 return static::BUFF_PROMPT;
882 }
883
884 return $this->config->getPrompt() ?: static::PROMPT;
885 }
886
887 /**
888 * Read a line of user input.
889 *
890 * This will return a line from the input buffer (if any exist). Otherwise,
891 * it will ask the user for input.
892 *
893 * If readline is enabled, this delegates to readline. Otherwise, it's an
894 * ugly `fgets` call.
895 *
896 * @return string One line of user input
897 */
898 protected function readline()
899 {
900 if (!empty($this->inputBuffer)) {
901 $line = array_shift($this->inputBuffer);
902 if (!$line instanceof SilentInput) {
903 $this->output->writeln(sprintf('<aside>%s %s</aside>', static::REPLAY, OutputFormatter::escape($line)));
904 }
905
906 return $line;
907 }
908
909 if ($bracketedPaste = $this->config->useBracketedPaste()) {
910 printf("\e[?2004h"); // Enable bracketed paste
911 }
912
913 $line = $this->readline->readline($this->getPrompt());
914
915 if ($bracketedPaste) {
916 printf("\e[?2004l"); // ... and disable it again
917 }
918
919 return $line;
920 }
921
922 /**
923 * Get the shell output header.
924 *
925 * @return string
926 */
927 protected function getHeader()
928 {
929 return sprintf('<aside>%s by Justin Hileman</aside>', $this->getVersion());
930 }
931
932 /**
933 * Get the current version of Psy Shell.
934 *
935 * @return string
936 */
937 public function getVersion()
938 {
939 $separator = $this->config->useUnicode() ? '—' : '-';
940
941 return sprintf('Psy Shell %s (PHP %s %s %s)', self::VERSION, phpversion(), $separator, php_sapi_name());
942 }
943
944 /**
945 * Get a PHP manual database instance.
946 *
947 * @return \PDO|null
948 */
949 public function getManualDb()
950 {
951 return $this->config->getManualDb();
952 }
953
954 /**
955 * Autocomplete variable names.
956 *
957 * This is used by `readline` for tab completion.
958 *
959 * @param string $text
960 *
961 * @return mixed Array possible completions for the given input, if any
962 */
963 protected function autocomplete($text)
964 {
965 $info = readline_info();
966 // $line = substr($info['line_buffer'], 0, $info['end']);
967
968 // Check whether there's a command for this
969 // $words = explode(' ', $line);
970 // $firstWord = reset($words);
971
972 // check whether this is a variable...
973 $firstChar = substr($info['line_buffer'], max(0, $info['end'] - strlen($text) - 1), 1);
974 if ($firstChar === '$') {
975 return $this->getScopeVariableNames();
976 }
977 }
978
979 /**
980 * Initialize tab completion matchers.
981 *
982 * If tab completion is enabled this adds tab completion matchers to the
983 * auto completer and sets context if needed.
984 */
985 protected function initializeTabCompletion()
986 {
987 // auto completer needs shell to be linked to configuration because of the context aware matchers
988 if ($this->config->getTabCompletion()) {
989 $this->completion = $this->config->getAutoCompleter();
990 $this->addTabCompletionMatchers($this->config->getTabCompletionMatchers());
991 foreach ($this->getTabCompletionMatchers() as $matcher) {
992 if ($matcher instanceof ContextAware) {
993 $matcher->setContext($this->context);
994 }
995 $this->completion->addMatcher($matcher);
996 }
997 $this->completion->activate();
998 }
999 }
1000
1001 /**
1002 * @todo Implement self-update
1003 * @todo Implement prompt to start update
1004 *
1005 * @return void|string
1006 */
1007 protected function writeVersionInfo()
1008 {
1009 if (PHP_SAPI !== 'cli') {
1010 return;
1011 }
1012
1013 try {
1014 $client = $this->config->getChecker();
1015 if (!$client->isLatest()) {
1016 $this->output->writeln(sprintf('New version is available (current: %s, latest: %s)',self::VERSION, $client->getLatest()));
1017 }
1018 } catch (\InvalidArgumentException $e) {
1019 $this->output->writeln($e->getMessage());
1020 }
1021 }
1022
1023 /**
1024 * Write a startup message if set.
1025 */
1026 protected function writeStartupMessage()
1027 {
1028 $message = $this->config->getStartupMessage();
1029 if ($message !== null && $message !== '') {
1030 $this->output->writeln($message);
1031 }
1032 }
1033 }