Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@gdamore
Copy link
Owner

@gdamore gdamore commented Dec 13, 2025

Specifically we pre-cache the width so it is only calculated once, and we eliminate some unused and redundant string storage.

Summary by CodeRabbit

  • Refactor
    • Internal width/storage handling reworked for more efficient and more reliable text sizing, reducing memory usage of the width property.
  • Bug Fixes
    • More accurate handling of empty, control, ASCII, wide and emoji characters to improve rendering, alignment and edge-case behavior.
  • Tests
    • Expanded unit tests covering additional character-width scenarios (including 0-, 1-, and 2-width cases) for improved correctness.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Refactors Cell to store text and display width privately (ss, dw), adds a private calcWidth() helper, updates constructors and mutation paths to maintain dw, and changes the public width property type from uint to ubyte with @safe.

Changes

Cohort / File(s) Summary
Cell struct width storage refactor
source/dcell/cell.d
Replaced public xtext with private ss (string) and dw (ubyte display width). Added private calcWidth() pure const @safe. Changed `@property uint width()` → `@property ubyte width() const pure `@safe. Constructors and text mutation now set dw = calcWidth(). Updated width/render logic and unittests; minor comment/typo fixes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Verify no caller assumes width > 255 after the uintubyte change.
  • Review calcWidth() handling of empty/control, ASCII, East Asian wide, emoji, and flag sequences.
  • Confirm all mutation and construction paths update dw consistently.
  • Check updated unittests for coverage of new edge cases.

Poem

I'm a rabbit in the code, nibbling bits so spry, 🐇
I found ss and dw where xtext said goodbye.
calcWidth() counts each hop and pace,
Aligning glyphs in tidy place.
I hop the diff and hum a coder's sigh.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective: optimizing cell performance by pre-caching width calculations and eliminating redundant storage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gdamore/cell-tweaks

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
source/dcell/cell.d (1)

62-68: Width docs/control handling don’t match behavior (0-width + DEL)

  • The doc says width will be 1 or 2, but code/tests allow 0 for empty/control ('\t' => 0). Update the comment to include 0.
  • calcWidth() treats DEL (0x7F) as width 1; it’s typically a control character and should likely be 0-width like other controls.
-        if (ss.length < 1 || ss[0] < ' ') // empty or control characters
+        if (ss.length < 1 || ss[0] < ' ' || ss[0] == 0x7F) // empty or control characters (incl DEL)
         {
             return (0);
         }

Also applies to: 84-91

🧹 Nitpick comments (1)
source/dcell/cell.d (1)

79-104: Clamp/check displayWidth() result before narrowing to ubyte

return cast(ubyte) displayWidth(d[0]); will silently wrap if displayWidth() ever returns a negative or a value > 255. Even if “shouldn’t happen”, a defensive clamp makes failures predictable (esp. with emoji/variation selectors where libraries disagree).

-        return cast(ubyte) displayWidth(d[0]);
+        auto w = displayWidth(d[0]);
+        if (w <= 0) return 0;
+        if (w >= 2) return 2;
+        return 1;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acf50c4 and a3166a5.

📒 Files selected for processing (1)
  • source/dcell/cell.d (6 hunks)
🔇 Additional comments (1)
source/dcell/cell.d (1)

36-48: Good caching change, but consider keeping width() return type for API stability

You’re already saving per-cell space by caching ubyte dw; changing the public property type to ubyte is an unnecessary breaking change for downstream code (and can cause overload/formatting surprises). Consider keeping the cached storage as ubyte, but returning uint (or size_t) from the property.

-    @property ubyte width() const pure @safe
+    @property uint width() const pure @safe
     {
-        return dw;
+        return dw;
     }

Likely an incorrect or invalid review comment.

@gdamore gdamore force-pushed the gdamore/cell-tweaks branch from a3166a5 to 2038985 Compare December 13, 2025 02:40
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
source/dcell/cell.d (1)

55-60: text setter should normalize input via toUTF8() like the constructors do.

The constructors use toUTF8(s) (line 45) but the setter stores input directly. If invalid UTF-8 is passed, calcWidth() will call toUTF32(ss) which throws, and the exception may not be handled appropriately at the call site.

 @property const(string) text(const(string) s) pure @safe
 {
-    ss = s;
+    ss = toUTF8(s);
     dw = calcWidth();
     return s;
 }
🧹 Nitpick comments (1)
source/dcell/cell.d (1)

85-119: Consider exception handling in calcWidth() for robustness.

The function is marked pure @safe but can throw in two places:

  1. Line 98: toUTF32(ss) throws UTFException on malformed UTF-8
  2. Line 104: enforce(d.length > 0) throws if the condition fails

Since calcWidth() is called from setters and constructors, an exception here propagates to the caller. This is fine if the invariant is that ss is always valid UTF-8 (maintained by constructors), but the text setter currently doesn't enforce this (see earlier comment).

The enforce at line 104 appears defensive—after line 90's check, toUTF32 on a non-empty string with a printable first byte should always produce a non-empty result. If it's truly unreachable, consider using assert instead to document the invariant without runtime cost in release builds.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3166a5 and 2038985.

📒 Files selected for processing (1)
  • source/dcell/cell.d (6 hunks)
🔇 Additional comments (2)
source/dcell/cell.d (2)

36-48: LGTM!

Both constructors properly normalize input via toUTF8() and then compute width once using calcWidth(). This aligns with the PR's optimization goal of calculating width only once.


75-78: Return type changed from uint to ubyte — no compatibility issues found.

The width property now returns ubyte instead of uint. Analysis of all callers (4 in ttyscreen.d and multiple unit tests in cell.d) shows only implicit conversions and arithmetic operations that are fully compatible with ubyte. D handles the conversion seamlessly in all contexts: returns to int, additions with int, compound assignments, and comparisons with literals.

Specifically we pre-cache the width so it is only calculated once,
and we eliminate some unused and redundant string storage.
@gdamore gdamore force-pushed the gdamore/cell-tweaks branch from 2038985 to dd315f5 Compare December 13, 2025 02:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
source/dcell/cell.d (1)

14-14: Remove unused import.

The std.exception import doesn't appear to be used in this file. The toUTF32 and toUTF8 calls are from std.utf and don't require this import.

-import std.exception;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2038985 and dd315f5.

📒 Files selected for processing (1)
  • source/dcell/cell.d (6 hunks)
🔇 Additional comments (5)
source/dcell/cell.d (5)

36-48: LGTM!

The constructors correctly initialize the new private storage (ss and dw) and use calcWidth() to pre-compute the display width. This aligns with the PR's optimization goals.


55-60: LGTM!

The text setter now normalizes input via toUTF8(s) (consistent with the constructors) and recalculates the cached width. This addresses the previous review concern while maintaining the performance-oriented design.


62-78: LGTM!

The width property refactoring delivers the PR's optimization goals: returning the pre-cached dw field avoids repeated calculation. The type change from uint to ubyte is appropriate given the valid range (0-2), and adding @safe improves memory safety guarantees.


122-145: LGTM!

The unit test updates appropriately cover the refactored width calculation:

  • Control characters and empty strings correctly expect width 0
  • Expanded coverage for emoji sequences and skin tone modifiers
  • The past review concerns about duplicate assertions have been addressed

The commented-out ZWJ test at line 144 acknowledges a known limitation with the current implementation/library combination.


85-119: Implementation looks sound; verify library version if considering updates.

The calcWidth() logic is correct with appropriate fast paths for empty/control/ASCII cases and defensive clamping for the east_asian_width library. The current version 1.0.0 works as intended. If you plan to update to version 1.1.0 (available since 2017), test edge cases like emoji and regional indicators to ensure behavior remains consistent.

@gdamore gdamore merged commit fb46004 into main Dec 13, 2025
3 checks passed
@gdamore gdamore deleted the gdamore/cell-tweaks branch December 13, 2025 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants