dblume commited on 2022-08-10 11:52:28
Showing 4 changed files, with 422 additions and 1 deletions.
... | ... |
@@ -0,0 +1,166 @@ |
1 |
+*rooter.txt* Plugin that changes to a buffer's root directory |
|
2 |
+ |
|
3 |
+ ____ __ |
|
4 |
+ / __ \____ ____ / /____ _____ |
|
5 |
+ / /_/ / __ \/ __ \/ __/ _ \/ ___/ |
|
6 |
+ / _, _/ /_/ / /_/ / /_/ __/ / |
|
7 |
+ /_/ \_\\____/\____/\__/\___/_/ |
|
8 |
+ |
|
9 |
+ |
|
10 |
+============================================================================== |
|
11 |
+Rooter |rooter| |
|
12 |
+Introduction ........................................... |rooter-introduction| |
|
13 |
+Usage ......................................................... |rooter-usage| |
|
14 |
+Configuration ......................................... |rooter-configuration| |
|
15 |
+Using in other scripts ......................... |rooter-use-by-other-plugins| |
|
16 |
+ |
|
17 |
+ |
|
18 |
+============================================================================== |
|
19 |
+Introduction *rooter* *rooter-introduction* |
|
20 |
+ |
|
21 |
+Rooter is a Vim plugin which changes the working directory to the project root |
|
22 |
+when you open a file or directory. |
|
23 |
+ |
|
24 |
+The project root can be identified by: |
|
25 |
+ |
|
26 |
+- being a known directory; |
|
27 |
+- having a known directory or file; |
|
28 |
+- being a subdirectory of a known directory. |
|
29 |
+ |
|
30 |
+You can also exclude directories. |
|
31 |
+ |
|
32 |
+For a file or directory which doesn't have a root, Rooter can: do nothing; |
|
33 |
+change to the file's directory (similar to 'autochdir'); or change to your |
|
34 |
+home directory. |
|
35 |
+ |
|
36 |
+ |
|
37 |
+============================================================================== |
|
38 |
+Usage *rooter-usage* |
|
39 |
+ |
|
40 |
+By default you don't need to do anything: Rooter will change the working |
|
41 |
+directory automatically and echo the new working directory. |
|
42 |
+ |
|
43 |
+You can turn this off (see below) and use the |:Rooter| command to invoke |
|
44 |
+Rooter manually. |
|
45 |
+ |
|
46 |
+When Rooter changes the working directory it emits the |autocmd| |User| event |
|
47 |
+`RooterChDir`. |
|
48 |
+ |
|
49 |
+Rooter will unset 'autochdir' if it's set. |
|
50 |
+ |
|
51 |
+ |
|
52 |
+============================================================================== |
|
53 |
+Configuration *rooter-configuration* |
|
54 |
+ |
|
55 |
+Which buffers trigger Rooter *g:rooter_targets* |
|
56 |
+ |
|
57 |
+By default all files and directories trigger Rooter. Configure a comma |
|
58 |
+separated list of patterns to specify which files trigger. Include "/" to |
|
59 |
+trigger on directories. For example: |
|
60 |
+> |
|
61 |
+ let g:rooter_targets = '/,*.rb,*.haml,*.js' |
|
62 |
+< |
|
63 |
+Default: '/,*' |
|
64 |
+ |
|
65 |
+------------------------------------------------------------------------------ |
|
66 |
+How to identify a root directory *g:rooter_patterns* |
|
67 |
+ |
|
68 |
+Default: ['.git', '_darcs', '.hg', '.bzr', '.svn', 'Makefile', 'package.json'] |
|
69 |
+ |
|
70 |
+Set `g:rooter_patterns` to a list of identifiers. They are checked breadth- |
|
71 |
+first as Rooter walks up the directory tree and the first match is used. |
|
72 |
+ |
|
73 |
+To specify the root is a certain directory, prefix it with `=`. |
|
74 |
+> |
|
75 |
+ let g:rooter_patterns = ['=src'] |
|
76 |
+< |
|
77 |
+To specify the root has a certain directory or file (which may be a glob), |
|
78 |
+just give the name: |
|
79 |
+> |
|
80 |
+ let g:rooter_patterns = ['.git', 'Makefile', '*.sln', 'build/env.sh'] |
|
81 |
+< |
|
82 |
+To specify the root has a certain directory as an ancestor (useful for |
|
83 |
+excluding directories), prefix it with `^`: |
|
84 |
+> |
|
85 |
+ let g:rooter_patterns = ['^fixtures'] |
|
86 |
+< |
|
87 |
+To specify the root has a certain directory as its direct ancestor / parent |
|
88 |
+(useful for when you put working projects in a common direcotry), prefix it |
|
89 |
+with `>`: |
|
90 |
+> |
|
91 |
+ let g:rooter_patterns = ['>Latex'] |
|
92 |
+< |
|
93 |
+To exclude a pattern, prefix it with `!`. |
|
94 |
+> |
|
95 |
+ let g:rooter_patterns = ['!.git/worktrees', '!=src', '!build/env.sh', '!^fixtures'] |
|
96 |
+< |
|
97 |
+List your exclusions before the patterns you do want. |
|
98 |
+ |
|
99 |
+------------------------------------------------------------------------------ |
|
100 |
+Non-project files *g:rooter_change_directory_for_non_project_files* |
|
101 |
+ |
|
102 |
+There are three options for non-project files/directories: |
|
103 |
+ |
|
104 |
+- Don't change directory (default). |
|
105 |
+> |
|
106 |
+ let g:rooter_change_directory_for_non_project_files = '' |
|
107 |
+< |
|
108 |
+- Change to file's directory (similar to 'autochdir'). |
|
109 |
+> |
|
110 |
+ let g:rooter_change_directory_for_non_project_files = 'current' |
|
111 |
+< |
|
112 |
+- Change to home directory. |
|
113 |
+> |
|
114 |
+ let g:rooter_change_directory_for_non_project_files = 'home' |
|
115 |
+< |
|
116 |
+Default: '' |
|
117 |
+ |
|
118 |
+------------------------------------------------------------------------------ |
|
119 |
+Running automatically or manually *g:rooter_manual_only* |
|
120 |
+ *:RooterToggle* |
|
121 |
+ *:Rooter* |
|
122 |
+ |
|
123 |
+To toggle between automatic and manual behaviour, use |:RooterToggle|. When |
|
124 |
+running manually you can invoke Rooter with |:Rooter|. |
|
125 |
+ |
|
126 |
+To make Rooter start in manual mode: |
|
127 |
+> |
|
128 |
+ let g:rooter_manual_only = 1 |
|
129 |
+< |
|
130 |
+Default: 0 |
|
131 |
+ |
|
132 |
+------------------------------------------------------------------------------ |
|
133 |
+To change the change-directory command: *g:rooter_cd_cmd* |
|
134 |
+> |
|
135 |
+ let g:rooter_cd_cmd = 'lcd' |
|
136 |
+< |
|
137 |
+Default: "cd" |
|
138 |
+ |
|
139 |
+------------------------------------------------------------------------------ |
|
140 |
+To stop Rooter echoing the project directory: *g:rooter_silent_chdir* |
|
141 |
+> |
|
142 |
+ let g:rooter_silent_chdir = 1 |
|
143 |
+< |
|
144 |
+Default: 0 |
|
145 |
+ |
|
146 |
+------------------------------------------------------------------------------ |
|
147 |
+Symlinks *g:rooter_resolve_links* |
|
148 |
+ |
|
149 |
+By default Rooter doesn't resolve symbolic links in the file or directory |
|
150 |
+which triggers it. To resolve links: |
|
151 |
+> |
|
152 |
+ let g:rooter_resolve_links = 1 |
|
153 |
+< |
|
154 |
+Default: 0 |
|
155 |
+ |
|
156 |
+ |
|
157 |
+============================================================================== |
|
158 |
+Using in other scripts *rooter-use-by-other-plugins* |
|
159 |
+ |
|
160 |
+The public function |FindRootDirectory()| returns the absolute path to the |
|
161 |
+root directory as a string, if a root directory is found, or an empty string |
|
162 |
+otherwise. |
|
163 |
+ |
|
164 |
+ |
|
165 |
+============================================================================== |
|
166 |
+vim:tw=78:sw=4:ts=8:ft=help:norl: |
... | ... |
@@ -0,0 +1,251 @@ |
1 |
+" Vim plugin to change the working directory to the project root. |
|
2 |
+" |
|
3 |
+" Copyright 2010-2020 Andrew Stewart, <boss@airbladesoftware.com> |
|
4 |
+" Released under the MIT licence. |
|
5 |
+ |
|
6 |
+if exists('g:loaded_rooter') || &cp |
|
7 |
+ finish |
|
8 |
+endif |
|
9 |
+let g:loaded_rooter = 1 |
|
10 |
+ |
|
11 |
+let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : '' |
|
12 |
+ |
|
13 |
+if !exists('g:rooter_manual_only') |
|
14 |
+ let g:rooter_manual_only = 0 |
|
15 |
+endif |
|
16 |
+ |
|
17 |
+if exists('+autochdir') && &autochdir && !g:rooter_manual_only |
|
18 |
+ set noautochdir |
|
19 |
+endif |
|
20 |
+ |
|
21 |
+if exists('g:rooter_use_lcd') |
|
22 |
+ echoerr 'vim-rooter: please replace g:rooter_use_lcd=1 with g:rooter_cd_cmd="lcd"' |
|
23 |
+ let g:rooter_cd_cmd = 'lcd' |
|
24 |
+endif |
|
25 |
+ |
|
26 |
+if !exists('g:rooter_cd_cmd') |
|
27 |
+ let g:rooter_cd_cmd = 'cd' |
|
28 |
+endif |
|
29 |
+ |
|
30 |
+if !exists('g:rooter_patterns') |
|
31 |
+ let g:rooter_patterns = ['.git', '_darcs', '.hg', '.bzr', '.svn', 'Makefile', 'package.json'] |
|
32 |
+endif |
|
33 |
+ |
|
34 |
+if !exists('g:rooter_targets') |
|
35 |
+ let g:rooter_targets = '/,*' |
|
36 |
+endif |
|
37 |
+ |
|
38 |
+if !exists('g:rooter_change_directory_for_non_project_files') |
|
39 |
+ let g:rooter_change_directory_for_non_project_files = '' |
|
40 |
+endif |
|
41 |
+ |
|
42 |
+if !exists('g:rooter_silent_chdir') |
|
43 |
+ let g:rooter_silent_chdir = 0 |
|
44 |
+endif |
|
45 |
+ |
|
46 |
+if !exists('g:rooter_resolve_links') |
|
47 |
+ let g:rooter_resolve_links = 0 |
|
48 |
+endif |
|
49 |
+ |
|
50 |
+ |
|
51 |
+" For third-parties. Not used by plugin. |
|
52 |
+function! FindRootDirectory() |
|
53 |
+ return s:root() |
|
54 |
+endfunction |
|
55 |
+ |
|
56 |
+ |
|
57 |
+command! -bar Rooter call <SID>rooter() |
|
58 |
+command! -bar RooterToggle call <SID>toggle() |
|
59 |
+ |
|
60 |
+ |
|
61 |
+augroup rooter |
|
62 |
+ autocmd! |
|
63 |
+ autocmd VimEnter,BufReadPost,BufEnter * nested if !g:rooter_manual_only | Rooter | endif |
|
64 |
+ autocmd BufWritePost * nested if !g:rooter_manual_only | call setbufvar('%', 'rootDir', '') | Rooter | endif |
|
65 |
+augroup END |
|
66 |
+ |
|
67 |
+ |
|
68 |
+function! s:rooter() |
|
69 |
+ if !s:activate() | return | endif |
|
70 |
+ |
|
71 |
+ let root = getbufvar('%', 'rootDir') |
|
72 |
+ if empty(root) |
|
73 |
+ let root = s:root() |
|
74 |
+ call setbufvar('%', 'rootDir', root) |
|
75 |
+ endif |
|
76 |
+ |
|
77 |
+ if empty(root) |
|
78 |
+ call s:rootless() |
|
79 |
+ return |
|
80 |
+ endif |
|
81 |
+ |
|
82 |
+ call s:cd(root) |
|
83 |
+endfunction |
|
84 |
+ |
|
85 |
+ |
|
86 |
+" Returns true if we should change to the buffer's root directory, false otherwise. |
|
87 |
+function! s:activate() |
|
88 |
+ " Directory browser plugins (e.g. vim-dirvish, NERDTree) tend to |
|
89 |
+ " set a nofile buftype when you open a directory. |
|
90 |
+ if &buftype != '' && &buftype != 'nofile' | return 0 | endif |
|
91 |
+ |
|
92 |
+ let patterns = split(g:rooter_targets, ',') |
|
93 |
+ let fn = expand('%:p', 1) |
|
94 |
+ |
|
95 |
+ if fn =~ 'NERD_tree_\d\+$' | let fn = b:NERDTree.root.path.str().'/' | endif |
|
96 |
+ |
|
97 |
+ " directory |
|
98 |
+ if empty(fn) || fn[-1:] == '/' |
|
99 |
+ return index(patterns, '/') != -1 |
|
100 |
+ endif |
|
101 |
+ |
|
102 |
+ " file |
|
103 |
+ if !filereadable(fn) | return 0 | endif |
|
104 |
+ if !exists('*glob2regpat') | return 1 | endif |
|
105 |
+ |
|
106 |
+ for p in filter(copy(patterns), 'v:val != "/"') |
|
107 |
+ if fn =~ glob2regpat(p) |
|
108 |
+ return 1 |
|
109 |
+ endif |
|
110 |
+ endfor |
|
111 |
+ |
|
112 |
+ return 0 |
|
113 |
+endfunction |
|
114 |
+ |
|
115 |
+ |
|
116 |
+" Returns the root directory or an empty string if no root directory found. |
|
117 |
+function! s:root() |
|
118 |
+ let dir = s:current() |
|
119 |
+ |
|
120 |
+ " breadth-first search |
|
121 |
+ while 1 |
|
122 |
+ for pattern in g:rooter_patterns |
|
123 |
+ if pattern[0] == '!' |
|
124 |
+ let [p, exclude] = [pattern[1:], 1] |
|
125 |
+ else |
|
126 |
+ let [p, exclude] = [pattern, 0] |
|
127 |
+ endif |
|
128 |
+ if s:match(dir, p) |
|
129 |
+ if exclude |
|
130 |
+ break |
|
131 |
+ else |
|
132 |
+ return dir |
|
133 |
+ endif |
|
134 |
+ endif |
|
135 |
+ endfor |
|
136 |
+ |
|
137 |
+ let [current, dir] = [dir, s:parent(dir)] |
|
138 |
+ if current == dir | break | endif |
|
139 |
+ endwhile |
|
140 |
+ |
|
141 |
+ return '' |
|
142 |
+endfunction |
|
143 |
+ |
|
144 |
+ |
|
145 |
+function s:match(dir, pattern) |
|
146 |
+ if a:pattern[0] == '=' |
|
147 |
+ return s:is(a:dir, a:pattern[1:]) |
|
148 |
+ elseif a:pattern[0] == '^' |
|
149 |
+ return s:sub(a:dir, a:pattern[1:]) |
|
150 |
+ elseif a:pattern[0] == '>' |
|
151 |
+ return s:child(a:dir, a:pattern[1:]) |
|
152 |
+ else |
|
153 |
+ return s:has(a:dir, a:pattern) |
|
154 |
+ endif |
|
155 |
+endfunction |
|
156 |
+ |
|
157 |
+ |
|
158 |
+" Returns true if dir is identifier, false otherwise. |
|
159 |
+" |
|
160 |
+" dir - full path to a directory |
|
161 |
+" identifier - a directory name |
|
162 |
+function! s:is(dir, identifier) |
|
163 |
+ let identifier = substitute(a:identifier, '/$', '', '') |
|
164 |
+ return fnamemodify(a:dir, ':t') ==# identifier |
|
165 |
+endfunction |
|
166 |
+ |
|
167 |
+ |
|
168 |
+" Returns true if dir contains identifier, false otherwise. |
|
169 |
+" |
|
170 |
+" dir - full path to a directory |
|
171 |
+" identifier - a file name or a directory name; may be a glob |
|
172 |
+function! s:has(dir, identifier) |
|
173 |
+ " We do not want a:dir to be treated as a glob so escape any wildcards. |
|
174 |
+ " If this approach is problematic (e.g. on Windows), an alternative |
|
175 |
+ " might be to change directory to a:dir, call globpath() with just |
|
176 |
+ " a:identifier, then restore the working directory. |
|
177 |
+ return !empty(globpath(escape(a:dir, '?*[]'), a:identifier, 1)) |
|
178 |
+endfunction |
|
179 |
+ |
|
180 |
+ |
|
181 |
+" Returns true if identifier is an ancestor of dir, |
|
182 |
+" i.e. dir is a subdirectory (no matter how many levels) of identifier; |
|
183 |
+" false otherwise. |
|
184 |
+" |
|
185 |
+" dir - full path to a directory |
|
186 |
+" identifier - a directory name |
|
187 |
+function! s:sub(dir, identifier) |
|
188 |
+ let path = s:parent(a:dir) |
|
189 |
+ while 1 |
|
190 |
+ if fnamemodify(path, ':t') ==# a:identifier | return 1 | endif |
|
191 |
+ let [current, path] = [path, s:parent(path)] |
|
192 |
+ if current == path | break | endif |
|
193 |
+ endwhile |
|
194 |
+ return 0 |
|
195 |
+endfunction |
|
196 |
+ |
|
197 |
+" Return true if identifier is a direct ancestor (parent) of dir, |
|
198 |
+" i.e. dir is a direct subdirectory (child) of identifier; false otherwise |
|
199 |
+" |
|
200 |
+" dir - full path to a directory |
|
201 |
+" identifier - a directory name |
|
202 |
+function! s:child(dir, identifier) |
|
203 |
+ let path = s:parent(a:dir) |
|
204 |
+ return fnamemodify(path, ':t') ==# a:identifier |
|
205 |
+endfunction |
|
206 |
+ |
|
207 |
+" Returns full path of directory of current file name (which may be a directory). |
|
208 |
+function! s:current() |
|
209 |
+ let fn = expand('%:p', 1) |
|
210 |
+ if fn =~ 'NERD_tree_\d\+$' | let fn = b:NERDTree.root.path.str().'/' | endif |
|
211 |
+ if empty(fn) | return getcwd() | endif " opening vim without a file |
|
212 |
+ if g:rooter_resolve_links | let fn = resolve(fn) | endif |
|
213 |
+ return fnamemodify(fn, ':h') |
|
214 |
+endfunction |
|
215 |
+ |
|
216 |
+ |
|
217 |
+" Returns full path of dir's parent directory. |
|
218 |
+function! s:parent(dir) |
|
219 |
+ return fnamemodify(a:dir, ':h') |
|
220 |
+endfunction |
|
221 |
+ |
|
222 |
+ |
|
223 |
+" Changes to the given directory unless it is already the current one. |
|
224 |
+function! s:cd(dir) |
|
225 |
+ if a:dir == getcwd() | return | endif |
|
226 |
+ execute g:rooter_cd_cmd fnameescape(a:dir) |
|
227 |
+ if !g:rooter_silent_chdir | redraw | echo 'cwd: '.a:dir | endif |
|
228 |
+ if exists('#User#RooterChDir') |
|
229 |
+ execute 'doautocmd' s:nomodeline 'User RooterChDir' |
|
230 |
+ endif |
|
231 |
+endfunction |
|
232 |
+ |
|
233 |
+ |
|
234 |
+function! s:rootless() |
|
235 |
+ let dir = '' |
|
236 |
+ if g:rooter_change_directory_for_non_project_files ==? 'current' |
|
237 |
+ let dir = s:current() |
|
238 |
+ elseif g:rooter_change_directory_for_non_project_files ==? 'home' |
|
239 |
+ let dir = $HOME |
|
240 |
+ endif |
|
241 |
+ if !empty(dir) | call s:cd(dir) | endif |
|
242 |
+endfunction |
|
243 |
+ |
|
244 |
+ |
|
245 |
+function! s:toggle() |
|
246 |
+ if g:rooter_manual_only | Rooter | endif |
|
247 |
+ let g:rooter_manual_only = !g:rooter_manual_only |
|
248 |
+endfunction |
|
249 |
+ |
|
250 |
+ |
|
251 |
+" vim:set ft=vim sw=2 sts=2 et: |
... | ... |
@@ -353,3 +353,6 @@ let g:airline_right_alt_sep = '|' |
353 | 353 |
let g:airline_theme = 'powerlineish' |
354 | 354 |
let g:airline#extensions#wordcount#enabled = 0 |
355 | 355 |
" let g:airline_exclude_filetypes = [] |
356 |
+ |
|
357 |
+" Experimenting with vim-rooter |
|
358 |
+let g:rooter_patterns = ['.git', 'Makefile', 'builds/'] |
... | ... |
@@ -48,7 +48,8 @@ See [config.dlma.com](http://config.dlma.com) for more. |
48 | 48 |
3. [taglist](http://www.vim.org/scripts/script.php?script_id=273), a ctags tree-view explorer. |
49 | 49 |
4. [file-line](http://www.vim.org/scripts/script.php?script_id=2184), to open file:line as from a compiler error. |
50 | 50 |
5. [visual-star-search](http://got-ravings.blogspot.com/2008/07/vim-pr0n-visual-search-mappings.html), so * and # work in visual mode too. |
51 |
- 6. Assorted favorite colors like [desert](https://github.com/dblume/desert.vim). |
|
51 |
+ 6. [vim-rooter](https://github.com/airblade/vim-rooter), automatically find and set root project directory |
|
52 |
+ 7. Assorted favorite colors like [desert](https://github.com/dblume/desert.vim). |
|
52 | 53 |
3. .gitconfig and .gitignore |
53 | 54 |
4. .tmux.conf |
54 | 55 |
5. .inputrc, for vi mode and a [partially matched command history traversal](http://askubuntu.com/questions/59846/bash-history-search-partial-up-arrow/59855#59855). |
55 | 56 |