@@ -86,10 +86,34 @@ impl SuggestionsRenderer {
8686 let column_spacing = 2 ;
8787 let available_width = inner_area. width as usize ;
8888
89+ // Check if any visible suggestions have keybinding or source
90+ // If not, we can use more space for the name column
91+ let has_keybinding = visible_suggestions. iter ( ) . any ( |s| s. keybinding . is_some ( ) ) ;
92+ let has_source = visible_suggestions. iter ( ) . any ( |s| s. source . is_some ( ) ) ;
93+
8994 // Fixed column widths for consistent layout
90- let name_column_width = 30 ; // Fixed width for command names
91- let keybinding_column_width = 12 ; // Fixed width for keybindings (e.g., "Ctrl+Shift+P")
92- let source_column_width = 15 ; // Fixed width for source (e.g., "builtin", "live_grep")
95+ let keybinding_column_width = if has_keybinding { 12 } else { 0 } ;
96+ let source_column_width = if has_source { 15 } else { 0 } ;
97+
98+ // Calculate name column width dynamically based on available space
99+ // Reserve space for: left_margin + name + spacing + keybinding + spacing + desc + spacing + source
100+ let reserved_for_other_columns = left_margin
101+ + column_spacing // after name
102+ + keybinding_column_width
103+ + ( if has_keybinding { column_spacing } else { 0 } ) // after keybinding
104+ + column_spacing // after desc
105+ + source_column_width;
106+
107+ // Give name column a reasonable portion of remaining space
108+ // Minimum 30, but can expand if there's room and no keybinding/source
109+ let base_name_width = 30 ;
110+ let name_column_width = if !has_keybinding && !has_source {
111+ // For file finders etc., use up to 60% of available width for name
112+ let max_name_width = ( available_width * 60 / 100 ) . max ( base_name_width) ;
113+ max_name_width. min ( available_width. saturating_sub ( reserved_for_other_columns) )
114+ } else {
115+ base_name_width
116+ } ;
93117
94118 for ( idx, suggestion) in visible_suggestions. iter ( ) . enumerate ( ) {
95119 let actual_idx = start_idx + idx;
@@ -141,82 +165,110 @@ impl SuggestionsRenderer {
141165 let name_text = if name_visual_width > name_column_width {
142166 // Truncate name by visual width
143167 let truncate_at = name_column_width. saturating_sub ( 1 ) ; // -1 for "…"
144- let mut width = 0 ;
145- let truncated: String = name
146- . chars ( )
147- . take_while ( |ch| {
148- let w = char_width ( * ch) ;
149- if width + w <= truncate_at {
150- width += w;
151- true
168+
169+ // For file paths (containing '/'), truncate from the beginning
170+ // to preserve the filename which is usually at the end
171+ if name. contains ( '/' ) || name. contains ( '\\' ) {
172+ // Calculate how many chars to skip from the beginning
173+ let mut total_width = 0 ;
174+ let char_widths: Vec < ( char , usize ) > =
175+ name. chars ( ) . map ( |ch| ( ch, char_width ( ch) ) ) . collect ( ) ;
176+
177+ // Find where to start to fit within truncate_at
178+ let mut start_idx = 0 ;
179+ for ( i, & ( _, w) ) in char_widths. iter ( ) . enumerate ( ) . rev ( ) {
180+ if total_width + w <= truncate_at {
181+ total_width += w;
182+ start_idx = i;
152183 } else {
153- false
184+ break ;
154185 }
155- } )
156- . collect ( ) ;
157- format ! ( "{}…" , truncated)
158- } else {
159- name. clone ( )
160- } ;
161- spans. push ( Span :: styled ( name_text. clone ( ) , base_style) ) ;
162- let name_display_width = str_width ( & name_text) ;
163- let name_padding = name_column_width. saturating_sub ( name_display_width) ;
164- if name_padding > 0 {
165- spans. push ( Span :: styled ( " " . repeat ( name_padding) , base_style) ) ;
166- }
167-
168- // Spacing before keybinding column
169- spans. push ( Span :: styled ( " " . repeat ( column_spacing) , base_style) ) ;
170-
171- // Column 2: Keyboard shortcut (fixed width)
172- let keybinding_style = if suggestion. disabled {
173- base_style
174- } else if is_selected {
175- Style :: default ( )
176- . fg ( theme. help_key_fg )
177- . bg ( theme. suggestion_selected_bg )
178- } else if is_hovered {
179- Style :: default ( )
180- . fg ( theme. help_key_fg )
181- . bg ( theme. menu_hover_bg )
182- } else {
183- Style :: default ( )
184- . fg ( theme. line_number_fg )
185- . bg ( theme. suggestion_bg )
186- } ;
186+ }
187187
188- if let Some ( keybinding) = & suggestion. keybinding {
189- let kb_visual_width = str_width ( keybinding) ;
190- let kb_text = if kb_visual_width > keybinding_column_width {
191- // Truncate keybinding by visual width
188+ let truncated: String =
189+ char_widths[ start_idx..] . iter ( ) . map ( |( ch, _) | * ch) . collect ( ) ;
190+ format ! ( "…{}" , truncated)
191+ } else {
192+ // For non-paths, truncate from the end as before
192193 let mut width = 0 ;
193- keybinding
194+ let truncated : String = name
194195 . chars ( )
195196 . take_while ( |ch| {
196197 let w = char_width ( * ch) ;
197- if width + w <= keybinding_column_width {
198+ if width + w <= truncate_at {
198199 width += w;
199200 true
200201 } else {
201202 false
202203 }
203204 } )
204- . collect ( )
205+ . collect ( ) ;
206+ format ! ( "{}…" , truncated)
207+ }
208+ } else {
209+ name. clone ( )
210+ } ;
211+ spans. push ( Span :: styled ( name_text. clone ( ) , base_style) ) ;
212+ let name_display_width = str_width ( & name_text) ;
213+ let name_padding = name_column_width. saturating_sub ( name_display_width) ;
214+ if name_padding > 0 {
215+ spans. push ( Span :: styled ( " " . repeat ( name_padding) , base_style) ) ;
216+ }
217+
218+ // Column 2: Keyboard shortcut (only if any suggestions have keybindings)
219+ if has_keybinding {
220+ // Spacing before keybinding column
221+ spans. push ( Span :: styled ( " " . repeat ( column_spacing) , base_style) ) ;
222+
223+ let keybinding_style = if suggestion. disabled {
224+ base_style
225+ } else if is_selected {
226+ Style :: default ( )
227+ . fg ( theme. help_key_fg )
228+ . bg ( theme. suggestion_selected_bg )
229+ } else if is_hovered {
230+ Style :: default ( )
231+ . fg ( theme. help_key_fg )
232+ . bg ( theme. menu_hover_bg )
205233 } else {
206- keybinding. clone ( )
234+ Style :: default ( )
235+ . fg ( theme. line_number_fg )
236+ . bg ( theme. suggestion_bg )
207237 } ;
208- spans. push ( Span :: styled ( kb_text. clone ( ) , keybinding_style) ) ;
209- let kb_display_width = str_width ( & kb_text) ;
210- let kb_padding = keybinding_column_width. saturating_sub ( kb_display_width) ;
211- if kb_padding > 0 {
212- spans. push ( Span :: styled ( " " . repeat ( kb_padding) , base_style) ) ;
238+
239+ if let Some ( keybinding) = & suggestion. keybinding {
240+ let kb_visual_width = str_width ( keybinding) ;
241+ let kb_text = if kb_visual_width > keybinding_column_width {
242+ // Truncate keybinding by visual width
243+ let mut width = 0 ;
244+ keybinding
245+ . chars ( )
246+ . take_while ( |ch| {
247+ let w = char_width ( * ch) ;
248+ if width + w <= keybinding_column_width {
249+ width += w;
250+ true
251+ } else {
252+ false
253+ }
254+ } )
255+ . collect ( )
256+ } else {
257+ keybinding. clone ( )
258+ } ;
259+ spans. push ( Span :: styled ( kb_text. clone ( ) , keybinding_style) ) ;
260+ let kb_display_width = str_width ( & kb_text) ;
261+ let kb_padding = keybinding_column_width. saturating_sub ( kb_display_width) ;
262+ if kb_padding > 0 {
263+ spans. push ( Span :: styled ( " " . repeat ( kb_padding) , base_style) ) ;
264+ }
265+ } else {
266+ // No keybinding for this command, pad the column
267+ spans. push ( Span :: styled (
268+ " " . repeat ( keybinding_column_width) ,
269+ base_style,
270+ ) ) ;
213271 }
214- } else {
215- // No keybinding for this command, pad the column
216- spans. push ( Span :: styled (
217- " " . repeat ( keybinding_column_width) ,
218- base_style,
219- ) ) ;
220272 }
221273
222274 // Spacing before description column
@@ -226,11 +278,18 @@ impl SuggestionsRenderer {
226278 let fixed_columns_width = left_margin
227279 + name_column_width
228280 + column_spacing
229- + keybinding_column_width
230- + column_spacing;
281+ + ( if has_keybinding {
282+ keybinding_column_width + column_spacing
283+ } else {
284+ 0
285+ } ) ;
231286
232- // Reserve space for source column at the end
233- let source_reserved = column_spacing + source_column_width;
287+ // Reserve space for source column at the end (only if showing sources)
288+ let source_reserved = if has_source {
289+ column_spacing + source_column_width
290+ } else {
291+ 0
292+ } ;
234293
235294 // Column 3: Description (flexible width, leaves room for source)
236295 if let Some ( desc) = & suggestion. description {
@@ -279,65 +338,67 @@ impl SuggestionsRenderer {
279338 }
280339 }
281340
282- // Spacing before source column
283- spans. push ( Span :: styled ( " " . repeat ( column_spacing) , base_style) ) ;
341+ // Column 4: Source (only if any suggestions have source info)
342+ if has_source {
343+ // Spacing before source column
344+ spans. push ( Span :: styled ( " " . repeat ( column_spacing) , base_style) ) ;
284345
285- // Column 4: Source (right-aligned, fixed width)
286- let source_style = if suggestion. disabled {
287- base_style
288- } else if is_selected {
289- Style :: default ( )
290- . fg ( theme. line_number_fg )
291- . bg ( theme. suggestion_selected_bg )
292- . add_modifier ( Modifier :: DIM )
293- } else if is_hovered {
294- Style :: default ( )
295- . fg ( theme. line_number_fg )
296- . bg ( theme. menu_hover_bg )
297- . add_modifier ( Modifier :: DIM )
298- } else {
299- Style :: default ( )
300- . fg ( theme. line_number_fg )
301- . bg ( theme. suggestion_bg )
302- . add_modifier ( Modifier :: DIM )
303- } ;
304-
305- if let Some ( source) = & suggestion. source {
306- let source_text = match source {
307- CommandSource :: Builtin => "builtin" . to_string ( ) ,
308- CommandSource :: Plugin ( name) => name. clone ( ) ,
309- } ;
310- let source_visual_width = str_width ( & source_text) ;
311- let source_display = if source_visual_width > source_column_width {
312- // Truncate source by visual width
313- let truncate_at = source_column_width. saturating_sub ( 1 ) ; // -1 for "…"
314- let mut width = 0 ;
315- let truncated: String = source_text
316- . chars ( )
317- . take_while ( |ch| {
318- let w = char_width ( * ch) ;
319- if width + w <= truncate_at {
320- width += w;
321- true
322- } else {
323- false
324- }
325- } )
326- . collect ( ) ;
327- format ! ( "{}…" , truncated)
346+ let source_style = if suggestion. disabled {
347+ base_style
348+ } else if is_selected {
349+ Style :: default ( )
350+ . fg ( theme. line_number_fg )
351+ . bg ( theme. suggestion_selected_bg )
352+ . add_modifier ( Modifier :: DIM )
353+ } else if is_hovered {
354+ Style :: default ( )
355+ . fg ( theme. line_number_fg )
356+ . bg ( theme. menu_hover_bg )
357+ . add_modifier ( Modifier :: DIM )
328358 } else {
329- source_text
359+ Style :: default ( )
360+ . fg ( theme. line_number_fg )
361+ . bg ( theme. suggestion_bg )
362+ . add_modifier ( Modifier :: DIM )
330363 } ;
331- let source_display_width = str_width ( & source_display) ;
332- // Right-align the source text within its column
333- let source_padding = source_column_width. saturating_sub ( source_display_width) ;
334- if source_padding > 0 {
335- spans. push ( Span :: styled ( " " . repeat ( source_padding) , base_style) ) ;
364+
365+ if let Some ( source) = & suggestion. source {
366+ let source_text = match source {
367+ CommandSource :: Builtin => "builtin" . to_string ( ) ,
368+ CommandSource :: Plugin ( name) => name. clone ( ) ,
369+ } ;
370+ let source_visual_width = str_width ( & source_text) ;
371+ let source_display = if source_visual_width > source_column_width {
372+ // Truncate source by visual width
373+ let truncate_at = source_column_width. saturating_sub ( 1 ) ; // -1 for "…"
374+ let mut width = 0 ;
375+ let truncated: String = source_text
376+ . chars ( )
377+ . take_while ( |ch| {
378+ let w = char_width ( * ch) ;
379+ if width + w <= truncate_at {
380+ width += w;
381+ true
382+ } else {
383+ false
384+ }
385+ } )
386+ . collect ( ) ;
387+ format ! ( "{}…" , truncated)
388+ } else {
389+ source_text
390+ } ;
391+ let source_display_width = str_width ( & source_display) ;
392+ // Right-align the source text within its column
393+ let source_padding = source_column_width. saturating_sub ( source_display_width) ;
394+ if source_padding > 0 {
395+ spans. push ( Span :: styled ( " " . repeat ( source_padding) , base_style) ) ;
396+ }
397+ spans. push ( Span :: styled ( source_display, source_style) ) ;
398+ } else {
399+ // No source info, just pad
400+ spans. push ( Span :: styled ( " " . repeat ( source_column_width) , base_style) ) ;
336401 }
337- spans. push ( Span :: styled ( source_display, source_style) ) ;
338- } else {
339- // No source info, just pad
340- spans. push ( Span :: styled ( " " . repeat ( source_column_width) , base_style) ) ;
341402 }
342403
343404 // Fill any remaining space with background (shouldn't be needed but safe)
0 commit comments