Split text-diff expander into up, both, and down controls #3069
Service Desk opened 6 hours ago

Motivation

Text diffs use a single Show more lines control. Between two change hunks, each click grows the hidden gap from both ends (about 15 lines toward the hunk above and 15 toward the hunk below). The expander row stays in the remaining gap. At the start of a file the only direction is more lines above the first change; at the end of a file the only direction is more lines below the last change.

The current control is one small icon in the gutter. Reviewers need large, obvious hit targets, plus a way to expand toward only one hunk without also growing the other side.

Expected behavior

  • Apply on every text diff that uses this expander: commit detail, pull requests, and revision compare (unified and split, blame on or off), plus other PlainTextDiffPanel surfaces (blob edit preview, comment revisions, workspace changes).
  • Between two change hunks, replace the single expander with three equal-width hit targets across the full row (up | both | down). Simple vertical splits; no angled divider.
    • Left: expand only toward the hunk above (DIFF_EXPAND_SIZE more lines from the top of the remaining gap).
    • Middle: today's both-sides expand (DIFF_EXPAND_SIZE from both ends). Put the skipped-line count on this control.
    • Right: expand only toward the hunk below (DIFF_EXPAND_SIZE more lines from the bottom of the remaining gap).
  • At the start or end of a file, keep a single expander (today's control). Only one direction exists.
  • After one-sided expands, if only one direction still has hidden lines, drop to that same single expander. When the remaining equal-block is fully shown, the expander row disappears (existing behavior).
  • Repeat clicks until the remaining equal-block is fully shown.
  • Out of scope: a separate "reveal all remaining hidden lines" control.

Current implementation

Shared expand logic lives in DiffExpandSupport:

  • One contextSize per equal-block (default WebConstants.DIFF_CONTEXT_SIZE = 3). Each click adds WebConstants.DIFF_EXPAND_SIZE (15) to both ends.
  • First equal-block: hide a prefix, expander at the top, then the last N context lines.
  • Last equal-block: first N context lines, expander at the bottom, hide a suffix.
  • Middle equal-block: first N lines, expander, last N lines. A click increases N from both ends. When 2 * contextSize covers the block, the expander is omitted and the rest of the block is shown.

Markup is built by:

  • BlobTextDiffPanel.appendExpander — commit/PR/compare text diffs (BlobDiffPanel uses this panel). Unified vs split and blame vs no-blame use different colspans. Icon is expand2. Click calls callback('expand', blockIndex).
  • PlainTextDiffPanel.appendExpander — other text diffs. Same single-icon row.

Styling is in server-core/src/main/java/io/onedev/server/web/asset/textdiff/text-diff.css (td.expander a is already a large block with hover). After expand, blob-text-diff.js and plain-text-diff.js replace tr.expander{blockIndex} and re-bind tippy on td.expander a.

Implementation plan

  1. Track top and bottom context independently for middle gaps. DiffExpandSupport currently stores one contextSize per block and expand() grows both ends. Keep that for first/last blocks and for the middle "both" action. For middle blocks, also store separate top/bottom sizes (or equivalent) so expand-up and expand-down can grow one end only. Cap so top + bottom never exceed the block length.

  2. Tell the renderer which controls to draw. appendEquals already knows first / middle / last, and can know when only one end of a remaining gap can still grow. Extend ExpandCallback.appendExpander with enough information to render:

    • three controls (both directions still available),
    • or a single expander (file start/end, or one direction exhausted). Do not change first/last expand math except to keep using the existing single expand().
  3. Render the controls. In BlobTextDiffPanel.appendExpander and PlainTextDiffPanel.appendExpander:

    • Middle with both directions: three adjacent <a> elements spanning the full row (expander + skipped cells) so they stay easy to click in split view. Left: up chevron, expand up. Middle: expand2 (or equivalent) plus skipped-line count, expand both. Right: down chevron, expand down.
    • Start/end, or only one remaining direction: keep today's single expand2 control and "skipped N lines" label.
    • Preserve unified/split and blame colspans so the table layout does not break.
  4. Wire direction through the Ajax callback. BlobTextDiffPanel already passes param1param8. Use a direction argument (up / down / both) with the existing expand action (or dedicated actions). PlainTextDiffPanel currently only passes action and param1; add a direction parameter there. First/last single expander keeps today's expand (only one direction exists).

  5. Icons. Keep expand2 for the single expander and the middle "both" control. For up/down, use existing arrow/chevron sprites (arrow, arrow2, arrow3, arrow4) rotated to point up and down, or add two dedicated chevron icons if those assets do not read clearly at this size. No angled-divider artwork.

  6. CSS. Update text-diff.css (and blob-text-diff.css only if blob-specific borders/colspans need it):

    • Three equal-width hit targets on one row, simple vertical separators (not angled).
    • Keep the large hit area and existing hover/focus (--primary / white) and dark-mode colors.
    • Do not shrink the start/end single expander.
  7. JS. Row replacement stays the same (tr.expander{blockIndex}). Re-bind tippy on every td.expander a after expand (already the case). Selection-drag code that treats tr.expander as a unit should keep working if the extra links stay inside that row.

  8. Translations. Tooltip can stay "Show more lines" for the single expander and the middle "both" control. Directional tooltips for left/right are fine if they stay in the same _T(...) pattern. Do not edit generated Translation_*.java files.

  9. Verify.

    • Commit detail, pull request, revision compare.
    • Unified and split; blame on and off.
    • File-start single expander, file-end single expander, middle three-control expander.
    • Expand up only, down only, and both; several clicks until the gap is gone.
    • After one-sided expands, when only one direction remains, the row becomes a single expander.
    • PlainTextDiffPanel surfaces (edit preview, comment revision).
    • Keyboard focus and tooltip on each target.
    • Light and dark mode.

Related background (not required to implement): issue #3068.

  • Zak Siddiqui commented 6 hours ago

    Between two change hunks, each click grows the hidden gap from both ends (about 15 lines toward the hunk above and 15 toward the hunk below)

    This is not entirely correct. The reason I created this issue is because only the lines above are being expanded.

  • Service Desk commented 6 hours ago

    @zaakiy Thanks — I re-checked that claim.

    For a gap between two change hunks, the expand action does add about 15 more context lines on both sides of the remaining gap. The expander row is replaced in place, so the newly revealed lines on the top side appear where you clicked. Extra context just above the next hunk is added below the expander and is easy to miss.

    For the expander at the start of a file, a click only reveals more lines above the first change. That matches “only the lines above are being expanded.”

    There is still no control that expands only toward the hunk below, which is the gap you described in issue #3068.

    On that question you chose 3B (both sides run the same expand). That keeps today’s middle-gap behavior and only makes the hit targets larger. It does not add a way to grow toward the next hunk without also growing from the top.

    Please confirm what the two side-by-side controls should do:

    1. Same action on both sides (current issue #3069 scope)
    2. Independent directions: left expands only toward the hunk above; right expands only toward the hunk below

    I will update the issue description and plan after you confirm.

  • Zak Siddiqui commented 6 hours ago
    1. Independent directions: left expands only toward the hunk above; right expands only toward the hunk below

    Retain the current behavior of expanding both sides by having a button in the middle of the left and right buttons.

    Hope that makes sense. Don't assume. Ask high-level clarifying questions if needed.

    • Numbered questions (1, 2, 3, etc)
    • Lettered answer options (A, B, C, etc)
    • Include a markdown table that contains the following for each question:
      • Pros and Cons?
      • Your recommendation (including justification)?
  • Service Desk commented 6 hours ago

    @zaakiy Got it: between two hunks the row should have three controls — left expands only toward the hunk above, right only toward the hunk below, and a middle control keeps today’s both-sides expand.

    A few layout/behavior questions before I rewrite issue #3069. A reply like 1A, 2A, 3A is enough.

    1. How should the three controls share the expander row?

    A. Three equal-width hit targets across the full row (up | both | down). The skipped-line count can sit on the middle control or as small text on the row. B. Three small controls in the gutter only; keep the current “skipped N lines” cell as non-clickable space. C. Wide left and right targets, with a compact middle “both” control.

    2. What should appear at the start or end of a file, where only one direction exists?

    A. Keep a single expander (today’s control). B. Show only the applicable direction button. C. Show direction + both (they would do the same thing). D. Still show all three; hide or disable the impossible direction.

    3. After one-sided expands, if one direction has no hidden lines left, what should the row become?

    A. Drop to a single expander for the remaining direction (same idea as start/end). B. Keep the remaining direction plus the middle “both” control. C. Keep all three; hide or disable the exhausted direction.

    #Pros and consRecommendation
    1A keeps every action easy to click, including “both.” B is compact but the targets get small — the same problem that ruled out stacked icons. C favors up/down at the expense of the action you want to keep.A. Easy-to-click was the original constraint; three equal targets across the row is the most consistent way to keep all three usable, including in split view where the gutter is narrow.
    2A stays simple and matches your earlier start/end choice. B is slightly more consistent with directional controls. C duplicates the same action. D can look broken.A. Only show three controls when both directions exist.
    3A matches start/end and avoids a dead control. B keeps “both,” but “both” would then equal the remaining direction. C is consistent but noisy.A. Once only one direction remains, a single expander is enough.

    Once you confirm, I will update the issue #3069 description and implementation plan to this three-control design.

  • Zak Siddiqui commented 6 hours ago
    1. A. Three equal-width hit targets across the full row (up | both | down). The skipped-line count can sit on the middle control or as small text on the row.
    2. A. Keep a single expander (today’s control).
    3. A. Drop to a single expander for the remaining direction (same idea as start/end).
  • Service Desk changed title 6 hours ago
    Previous Value Current Value
    Split text-diff expander into two wide side-by-side controls
    Split text-diff expander into up, both, and down controls
  • Service Desk commented 6 hours ago

    @zaakiy Confirmed as 1A, 2A, 3A. I updated the issue #3069 title, description, and implementation plan to the three-control design:

    • Between hunks: three equal-width targets (up | both | down)
    • Left expands only toward the hunk above; right only toward the hunk below; middle keeps today's both-sides expand
    • File start/end: keep the current single expander
    • After one-sided expands, if only one direction remains, drop to that single expander
  • Robin Shen commented 29 minutes ago

    @wh assign this issue to yourself and work on it. Verify your work with playwright and come up with e2e test cases.

  • Work Horse commented 28 minutes ago

    @robin I could not prepare the issue checkout, so no code or verification could be performed. tod issue checkout --for-write 3069 failed while fetching the required server-ee submodule with fatal: expected 'acknowledgments'. The repository/submodule fetch configuration or access needs to be repaired before I can implement and verify the requested work with Playwright and e2e cases.

  • Robin Shen commented 23 minutes ago

    @wh please retry

  • Work Horse changed fields 22 minutes ago
    Name Previous Value Current Value
    Assignee
    robin
    wh
  • Work Horse referenced from pull request 7 minutes ago
  • Work Horse commented 7 minutes ago

    Implemented independent up, both, and down expansion for middle text-diff gaps, including equal-width accessible controls, single-control fallback when one direction is exhausted, and shared support for blob and plain text diffs.

    Verification passed:

    • mvn -pl server-core -am -Dtest=DiffExpandSupportTest -Dsurefire.failIfNoSpecifiedTests=false test
    • PLAYWRIGHT_HTML_OPEN=never npx playwright test tests/text-diff-expander.spec.js --reporter=line

    Submitted as PR #116.

1/1
Type
Improvement
Priority
Normal
Assignee
Labels
No labels
Issue Votes (0)
Watchers (4)
Reference
OD-3069
Please wait...
Connection lost or session expired, reload to recover
Page is in error, reload to recover