@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.

Make "Open in Editor" use the simple line number of the current selected block

Summary:
Ref PHI1749. Instead of opening files to the last unchanged line on either side of the change, open files to the "simple" line number of the selected block.

For inlines, this is the inline line number.

For blocks, this is the first new-file line number, or the first old-file line number if no new-file line number exists in the block.

This may not always be what the user is hoping for (we can't know what the state of their working copy is) but should produce more obvious behavior.

Test Plan:
- In Diffusion, used "Open in Editor" with and without line selections. Saw same behavior as before.
- Used "n" and "r" to leave an inline with the keyboard, saw same behavior as before.
- Used "\" and "Open in Editor" menu item to open a file with:
- Nothing selected or changeset selected (line: 1).
- An inline selected (line: inline line).
- A block selected (line: first line in block, per above).

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

+209 -104
+39 -31
resources/celerity/map.php
··· 13 13 'core.pkg.js' => '845355f4', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => '5c459f92', 16 - 'differential.pkg.js' => '24616785', 16 + 'differential.pkg.js' => 'a7171fb6', 17 17 'diffusion.pkg.css' => '42c75c37', 18 18 'diffusion.pkg.js' => 'a98c0bf7', 19 19 'maniphest.pkg.css' => '35995d6d', ··· 379 379 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be', 380 380 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9', 381 381 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8', 382 - 'rsrc/js/application/diff/DiffChangeset.js' => '6e5e03d2', 383 - 'rsrc/js/application/diff/DiffChangesetList.js' => 'b51ba93a', 382 + 'rsrc/js/application/diff/DiffChangeset.js' => '3a1ca35b', 383 + 'rsrc/js/application/diff/DiffChangesetList.js' => 'cc2c5de5', 384 384 'rsrc/js/application/diff/DiffInline.js' => '008b6a15', 385 385 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9', 386 386 'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b', 387 387 'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd', 388 388 'rsrc/js/application/differential/behavior-populate.js' => 'b86ef6c2', 389 389 'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89', 390 + 'rsrc/js/application/diffusion/ExternalEditorLinkEngine.js' => '48a8641f', 390 391 'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831', 391 392 'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572', 392 393 'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2', ··· 484 485 'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731', 485 486 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b', 486 487 'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf', 487 - 'rsrc/js/core/behavior-line-linker.js' => '590e6527', 488 + 'rsrc/js/core/behavior-line-linker.js' => '0d915ff5', 488 489 'rsrc/js/core/behavior-linked-container.js' => '74446546', 489 490 'rsrc/js/core/behavior-more.js' => '506aa3f4', 490 491 'rsrc/js/core/behavior-object-selector.js' => '98ef467f', ··· 645 646 'javelin-behavior-phabricator-gesture-example' => '242dedd0', 646 647 'javelin-behavior-phabricator-keyboard-pager' => '1325b731', 647 648 'javelin-behavior-phabricator-keyboard-shortcuts' => '42c44e8b', 648 - 'javelin-behavior-phabricator-line-linker' => '590e6527', 649 + 'javelin-behavior-phabricator-line-linker' => '0d915ff5', 649 650 'javelin-behavior-phabricator-notification-example' => '29819b75', 650 651 'javelin-behavior-phabricator-object-selector' => '98ef467f', 651 652 'javelin-behavior-phabricator-oncopy' => 'da8f5259', ··· 709 710 'javelin-dom' => '94681e22', 710 711 'javelin-dynval' => '202a2e85', 711 712 'javelin-event' => 'c03f2fb4', 713 + 'javelin-external-editor-link-engine' => '48a8641f', 712 714 'javelin-fx' => '34450586', 713 715 'javelin-history' => '030b4f7a', 714 716 'javelin-install' => '5902260c', ··· 774 776 'phabricator-darklog' => '3b869402', 775 777 'phabricator-darkmessage' => '26cd4b73', 776 778 'phabricator-dashboard-css' => '5a205b9d', 777 - 'phabricator-diff-changeset' => '6e5e03d2', 778 - 'phabricator-diff-changeset-list' => 'b51ba93a', 779 + 'phabricator-diff-changeset' => '3a1ca35b', 780 + 'phabricator-diff-changeset-list' => 'cc2c5de5', 779 781 'phabricator-diff-inline' => '008b6a15', 780 782 'phabricator-diff-path-view' => '8207abf9', 781 783 'phabricator-diff-tree-view' => '5d83623b', ··· 1013 1015 '0d2490ce' => array( 1014 1016 'javelin-install', 1015 1017 ), 1018 + '0d915ff5' => array( 1019 + 'javelin-behavior', 1020 + 'javelin-stratcom', 1021 + 'javelin-dom', 1022 + 'javelin-history', 1023 + 'javelin-external-editor-link-engine', 1024 + ), 1016 1025 '0eaa33a9' => array( 1017 1026 'javelin-behavior', 1018 1027 'javelin-dom', ··· 1222 1231 'trigger-rule', 1223 1232 'trigger-rule-type', 1224 1233 ), 1234 + '3a1ca35b' => array( 1235 + 'javelin-dom', 1236 + 'javelin-util', 1237 + 'javelin-stratcom', 1238 + 'javelin-install', 1239 + 'javelin-workflow', 1240 + 'javelin-router', 1241 + 'javelin-behavior-device', 1242 + 'javelin-vector', 1243 + 'phabricator-diff-inline', 1244 + 'phabricator-diff-path-view', 1245 + 'phuix-button-view', 1246 + 'javelin-external-editor-link-engine', 1247 + ), 1225 1248 '3ae89b20' => array( 1226 1249 'phui-workcard-view-css', 1227 1250 ), ··· 1305 1328 'javelin-workflow', 1306 1329 'javelin-dom', 1307 1330 'phabricator-draggable-list', 1331 + ), 1332 + '48a8641f' => array( 1333 + 'javelin-install', 1308 1334 ), 1309 1335 '48fe33d0' => array( 1310 1336 'javelin-behavior', ··· 1442 1468 'javelin-util', 1443 1469 'javelin-magical-init', 1444 1470 ), 1445 - '590e6527' => array( 1446 - 'javelin-behavior', 1447 - 'javelin-stratcom', 1448 - 'javelin-dom', 1449 - 'javelin-history', 1450 - ), 1451 1471 '5a6f6a06' => array( 1452 1472 'javelin-behavior', 1453 1473 'javelin-quicksand', ··· 1551 1571 'javelin-install', 1552 1572 'javelin-util', 1553 1573 ), 1554 - '6e5e03d2' => array( 1555 - 'javelin-dom', 1556 - 'javelin-util', 1557 - 'javelin-stratcom', 1558 - 'javelin-install', 1559 - 'javelin-workflow', 1560 - 'javelin-router', 1561 - 'javelin-behavior-device', 1562 - 'javelin-vector', 1563 - 'phabricator-diff-inline', 1564 - 'phabricator-diff-path-view', 1565 - 'phuix-button-view', 1566 - ), 1567 1574 70245195 => array( 1568 1575 'javelin-behavior', 1569 1576 'javelin-stratcom', ··· 1970 1977 'b517bfa0' => array( 1971 1978 'phui-oi-list-view-css', 1972 1979 ), 1973 - 'b51ba93a' => array( 1974 - 'javelin-install', 1975 - 'phuix-button-view', 1976 - 'phabricator-diff-tree-view', 1977 - ), 1978 1980 'b557770a' => array( 1979 1981 'javelin-install', 1980 1982 'javelin-util', ··· 2081 2083 'javelin-util', 2082 2084 'phuix-icon-view', 2083 2085 'phabricator-busy', 2086 + ), 2087 + 'cc2c5de5' => array( 2088 + 'javelin-install', 2089 + 'phuix-button-view', 2090 + 'phabricator-diff-tree-view', 2084 2091 ), 2085 2092 'cef53b3e' => array( 2086 2093 'javelin-install', ··· 2422 2429 'phuix-formation-view', 2423 2430 'phuix-formation-column-view', 2424 2431 'phuix-formation-flank-view', 2432 + 'javelin-external-editor-link-engine', 2425 2433 ), 2426 2434 'diffusion.pkg.css' => array( 2427 2435 'diffusion-icons-css',
+2
resources/celerity/packages.php
··· 220 220 'phuix-formation-view', 221 221 'phuix-formation-column-view', 222 222 'phuix-formation-flank-view', 223 + 224 + 'javelin-external-editor-link-engine', 223 225 ), 224 226 'diffusion.pkg.css' => array( 225 227 'diffusion-icons-css',
+3 -5
src/applications/differential/view/DifferentialChangesetDetailView.php
··· 252 252 'isLowImportance' => $changeset->getIsLowImportanceChangeset(), 253 253 'isOwned' => $changeset->getIsOwnedChangeset(), 254 254 255 - 'editorURI' => $this->getEditorURI(), 255 + 'editorURITemplate' => $this->getEditorURITemplate(), 256 256 'editorConfigureURI' => $this->getEditorConfigureURI(), 257 257 258 258 'loaded' => $is_loaded, ··· 321 321 return $this->diff; 322 322 } 323 323 324 - private function getEditorURI() { 324 + private function getEditorURITemplate() { 325 325 $repository = $this->getRepository(); 326 326 if (!$repository) { 327 327 return null; ··· 342 342 $path = $changeset->getAbsoluteRepositoryPath($repository, $diff); 343 343 $path = ltrim($path, '/'); 344 344 345 - $line = idx($changeset->getMetadata(), 'line:first', 1); 346 - 347 - return $link_engine->getURIForPath($path, $line); 345 + return $link_engine->getURITokensForPath($path); 348 346 } 349 347 350 348 private function getEditorConfigureURI() {
+5 -4
webroot/rsrc/js/application/diff/DiffChangeset.js
··· 11 11 * phabricator-diff-inline 12 12 * phabricator-diff-path-view 13 13 * phuix-button-view 14 + * javelin-external-editor-link-engine 14 15 * @javelin 15 16 */ 16 17 ··· 33 34 this._pathParts = data.pathParts; 34 35 this._icon = data.icon; 35 36 36 - this._editorURI = data.editorURI; 37 + this._editorURITemplate = data.editorURITemplate; 37 38 this._editorConfigureURI = data.editorConfigureURI; 38 39 this._showPathURI = data.showPathURI; 39 40 this._showDirectoryURI = data.showDirectoryURI; ··· 87 88 _changesetList: null, 88 89 _icon: null, 89 90 90 - _editorURI: null, 91 + _editorURITemplate: null, 91 92 _editorConfigureURI: null, 92 93 _showPathURI: null, 93 94 _showDirectoryURI: null, ··· 102 103 _isSelected: false, 103 104 _viewMenu: null, 104 105 105 - getEditorURI: function() { 106 - return this._editorURI; 106 + getEditorURITemplate: function() { 107 + return this._editorURITemplate; 107 108 }, 108 109 109 110 getEditorConfigureURI: function() {
+112 -45
webroot/rsrc/js/application/diff/DiffChangesetList.js
··· 355 355 // lines) reply on the old file. 356 356 357 357 if (cursor.type == 'change') { 358 - var origin = cursor.nodes.begin; 359 - var target = cursor.nodes.end; 358 + var cells = this._getLineNumberCellsForChangeBlock( 359 + cursor.nodes.begin, 360 + cursor.nodes.end); 360 361 361 - // The "origin" and "target" are entire rows, but we need to find 362 - // a range of "<th />" nodes to actually create an inline, so go 363 - // fishing. 362 + cursor.changeset.newInlineForRange(cells.src, cells.dst); 364 363 365 - var old_list = []; 366 - var new_list = []; 364 + this.setFocus(null); 365 + return; 366 + } 367 + } 367 368 368 - var row = origin; 369 - while (row) { 370 - var header = row.firstChild; 371 - while (header) { 372 - if (this.getLineNumberFromHeader(header)) { 373 - if (header.className.indexOf('old') !== -1) { 374 - old_list.push(header); 375 - } else if (header.className.indexOf('new') !== -1) { 376 - new_list.push(header); 377 - } 378 - } 379 - header = header.nextSibling; 380 - } 369 + var pht = this.getTranslations(); 370 + this._warnUser(pht('You must select a comment or change to reply to.')); 371 + }, 381 372 382 - if (row == target) { 383 - break; 384 - } 373 + _getLineNumberCellsForChangeBlock: function(origin, target) { 374 + // The "origin" and "target" are entire rows, but we need to find 375 + // a range of cell nodes to actually create an inline, so go 376 + // fishing. 385 377 386 - row = row.nextSibling; 387 - } 378 + var old_list = []; 379 + var new_list = []; 388 380 389 - var use_list; 390 - if (new_list.length) { 391 - use_list = new_list; 392 - } else { 393 - use_list = old_list; 381 + var row = origin; 382 + while (row) { 383 + var header = row.firstChild; 384 + while (header) { 385 + if (this.getLineNumberFromHeader(header)) { 386 + if (header.className.indexOf('old') !== -1) { 387 + old_list.push(header); 388 + } else if (header.className.indexOf('new') !== -1) { 389 + new_list.push(header); 390 + } 394 391 } 392 + header = header.nextSibling; 393 + } 395 394 396 - var src = use_list[0]; 397 - var dst = use_list[use_list.length - 1]; 395 + if (row == target) { 396 + break; 397 + } 398 398 399 - cursor.changeset.newInlineForRange(src, dst); 399 + row = row.nextSibling; 400 + } 400 401 401 - this.setFocus(null); 402 - return; 403 - } 402 + var use_list; 403 + if (new_list.length) { 404 + use_list = new_list; 405 + } else { 406 + use_list = old_list; 404 407 } 405 408 406 - var pht = this.getTranslations(); 407 - this._warnUser(pht('You must select a comment or change to reply to.')); 409 + var src = use_list[0]; 410 + var dst = use_list[use_list.length - 1]; 411 + 412 + return { 413 + src: src, 414 + dst: dst 415 + }; 408 416 }, 409 417 410 418 _onkeyedit: function() { ··· 505 513 return changeset; 506 514 }, 507 515 508 - _onkeyopeneditor: function() { 516 + _onkeyopeneditor: function(e) { 509 517 var pht = this.getTranslations(); 510 518 var changeset = this._getChangesetForKeyCommand(); 511 519 ··· 514 522 return; 515 523 } 516 524 517 - var editor_uri = changeset.getEditorURI(); 525 + this._openEditor(changeset); 526 + }, 518 527 519 - if (editor_uri === null) { 528 + _openEditor: function(changeset) { 529 + var pht = this.getTranslations(); 530 + 531 + var editor_template = changeset.getEditorURITemplate(); 532 + if (editor_template === null) { 520 533 this._warnUser(pht('No external editor is configured.')); 521 534 return; 522 535 } 536 + 537 + var line = null; 538 + 539 + // See PHI1749. We aren't exactly sure what the user intends when they 540 + // use the keyboard to select a change block and then activate the 541 + // "Open in Editor" function: they might mean to open the old or new 542 + // offset, and may have the old or new state (or some other state) in 543 + // their working copy. 544 + 545 + // For now, pick: the new state line number if one exists; or the old 546 + // state line number if one does not. If nothing else, this behavior is 547 + // simple. 548 + 549 + // If there's a document engine, just open the file to the first line. 550 + // We currently can not map display blocks to source lines. 551 + 552 + // If there's an inline, open the file to that line. 553 + 554 + if (changeset.getResponseDocumentEngineKey() === null) { 555 + var cursor = this._cursorItem; 556 + if (cursor && (cursor.changeset === changeset)) { 557 + if (cursor.type == 'change') { 558 + var cells = this._getLineNumberCellsForChangeBlock( 559 + cursor.nodes.begin, 560 + cursor.nodes.end); 561 + line = this.getLineNumberFromHeader(cells.src); 562 + } 563 + 564 + if (cursor.type === 'comment') { 565 + var inline = cursor.target; 566 + line = inline.getLineNumber(); 567 + } 568 + } 569 + } 570 + 571 + var variables = { 572 + l: line || 1 573 + }; 574 + 575 + var editor_uri = new JX.ExternalEditorLinkEngine() 576 + .setTemplate(editor_template) 577 + .setVariables(variables) 578 + .newURI(); 523 579 524 580 JX.$U(editor_uri).go(); 525 581 }, ··· 1029 1085 changeset.getShowPathURI()) 1030 1086 .setKeyCommand('d'); 1031 1087 1032 - var editor_uri = changeset.getEditorURI(); 1033 - if (editor_uri !== null) { 1034 - add_link('fa-i-cursor', pht('Open in Editor'), editor_uri, true) 1035 - .setKeyCommand('\\'); 1088 + var editor_template = changeset.getEditorURITemplate(); 1089 + if (editor_template !== null) { 1090 + var editor_item = new JX.PHUIXActionView() 1091 + .setIcon('fa-i-cursor') 1092 + .setName(pht('Open in Editor')) 1093 + .setKeyCommand('\\') 1094 + .setHandler(function(e) { 1095 + 1096 + changeset_list._openEditor(changeset); 1097 + 1098 + e.prevent(); 1099 + menu.close(); 1100 + }); 1101 + 1102 + list.addItem(editor_item); 1036 1103 } else { 1037 1104 var configure_uri = changeset.getEditorConfigureURI(); 1038 1105 if (configure_uri !== null) {
+41
webroot/rsrc/js/application/diffusion/ExternalEditorLinkEngine.js
··· 1 + /** 2 + * @provides javelin-external-editor-link-engine 3 + * @requires javelin-install 4 + * @javelin 5 + */ 6 + 7 + JX.install('ExternalEditorLinkEngine', { 8 + 9 + properties: { 10 + template: null, 11 + variables: null 12 + }, 13 + 14 + members: { 15 + newURI: function() { 16 + var template = this.getTemplate(); 17 + var variables = this.getVariables(); 18 + 19 + var parts = []; 20 + for (var ii = 0; ii < template.length; ii++) { 21 + var part = template[ii]; 22 + var value = part.value; 23 + 24 + if (part.type === 'literal') { 25 + parts.push(value); 26 + continue; 27 + } 28 + 29 + if (part.type === 'variable') { 30 + if (variables.hasOwnProperty(value)) { 31 + var replacement = variables[value]; 32 + replacement = encodeURIComponent(replacement); 33 + parts.push(replacement); 34 + } 35 + } 36 + } 37 + 38 + return parts.join(''); 39 + } 40 + } 41 + });
+7 -19
webroot/rsrc/js/core/behavior-line-linker.js
··· 4 4 * javelin-stratcom 5 5 * javelin-dom 6 6 * javelin-history 7 + * javelin-external-editor-link-engine 7 8 */ 8 9 9 10 JX.behavior('phabricator-line-linker', function() { ··· 170 171 171 172 if (editor_link) { 172 173 var data = JX.Stratcom.getData(editor_link); 173 - var template = data.template; 174 174 175 175 var variables = { 176 176 l: parseInt(Math.min(o, t), 10), 177 177 }; 178 178 179 - var parts = []; 180 - for (var ii = 0; ii < template.length; ii++) { 181 - var part = template[ii]; 182 - var value = part.value; 179 + var template = data.template; 183 180 184 - if (part.type === 'literal') { 185 - parts.push(value); 186 - continue; 187 - } 181 + var editor_uri = new JX.ExternalEditorLinkEngine() 182 + .setTemplate(template) 183 + .setVariables(variables) 184 + .newURI(); 188 185 189 - if (part.type === 'variable') { 190 - if (variables.hasOwnProperty(value)) { 191 - var replacement = variables[value]; 192 - replacement = encodeURIComponent(replacement); 193 - parts.push(replacement); 194 - } 195 - } 196 - } 197 - 198 - editor_link.href = parts.join(''); 186 + editor_link.href = editor_uri; 199 187 } 200 188 }); 201 189