@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Provide a highlighter cache for Paste

Summary:
D4188 adds a preview of Paste contents to the list, but gets slow for large lists if you have Pygments installed since it has to spend ~200ms/item highlighting them. Instead, cache the highlighted output.

- Adds a Paste highlighting cache. This uses RemarkupCache because it has automatic GC and we don't have a more generalized cache for the moment.
- Uses the Paste cache on paste list and paste detail.
- Adds a little padding to the summary.
- Adds "..." if there's more content.
- Adds line count and language, if available. These are hidden on Mobile.
- Nothing actually uses needRawContent() but I left it there since it doesn't hurt anything and is used internally (I thought the detail view did, but it uses the file content directly, and must for security reasons).

Test Plan:
{F27710}

- Profiled paste list, saw good performance and few service calls.
- Viewed paste.
- Viewed raw paste content.

Reviewers: codeblock, btrahan, chad

Reviewed By: btrahan

CC: aran

Differential Revision: https://secure.phabricator.com/D4204

+165 -53
+1 -3
src/__phutil_library_map__.php
··· 781 781 'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php', 782 782 'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php', 783 783 'PhabricatorFileShortcutController' => 'applications/files/controller/PhabricatorFileShortcutController.php', 784 - 'PhabricatorFileSideNavView' => 'applications/files/view/PhabricatorFileSideNavView.php', 785 784 'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php', 786 785 'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php', 787 786 'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php', ··· 2066 2065 'PhabricatorFileListController' => 'PhabricatorFileController', 2067 2066 'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2068 2067 'PhabricatorFileShortcutController' => 'PhabricatorFileController', 2069 - 'PhabricatorFileSideNavView' => 'AphrontView', 2070 2068 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 2071 2069 'PhabricatorFileStorageConfigurationException' => 'Exception', 2072 2070 'PhabricatorFileTransformController' => 'PhabricatorFileController', ··· 2278 2276 'PhabricatorRemarkupRuleCountdown' => 'PhutilRemarkupRule', 2279 2277 'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName', 2280 2278 'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle', 2281 - 'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule', 2279 + 'PhabricatorRemarkupRuleDiffusion' => 'PhabricatorRemarkupRuleObjectName', 2282 2280 'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule', 2283 2281 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', 2284 2282 'PhabricatorRemarkupRuleManiphest' => 'PhabricatorRemarkupRuleObjectName',
+3 -19
src/applications/paste/controller/PhabricatorPasteController.php
··· 41 41 42 42 public function buildSourceCodeView( 43 43 PhabricatorPaste $paste, 44 - PhabricatorFile $file, 45 44 $max_lines = null) { 46 45 47 - $language = $paste->getLanguage(); 48 - $source = $file->loadFileData(); 49 - 50 - if (empty($language)) { 51 - $source = PhabricatorSyntaxHighlighter::highlightWithFilename( 52 - $paste->getTitle(), 53 - $source); 54 - } else { 55 - $source = PhabricatorSyntaxHighlighter::highlightWithLanguage( 56 - $language, 57 - $source); 58 - } 59 - 60 - $lines = explode("\n", $source); 61 - 62 - if ($max_lines) { 63 - $lines = array_slice($lines, 0, $max_lines); 64 - } 46 + $lines = explode("\n", rtrim($paste->getContent())); 65 47 66 48 return id(new PhabricatorSourceCodeView()) 49 + ->setLimit($max_lines) 67 50 ->setLines($lines); 68 51 } 52 + 69 53 }
+20 -14
src/applications/paste/controller/PhabricatorPasteListController.php
··· 16 16 $request = $this->getRequest(); 17 17 $user = $request->getUser(); 18 18 19 - $query = new PhabricatorPasteQuery(); 20 - $query->setViewer($user); 19 + $query = id(new PhabricatorPasteQuery()) 20 + ->setViewer($user) 21 + ->needContent(true); 21 22 22 23 $nav = $this->buildSideNavView($this->filter); 23 24 $filter = $nav->getSelectedFilter(); ··· 76 77 77 78 $this->loadHandles(mpull($pastes, 'getAuthorPHID')); 78 79 79 - $file_phids = mpull($pastes, 'getFilePHID'); 80 - $files = array(); 81 - if ($file_phids) { 82 - $files = id(new PhabricatorFile())->loadAllWhere( 83 - "phid IN (%Ls)", 84 - $file_phids); 85 - } 86 - $files_map = mpull($files, null, 'getPHID'); 80 + $lang_map = PhabricatorEnv::getEnvConfig('pygments.dropdown-choices'); 87 81 88 82 $list = new PhabricatorObjectItemListView(); 89 83 $list->setViewer($user); 90 84 foreach ($pastes as $paste) { 91 85 $created = phabricator_date($paste->getDateCreated(), $user); 92 86 $author = $this->getHandle($paste->getAuthorPHID())->renderLink(); 93 - 94 - $file_phid = $paste->getFilePHID(); 95 - $file = idx($files_map, $file_phid); 87 + $source_code = $this->buildSourceCodeView($paste, 5)->render(); 88 + $source_code = phutil_render_tag( 89 + 'div', 90 + array( 91 + 'class' => 'phabricator-source-code-summary', 92 + ), 93 + $source_code); 96 94 97 - $source_code = $this->buildSourceCodeView($paste, $file, 5)->render(); 95 + $line_count = count(explode("\n", $paste->getContent())); 98 96 99 97 $item = id(new PhabricatorObjectItemView()) 100 98 ->setHeader($paste->getFullName()) 101 99 ->setHref('/P'.$paste->getID()) 102 100 ->setObject($paste) 103 101 ->addAttribute(pht('Created %s by %s', $created, $author)) 102 + ->addIcon('none', pht('%s Line(s)', number_format($line_count))) 104 103 ->appendChild($source_code); 104 + 105 + $lang_name = $paste->getLanguage(); 106 + if ($lang_name) { 107 + $lang_name = idx($lang_map, $lang_name, $lang_name); 108 + $item->addIcon('none', phutil_escape_html($lang_name)); 109 + } 110 + 105 111 $list->addItem($item); 106 112 } 107 113
+2 -1
src/applications/paste/controller/PhabricatorPasteViewController.php
··· 20 20 $paste = id(new PhabricatorPasteQuery()) 21 21 ->setViewer($user) 22 22 ->withIDs(array($this->id)) 23 + ->needContent(true) 23 24 ->executeOne(); 24 25 if (!$paste) { 25 26 return new Aphront404Response(); ··· 49 50 $header = $this->buildHeaderView($paste); 50 51 $actions = $this->buildActionView($user, $paste, $file); 51 52 $properties = $this->buildPropertyView($paste, $fork_phids); 52 - $source_code = $this->buildSourceCodeView($paste, $file); 53 + $source_code = $this->buildSourceCodeView($paste); 53 54 54 55 $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()) 55 56 ->addCrumb(
+89 -13
src/applications/paste/query/PhabricatorPasteQuery.php
··· 9 9 private $parentPHIDs; 10 10 11 11 private $needContent; 12 + private $needRawContent; 12 13 13 14 public function withIDs(array $ids) { 14 15 $this->ids = $ids; ··· 35 36 return $this; 36 37 } 37 38 39 + public function needRawContent($need_raw_content) { 40 + $this->needRawContent = $need_raw_content; 41 + return $this; 42 + } 43 + 38 44 public function loadPage() { 39 45 $table = new PhabricatorPaste(); 40 46 $conn_r = $table->establishConnection('r'); ··· 49 55 50 56 $pastes = $table->loadAllFromArray($data); 51 57 58 + if ($pastes && $this->needRawContent) { 59 + $this->loadRawContent($pastes); 60 + } 61 + 52 62 if ($pastes && $this->needContent) { 53 - $file_phids = mpull($pastes, 'getFilePHID'); 54 - $files = id(new PhabricatorFile())->loadAllWhere( 55 - 'phid IN (%Ls)', 56 - $file_phids); 57 - $files = mpull($files, null, 'getPHID'); 58 - foreach ($pastes as $paste) { 59 - $file = idx($files, $paste->getFilePHID()); 60 - if ($file) { 61 - $paste->attachContent($file->loadFileData()); 62 - } else { 63 - $paste->attachContent(''); 64 - } 65 - } 63 + $this->loadContent($pastes); 66 64 } 67 65 68 66 return $pastes; ··· 102 100 } 103 101 104 102 return $this->formatWhereClause($where); 103 + } 104 + 105 + private function getContentCacheKey(PhabricatorPaste $paste) { 106 + return 'P'.$paste->getID().':content/'.$paste->getLanguage(); 107 + } 108 + 109 + private function loadRawContent(array $pastes) { 110 + $file_phids = mpull($pastes, 'getFilePHID'); 111 + $files = id(new PhabricatorFile())->loadAllWhere( 112 + 'phid IN (%Ls)', 113 + $file_phids); 114 + $files = mpull($files, null, 'getPHID'); 115 + 116 + foreach ($pastes as $paste) { 117 + $file = idx($files, $paste->getFilePHID()); 118 + if ($file) { 119 + $paste->attachRawContent($file->loadFileData()); 120 + } else { 121 + $paste->attachRawContent(''); 122 + } 123 + } 124 + } 125 + 126 + private function loadContent(array $pastes) { 127 + $keys = array(); 128 + foreach ($pastes as $paste) { 129 + $keys[] = $this->getContentCacheKey($paste); 130 + } 131 + 132 + // TODO: Move to a more appropriate/general cache once we have one? For 133 + // now, this gets automatic GC. 134 + $caches = id(new PhabricatorMarkupCache())->loadAllWhere( 135 + 'cacheKey IN (%Ls)', 136 + $keys); 137 + $caches = mpull($caches, null, 'getCacheKey'); 138 + 139 + $need_raw = array(); 140 + foreach ($pastes as $paste) { 141 + $key = $this->getContentCacheKey($paste); 142 + if (isset($caches[$key])) { 143 + $paste->attachContent($caches[$key]->getCacheData()); 144 + } else { 145 + $need_raw[] = $paste; 146 + } 147 + } 148 + 149 + if (!$need_raw) { 150 + return; 151 + } 152 + 153 + $this->loadRawContent($need_raw); 154 + foreach ($need_raw as $paste) { 155 + $content = $this->buildContent($paste); 156 + $paste->attachContent($content); 157 + 158 + $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); 159 + id(new PhabricatorMarkupCache()) 160 + ->setCacheKey($this->getContentCacheKey($paste)) 161 + ->setCacheData($content) 162 + ->replace(); 163 + unset($guard); 164 + } 165 + } 166 + 167 + 168 + private function buildContent(PhabricatorPaste $paste) { 169 + $language = $paste->getLanguage(); 170 + $source = $paste->getRawContent(); 171 + 172 + if (empty($language)) { 173 + return PhabricatorSyntaxHighlighter::highlightWithFilename( 174 + $paste->getTitle(), 175 + $source); 176 + } else { 177 + return PhabricatorSyntaxHighlighter::highlightWithLanguage( 178 + $language, 179 + $source); 180 + } 105 181 } 106 182 107 183 }
+13
src/applications/paste/storage/PhabricatorPaste.php
··· 12 12 protected $viewPolicy; 13 13 14 14 private $content; 15 + private $rawContent; 15 16 16 17 public function getURI() { 17 18 return '/P'.$this->getID(); ··· 63 64 64 65 public function attachContent($content) { 65 66 $this->content = $content; 67 + return $this; 68 + } 69 + 70 + public function getRawContent() { 71 + if ($this->rawContent === null) { 72 + throw new Exception("Call attachRawContent() before getRawContent()!"); 73 + } 74 + return $this->rawContent; 75 + } 76 + 77 + public function attachRawContent($raw_content) { 78 + $this->rawContent = $raw_content; 66 79 return $this; 67 80 } 68 81
+5
src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php
··· 193 193 ), 194 194 ), 195 195 196 + '%s Line(s)' => array( 197 + '%s Line', 198 + '%s Lines', 199 + ), 200 + 196 201 ); 197 202 } 198 203
+28 -3
src/view/layout/PhabricatorSourceCodeView.php
··· 3 3 final class PhabricatorSourceCodeView extends AphrontView { 4 4 5 5 private $lines; 6 + private $limit; 7 + 8 + public function setLimit($limit) { 9 + $this->limit = $limit; 10 + return $this; 11 + } 6 12 7 13 public function setLines(array $lines) { 8 14 $this->lines = $lines; ··· 19 25 20 26 $rows = array(); 21 27 foreach ($this->lines as $line) { 28 + $hit_limit = $this->limit && 29 + ($line_number == $this->limit) && 30 + (count($this->lines) != $this->limit); 31 + 32 + if ($hit_limit) { 33 + $content_number = ''; 34 + $content_line = phutil_render_tag( 35 + 'span', 36 + array( 37 + 'class' => 'c', 38 + ), 39 + pht('...')); 40 + } else { 41 + $content_number = phutil_escape_html($line_number); 42 + $content_line = "\xE2\x80\x8B".$line; 43 + } 22 44 23 45 // TODO: Provide nice links. 24 46 25 47 $rows[] = 26 48 '<tr>'. 27 49 '<th class="phabricator-source-line">'. 28 - phutil_escape_html($line_number). 50 + $content_number. 29 51 '</th>'. 30 52 '<td class="phabricator-source-code">'. 31 - "\xE2\x80\x8B". 32 - $line. 53 + $content_line. 33 54 '</td>'. 34 55 '</tr>'; 56 + 57 + if ($hit_limit) { 58 + break; 59 + } 35 60 36 61 $line_number++; 37 62 }
+4
webroot/rsrc/css/layout/phabricator-source-code-view.css
··· 24 24 -ms-user-select: none; 25 25 user-select: none; 26 26 } 27 + 28 + .phabricator-source-code-summary { 29 + margin-bottom: 4px; 30 + }