Skip to content

Commit 3cc1ff9

Browse files
liuchengxuclaude
andauthored
Add path prefix dimming for file providers (#1130)
* Refactor: Add centralized theme and window management utilities - Create theme_utils.vim with centralized color extraction and highlight management - Create ui/window_manager.vim for buffer/window lifecycle management - Update themes.vim to use centralized utilities instead of inline code - Update floating_win.vim to use centralized winhl strings and color constants This is Phase 1 of the fuzzy finder UI refactoring, establishing a foundation for more extensible and maintainable UI components. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add modular UI components: indicator, preview regions, layout builder - Create indicator_components.vim with extensible component system - Support for match count, keybind hints, provider mode - Component registration with priority ordering - Create preview_regions.vim for modular preview management - Header, content, and scrollbar region support - Rich header formatting with file info - Scrollbar position calculation - Create layout_builder.vim for flexible window arrangement - Dimension calculation with percentage/keyword support - Layout presets (center, top, bottom) - Dynamic height support - Sub-window config builders This is Phase 2 of the fuzzy finder UI refactoring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add UI orchestrator for component coordination - Create orchestrator.vim to coordinate all UI components - Event hooks system (before_open, after_open, on_resize, etc.) - Backend detection (floating/popup/sidebar) - High-level operations for open/close lifecycle - Preview management integration - Window focus coordination This completes Phase 3 of the fuzzy finder UI refactoring foundation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add visual polish: softer colors, improved selection, better borders Visual improvements for a more modern and pleasant fuzzy finder experience: - Softer match highlight colors using Tokyo Night-inspired palette - Blues (#7dcfff, #7aa2f7), cyans (#94e2d5), soft greens (#9ece6a) - Lavenders and pinks for variety (#cba6f7, #f5c2e7) - Dimmed path prefix highlight groups (ClapPathPrefix, ClapFileName) - Directory paths dimmed, filenames bright and bold - Improved current selection styling - Visible background highlight for current line - Nicer arrow indicator (▶) with customizable g:clap_current_selection_arrow - Better sign styling with filled circle (●) for multi-select - Enhanced border styling - ClapBorder and ClapBorderText highlight groups - Subtle contrast for borders in dark/light modes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "Add visual polish: softer colors, improved selection, better borders" This reverts commit 6e7fce9. * Add dimmed path prefix highlighting for file providers - Directory prefixes are dimmed (ClapPathPrefix) while filenames stay bright (ClapFileName) - Root-level files get consistent filename highlighting - Works with icons, grep format (file:line:col), and both path separators - Uses matchaddpos for reliable highlighting over signs - Configurable via g:clap_enable_path_dimming (default: enabled) - Highlight groups defined in s:init_theme() to work with all theme palettes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove unused UI component files These modular UI components (orchestrator, layout_builder, indicator_components, preview_regions, window_manager) were built as infrastructure but not yet integrated into the main vim-clap code. Removing to keep the branch focused on actively used changes. * Merge theme_utils.vim into themes.vim Consolidate theme utility functions (color extraction, highlight creation, winhl strings) directly into themes.vim instead of having a separate file. This simplifies the codebase while keeping the same functionality. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c8b361f commit 3cc1ff9

File tree

5 files changed

+529
-102
lines changed

5 files changed

+529
-102
lines changed

autoload/clap/floating_win.vim

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,30 @@ let s:symbol_left = g:__clap_search_box_border_symbol.left
4040
let s:symbol_right = g:__clap_search_box_border_symbol.right
4141
let s:symbol_width = strdisplaywidth(s:symbol_right)
4242

43-
let s:shadow_winhl = 'Normal:ClapShadow,NormalNC:ClapShadow,EndOfBuffer:ClapShadow'
44-
let s:display_winhl = 'Normal:ClapDisplay,EndOfBuffer:ClapDisplayInvisibleEndOfBuffer,SignColumn:ClapDisplay,ColorColumn:ClapDisplay'
45-
let s:preview_winhl = 'Normal:ClapPreview,EndOfBuffer:ClapPreviewInvisibleEndOfBuffer,SignColumn:ClapPreview,ColorColumn:ClapPreview'
46-
let s:preview_scrollbar_winhl = 'Normal:ClapPreviewScrollbar,EndOfBuffer:ClapPreviewInvisibleEndOfBuffer,SignColumn:ClapPreviewScrollbar,ColorColumn:ClapPreviewScrollbar'
47-
48-
if &background ==# 'dark'
49-
if empty(get(g:clap_preview.scrollbar, 'fill_char', ''))
50-
hi ClapDefaultPreviewScrollbar ctermbg=237 guibg=#3E4452 ctermfg=173 guifg=#e18254 cterm=bold,reverse gui=bold,reverse
51-
else
52-
let s:preview_scrollbar_fill_char = g:clap_preview.scrollbar.fill_char
53-
hi ClapDefaultPreviewScrollbar ctermbg=237 guibg=#3E4452 ctermfg=173 guifg=#e18254 cterm=bold gui=bold
54-
endif
55-
else
56-
if empty(get(g:clap_preview.scrollbar, 'fill_char', ''))
57-
hi ClapDefaultPreviewScrollbar ctermbg=7 guibg=#ecf5ff ctermfg=173 guifg=#e18254 cterm=bold,reverse gui=bold,reverse
58-
else
59-
let s:preview_scrollbar_fill_char = g:clap_preview.scrollbar.fill_char
60-
hi ClapDefaultPreviewScrollbar ctermbg=7 guibg=#ecf5ff ctermfg=173 guifg=#e18254 cterm=bold gui=bold
61-
endif
43+
" Use centralized winhl strings from themes
44+
let s:shadow_winhl = g:clap#themes#winhl.shadow
45+
let s:display_winhl = g:clap#themes#winhl.display
46+
let s:preview_winhl = g:clap#themes#winhl.preview
47+
let s:preview_scrollbar_winhl = clap#themes#winhl('ClapPreviewScrollbar', 'ClapPreviewInvisibleEndOfBuffer')
48+
49+
" Setup preview scrollbar highlight using centralized colors
50+
let s:defaults = g:clap#themes#defaults
51+
let s:preview_bg = clap#themes#display_bg()
52+
let s:scrollbar_attrs = empty(get(g:clap_preview.scrollbar, 'fill_char', '')) ? 'bold,reverse' : 'bold'
53+
54+
if !empty(get(g:clap_preview.scrollbar, 'fill_char', ''))
55+
let s:preview_scrollbar_fill_char = g:clap_preview.scrollbar.fill_char
6256
endif
6357

58+
call clap#themes#highlight('ClapDefaultPreviewScrollbar', {
59+
\ 'guibg': s:preview_bg.gui,
60+
\ 'ctermbg': s:preview_bg.cterm,
61+
\ 'guifg': s:defaults.scrollbar_fg.gui,
62+
\ 'ctermfg': s:defaults.scrollbar_fg.cterm,
63+
\ 'gui': s:scrollbar_attrs,
64+
\ 'cterm': s:scrollbar_attrs,
65+
\ })
66+
6467
hi default link ClapPreviewScrollbar ClapDefaultPreviewScrollbar
6568

6669
" shadow

autoload/clap/highlighter/path.vim

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
" Author: liuchengxu <xuliuchengxlc@gmail.com>
2+
" Description: Path highlighting - dims directory prefix, brightens filename.
3+
4+
let s:save_cpo = &cpoptions
5+
set cpoptions&vim
6+
7+
" Providers that display file paths and should have dimmed prefixes
8+
let s:path_providers = ['files', 'git_files', 'recent_files', 'history', 'filer', 'grep', 'live_grep', 'proj_tags', 'tags', 'jumps']
9+
10+
" Check if the current provider should have path highlighting
11+
function! clap#highlighter#path#should_highlight() abort
12+
if !get(g:, 'clap_enable_path_dimming', 1)
13+
return v:false
14+
endif
15+
return index(s:path_providers, g:clap.provider.id) >= 0
16+
endfunction
17+
18+
" Find path boundaries in a line
19+
" Returns: [prefix_start, prefix_end, filename_start, filename_end] or [] if not a path
20+
" All indices are byte-based, 0-indexed
21+
function! s:find_path_boundaries(line) abort
22+
if empty(a:line)
23+
return []
24+
endif
25+
26+
" Determine the end of file path (before : for grep format, or end of line)
27+
let l:path_end = len(a:line) - 1
28+
29+
" Check for grep format: file:line:col:content
30+
" Find first colon that's followed by a digit (line number)
31+
let l:colon_pos = 0
32+
while l:colon_pos < len(a:line)
33+
let l:colon_pos = stridx(a:line, ':', l:colon_pos)
34+
if l:colon_pos == -1
35+
break
36+
endif
37+
" Check if next char is a digit (line number)
38+
if l:colon_pos + 1 < len(a:line) && a:line[l:colon_pos + 1] =~# '\d'
39+
let l:path_end = l:colon_pos - 1
40+
break
41+
endif
42+
let l:colon_pos += 1
43+
endwhile
44+
45+
" Determine where the actual path starts (skip icon if present)
46+
let l:path_start = 0
47+
if g:__clap_icon_added_by_maple
48+
" Icon is typically a multi-byte character + space
49+
" Scan for first alnum or path character after initial icon bytes
50+
let l:byte_idx = 0
51+
for l:char in split(a:line, '\zs')
52+
if l:byte_idx > 0 && (l:char =~# '[a-zA-Z0-9_./\\]')
53+
let l:path_start = l:byte_idx
54+
break
55+
endif
56+
let l:byte_idx += len(l:char)
57+
endfor
58+
endif
59+
60+
" Find the last path separator before path_end
61+
let l:last_sep = -1
62+
let l:byte_idx = 0
63+
for l:char in split(a:line[:l:path_end], '\zs')
64+
if l:char ==# '/' || l:char ==# '\'
65+
let l:last_sep = l:byte_idx
66+
endif
67+
let l:byte_idx += len(l:char)
68+
endfor
69+
70+
" Return: [prefix_start, prefix_end, filename_start, filename_end]
71+
" For root-level files (no separator), prefix is -1,-1 and filename covers the whole name
72+
if l:last_sep == -1
73+
return [-1, -1, l:path_start, l:path_end]
74+
endif
75+
76+
return [l:path_start, l:last_sep, l:last_sep + 1, l:path_end]
77+
endfunction
78+
79+
" Use matchaddpos for higher priority highlighting (works in display window)
80+
" This is the same approach used by fuzzy match highlighting
81+
82+
" Store match IDs in window-local variable for cleanup
83+
function! clap#highlighter#path#clear_matches() abort
84+
if exists('w:clap_path_match_ids')
85+
for l:id in w:clap_path_match_ids
86+
try
87+
call matchdelete(l:id)
88+
catch
89+
" Ignore if already deleted
90+
endtry
91+
endfor
92+
endif
93+
let w:clap_path_match_ids = []
94+
endfunction
95+
96+
function! s:clear_path_highlights(bufnr) abort
97+
if exists('*win_execute') && exists('g:clap.display.winid')
98+
call win_execute(g:clap.display.winid, 'call clap#highlighter#path#clear_matches()')
99+
endif
100+
endfunction
101+
102+
" lnum is 0-based, col_start is 0-based byte index
103+
function! clap#highlighter#path#add_match(lnum, col_start, length, hl_group) abort
104+
" matchaddpos uses 1-based line and column numbers
105+
try
106+
let l:id = matchaddpos(a:hl_group, [[a:lnum + 1, a:col_start + 1, a:length]], 5)
107+
if !exists('w:clap_path_match_ids')
108+
let w:clap_path_match_ids = []
109+
endif
110+
call add(w:clap_path_match_ids, l:id)
111+
catch
112+
" Ignore errors
113+
endtry
114+
endfunction
115+
116+
if has('nvim')
117+
function! s:add_path_highlight(bufnr, lnum, col_start, col_end, hl_group) abort
118+
let l:length = a:col_end - a:col_start + 1
119+
if exists('*win_execute') && exists('g:clap.display.winid')
120+
call win_execute(g:clap.display.winid,
121+
\ 'call clap#highlighter#path#add_match(' . a:lnum . ',' . a:col_start . ',' . l:length . ',"' . a:hl_group . '")')
122+
endif
123+
endfunction
124+
else
125+
function! s:add_path_highlight(bufnr, lnum, col_start, col_end, hl_group) abort
126+
try
127+
call prop_add(a:lnum + 1, a:col_start + 1, {
128+
\ 'length': a:col_end - a:col_start + 1,
129+
\ 'type': a:hl_group,
130+
\ 'bufnr': a:bufnr
131+
\ })
132+
catch
133+
" Ignore errors
134+
endtry
135+
endfunction
136+
endif
137+
138+
" Debug function to check if path highlighting is working
139+
function! clap#highlighter#path#debug() abort
140+
echo 'Provider: ' . g:clap.provider.id
141+
echo 'Should highlight: ' . clap#highlighter#path#should_highlight()
142+
echo 'Icon added: ' . g:__clap_icon_added_by_maple
143+
echo 'Display bufnr: ' . g:clap.display.bufnr
144+
echo 'Display winid: ' . g:clap.display.winid
145+
echo ''
146+
147+
" Test with actual display lines
148+
let l:lines = getbufline(g:clap.display.bufnr, 1, 5)
149+
echo 'Display lines (' . len(l:lines) . '):'
150+
for l:i in range(len(l:lines))
151+
echo 'Line ' . l:i . ': "' . l:lines[l:i] . '"'
152+
let l:bounds = s:find_path_boundaries(l:lines[l:i])
153+
echo ' Bounds: ' . string(l:bounds)
154+
if !empty(l:bounds)
155+
let [l:ps, l:pe, l:fs, l:fe] = l:bounds
156+
if l:ps >= 0
157+
echo ' Prefix: "' . l:lines[l:i][l:ps : l:pe] . '"'
158+
else
159+
echo ' Prefix: (none - root level file)'
160+
endif
161+
echo ' Filename: "' . l:lines[l:i][l:fs : l:fe] . '"'
162+
endif
163+
endfor
164+
165+
echo ''
166+
echo '--- Synthetic tests ---'
167+
" Test with a path that has directories
168+
let l:test_line = ' autoload/clap/picker.vim'
169+
echo 'With dir: "' . l:test_line . '"'
170+
let l:bounds = s:find_path_boundaries(l:test_line)
171+
if !empty(l:bounds)
172+
let [l:ps, l:pe, l:fs, l:fe] = l:bounds
173+
if l:ps >= 0
174+
echo ' Prefix: "' . l:test_line[l:ps : l:pe] . '"'
175+
endif
176+
echo ' Filename: "' . l:test_line[l:fs : l:fe] . '"'
177+
endif
178+
179+
" Test with a root-level file
180+
let l:test_line2 = ' Cargo.toml'
181+
echo 'Root file: "' . l:test_line2 . '"'
182+
let l:bounds2 = s:find_path_boundaries(l:test_line2)
183+
if !empty(l:bounds2)
184+
let [l:ps, l:pe, l:fs, l:fe] = l:bounds2
185+
if l:ps >= 0
186+
echo ' Prefix: "' . l:test_line2[l:ps : l:pe] . '"'
187+
else
188+
echo ' Prefix: (none)'
189+
endif
190+
echo ' Filename: "' . l:test_line2[l:fs : l:fe] . '"'
191+
endif
192+
193+
echo ''
194+
echo 'ClapPathPrefix exists: ' . hlexists('ClapPathPrefix')
195+
echo 'ClapFileName exists: ' . hlexists('ClapFileName')
196+
197+
" Check if matches are applied in the display window
198+
if exists('g:clap.display.winid')
199+
let l:match_ids = getwinvar(g:clap.display.winid, 'clap_path_match_ids', [])
200+
echo 'Path match IDs in display window: ' . string(l:match_ids)
201+
endif
202+
203+
echo ''
204+
echo 'TIP: Type a query like "src/" or "autoload/" to see files with directory paths'
205+
echo 'Run :call clap#highlighter#path#apply() to manually re-apply highlights'
206+
endfunction
207+
208+
" Apply path highlighting to all visible lines in the display buffer
209+
function! clap#highlighter#path#apply() abort
210+
if !clap#highlighter#path#should_highlight()
211+
return
212+
endif
213+
214+
let l:bufnr = g:clap.display.bufnr
215+
if !bufexists(l:bufnr)
216+
return
217+
endif
218+
219+
" Clear previous path highlights
220+
call s:clear_path_highlights(l:bufnr)
221+
222+
" Get all lines in the display buffer
223+
let l:lines = getbufline(l:bufnr, 1, '$')
224+
225+
" Apply highlighting to each line
226+
let l:lnum = 0
227+
for l:line in l:lines
228+
if empty(l:line) || l:line ==# g:clap_no_matches_msg
229+
let l:lnum += 1
230+
continue
231+
endif
232+
233+
let l:bounds = s:find_path_boundaries(l:line)
234+
if !empty(l:bounds)
235+
let [l:prefix_start, l:prefix_end, l:filename_start, l:filename_end] = l:bounds
236+
237+
" Highlight directory prefix (dimmed) - includes the trailing /
238+
if l:prefix_end > l:prefix_start
239+
call s:add_path_highlight(l:bufnr, l:lnum, l:prefix_start, l:prefix_end, 'ClapPathPrefix')
240+
endif
241+
242+
" Highlight filename (bright)
243+
if l:filename_end >= l:filename_start
244+
call s:add_path_highlight(l:bufnr, l:lnum, l:filename_start, l:filename_end, 'ClapFileName')
245+
endif
246+
endif
247+
248+
let l:lnum += 1
249+
endfor
250+
endfunction
251+
252+
" Clear path highlights
253+
function! clap#highlighter#path#clear() abort
254+
if bufexists(g:clap.display.bufnr)
255+
call s:clear_path_highlights(g:clap.display.bufnr)
256+
endif
257+
endfunction
258+
259+
let &cpoptions = s:save_cpo
260+
unlet s:save_cpo

autoload/clap/init.vim

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,25 @@ function! s:init_fuzzy_match_hl_groups() abort
6666
let g:__clap_fuzzy_last_hl_group = 'ClapFuzzyMatches'.g:__clap_fuzzy_matches_hl_group_cnt
6767
endfunction
6868

69+
function! s:init_path_highlight_groups() abort
70+
" For Vim, we need to define text property types for path highlighting
71+
if !has('nvim')
72+
try
73+
call prop_type_add('ClapPathPrefix', {'highlight': 'ClapPathPrefix'})
74+
call prop_type_add('ClapFileName', {'highlight': 'ClapFileName'})
75+
catch
76+
" Types may already exist
77+
endtry
78+
endif
79+
endfunction
80+
6981
function! clap#init#() abort
7082
call clap#api#clap#init()
7183
call clap#themes#init()
7284

7385
call s:init_submatches_hl_group()
7486
call s:init_fuzzy_match_hl_groups()
87+
call s:init_path_highlight_groups()
7588

7689
" Spawn the daemon process if not running
7790
if !clap#job#daemon#is_running()

autoload/clap/picker.vim

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ function! clap#picker#init(lines, truncated_map, icon_added, using_cache) abort
3232
call clap#sign#ensure_exists()
3333
call clap#spinner#refresh()
3434
call clap#preview#update_with_delay()
35+
36+
" Apply path prefix dimming for file-related providers
37+
call clap#highlighter#path#apply()
3538
endfunction
3639

3740
function! clap#picker#process_progress(matched, processed) abort
@@ -88,6 +91,9 @@ function! clap#picker#update(update_info) abort
8891
return
8992
endtry
9093
endif
94+
95+
" Apply path prefix dimming for file-related providers
96+
call clap#highlighter#path#apply()
9197
endfunction
9298

9399
function! clap#picker#update_on_empty_query(lines, truncated_map, icon_added) abort
@@ -106,6 +112,9 @@ function! clap#picker#update_on_empty_query(lines, truncated_map, icon_added) ab
106112
call g:clap.display.clear_highlight()
107113
call clap#indicator#update_matched(0)
108114
call clap#preview#update_with_delay()
115+
116+
" Apply path prefix dimming for file-related providers
117+
call clap#highlighter#path#apply()
109118
endfunction
110119

111120
function! clap#picker#update_preview(preview) abort

0 commit comments

Comments
 (0)