Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/console/core/DarkConsoleCore.php b/src/applications/console/core/DarkConsoleCore.php
index ddb22b9cfe..437d6f0be7 100644
--- a/src/applications/console/core/DarkConsoleCore.php
+++ b/src/applications/console/core/DarkConsoleCore.php
@@ -1,136 +1,138 @@
<?php
final class DarkConsoleCore extends Phobject {
private $plugins = array();
const STORAGE_VERSION = 1;
public function __construct() {
$this->plugins = id(new PhutilClassMapQuery())
->setAncestorClass('DarkConsolePlugin')
->execute();
foreach ($this->plugins as $plugin) {
$plugin->setConsoleCore($this);
$plugin->didStartup();
}
}
public function getPlugins() {
return $this->plugins;
}
public function getKey(AphrontRequest $request) {
$plugins = $this->getPlugins();
foreach ($plugins as $plugin) {
$plugin->setRequest($request);
$plugin->willShutdown();
}
foreach ($plugins as $plugin) {
$plugin->didShutdown();
}
foreach ($plugins as $plugin) {
$plugin->setData($plugin->generateData());
}
$plugins = msort($plugins, 'getOrderKey');
$key = Filesystem::readRandomCharacters(24);
$tabs = array();
$data = array();
foreach ($plugins as $plugin) {
$class = get_class($plugin);
$tabs[] = array(
'class' => $class,
'name' => $plugin->getName(),
'color' => $plugin->getColor(),
);
$data[$class] = $this->sanitizeForJSON($plugin->getData());
}
$storage = array(
'vers' => self::STORAGE_VERSION,
'tabs' => $tabs,
'data' => $data,
'user' => $request->getUser()
? $request->getUser()->getPHID()
: null,
);
$cache = new PhabricatorKeyValueDatabaseCache();
$cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance());
// This encoding may fail if there are, e.g., database queries which
// include binary data. It would be a little cleaner to try to strip these,
// but just do something non-broken here if we end up with unrepresentable
// data.
$json = @json_encode($storage);
if (!$json) {
$json = '{}';
}
$cache->setKeys(
array(
'darkconsole:'.$key => $json,
),
$ttl = (60 * 60 * 6));
return $key;
}
public function getColor() {
foreach ($this->getPlugins() as $plugin) {
if ($plugin->getColor()) {
return $plugin->getColor();
}
}
}
public function render(AphrontRequest $request) {
$user = $request->getUser();
$visible = $user->getUserSetting(
PhabricatorDarkConsoleVisibleSetting::SETTINGKEY);
return javelin_tag(
'div',
array(
'id' => 'darkconsole',
'class' => 'dark-console',
'style' => $visible ? '' : 'display: none;',
'data-console-key' => $this->getKey($request),
'data-console-color' => $this->getColor(),
),
'');
}
/**
* Sometimes, tab data includes binary information (like INSERT queries which
* write file data into the database). To successfully JSON encode it, we
* need to convert it to UTF-8.
*/
private function sanitizeForJSON($data) {
if (is_object($data)) {
return '<object:'.get_class($data).'>';
} else if (is_array($data)) {
foreach ($data as $key => $value) {
$data[$key] = $this->sanitizeForJSON($value);
}
return $data;
+ } else if (is_resource($data)) {
+ return '<resource>';
} else {
// Truncate huge strings. Since the data doesn't really matter much,
// just truncate bytes to avoid PhutilUTF8StringTruncator overhead.
$length = strlen($data);
$max = 4096;
if ($length > $max) {
$data = substr($data, 0, $max).'...<'.$length.' bytes>...';
}
return phutil_utf8ize($data);
}
}
}
diff --git a/src/applications/files/document/PhabricatorDocumentEngine.php b/src/applications/files/document/PhabricatorDocumentEngine.php
index 9593e1d98d..3a13c5eb4a 100644
--- a/src/applications/files/document/PhabricatorDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorDocumentEngine.php
@@ -1,284 +1,288 @@
<?php
abstract class PhabricatorDocumentEngine
extends Phobject {
private $viewer;
private $highlightedLines = array();
private $encodingConfiguration;
private $highlightingConfiguration;
private $blameConfiguration = true;
final public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setHighlightedLines(array $highlighted_lines) {
$this->highlightedLines = $highlighted_lines;
return $this;
}
final public function getHighlightedLines() {
return $this->highlightedLines;
}
final public function canRenderDocument(PhabricatorDocumentRef $ref) {
return $this->canRenderDocumentType($ref);
}
public function canDiffDocuments(
PhabricatorDocumentRef $uref = null,
PhabricatorDocumentRef $vref = null) {
return false;
}
public function newBlockDiffViews(
PhabricatorDocumentRef $uref,
PhabricatorDocumentEngineBlock $ublock,
PhabricatorDocumentRef $vref,
PhabricatorDocumentEngineBlock $vblock) {
$u_content = $this->newBlockContentView($uref, $ublock);
$v_content = $this->newBlockContentView($vref, $vblock);
return id(new PhabricatorDocumentEngineBlockDiff())
->setOldContent($u_content)
->addOldClass('old')
->addOldClass('old-full')
->setNewContent($v_content)
->addNewClass('new')
->addNewClass('new-full');
}
public function newBlockContentView(
PhabricatorDocumentRef $ref,
PhabricatorDocumentEngineBlock $block) {
return $block->getContent();
}
public function newEngineBlocks(
PhabricatorDocumentRef $uref,
PhabricatorDocumentRef $vref) {
throw new PhutilMethodNotImplementedException();
}
public function canConfigureEncoding(PhabricatorDocumentRef $ref) {
return false;
}
public function canConfigureHighlighting(PhabricatorDocumentRef $ref) {
return false;
}
public function canBlame(PhabricatorDocumentRef $ref) {
return false;
}
final public function setEncodingConfiguration($config) {
$this->encodingConfiguration = $config;
return $this;
}
final public function getEncodingConfiguration() {
return $this->encodingConfiguration;
}
final public function setHighlightingConfiguration($config) {
$this->highlightingConfiguration = $config;
return $this;
}
final public function getHighlightingConfiguration() {
return $this->highlightingConfiguration;
}
final public function setBlameConfiguration($blame_configuration) {
$this->blameConfiguration = $blame_configuration;
return $this;
}
final public function getBlameConfiguration() {
return $this->blameConfiguration;
}
final protected function getBlameEnabled() {
return $this->blameConfiguration;
}
public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
return false;
}
abstract protected function canRenderDocumentType(
PhabricatorDocumentRef $ref);
final public function newDocument(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
$can_partial = $this->canRenderPartialDocument($ref);
if (!$can_complete && !$can_partial) {
return $this->newMessage(
pht(
'This document is too large to be rendered inline. (The document '.
'is %s bytes, the limit for this engine is %s bytes.)',
new PhutilNumber($ref->getByteLength()),
new PhutilNumber($this->getByteLengthLimit())));
}
return $this->newDocumentContent($ref);
}
final public function newDocumentIcon(PhabricatorDocumentRef $ref) {
return id(new PHUIIconView())
->setIcon($this->getDocumentIconIcon($ref));
}
abstract protected function newDocumentContent(
PhabricatorDocumentRef $ref);
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-o';
}
protected function getDocumentRenderingText(PhabricatorDocumentRef $ref) {
return pht('Loading...');
}
final public function getDocumentEngineKey() {
return $this->getPhobjectClassConstant('ENGINEKEY');
}
final public static function getAllEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getDocumentEngineKey')
->execute();
}
final public function newSortVector(PhabricatorDocumentRef $ref) {
$content_score = $this->getContentScore($ref);
// Prefer engines which can render the entire file over engines which
// can only render a header, and engines which can render a header over
// engines which can't render anything.
if ($this->canRenderCompleteDocument($ref)) {
$limit_score = 0;
} else if ($this->canRenderPartialDocument($ref)) {
$limit_score = 1;
} else {
$limit_score = 2;
}
return id(new PhutilSortVector())
->addInt($limit_score)
->addInt(-$content_score);
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
return 2000;
}
abstract public function getViewAsLabel(PhabricatorDocumentRef $ref);
public function getViewAsIconIcon(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
$can_partial = $this->canRenderPartialDocument($ref);
if (!$can_complete && !$can_partial) {
return 'fa-times';
}
return $this->getDocumentIconIcon($ref);
}
public function getViewAsIconColor(PhabricatorDocumentRef $ref) {
$can_complete = $this->canRenderCompleteDocument($ref);
if (!$can_complete) {
return 'grey';
}
return null;
}
final public static function getEnginesForRef(
PhabricatorUser $viewer,
PhabricatorDocumentRef $ref) {
$engines = self::getAllEngines();
foreach ($engines as $key => $engine) {
$engine = id(clone $engine)
->setViewer($viewer);
if (!$engine->canRenderDocument($ref)) {
unset($engines[$key]);
continue;
}
$engines[$key] = $engine;
}
if (!$engines) {
throw new Exception(pht('No content engine can render this document.'));
}
$vectors = array();
foreach ($engines as $key => $usable_engine) {
$vectors[$key] = $usable_engine->newSortVector($ref);
}
$vectors = msortv($vectors, 'getSelf');
return array_select_keys($engines, array_keys($vectors));
}
protected function getByteLengthLimit() {
return (1024 * 1024 * 8);
}
protected function canRenderCompleteDocument(PhabricatorDocumentRef $ref) {
$limit = $this->getByteLengthLimit();
if ($limit) {
$length = $ref->getByteLength();
if ($length > $limit) {
return false;
}
}
return true;
}
protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) {
return false;
}
protected function newMessage($message) {
return phutil_tag(
'div',
array(
'class' => 'document-engine-error',
),
$message);
}
final public function newLoadingContent(PhabricatorDocumentRef $ref) {
$spinner = id(new PHUIIconView())
->setIcon('fa-gear')
->addClass('ph-spin');
return phutil_tag(
'div',
array(
'class' => 'document-engine-loading',
),
array(
$spinner,
$this->getDocumentRenderingText($ref),
));
}
+ public function shouldSuggestEngine(PhabricatorDocumentRef $ref) {
+ return false;
+ }
+
}
diff --git a/src/applications/files/document/PhabricatorJupyterDocumentEngine.php b/src/applications/files/document/PhabricatorJupyterDocumentEngine.php
index 834a880b42..3e479fdace 100644
--- a/src/applications/files/document/PhabricatorJupyterDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorJupyterDocumentEngine.php
@@ -1,751 +1,755 @@
<?php
final class PhabricatorJupyterDocumentEngine
extends PhabricatorDocumentEngine {
const ENGINEKEY = 'jupyter';
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
return pht('View as Jupyter Notebook');
}
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-sun-o';
}
protected function getDocumentRenderingText(PhabricatorDocumentRef $ref) {
return pht('Rendering Jupyter Notebook...');
}
public function shouldRenderAsync(PhabricatorDocumentRef $ref) {
return true;
}
protected function getContentScore(PhabricatorDocumentRef $ref) {
$name = $ref->getName();
if (preg_match('/\\.ipynb\z/i', $name)) {
return 2000;
}
return 500;
}
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
return $ref->isProbablyJSON();
}
public function canDiffDocuments(
PhabricatorDocumentRef $uref = null,
PhabricatorDocumentRef $vref = null) {
return true;
}
public function newEngineBlocks(
PhabricatorDocumentRef $uref = null,
PhabricatorDocumentRef $vref = null) {
$blocks = new PhabricatorDocumentEngineBlocks();
try {
if ($uref) {
$u_blocks = $this->newDiffBlocks($uref);
} else {
$u_blocks = array();
}
if ($vref) {
$v_blocks = $this->newDiffBlocks($vref);
} else {
$v_blocks = array();
}
$blocks->addBlockList($uref, $u_blocks);
$blocks->addBlockList($vref, $v_blocks);
} catch (Exception $ex) {
$blocks->addMessage($ex->getMessage());
}
return $blocks;
}
public function newBlockDiffViews(
PhabricatorDocumentRef $uref,
PhabricatorDocumentEngineBlock $ublock,
PhabricatorDocumentRef $vref,
PhabricatorDocumentEngineBlock $vblock) {
$ucell = $ublock->getContent();
$vcell = $vblock->getContent();
$utype = idx($ucell, 'cell_type');
$vtype = idx($vcell, 'cell_type');
if ($utype === $vtype) {
switch ($utype) {
case 'markdown':
$usource = idx($ucell, 'source');
$usource = implode('', $usource);
$vsource = idx($vcell, 'source');
$vsource = implode('', $vsource);
$diff = id(new PhutilProseDifferenceEngine())
->getDiff($usource, $vsource);
$u_content = $this->newProseDiffCell($diff, array('=', '-'));
$v_content = $this->newProseDiffCell($diff, array('=', '+'));
$u_content = $this->newJupyterCell(null, $u_content, null);
$v_content = $this->newJupyterCell(null, $v_content, null);
$u_content = $this->newCellContainer($u_content);
$v_content = $this->newCellContainer($v_content);
return id(new PhabricatorDocumentEngineBlockDiff())
->setOldContent($u_content)
->addOldClass('old')
->setNewContent($v_content)
->addNewClass('new');
case 'code/line':
$usource = idx($ucell, 'raw');
$vsource = idx($vcell, 'raw');
$udisplay = idx($ucell, 'display');
$vdisplay = idx($vcell, 'display');
$ulabel = idx($ucell, 'label');
$vlabel = idx($vcell, 'label');
$intraline_segments = ArcanistDiffUtils::generateIntralineDiff(
$usource,
$vsource);
$u_segments = array();
foreach ($intraline_segments[0] as $u_segment) {
$u_segments[] = $u_segment;
}
$v_segments = array();
foreach ($intraline_segments[1] as $v_segment) {
$v_segments[] = $v_segment;
}
$usource = PhabricatorDifferenceEngine::applyIntralineDiff(
$udisplay,
$u_segments);
$vsource = PhabricatorDifferenceEngine::applyIntralineDiff(
$vdisplay,
$v_segments);
$u_content = $this->newCodeLineCell($ucell, $usource);
$v_content = $this->newCodeLineCell($vcell, $vsource);
$classes = array(
'jupyter-cell-flush',
);
$u_content = $this->newJupyterCell($ulabel, $u_content, $classes);
$v_content = $this->newJupyterCell($vlabel, $v_content, $classes);
$u_content = $this->newCellContainer($u_content);
$v_content = $this->newCellContainer($v_content);
return id(new PhabricatorDocumentEngineBlockDiff())
->setOldContent($u_content)
->addOldClass('old')
->setNewContent($v_content)
->addNewClass('new');
}
}
return parent::newBlockDiffViews($uref, $ublock, $vref, $vblock);
}
public function newBlockContentView(
PhabricatorDocumentRef $ref,
PhabricatorDocumentEngineBlock $block) {
$viewer = $this->getViewer();
$cell = $block->getContent();
$cell_content = $this->renderJupyterCell($viewer, $cell);
return $this->newCellContainer($cell_content);
}
private function newCellContainer($cell_content) {
$notebook_table = phutil_tag(
'table',
array(
'class' => 'jupyter-notebook',
),
$cell_content);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-jupyter document-engine-diff',
),
$notebook_table);
return $container;
}
private function newProseDiffCell(PhutilProseDiff $diff, array $mask) {
$mask = array_fuse($mask);
$result = array();
foreach ($diff->getParts() as $part) {
$type = $part['type'];
$text = $part['text'];
if (!isset($mask[$type])) {
continue;
}
switch ($type) {
case '-':
$result[] = phutil_tag(
'span',
array(
'class' => 'bright',
),
$text);
break;
case '+':
$result[] = phutil_tag(
'span',
array(
'class' => 'bright',
),
$text);
break;
case '=':
$result[] = $text;
break;
}
}
return array(
null,
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-markdown',
),
$result),
);
}
private function newDiffBlocks(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$content = $ref->loadData();
$cells = $this->newCells($content, true);
$idx = 1;
$blocks = array();
foreach ($cells as $cell) {
// When the cell is a source code line, we can hash just the raw
// input rather than all the cell metadata.
switch (idx($cell, 'cell_type')) {
case 'code/line':
$hash_input = $cell['raw'];
break;
case 'markdown':
$hash_input = implode('', $cell['source']);
break;
default:
$hash_input = serialize($cell);
break;
}
$hash = PhabricatorHash::digestWithNamedKey(
$hash_input,
'document-engine.content-digest');
$blocks[] = id(new PhabricatorDocumentEngineBlock())
->setBlockKey($idx)
->setDifferenceHash($hash)
->setContent($cell);
$idx++;
}
return $blocks;
}
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
$viewer = $this->getViewer();
$content = $ref->loadData();
try {
$cells = $this->newCells($content, false);
} catch (Exception $ex) {
return $this->newMessage($ex->getMessage());
}
$rows = array();
foreach ($cells as $cell) {
$rows[] = $this->renderJupyterCell($viewer, $cell);
}
$notebook_table = phutil_tag(
'table',
array(
'class' => 'jupyter-notebook',
),
$rows);
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-jupyter',
),
$notebook_table);
return $container;
}
private function newCells($content, $for_diff) {
try {
$data = phutil_json_decode($content);
} catch (PhutilJSONParserException $ex) {
throw new Exception(
pht(
'This is not a valid JSON document and can not be rendered as '.
'a Jupyter notebook: %s.',
$ex->getMessage()));
}
if (!is_array($data)) {
throw new Exception(
pht(
'This document does not encode a valid JSON object and can not '.
'be rendered as a Jupyter notebook.'));
}
$nbformat = idx($data, 'nbformat');
if (!strlen($nbformat)) {
throw new Exception(
pht(
'This document is missing an "nbformat" field. Jupyter notebooks '.
'must have this field.'));
}
if ($nbformat !== 4) {
throw new Exception(
pht(
'This Jupyter notebook uses an unsupported version of the file '.
'format (found version %s, expected version 4).',
$nbformat));
}
$cells = idx($data, 'cells');
if (!is_array($cells)) {
throw new Exception(
pht(
'This Jupyter notebook does not specify a list of "cells".'));
}
if (!$cells) {
throw new Exception(
pht(
'This Jupyter notebook does not specify any notebook cells.'));
}
if (!$for_diff) {
return $cells;
}
// If we're extracting cells to build a diff view, split code cells into
// individual lines and individual outputs. We want users to be able to
// add inline comments to each line and each output block.
$results = array();
foreach ($cells as $cell) {
$cell_type = idx($cell, 'cell_type');
if ($cell_type === 'markdown') {
$source = $cell['source'];
$source = implode('', $source);
// Attempt to split contiguous blocks of markdown into smaller
// pieces.
$chunks = preg_split(
'/\n\n+/',
$source);
foreach ($chunks as $chunk) {
$result = $cell;
$result['source'] = array($chunk);
$results[] = $result;
}
continue;
}
if ($cell_type !== 'code') {
$results[] = $cell;
continue;
}
$label = $this->newCellLabel($cell);
$lines = idx($cell, 'source');
if (!is_array($lines)) {
$lines = array();
}
$content = $this->highlightLines($lines);
$count = count($lines);
for ($ii = 0; $ii < $count; $ii++) {
$is_head = ($ii === 0);
$is_last = ($ii === ($count - 1));
if ($is_head) {
$line_label = $label;
} else {
$line_label = null;
}
$results[] = array(
'cell_type' => 'code/line',
'label' => $line_label,
'raw' => $lines[$ii],
'display' => idx($content, $ii),
'head' => $is_head,
'last' => $is_last,
);
}
$outputs = array();
$output_list = idx($cell, 'outputs');
if (is_array($output_list)) {
foreach ($output_list as $output) {
$results[] = array(
'cell_type' => 'code/output',
'output' => $output,
);
}
}
}
return $results;
}
private function renderJupyterCell(
PhabricatorUser $viewer,
array $cell) {
list($label, $content) = $this->renderJupyterCellContent($viewer, $cell);
$classes = null;
switch (idx($cell, 'cell_type')) {
case 'code/line':
$classes = 'jupyter-cell-flush';
break;
}
return $this->newJupyterCell(
$label,
$content,
$classes);
}
private function newJupyterCell($label, $content, $classes) {
$label_cell = phutil_tag(
'td',
array(
'class' => 'jupyter-label',
),
$label);
$content_cell = phutil_tag(
'td',
array(
'class' => $classes,
),
$content);
return phutil_tag(
'tr',
array(),
array(
$label_cell,
$content_cell,
));
}
private function renderJupyterCellContent(
PhabricatorUser $viewer,
array $cell) {
$cell_type = idx($cell, 'cell_type');
switch ($cell_type) {
case 'markdown':
return $this->newMarkdownCell($cell);
case 'code':
return $this->newCodeCell($cell);
case 'code/line':
return $this->newCodeLineCell($cell);
case 'code/output':
return $this->newCodeOutputCell($cell);
}
$json_content = id(new PhutilJSON())
->encodeFormatted($cell);
return $this->newRawCell($json_content);
}
private function newRawCell($content) {
return array(
null,
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-raw PhabricatorMonospaced',
),
$content),
);
}
private function newMarkdownCell(array $cell) {
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
// TODO: This should ideally highlight as Markdown, but the "md"
// highlighter in Pygments is painfully slow and not terribly useful.
$content = $this->highlightLines($content, 'txt');
return array(
null,
phutil_tag(
'div',
array(
'class' => 'jupyter-cell-markdown',
),
$content),
);
}
private function newCodeCell(array $cell) {
$label = $this->newCellLabel($cell);
$content = idx($cell, 'source');
if (!is_array($content)) {
$content = array();
}
$content = $this->highlightLines($content);
$outputs = array();
$output_list = idx($cell, 'outputs');
if (is_array($output_list)) {
foreach ($output_list as $output) {
$outputs[] = $this->newOutput($output);
}
}
return array(
$label,
array(
phutil_tag(
'div',
array(
'class' =>
'jupyter-cell-code jupyter-cell-code-block '.
'PhabricatorMonospaced remarkup-code',
),
array(
$content,
)),
$outputs,
),
);
}
private function newCodeLineCell(array $cell, $content = null) {
$classes = array();
$classes[] = 'PhabricatorMonospaced';
$classes[] = 'remarkup-code';
$classes[] = 'jupyter-cell-code';
$classes[] = 'jupyter-cell-code-line';
if ($cell['head']) {
$classes[] = 'jupyter-cell-code-head';
}
if ($cell['last']) {
$classes[] = 'jupyter-cell-code-last';
}
$classes = implode(' ', $classes);
if ($content === null) {
$content = $cell['display'];
}
return array(
$cell['label'],
array(
phutil_tag(
'div',
array(
'class' => $classes,
),
array(
$content,
)),
),
);
}
private function newCodeOutputCell(array $cell) {
return array(
null,
$this->newOutput($cell['output']),
);
}
private function newOutput(array $output) {
if (!is_array($output)) {
return pht('<Invalid Output>');
}
$classes = array(
'jupyter-output',
'PhabricatorMonospaced',
);
$output_name = idx($output, 'name');
switch ($output_name) {
case 'stderr':
$classes[] = 'jupyter-output-stderr';
break;
}
$output_type = idx($output, 'output_type');
switch ($output_type) {
case 'execute_result':
case 'display_data':
$data = idx($output, 'data');
$image_formats = array(
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
);
foreach ($image_formats as $image_format) {
if (!isset($data[$image_format])) {
continue;
}
$raw_data = $data[$image_format];
if (!is_array($raw_data)) {
$raw_data = array($raw_data);
}
$raw_data = implode('', $raw_data);
$content = phutil_tag(
'img',
array(
'src' => 'data:'.$image_format.';base64,'.$raw_data,
));
break 2;
}
if (isset($data['text/html'])) {
$content = $data['text/html'];
$classes[] = 'jupyter-output-html';
break;
}
if (isset($data['application/javascript'])) {
$content = $data['application/javascript'];
$classes[] = 'jupyter-output-html';
break;
}
if (isset($data['text/plain'])) {
$content = $data['text/plain'];
break;
}
break;
case 'stream':
default:
$content = idx($output, 'text');
if (!is_array($content)) {
$content = array();
}
$content = implode('', $content);
break;
}
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
),
$content);
}
private function newCellLabel(array $cell) {
$execution_count = idx($cell, 'execution_count');
if ($execution_count) {
$label = 'In ['.$execution_count.']:';
} else {
$label = null;
}
return $label;
}
private function highlightLines(array $lines, $force_language = null) {
if ($force_language === null) {
$head = head($lines);
$matches = null;
if (preg_match('/^%%(.*)$/', $head, $matches)) {
$restore = array_shift($lines);
$lang = $matches[1];
} else {
$restore = null;
$lang = 'py';
}
} else {
$restore = null;
$lang = $force_language;
}
$content = PhabricatorSyntaxHighlighter::highlightWithLanguage(
$lang,
implode('', $lines));
$content = phutil_split_lines($content);
if ($restore !== null) {
$language_tag = phutil_tag(
'span',
array(
'class' => 'language-tag',
),
$restore);
array_unshift($content, $language_tag);
}
return $content;
}
+ public function shouldSuggestEngine(PhabricatorDocumentRef $ref) {
+ return true;
+ }
+
}
diff --git a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
index 2524f77821..5a4cb77340 100644
--- a/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
+++ b/src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
@@ -1,338 +1,344 @@
<?php
abstract class PhabricatorDocumentRenderingEngine
extends Phobject {
private $request;
private $controller;
private $activeEngine;
private $ref;
final public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
final public function getRequest() {
if (!$this->request) {
throw new PhutilInvalidStateException('setRequest');
}
return $this->request;
}
final public function setController(PhabricatorController $controller) {
$this->controller = $controller;
return $this;
}
final public function getController() {
if (!$this->controller) {
throw new PhutilInvalidStateException('setController');
}
return $this->controller;
}
final protected function getActiveEngine() {
return $this->activeEngine;
}
final protected function getRef() {
return $this->ref;
}
final public function newDocumentView(PhabricatorDocumentRef $ref) {
$request = $this->getRequest();
$viewer = $request->getViewer();
$engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
$engine_key = $this->getSelectedDocumentEngineKey();
if (!isset($engines[$engine_key])) {
$engine_key = head_key($engines);
}
$engine = $engines[$engine_key];
$lines = $this->getSelectedLineRange();
if ($lines) {
$engine->setHighlightedLines(range($lines[0], $lines[1]));
}
$encode_setting = $request->getStr('encode');
if (strlen($encode_setting)) {
$engine->setEncodingConfiguration($encode_setting);
}
$highlight_setting = $request->getStr('highlight');
if (strlen($highlight_setting)) {
$engine->setHighlightingConfiguration($highlight_setting);
}
$blame_setting = ($request->getStr('blame') !== 'off');
$engine->setBlameConfiguration($blame_setting);
$views = array();
foreach ($engines as $candidate_key => $candidate_engine) {
$label = $candidate_engine->getViewAsLabel($ref);
if ($label === null) {
continue;
}
$view_uri = $this->newRefViewURI($ref, $candidate_engine);
$view_icon = $candidate_engine->getViewAsIconIcon($ref);
$view_color = $candidate_engine->getViewAsIconColor($ref);
$loading = $candidate_engine->newLoadingContent($ref);
$views[] = array(
'viewKey' => $candidate_engine->getDocumentEngineKey(),
'icon' => $view_icon,
'color' => $view_color,
'name' => $label,
'engineURI' => $this->newRefRenderURI($ref, $candidate_engine),
'viewURI' => $view_uri,
'loadingMarkup' => hsprintf('%s', $loading),
'canEncode' => $candidate_engine->canConfigureEncoding($ref),
'canHighlight' => $candidate_engine->canConfigureHighlighting($ref),
'canBlame' => $candidate_engine->canBlame($ref),
);
}
$viewport_id = celerity_generate_unique_node_id();
$control_id = celerity_generate_unique_node_id();
$icon = $engine->newDocumentIcon($ref);
$config = array(
'controlID' => $control_id,
);
$this->willStageRef($ref);
if ($engine->shouldRenderAsync($ref)) {
$content = $engine->newLoadingContent($ref);
$config['next'] = 'render';
} else {
$this->willRenderRef($ref);
$content = $engine->newDocument($ref);
if ($engine->canBlame($ref)) {
$config['next'] = 'blame';
}
}
Javelin::initBehavior('document-engine', $config);
$viewport = phutil_tag(
'div',
array(
'id' => $viewport_id,
),
$content);
$meta = array(
'viewportID' => $viewport_id,
'viewKey' => $engine->getDocumentEngineKey(),
'views' => $views,
'encode' => array(
'icon' => 'fa-font',
'name' => pht('Change Text Encoding...'),
'uri' => '/services/encoding/',
'value' => $encode_setting,
),
'highlight' => array(
'icon' => 'fa-lightbulb-o',
'name' => pht('Highlight As...'),
'uri' => '/services/highlight/',
'value' => $highlight_setting,
),
'blame' => array(
'icon' => 'fa-backward',
'hide' => pht('Hide Blame'),
'show' => pht('Show Blame'),
'uri' => $ref->getBlameURI(),
'enabled' => $blame_setting,
'value' => null,
),
'coverage' => array(
'labels' => array(
// TODO: Modularize this properly, see T13125.
array(
'C' => pht('Covered'),
'U' => pht('Not Covered'),
'N' => pht('Not Executable'),
'X' => pht('Not Reachable'),
),
),
),
);
$view_button = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Options'))
->setIcon('fa-file-image-o')
->setColor(PHUIButtonView::GREY)
->setID($control_id)
->setMetadata($meta)
->setDropdown(true)
->addSigil('document-engine-view-dropdown');
$header = id(new PHUIHeaderView())
->setHeaderIcon($icon)
->setHeader($ref->getName())
->addActionLink($view_button);
return id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header)
->appendChild($viewport);
}
final public function newRenderResponse(PhabricatorDocumentRef $ref) {
$this->willStageRef($ref);
$this->willRenderRef($ref);
$request = $this->getRequest();
$viewer = $request->getViewer();
$engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
$engine_key = $this->getSelectedDocumentEngineKey();
if (!isset($engines[$engine_key])) {
return $this->newErrorResponse(
pht(
'The engine ("%s") is unknown, or unable to render this document.',
$engine_key));
}
$engine = $engines[$engine_key];
$this->activeEngine = $engine;
$encode_setting = $request->getStr('encode');
if (strlen($encode_setting)) {
$engine->setEncodingConfiguration($encode_setting);
}
$highlight_setting = $request->getStr('highlight');
if (strlen($highlight_setting)) {
$engine->setHighlightingConfiguration($highlight_setting);
}
$blame_setting = ($request->getStr('blame') !== 'off');
$engine->setBlameConfiguration($blame_setting);
try {
$content = $engine->newDocument($ref);
} catch (Exception $ex) {
return $this->newErrorResponse($ex->getMessage());
}
return $this->newContentResponse($content);
}
public function newErrorResponse($message) {
$container = phutil_tag(
'div',
array(
'class' => 'document-engine-error',
),
array(
id(new PHUIIconView())
->setIcon('fa-exclamation-triangle red'),
' ',
$message,
));
return $this->newContentResponse($container);
}
private function newContentResponse($content) {
$request = $this->getRequest();
$viewer = $request->getViewer();
$controller = $this->getController();
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())
->setContent(
array(
'markup' => hsprintf('%s', $content),
));
}
$crumbs = $this->newCrumbs();
$crumbs->setBorder(true);
$content_frame = id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($content);
$page_frame = id(new PHUITwoColumnView())
->setFooter($content_frame);
$title = array();
$ref = $this->getRef();
if ($ref) {
$title = array(
$ref->getName(),
pht('Standalone'),
);
} else {
$title = pht('Document');
}
return $controller->newPage()
->setCrumbs($crumbs)
->setTitle($title)
->appendChild($page_frame);
}
protected function newCrumbs() {
$engine = $this->getActiveEngine();
$controller = $this->getController();
$crumbs = $controller->buildApplicationCrumbsForEditEngine();
$ref = $this->getRef();
$this->addApplicationCrumbs($crumbs, $ref);
if ($ref) {
$label = $engine->getViewAsLabel($ref);
if ($label) {
$crumbs->addTextCrumb($label);
}
}
return $crumbs;
}
+ public function getRefViewURI(
+ PhabricatorDocumentRef $ref,
+ PhabricatorDocumentEngine $engine) {
+ return $this->newRefViewURI($ref, $engine);
+ }
+
abstract protected function newRefViewURI(
PhabricatorDocumentRef $ref,
PhabricatorDocumentEngine $engine);
abstract protected function newRefRenderURI(
PhabricatorDocumentRef $ref,
PhabricatorDocumentEngine $engine);
protected function getSelectedDocumentEngineKey() {
return $this->getRequest()->getURIData('engineKey');
}
protected function getSelectedLineRange() {
return $this->getRequest()->getURILineRange('lines', 1000);
}
protected function addApplicationCrumbs(
PHUICrumbsView $crumbs,
PhabricatorDocumentRef $ref = null) {
return;
}
protected function willStageRef(PhabricatorDocumentRef $ref) {
return;
}
protected function willRenderRef(PhabricatorDocumentRef $ref) {
return;
}
}
diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php
index 9b4079fd46..b5736b784e 100644
--- a/src/applications/paste/controller/PhabricatorPasteViewController.php
+++ b/src/applications/paste/controller/PhabricatorPasteViewController.php
@@ -1,173 +1,230 @@
<?php
final class PhabricatorPasteViewController extends PhabricatorPasteController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$paste = id(new PhabricatorPasteQuery())
->setViewer($viewer)
->withIDs(array($id))
->needContent(true)
->needRawContent(true)
->executeOne();
if (!$paste) {
return new Aphront404Response();
}
$lines = $request->getURILineRange('lines', 1000);
if ($lines) {
$map = range($lines[0], $lines[1]);
} else {
$map = array();
}
$header = $this->buildHeaderView($paste);
$curtain = $this->buildCurtain($paste);
$subheader = $this->buildSubheaderView($paste);
$source_code = $this->buildSourceCodeView($paste, $map);
require_celerity_resource('paste-css');
$monogram = $paste->getMonogram();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($monogram)
->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$paste,
new PhabricatorPasteTransactionQuery());
$comment_view = id(new PhabricatorPasteEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($paste);
$timeline->setQuoteRef($monogram);
$comment_view->setTransactionTimeline($timeline);
+ $recommendation_view = $this->newDocumentRecommendationView($paste);
+
$paste_view = id(new PHUITwoColumnView())
->setHeader($header)
->setSubheader($subheader)
- ->setMainColumn(array(
+ ->setMainColumn(
+ array(
+ $recommendation_view,
$source_code,
$timeline,
$comment_view,
))
- ->setCurtain($curtain)
- ->addClass('ponder-question-view');
+ ->setCurtain($curtain);
return $this->newPage()
->setTitle($paste->getFullName())
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$paste->getPHID(),
))
->appendChild($paste_view);
}
private function buildHeaderView(PhabricatorPaste $paste) {
$title = (nonempty($paste->getTitle())) ?
$paste->getTitle() : pht('(An Untitled Masterwork)');
if ($paste->isArchived()) {
$header_icon = 'fa-ban';
$header_name = pht('Archived');
$header_color = 'dark';
} else {
$header_icon = 'fa-check';
$header_name = pht('Active');
$header_color = 'bluegrey';
}
$header = id(new PHUIHeaderView())
->setHeader($title)
->setUser($this->getRequest()->getUser())
->setStatus($header_icon, $header_color, $header_name)
->setPolicyObject($paste)
->setHeaderIcon('fa-clipboard');
return $header;
}
private function buildCurtain(PhabricatorPaste $paste) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($paste);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$paste,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $paste->getID();
$edit_uri = $this->getApplicationURI("edit/{$id}/");
$archive_uri = $this->getApplicationURI("archive/{$id}/");
$raw_uri = $this->getApplicationURI("raw/{$id}/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Paste'))
->setIcon('fa-pencil')
->setDisabled(!$can_edit)
->setHref($edit_uri));
if ($paste->isArchived()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Activate Paste'))
->setIcon('fa-check')
->setDisabled(!$can_edit)
->setWorkflow($can_edit)
->setHref($archive_uri));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Archive Paste'))
->setIcon('fa-ban')
->setDisabled(!$can_edit)
->setWorkflow($can_edit)
->setHref($archive_uri));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('View Raw File'))
->setIcon('fa-file-text-o')
->setHref($raw_uri));
return $curtain;
}
private function buildSubheaderView(
PhabricatorPaste $paste) {
$viewer = $this->getViewer();
$author = $viewer->renderHandle($paste->getAuthorPHID())->render();
$date = phabricator_datetime($paste->getDateCreated(), $viewer);
$author = phutil_tag('strong', array(), $author);
$author_info = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($paste->getAuthorPHID()))
->needProfileImage(true)
->executeOne();
$image_uri = $author_info->getProfileImageURI();
$image_href = '/p/'.$author_info->getUsername();
$content = pht('Authored by %s on %s.', $author, $date);
return id(new PHUIHeadThingView())
->setImage($image_uri)
->setImageHref($image_href)
->setContent($content);
}
+ private function newDocumentRecommendationView(PhabricatorPaste $paste) {
+ $viewer = $this->getViewer();
+
+ // See PHI1703. If a viewer is looking at a document in Paste which has
+ // a good rendering via a DocumentEngine, suggest they view the content
+ // in Files instead so they can see it rendered.
+
+ $ref = id(new PhabricatorDocumentRef())
+ ->setName($paste->getTitle())
+ ->setData($paste->getRawContent());
+
+ $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
+ if (!$engines) {
+ return null;
+ }
+
+ $engine = head($engines);
+ if (!$engine->shouldSuggestEngine($ref)) {
+ return null;
+ }
+
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($paste->getFilePHID()))
+ ->executeOne();
+ if (!$file) {
+ return null;
+ }
+
+ $file_ref = id(new PhabricatorDocumentRef())
+ ->setFile($file);
+
+ $view_uri = id(new PhabricatorFileDocumentRenderingEngine())
+ ->getRefViewURI($file_ref, $engine);
+
+ $view_as_label = $engine->getViewAsLabel($file_ref);
+
+ $view_as_hint = pht(
+ 'This content can be rendered as a document in Files.');
+
+ return id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
+ ->addButton(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setText($view_as_label)
+ ->setHref($view_uri)
+ ->setColor('grey'))
+ ->setErrors(
+ array(
+ $view_as_hint,
+ ));
+ }
+
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Dec 2, 12:38 AM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
431445
Default Alt Text
(47 KB)

Event Timeline