@@ -164,8 +164,33 @@ def common_suffix_len(s1: str, s2: str) -> int:
164164 return max_len
165165
166166
167+ def is_word_char (c : str ) -> bool :
168+ """Check if character is part of a word/identifier."""
169+ return c .isalnum () or c == '_'
170+
171+
172+ def expand_to_word_boundary (line : str , start : int , end : int ) -> tuple [int , int ]:
173+ """Expand a range to include complete words at boundaries.
174+
175+ If start is in the middle of a word, expand left to word start.
176+ If end is in the middle of a word, expand right to word end.
177+ """
178+ # Expand start leftward if in middle of word
179+ while start > 0 and is_word_char (line [start - 1 ]) and is_word_char (line [start ]):
180+ start -= 1
181+
182+ # Expand end rightward if in middle of word
183+ while end < len (line ) and is_word_char (line [end - 1 ]) and is_word_char (line [end ]):
184+ end += 1
185+
186+ return start , end
187+
188+
167189def highlight_word_diff (old_line : str , new_line : str , is_old : bool , used : UsedSymbols ) -> str :
168- """Apply word-level diff highlighting with [] brackets."""
190+ """Apply word-level diff highlighting with [] brackets.
191+
192+ Expands bracket boundaries to complete words to avoid splitting identifiers.
193+ """
169194 prefix_len = common_prefix_len (old_line , new_line )
170195 suffix_len = common_suffix_len (old_line , new_line )
171196
@@ -183,18 +208,24 @@ def highlight_word_diff(old_line: str, new_line: str, is_old: bool, used: UsedSy
183208 if old_diff_len <= 0 or new_diff_len <= 0 :
184209 return old_line if is_old else new_line
185210
186- used .bracket = True
187-
211+ # Expand to word boundaries for cleaner highlighting
188212 if is_old :
189- prefix = old_line [:prefix_len ]
190- diff_part = old_line [prefix_len :prefix_len + old_diff_len ]
191- suffix = old_line [prefix_len + old_diff_len :]
192- return f"{ prefix } [{ diff_part } ]{ suffix } "
213+ start = prefix_len
214+ end = prefix_len + old_diff_len
215+ start , end = expand_to_word_boundary (old_line , start , end )
216+ prefix = old_line [:start ]
217+ diff_part = old_line [start :end ]
218+ suffix = old_line [end :]
193219 else :
194- prefix = new_line [:prefix_len ]
195- diff_part = new_line [prefix_len :prefix_len + new_diff_len ]
196- suffix = new_line [prefix_len + new_diff_len :]
197- return f"{ prefix } [{ diff_part } ]{ suffix } "
220+ start = prefix_len
221+ end = prefix_len + new_diff_len
222+ start , end = expand_to_word_boundary (new_line , start , end )
223+ prefix = new_line [:start ]
224+ diff_part = new_line [start :end ]
225+ suffix = new_line [end :]
226+
227+ used .bracket = True
228+ return f"{ prefix } [{ diff_part } ]{ suffix } "
198229
199230
200231class DiffRenderer :
@@ -287,6 +318,47 @@ def _print_column_header(self, context: str):
287318 f"{ fill_char (BOX_HORIZONTAL , self .content_width + 1 )} { BOX_T_LEFT } "
288319 )
289320
321+ def _find_wrap_point (self , content : str , max_width : int ) -> int :
322+ """Find safe wrap point that doesn't split inside [...] brackets.
323+
324+ Returns character index to wrap at, preserving bracket integrity.
325+ """
326+ if len (content ) <= max_width :
327+ return len (content )
328+
329+ # Track bracket nesting to avoid splitting inside [...]
330+ bracket_depth = 0
331+ last_safe_point = 0
332+ current_width = 0
333+
334+ for i , char in enumerate (content ):
335+ # Calculate display width
336+ ea = unicodedata .east_asian_width (char )
337+ char_width = 2 if ea in ('W' , 'F' ) else 1
338+
339+ if current_width + char_width > max_width - 1 : # -1 for wrap indicator
340+ # Must wrap here or earlier
341+ if bracket_depth == 0 :
342+ return i
343+ elif last_safe_point > 0 :
344+ return last_safe_point
345+ else :
346+ # No safe point found, wrap anyway (rare edge case)
347+ return i
348+
349+ current_width += char_width
350+
351+ if char == '[' :
352+ bracket_depth += 1
353+ elif char == ']' :
354+ bracket_depth = max (0 , bracket_depth - 1 )
355+
356+ # Safe points are outside brackets
357+ if bracket_depth == 0 :
358+ last_safe_point = i + 1
359+
360+ return len (content )
361+
290362 def _print_row (self , old_num : str , symbol : str , new_num : str , content : str ):
291363 """Print a content row, handling wrapping for long lines."""
292364 content_len = display_width (content )
@@ -300,16 +372,20 @@ def _print_row(self, old_num: str, symbol: str, new_num: str, content: str):
300372 f"{ padded_content } { BOX_VERTICAL } "
301373 )
302374 else :
303- # Wrap long lines
375+ # Wrap long lines, preserving bracket integrity
304376 self .used .wrap = True
305- first_part = content [:self .content_width - 1 ]
377+ wrap_point = self ._find_wrap_point (content , self .content_width )
378+ first_part = content [:wrap_point ]
379+ first_width = display_width (first_part )
380+ # Pad to content_width - 1 to leave room for wrap indicator
381+ padding = ' ' * max (0 , self .content_width - 1 - first_width )
306382 self .output .append (
307383 f"{ BOX_VERTICAL } { pad_num (old_num , COL_OLD )} { BOX_VERTICAL } { symbol } "
308384 f"{ BOX_VERTICAL } { pad_num (new_num , COL_NEW )} { BOX_VERTICAL } "
309- f"{ first_part } ↩{ BOX_VERTICAL } "
385+ f"{ first_part } { padding } ↩{ BOX_VERTICAL } "
310386 )
311387
312- remaining = content [self . content_width - 1 :]
388+ remaining = content [wrap_point :]
313389 while remaining :
314390 part_len = display_width (remaining )
315391 if part_len <= self .content_width :
@@ -321,13 +397,16 @@ def _print_row(self, old_num: str, symbol: str, new_num: str, content: str):
321397 )
322398 remaining = ''
323399 else :
324- next_part = remaining [:self .content_width - 1 ]
400+ wrap_point = self ._find_wrap_point (remaining , self .content_width )
401+ next_part = remaining [:wrap_point ]
402+ next_width = display_width (next_part )
403+ padding = ' ' * max (0 , self .content_width - 1 - next_width )
325404 self .output .append (
326405 f"{ BOX_VERTICAL } { ' ' * COL_OLD } { BOX_VERTICAL } "
327406 f"{ BOX_VERTICAL } { ' ' * COL_NEW } { BOX_VERTICAL } "
328- f"{ next_part } ↩{ BOX_VERTICAL } "
407+ f"{ next_part } { padding } ↩{ BOX_VERTICAL } "
329408 )
330- remaining = remaining [self . content_width - 1 :]
409+ remaining = remaining [wrap_point :]
331410
332411 def _print_hunk_bottom (self ):
333412 """Print hunk box bottom."""
0 commit comments