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 |