├── LICENSE.md
├── README.md
├── autoload
└── gitgrep.vim
├── doc
└── gitgrep.txt
├── images
└── preview.gif
├── plugin
└── gitgrep.vim
└── syntax
└── gitgrep.vim
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright © 2021 Rico Sta. Cruz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | vim-gitgrep
3 |
4 |
5 |
6 | Searches the project using :GG <search>
and show it in a new buffer. Inspired by Sublime Text's Find in Files feature.
7 |
8 |
9 | 
10 |
11 | ## Install
12 |
13 | Install using vim-plug:
14 |
15 | ```vim
16 | Plug 'rstacruz/vim-gitgrep'
17 | ```
18 |
19 | Search using `:GG`:
20 |
21 | ```
22 | :GG hello
23 | ```
24 |
25 | Read the [documentation](./doc/gitgrep.txt):
26 |
27 | ```vim
28 | :help gitgrep
29 | ```
30 |
31 | ## Tips
32 |
33 | This will bind leaderg to search the current word under the cursor, similar to pressing \*.
34 |
35 | ```vim
36 | nnoremap * :GG!
37 | vnoremap * y:GG! "
38 | ```
39 |
40 | This will bend leader/ to put `:GG! ` in the command line, ready to accept input.
41 |
42 | ```vim
43 | nnoremap / :GG!
44 | ```
45 |
46 | ## Thanks
47 |
48 | :heart: MIT
49 |
--------------------------------------------------------------------------------
/autoload/gitgrep.vim:
--------------------------------------------------------------------------------
1 | " Include guard
2 | if exists('g:autoloaded_gitgrep') || &compatible | finish | endif
3 | let g:autoloaded_gitgrep = 1
4 |
5 | " Name of the gitgrep window
6 | if !exists('g:gitgrep_window')
7 | let g:gitgrep_window = 'GitGrep'
8 | endif
9 |
10 | " Position in :VG (right or left)
11 | if !exists('g:gitgrep_vertical_position')
12 | let g:gitgrep_vertical_position = 'right'
13 | endif
14 |
15 | if !exists('g:gitgrep_use_cursor_line')
16 | let g:gitgrep_use_cursor_line = 1
17 | endif
18 |
19 | " Position in :SG (top or bottom)
20 | if !exists('g:gitgrep_horizontal_position')
21 | let g:gitgrep_horizontal_position = 'bottom'
22 | endif
23 |
24 | " Height for the window
25 | if !exists('g:gitgrep_height')
26 | let g:gitgrep_height = 30
27 | endif
28 |
29 | " `winmode` can be [vertical]split, [split], [tab] or [max]imised
30 | if !exists('g:gitgrep_window_mode')
31 | let g:gitgrep_window_mode = 'vertical'
32 | endif
33 |
34 | " Store the last known search here
35 | if !exists('g:gitgrep_last_query')
36 | let g:gitgrep_last_query = {'keywords': '', 'win_mode': '', 'ignorecase': -1}
37 | endif
38 |
39 | if !exists('g:gitgrep_keep_focus')
40 | let g:gitgrep_keep_focus = 1
41 | endif
42 |
43 | " The main entrypoint
44 | function! gitgrep#run(win_mode, keywords, options) " {{{
45 | let ignorecase = get(a:options, 'ignorecase', 0)
46 | let focusonly = get(a:options, 'focusonly', 0)
47 | let force = get(a:options, 'force', 0)
48 |
49 | if a:win_mode == ''
50 | let win_mode = g:gitgrep_window_mode
51 | else
52 | let win_mode = a:win_mode
53 | endif
54 |
55 | " What invoked it
56 | let source = winnr()
57 |
58 | " Get query
59 | if a:keywords == ''
60 | if g:gitgrep_last_query['keywords'] == ''
61 | echo "What do you want to search for?"
62 | return
63 | else
64 | " Repeat the last query
65 | let query = g:gitgrep_last_query
66 | end
67 | else
68 | " New query
69 | let query = { 'keywords': a:keywords, 'ignorecase': ignorecase, 'win_mode': win_mode }
70 | endif
71 |
72 | " Open and focus on the window
73 | let window_mode = gitgrep#prepare_window(win_mode, { 'focusonly': focusonly })
74 | if window_mode > 0
75 | if g:gitgrep_last_query['keywords'] == query['keywords'] && force != 1
76 | " If it's just refocusing, and the query hasn't changed,
77 | " don't do anything. TODO: account for ignorecase
78 | return
79 | else
80 | " Otherwise, clear out the entire buffer
81 | setlocal noreadonly modifiable
82 | norm ggVG"_d
83 | endif
84 | endif
85 |
86 | " Perform an git grep search
87 | let escaped_query = shellescape(query['keywords'])
88 |
89 | let grep_params = ''
90 | if query['ignorecase'] == 1
91 | let grep_params = '-i '
92 | endif
93 |
94 | silent! exec 'r!git grep --heading --line-number -E ' . grep_params . escaped_query
95 |
96 | " check line count to see if there are results
97 | if line('$') != 1
98 | try
99 | " Format lines
100 | silent! %s#^\d\+:# & #g
101 |
102 | " Format filenames
103 | silent! %s#^[^ ].\+$#\rg
104 | catch /./
105 | endtry
106 |
107 | " Move cursor to top, remove first 2 lines
108 | normal gg
109 | normal "_2dd
110 |
111 | " Highlight currenty query
112 | call clearmatches()
113 | call matchadd('Search', (query['ignorecase'] == 1 ? '\c' : '') . query['keywords'])
114 |
115 | " Also allow pressing 'n' to move to next match
116 | let @/ = query['keywords']
117 | else
118 | exec "normal a! No results found for `" . query['keywords'] . "`"
119 | endif
120 |
121 | " Prevent it from being written, and other stuff
122 | " (Setting the filetype will bind buffer keys)
123 | silent! setlocal
124 | \ nocursorcolumn nobuflisted foldcolumn=0
125 | \ nolist nonumber norelativenumber nospell noswapfile signcolumn=no
126 | \ nomodifiable nonumber foldmethod=indent filetype=gitgrep buftype=nofile hlsearch ignorecase
127 |
128 | if g:gitgrep_use_cursor_line == 1
129 | silent! setlocal cursorline
130 | endif
131 |
132 | " Finally, let it be picked up later
133 | let g:gitgrep_last_query = query
134 | endfunction " }}}
135 |
136 | " Opens a window and focuses on it
137 | function gitgrep#open_window(win_mode) " {{{
138 | " `winmode` can be [vertical], [split], [tab] or [max]imised
139 | let mode = a:win_mode == '' ? 'max' : a:win_mode
140 |
141 | " save the referer window for splits
142 | let referer = winnr()
143 |
144 | if mode =~? '^v' " vertical
145 | let pos = g:gitgrep_vertical_position == 'right' ? 'botright' : 'topleft'
146 | exe 'vert ' . pos . ' new'
147 | let b:referer = referer
148 | elseif mode =~? '^t' " tab
149 | tabnew
150 | elseif mode =~? '^s' " split
151 | let pos = g:gitgrep_horizontal_position == 'bottom' ? 'bot' : 'top'
152 | exe '' . pos . ' new'
153 | exe 'resize ' . g:gitgrep_height
154 | let b:referer = referer
155 | elseif mode =~? 'm' " maximized
156 | " always opens on top
157 | exe 'top new'
158 | resize
159 | endif
160 | endfunction " }}}
161 |
162 | " Returns the buffer and window. Can return -1's
163 | "
164 | " let [buf, win] = gitgrep#get_current_window()
165 | "
166 | function! gitgrep#get_current_window() " {{{
167 | let buf = bufnr(g:gitgrep_window)
168 | let win = bufwinnr(buf)
169 | return [buf, win]
170 | endfunction "}}}
171 |
172 | " Opens a window, or focuses if it's already there
173 | function! gitgrep#prepare_window(win_mode, options) " {{{
174 | " Returns...
175 | " - `-1` if it did nothing
176 | " - `0` if it's a new window
177 | " - `1` if it's a new window & existing buffer
178 | " - `2` if it's an existing window & existing buffer
179 | let [buf, win] = gitgrep#get_current_window()
180 | let focusonly = get(a:options, 'focusonly', 0)
181 |
182 | if buf == -1
183 | " New buffer/window
184 | if focusonly == 1 | return -1 | endif
185 | call gitgrep#open_window(a:win_mode)
186 | " set buffer name
187 | exe 'file ' . g:gitgrep_window
188 | let b:gitgrep_buffer = 1
189 | return 0
190 | elseif win == -1
191 | " Old buffer, new window (reuse the old buffer)
192 | if focusonly == 1 | return -1 | endif
193 | call gitgrep#open_window(a:win_mode)
194 | exe 'b ' . buf
195 | return 1
196 | else
197 | " Old buffer, old window (focus on open window)
198 | exec '' . win . 'wincmd w'
199 | return 2
200 | endif
201 | endfunction " }}}
202 |
203 | " Binds keys
204 | function! gitgrep#bind_buffer_keys() " {{{
205 | nnoremap :call gitgrep#navigate('open')
206 | nnoremap o :call gitgrep#navigate('open')
207 | nnoremap f :call gitgrep#toggle_follow_cursor()
208 | nnoremap R :call gitgrep#refresh()
209 | autocmd CursorMoved call gitgrep#on_cursor_move()
210 | endfunction " }}}
211 |
212 | function! gitgrep#on_cursor_move() " {{{
213 | if exists('b:follow_cursor') && b:follow_cursor == 1
214 | call gitgrep#navigate('hover')
215 | endif
216 | endfunction " }}}
217 |
218 | " Navigates to a selected line in the search buffer
219 | function! gitgrep#navigate(mode) " {{{
220 | " TODO: this is being double-called, lets optimise that
221 | " only operate on the gitgrep buffer
222 | if get(b:, 'gitgrep_buffer', 0) != 1 | return | endif
223 |
224 | let old_g_value = getreg('g')
225 | let old_g_type = getregtype('g')
226 | let old_default_register = @"
227 | let follow_cursor = exists('b:follow_cursor') && b:follow_cursor == 1
228 |
229 | " keep track of original cursor location
230 | normal mg
231 |
232 | " copy the first word. it might be the line number
233 | normal ^"gyw
234 |
235 | " use it as the line number if it's numeric
236 | if @g =~? '^\d\+$'
237 | let linenr = @g
238 | else
239 | let linenr = '1'
240 | endif
241 |
242 | " move to the filename and copy
243 | normal {
244 | if line('.') != '1'
245 | normal j
246 | endif
247 | " normal 0v$l"gy
248 | normal "gyy
249 |
250 | " snap back to old location
251 | normal 'g
252 |
253 | " keep a reference to the search results window
254 | let src = winnr()
255 |
256 | let filepath = @g
257 | " use the referer window if possible. winwidth() will check
258 | " if the window is still open
259 | if exists('b:referer') && winwidth(b:referer) != -1
260 | let win = b:referer
261 | else
262 | vert bot new
263 | let win = winnr()
264 | silent! exe '' . src . 'windo w'
265 | let b:referer = win
266 | endif
267 | silent exe '' . win . 'windo edit +' . linenr . ' ' . filepath
268 |
269 | " refocus back to the search results window
270 | if follow_cursor
271 | setlocal cursorline
272 | silent! exe '' . src . 'windo w'
273 | endif
274 |
275 | " restore old register
276 | call setreg('g', old_g_value, old_g_type)
277 |
278 | " to make `p` work as usual, we'll set @" as the last register that was used
279 | let @" = old_default_register
280 |
281 | " Turn off follow cursor mode
282 | if a:mode == 'open'
283 | let b:follow_cursor = 0
284 | endif
285 | endfunction " }}}
286 |
287 | function gitgrep#toggle_follow_cursor() " {{{
288 | " only operate on the gitgrep buffer
289 | if get(b:, 'gitgrep_buffer', 0) != 1 | return | endif
290 |
291 | if exists('b:follow_cursor') && b:follow_cursor == 1
292 | echo "Follow cursor [off]"
293 | let b:follow_cursor = 0
294 | else
295 | echo "Follow cursor [on] - experimental!"
296 | let b:follow_cursor = 1
297 | end
298 | endfunction " }}}
299 |
300 | function gitgrep#refresh() " {{{
301 | call gitgrep#run('', '', { 'force': 1, 'focusonly': 1 })
302 | endfunction " }}}
303 |
--------------------------------------------------------------------------------
/doc/gitgrep.txt:
--------------------------------------------------------------------------------
1 | *gitgrep* Git grep tool
2 |
3 |
4 | GIT GREP TOOL
5 |
6 |
7 | =======================================================================================
8 | CONTENTS *gitgrep-contents*
9 |
10 | Commands ......................................... |gitgrep-commands|
11 | Configuration .................................... |gitgrep-configuration|
12 | Example config ................................... |gitgrep-example|
13 |
14 | =======================================================================================
15 | COMMANDS *gitgrep-commands*
16 | *:GG*
17 | >
18 | :GG []
19 | :GG! []
20 | <
21 | Perform a search for ``. If the query isn't given, it will re-perform
22 | the last query.
23 |
24 | If a `!` is given, then the search will be case-insensitive.
25 |
26 | =======================================================================================
27 | CONFIGURATION *gitgrep-configuration*
28 | *g:gitgrep_use_cursorline*
29 | >
30 | let g:gitgrep_use_cursorline = 1
31 | <
32 | If set to `1`, the current line in the search results will be highlighted.
33 |
34 | *g:gitgrep_window_mode*
35 | >
36 | let g:gitgrep_window_mode = 'vertical'
37 | <
38 | Changes the way the Gitgrep window appears. Valid values are:
39 |
40 | 'vertical' Vertical split (default)
41 | 'split' Horizontal split
42 | 'max' Maximized split
43 | 'tab' New tab
44 |
45 | *g:gitgrep_horizontal_position*
46 | >
47 | let g:gitgrep_horizontal_position = 'bottom'
48 | <
49 | Sets the position of when `g:gitgrep_window_mode` is set to 'split'.
50 | Can be set to either 'top' or 'bottom'.
51 |
52 | *g:gitgrep_vertical_position*
53 | >
54 | let g:gitgrep_vertical_position = 'right'
55 | <
56 | Sets the position of when `g:gitgrep_window_mode` is set to 'vertical'.
57 | Can be set to either 'left' or 'right'.
58 |
59 | *g:gitgrep_height*
60 | >
61 | let g:gitgrep_height = 30
62 | <
63 | Sets the height of the window when `g:gitgrep_window_mode` is set to 'split'.
64 |
65 | =======================================================================================
66 | KEY BINDINGS *gitgrep-keys*
67 |
68 | These keys are available in the gitgrep window:
69 |
70 | `` navigate to line
71 | `R` refresh
72 |
73 | =======================================================================================
74 | EXAMPLE CONFIG *gitgrep-example*
75 | >
76 | nnoremap / :GG!
77 | nnoremap * :GG!
78 | vnoremap * y:GG! "
79 | <
80 | =======================================================================================
81 | vim:tw=78:ts=8:noet:ft=help:norl:
82 |
--------------------------------------------------------------------------------
/images/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rstacruz/vim-gitgrep/f8cd8e687413d7b3be6bb0c1b10b96e27a345023/images/preview.gif
--------------------------------------------------------------------------------
/plugin/gitgrep.vim:
--------------------------------------------------------------------------------
1 | " Git grep
2 | command! -bang -nargs=* GG call gitgrep#run('', , { 'ignorecase': 0 })
3 |
4 | augroup gitgrep
5 | autocmd FileType gitgrep call gitgrep#bind_buffer_keys()
6 | augroup END
7 |
8 | " junegunn/vim-slash
9 | if globpath(&rtp, "plugin/slash.vim") != ""
10 | augroup gitgrep_slash
11 | autocmd FileType gitgrep autocmd! slash CursorMoved,CursorMovedI *
12 | augroup END
13 | endif
14 |
--------------------------------------------------------------------------------
/syntax/gitgrep.vim:
--------------------------------------------------------------------------------
1 | if exists('b:current_syntax') | finish | endif
2 |
3 | syntax match GitgrepFile /^[^ ].*/
4 | syntax match GitGrepLineNr /^ \+\d\+:/
5 | syntax match GitGrepError /^\(!\|fatal:\) .*/
6 |
7 | highlight default link GitgrepFile Title
8 | highlight default link GitgrepLineNr LineNr
9 | highlight default link GitgrepError Error
10 |
11 | let b:current_syntax = 'gitgrep'
12 |
--------------------------------------------------------------------------------