Added vim-rooter.
dblume

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