Chris@13: colorMode = $colorMode ?: Configuration::COLOR_MODE_AUTO; Chris@17: $this->backtrace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); Chris@13: Chris@13: parent::__construct(); Chris@13: } Chris@13: Chris@13: /** Chris@13: * {@inheritdoc} Chris@13: */ Chris@13: protected function configure() Chris@13: { Chris@13: $this Chris@13: ->setName('whereami') Chris@13: ->setDefinition([ Chris@13: new InputOption('num', 'n', InputOption::VALUE_OPTIONAL, 'Number of lines before and after.', '5'), Chris@13: ]) Chris@13: ->setDescription('Show where you are in the code.') Chris@13: ->setHelp( Chris@13: <<<'HELP' Chris@13: Show where you are in the code. Chris@13: Chris@13: Optionally, include how many lines before and after you want to display. Chris@13: Chris@13: e.g. Chris@13: > whereami Chris@13: > whereami -n10 Chris@13: HELP Chris@13: ); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Obtains the correct stack frame in the full backtrace. Chris@13: * Chris@13: * @return array Chris@13: */ Chris@13: protected function trace() Chris@13: { Chris@17: foreach (\array_reverse($this->backtrace) as $stackFrame) { Chris@13: if ($this->isDebugCall($stackFrame)) { Chris@13: return $stackFrame; Chris@13: } Chris@13: } Chris@13: Chris@17: return \end($this->backtrace); Chris@13: } Chris@13: Chris@13: private static function isDebugCall(array $stackFrame) Chris@13: { Chris@13: $class = isset($stackFrame['class']) ? $stackFrame['class'] : null; Chris@13: $function = isset($stackFrame['function']) ? $stackFrame['function'] : null; Chris@13: Chris@13: return ($class === null && $function === 'Psy\debug') || Chris@17: ($class === 'Psy\Shell' && \in_array($function, ['__construct', 'debug'])); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Determine the file and line based on the specific backtrace. Chris@13: * Chris@13: * @return array Chris@13: */ Chris@13: protected function fileInfo() Chris@13: { Chris@13: $stackFrame = $this->trace(); Chris@17: if (\preg_match('/eval\(/', $stackFrame['file'])) { Chris@17: \preg_match_all('/([^\(]+)\((\d+)/', $stackFrame['file'], $matches); Chris@13: $file = $matches[1][0]; Chris@13: $line = (int) $matches[2][0]; Chris@13: } else { Chris@13: $file = $stackFrame['file']; Chris@13: $line = $stackFrame['line']; Chris@13: } Chris@13: Chris@17: return \compact('file', 'line'); Chris@13: } Chris@13: Chris@13: /** Chris@13: * {@inheritdoc} Chris@13: */ Chris@13: protected function execute(InputInterface $input, OutputInterface $output) Chris@13: { Chris@13: $info = $this->fileInfo(); Chris@13: $num = $input->getOption('num'); Chris@13: $factory = new ConsoleColorFactory($this->colorMode); Chris@13: $colors = $factory->getConsoleColor(); Chris@13: $highlighter = new Highlighter($colors); Chris@17: $contents = \file_get_contents($info['file']); Chris@13: Chris@13: $output->startPaging(); Chris@13: $output->writeln(''); Chris@17: $output->writeln(\sprintf('From %s:%s:', $this->replaceCwd($info['file']), $info['line'])); Chris@13: $output->writeln(''); Chris@13: $output->write($highlighter->getCodeSnippet($contents, $info['line'], $num, $num), ShellOutput::OUTPUT_RAW); Chris@13: $output->stopPaging(); Chris@13: } Chris@13: Chris@13: /** Chris@13: * Replace the given directory from the start of a filepath. Chris@13: * Chris@13: * @param string $file Chris@13: * Chris@13: * @return string Chris@13: */ Chris@13: private function replaceCwd($file) Chris@13: { Chris@17: $cwd = \getcwd(); Chris@13: if ($cwd === false) { Chris@13: return $file; Chris@13: } Chris@13: Chris@17: $cwd = \rtrim($cwd, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; Chris@13: Chris@17: return \preg_replace('/^' . \preg_quote($cwd, '/') . '/', '', $file); Chris@13: } Chris@13: }