@@ -60,40 +60,82 @@ const ELLIPSIS_WIDTH = 3;
6060const MIN_DESC_WIDTH = 20 ;
6161const MIN_MAIN_DESC_WIDTH = 10 ;
6262
63+ function getCharDisplayWidth ( code : number ) : number {
64+ if (
65+ ( code >= 0x1100 && code <= 0x115f ) ||
66+ ( code >= 0x2e80 && code <= 0x303e ) ||
67+ ( code >= 0x3040 && code <= 0xa4cf ) ||
68+ ( code >= 0xac00 && code <= 0xd7af ) ||
69+ ( code >= 0xf900 && code <= 0xfaff ) ||
70+ ( code >= 0xfe10 && code <= 0xfe1f ) ||
71+ ( code >= 0xfe30 && code <= 0xfe6f ) ||
72+ ( code >= 0xff00 && code <= 0xff60 ) ||
73+ ( code >= 0xffe0 && code <= 0xffe6 ) ||
74+ ( code >= 0x1b000 && code <= 0x1b77f ) ||
75+ ( code >= 0x1f300 && code <= 0x1f64f ) ||
76+ ( code >= 0x1f900 && code <= 0x1f9ff ) ||
77+ ( code >= 0x20000 && code <= 0x2a6df ) ||
78+ ( code >= 0x2a700 && code <= 0x2ceaf ) ||
79+ ( code >= 0x2ceb0 && code <= 0x2ebef ) ||
80+ ( code >= 0x30000 && code <= 0x3134f )
81+ ) {
82+ return 2 ;
83+ }
84+ return 1 ;
85+ }
86+
87+ function getStringDisplayWidth ( str : string ) : number {
88+ let width = 0 ;
89+ for ( const char of str ) {
90+ const code = char . codePointAt ( 0 ) ?? 0 ;
91+ width += getCharDisplayWidth ( code ) ;
92+ }
93+ return width ;
94+ }
95+
96+ function truncateToDisplayWidth ( str : string , maxWidth : number ) : string {
97+ let width = 0 ;
98+ let i = 0 ;
99+ for ( const char of str ) {
100+ const code = char . codePointAt ( 0 ) ?? 0 ;
101+ const charWidth = getCharDisplayWidth ( code ) ;
102+ if ( width + charWidth > maxWidth ) break ;
103+ width += charWidth ;
104+ i += char . length ;
105+ }
106+ return str . slice ( 0 , i ) ;
107+ }
108+
63109export function SuggestionItem ( {
64110 name,
65111 description,
66112 isSelected,
67113 firstColumnWidth,
68114 maxWidth,
69115} : SuggestionItemProps ) {
70- // Calculate available width for description
71- // Account for: margin + firstColumnWidth + spacing + ellipsis reserve
72116 const reservedWidth =
73117 MARGIN_LEFT + firstColumnWidth + SPACING + ELLIPSIS_WIDTH ;
74118 const maxDescriptionWidth = Math . max (
75119 MIN_DESC_WIDTH ,
76120 maxWidth - reservedWidth ,
77121 ) ;
78122
79- // Extract source suffix (content in the last parentheses, e.g., "(global)")
80123 const sourceMatch = description . match ( / \( ( [ ^ ) ] + ) \) $ / ) ;
81124 const sourceSuffix = sourceMatch ? ` ${ sourceMatch [ 0 ] } ` : '' ;
82125 const mainDescription = sourceMatch
83126 ? description . slice ( 0 , sourceMatch . index ) . trim ( )
84127 : description ;
85128
86- // Truncate description if it exceeds max width, but preserve source suffix
87129 let truncatedDescription : string ;
88- if ( description . length > maxDescriptionWidth ) {
130+ const descDisplayWidth = getStringDisplayWidth ( description ) ;
131+ if ( descDisplayWidth > maxDescriptionWidth ) {
132+ const sourceSuffixWidth = getStringDisplayWidth ( sourceSuffix ) ;
89133 const availableForMain =
90- maxDescriptionWidth - sourceSuffix . length - ELLIPSIS_WIDTH ; // Reserve space for "..." and source
134+ maxDescriptionWidth - sourceSuffixWidth - ELLIPSIS_WIDTH ;
91135 if ( availableForMain > MIN_MAIN_DESC_WIDTH ) {
92- // If we have enough space, truncate main description and append source
93- truncatedDescription = `${ mainDescription . slice ( 0 , availableForMain ) } ...${ sourceSuffix } ` ;
136+ truncatedDescription = `${ truncateToDisplayWidth ( mainDescription , availableForMain ) } ...${ sourceSuffix } ` ;
94137 } else {
95- // If space is too tight, just truncate everything
96- truncatedDescription = `${ description . slice ( 0 , maxDescriptionWidth - ELLIPSIS_WIDTH ) } ...` ;
138+ truncatedDescription = `${ truncateToDisplayWidth ( description , maxDescriptionWidth - ELLIPSIS_WIDTH ) } ...` ;
97139 }
98140 } else {
99141 truncatedDescription = description ;
0 commit comments