diff --git a/src/toolset/ArcanistPrompt.php b/src/toolset/ArcanistPrompt.php index 8cd4a912..eab366d4 100644 --- a/src/toolset/ArcanistPrompt.php +++ b/src/toolset/ArcanistPrompt.php @@ -1,151 +1,171 @@ key = $key; return $this; } public function getKey() { return $this->key; } public function setWorkflow(ArcanistWorkflow $workflow) { $this->workflow = $workflow; return $this; } public function getWorkflow() { return $this->workflow; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setQuery($query) { $this->query = $query; return $this; } public function getQuery() { return $this->query; } public function execute() { $workflow = $this->getWorkflow(); if ($workflow) { $workflow_ok = $workflow->hasPrompt($this->getKey()); } else { $workflow_ok = false; } if (!$workflow_ok) { throw new Exception( pht( 'Prompt ("%s") is executing, but it is not properly bound to the '. 'invoking workflow. You may have called "newPrompt()" to execute a '. 'prompt instead of "getPrompt()". Use "newPrompt()" when defining '. 'prompts and "getPrompt()" when executing them.', $this->getKey())); } $query = $this->getQuery(); if (!strlen($query)) { throw new Exception( pht( 'Prompt ("%s") has no query text!', $this->getKey())); } $options = '[y/N]'; $default = 'N'; try { phutil_console_require_tty(); } catch (PhutilConsoleStdinNotInteractiveException $ex) { // TOOLSETS: Clean this up to provide more details to the user about how // they can configure prompts to be answered. // Throw after echoing the prompt so the user has some idea what happened. echo $query."\n"; throw $ex; } // NOTE: We're making stdin nonblocking so that we can respond to signals // immediately. If we don't, and you ^C during a prompt, the program does // not handle the signal until fgets() returns. $stdin = fopen('php://stdin', 'r'); if (!$stdin) { throw new Exception(pht('Failed to open stdin for reading.')); } $ok = stream_set_blocking($stdin, false); if (!$ok) { throw new Exception(pht('Unable to set stdin nonblocking.')); } echo "\n"; $result = null; while (true) { echo tsprintf( '** %s ** %s %s ', '>>>', $query, $options); while (true) { $read = array($stdin); $write = array(); $except = array(); - $ok = stream_select($read, $write, $except, 1); + $ok = @stream_select($read, $write, $except, 1); if ($ok === false) { - throw new Exception(pht('stream_select() failed!')); + // NOTE: We may be interrupted by a system call, particularly if + // the window is resized while a prompt is shown and the terminal + // sends SIGWINCH. + + // If we are, just continue below and try to read from stdin. If + // we were interrupted, we should read nothing and continue + // normally. If the pipe is broken, the read should fail. + } + + $response = ''; + while (true) { + $bytes = fread($stdin, 8192); + if ($bytes === false) { + throw new Exception( + pht('fread() from stdin failed with an error.')); + } + + if (!strlen($bytes)) { + break; + } + + $response .= $bytes; } - $response = fgets($stdin); if (!strlen($response)) { continue; } break; } $response = trim($response); if (!strlen($response)) { $response = $default; } if (phutil_utf8_strtolower($response) == 'y') { $result = true; break; } if (phutil_utf8_strtolower($response) == 'n') { $result = false; break; } } if (!$result) { throw new ArcanistUserAbortException(); } } }