├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .ruby-version ├── .vim-version ├── Flavorfile ├── Gemfile ├── Rakefile ├── autoload └── gf │ └── user.vim ├── doc └── gf-user.txt ├── plugin └── gf │ └── user.vim └── t ├── basics.vim └── mode.vim /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: 0 0 * * * 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Ruby 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 20 | - name: Get local Vim version 21 | run: echo "local_vim_version=$(<.vim-version)" >>$GITHUB_ENV 22 | - name: Set up Vim 23 | uses: thinca/action-setup-vim@v2 24 | with: 25 | vim_version: ${{ github.event_name == 'schedule' && 'head' || env.local_vim_version }} 26 | vim_type: vim 27 | download: never # For some reason 'available' doesn't build from source as a fallback. 28 | - name: Run tests 29 | # 2>&1 is required to avoid interleaved stdout and stderr in log. 30 | run: rake ci 2>&1 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vim-flavor 2 | Flavorfile.lock 3 | Gemfile.lock 4 | tags 5 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.2 2 | -------------------------------------------------------------------------------- /.vim-version: -------------------------------------------------------------------------------- 1 | v9.1.0686 2 | -------------------------------------------------------------------------------- /Flavorfile: -------------------------------------------------------------------------------- 1 | # No dependencies. 2 | 3 | # vim: filetype=ruby 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'vim-flavor', '~> 4.0' 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :ci => [:dump, :test] 2 | 3 | task :dump do 4 | sh 'vim --version' 5 | end 6 | 7 | task :test do 8 | sh 'bundle exec vim-flavor test' 9 | end 10 | -------------------------------------------------------------------------------- /autoload/gf/user.vim: -------------------------------------------------------------------------------- 1 | " gf-user - A framework to open a file by context 2 | " Version: 1.1.0 3 | " Copyright (C) 2012-2024 Kana Natsuno 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | " Interface "{{{1 25 | function! gf#user#extend(funcname, priority) "{{{2 26 | let s:ext_dict[a:funcname] = a:priority 27 | endfunction 28 | 29 | 30 | 31 | 32 | function! gf#user#set_priority(funcname, priority) "{{{2 33 | if has_key(s:ext_dict, a:funcname) 34 | let s:ext_dict[a:funcname] = a:priority 35 | else 36 | echoerr 'gf-user: Extension' string(a:funcname) 'is not registered.' 37 | endif 38 | endfunction 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | " Misc. "{{{1 48 | " Variables "{{{2 49 | 50 | if !exists('s:ext_dict') 51 | let s:ext_dict = {} " FuncName -> Priority 52 | endif 53 | 54 | 55 | 56 | 57 | function! gf#user#do(gf_cmd, mode) "{{{2 58 | try 59 | execute 'normal!' a:gf_cmd 60 | return 61 | catch /\C\V\^Vim\%((\a\+)\)\?:\(E446\|E447\):/ 62 | let last_exception = v:exception 63 | 64 | if a:mode ==# 'x' 65 | " If built-in gf commands fail in Visual mode, it clears Visual mode. 66 | " So Visual mode must be restarted for extension functions which may 67 | " refer the current mode. 68 | normal! gv 69 | endif 70 | 71 | try 72 | for funcname in gf#user#get_sorted_ext_list() 73 | if gf#user#try(funcname, a:gf_cmd) 74 | return 75 | endif 76 | endfor 77 | catch 78 | let last_exception = v:exception 79 | endtry 80 | 81 | if a:mode ==# 'x' 82 | " To behave like built-in gf commands, Visual mode should be cleared 83 | " if all extension functions fail. 84 | execute 'normal!' "\\" 85 | endif 86 | 87 | echohl ErrorMsg 88 | echomsg substitute(last_exception, '\C^Vim.\{-}:', '', '') 89 | echohl NONE 90 | endtry 91 | endfunction 92 | 93 | 94 | 95 | 96 | function! gf#user#get_ext_dict() "{{{2 97 | return s:ext_dict 98 | endfunction 99 | 100 | 101 | 102 | 103 | function! gf#user#get_sorted_ext_list() "{{{2 104 | return 105 | \ map( 106 | \ sort( 107 | \ map( 108 | \ keys(s:ext_dict), 109 | \ '[printf("%06d %s", s:ext_dict[v:val], v:val), v:val]' 110 | \ ) 111 | \ ), 112 | \ 'v:val[1]' 113 | \ ) 114 | endfunction 115 | 116 | 117 | 118 | 119 | function! gf#user#split(gf_cmd) "{{{2 120 | let nop_cmd = '' 121 | let split_cmd_table = { 122 | \ "gf": nop_cmd, 123 | \ "gF": nop_cmd, 124 | \ "\f": 'split', 125 | \ "\\": 'split', 126 | \ "\F": 'split', 127 | \ "\gf": 'tab split', 128 | \ "\gF": 'tab split', 129 | \ } 130 | execute get(split_cmd_table, a:gf_cmd, nop_cmd) 131 | endfunction 132 | 133 | 134 | 135 | 136 | function! gf#user#try(funcname, gf_cmd) "{{{2 137 | let p = function(a:funcname)() 138 | " p.path might be neither a file nor a directory. In this case, p.path is 139 | " expected to be "open"ed by other plugins. For example, if p.path == 140 | " 'http://www.example.com/', netrw will be used to "open" the path. So that 141 | " it's not necessary to check filereadable(p.path) || isdirectory(p.path). 142 | if p isnot 0 143 | call gf#user#split(a:gf_cmd) 144 | edit `=p.path` 145 | execute 'normal!' (p.line . 'gg') 146 | if p.col != 0 147 | execute 'normal!' (p.col . '|') 148 | endif 149 | return !0 150 | else 151 | return 0 152 | endif 153 | endfunction 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | " __END__ "{{{1 163 | " vim: foldmethod=marker 164 | -------------------------------------------------------------------------------- /doc/gf-user.txt: -------------------------------------------------------------------------------- 1 | *gf-user.txt* A framework to open a file by context 2 | 3 | Version 1.1.0 4 | Script ID: 3891 5 | Copyright (C) 2012-2024 Kana Natsuno 6 | License: MIT license {{{ 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included 16 | in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | }}} 26 | 27 | CONTENTS *gf-user-contents* 28 | 29 | Introduction |gf-user-introduction| 30 | Interface |gf-user-interface| 31 | Commands |gf-user-commands| 32 | Functions |gf-user-functions| 33 | Key Mappings |gf-user-key-mappings| 34 | Customization |gf-user-customization| 35 | Bugs |gf-user-bugs| 36 | Changelog |gf-user-changelog| 37 | 38 | 39 | 40 | 41 | ============================================================================== 42 | INTRODUCTION *gf-user-introduction* 43 | 44 | *gf-user* is a Vim plugin to provide a framwork to extend |gf|. Though |gf| 45 | is one of the most useful commands in Vim, it has the following problems: 46 | 47 | - The file to be opened is based on the word under the cursor. 48 | - It's not possible to specify the cursor position for the opened file. 49 | 50 | Sometimes you might wish |gf| used more text around the cursor to detect the 51 | file to be opened and the initial cursor position. For example, 52 | 53 | - While reading output from diff(1), go to the hunk under the cursor. 54 | - While editing files for an MVC framework, go to the view file corresponding 55 | to an action of the controller under the cursor. 56 | 57 | Though it's not hard to implement each extension, it's a bit troublesome to 58 | synthesize all extensions. |gf-user| provides a framework for the extensions. 59 | 60 | 61 | Requirements: 62 | - Vim 8.2.1978 or later 63 | 64 | Latest version: 65 | https://github.com/kana/vim-gf-user 66 | 67 | 68 | 69 | 70 | ============================================================================== 71 | INTERFACE *gf-user-interface* 72 | 73 | ------------------------------------------------------------------------------ 74 | COMMANDS *gf-user-commands* 75 | 76 | :GfUserDefaultKeyMappings[!] [args] *:GfUserDefaultKeyMappings* 77 | 78 | Define the default key mappings. If [!] is given, redefine the 79 | default key mappings. This command doesn't override existing {lhs}s 80 | unless [!] is given. 81 | Accepts optional arguments [args] such as or that are 82 | passed to nmap and xmap mapping (gf-user-gf), (gf-user-gF), 83 | (gf-user-f), ... to the corresponding gf, gF, CTRL-W_f, ... 84 | mappings. 85 | 86 | For example, the default mappings can be added buffer-locally to all 87 | buffers of filetype diff by adding the following to your |vimrc|: 88 | > 89 | let g:gf_user_no_default_key_mappings = 1 90 | autocmd FileType diff GfUserDefaultKeyMappings! 91 | < 92 | 93 | ------------------------------------------------------------------------------ 94 | FUNCTIONS *gf-user-functions* 95 | 96 | gf#user#extend({funcname}, {priority}) *gf#user#extend()* 97 | Register an extension. {funcname} is the name of 98 | a |gf-user-extension-function|. {priority} is an 99 | integer which means the default priority for the 100 | extension. See also |gf-user-extension-priority|. 101 | 102 | gf#user#set_priority({funcname}, {priority}) *gf#user#set_priority()* 103 | Customize the priority for an extension. {funcname} 104 | is the name of a |gf-user-extension-function|. 105 | {priority} is an integer which means the default 106 | priority for the extension. See also 107 | |gf-user-extension-priority|. 108 | 109 | 110 | ------------------------------------------------------------------------------ 111 | KEY MAPPINGS *gf-user-key-mappings* 112 | 113 | *g:gf_user_no_default_key_mappings* 114 | This plugin will define the following key mappings. If you don't want to 115 | define these default key mappings, define |g:gf_user_no_default_key_mappings| 116 | before this plugin is loaded (e.g. in your |vimrc|). You can also use 117 | |:GfUserDefaultKeyMappings| to redefine these key mappings. This command 118 | doesn't override existing {lhs}s unless [!] is given. 119 | 120 | 121 | DEFAULT KEY MAPPINGS *gf-user-default-key-mappings* 122 | 123 | Modes {lhs} {rhs} 124 | ------------------------------------------------ 125 | nv gf |(gf-user-gf)| 126 | nv gF |(gf-user-gF)| 127 | nv f |(gf-user-f)| 128 | nv |(gf-user-)| 129 | nv F |(gf-user-F)| 130 | nv gf |(gf-user-gf)| 131 | nv gF |(gf-user-gF)| 132 | 133 | 134 | NAMED KEY MAPPINGS *gf-user-named-key-mappings* 135 | 136 | (gf-user-gf) *(gf-user-gf)* 137 | (gf-user-gF) *(gf-user-gF)* 138 | (gf-user-f) *(gf-user-f)* 139 | (gf-user-) *(gf-user-)* 140 | (gf-user-F) *(gf-user-F)* 141 | (gf-user-gf) *(gf-user-gf)* 142 | (gf-user-gF) *(gf-user-gF)* 143 | Extended versions of |gf|, |gF|, |CTRL-W_f|, 144 | |CTRL-W_CTRL-F|, |CTRL-W_F|, |CTRL-W_gf| and 145 | |CTRL-W_gF|. 146 | 147 | 148 | 149 | 150 | ============================================================================== 151 | CUSTOMIZATION *gf-user-customization* 152 | 153 | ------------------------------------------------------------------------------ 154 | EXTENSION FUNCTION *gf-user-extension-function* 155 | 156 | An extension function: 157 | 158 | - Takes 0 arguments. 159 | - Returns 0 if any appropriate file is not found. 160 | - Returns a destination information if an appropriate file is found. 161 | - Should be named like "gf#{ext}#find", if it is an |autoload| function. 162 | 163 | A destination information is a dictionary which has the following members: 164 | 165 | "path" (string) 166 | A path of the file to be opened. 167 | 168 | "line" (number) 169 | The line number for the initial cursor position. 170 | 171 | "col" (number) 172 | The column number for the initial cursor position. 173 | If this value is 0, the cursor will be located to the first non-blank 174 | character (as |^| and |'| do). 175 | 176 | 177 | ------------------------------------------------------------------------------ 178 | EXTENSION PRIORITY *gf-user-extension-priority* 179 | 180 | Basics: 181 | 182 | - An extension has a priority which is an integer. 183 | - A priority is to control the order of extension functions to be used. 184 | - Registered extension functions are sorted by priority, then sorted by name. 185 | - The built-in functions such as |gf| is used at the first. Extensions are 186 | used if the built-in functions fail to find the file to be opened. 187 | 188 | Conventions: 189 | 190 | - Use 1000 if an extension uses only the text at the cursor line. 191 | - Use 2000 if an extension uses text around the cursor. 192 | - Use 3000 if an extension does not use text in the current buffer. 193 | 194 | 195 | 196 | 197 | ============================================================================== 198 | BUGS *gf-user-bugs* 199 | 200 | - Errors which occur in Visual mode like |v_gf| might not be displayed or 201 | might be displayed with |v:throwpoint|, depending on the situation. 202 | - See also https://github.com/kana/vim-gf-user/issues 203 | 204 | 205 | 206 | 207 | ============================================================================== 208 | CHANGELOG *gf-user-changelog* 209 | 210 | 1.1.0 2024-04-18T15:38:22+09:00 *gf-user-changelog-1.1.0* 211 | - |GfUserDefaultKeyMappings|: Support modifiers such as . 212 | 213 | 1.0.0 2023-10-08T12:19:46+09:00 *gf-user-changelog-1.0.0* 214 | - Change required Vim version to 8.2.1978 or later. 215 | - Fix |mode()| to return the correct mode within 216 | |gf-user-extension-function|. 217 | 218 | 0.0.1 2023-05-11T17:51:56+09:00 *gf-user-changelog-0.0.1* 219 | - Rewrite tests. 220 | 221 | 0.0.0 2012-01-12T19:29:33+09:00 *gf-user-changelog-0.0.0* 222 | - Initial version. 223 | 224 | 225 | 226 | 227 | ============================================================================== 228 | vim:tw=78:ts=8:ft=help:norl:fen:fdl=0:fdm=marker: 229 | -------------------------------------------------------------------------------- /plugin/gf/user.vim: -------------------------------------------------------------------------------- 1 | " gf-user - A framework to open a file by context 2 | " Version: 1.1.0 3 | " Copyright (C) 2012-2024 Kana Natsuno 4 | " License: MIT license {{{ 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be included 14 | " in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | " OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | " IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | " CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | " TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | " }}} 24 | 25 | if exists('g:loaded_gf_user') 26 | finish 27 | endif 28 | 29 | 30 | 31 | 32 | function! s:define_ui_key_mappings() 33 | for gf_cmd in ['gf', 'gF', 34 | \ 'f', '', 'F', 35 | \ 'gf', 'gF'] 36 | for mode_char in ['n', 'x'] 37 | execute printf( 38 | \ '%snoremap (%s-%s) call gf#user#do("%s", "%s")', 39 | \ mode_char, 40 | \ 'gf-user', 41 | \ gf_cmd, 42 | \ substitute(gf_cmd, '<\(\a-\a\)>', '\\\1>', 'g'), 43 | \ mode_char 44 | \ ) 45 | endfor 46 | endfor 47 | endfunction 48 | call s:define_ui_key_mappings() 49 | 50 | 51 | 52 | 53 | command! -bang -bar -nargs=* GfUserDefaultKeyMappings 54 | \ call s:cmd_GfUserDefaultKeyMappings(0, ) 55 | function! s:cmd_GfUserDefaultKeyMappings(banged_p, args) 56 | let modifier = a:banged_p ? '' : '' 57 | let modifier .= a:args 58 | for gf_cmd in ['gf', 'gF', 59 | \ 'f', '', 'F', 60 | \ 'gf', 'gF'] 61 | for map_cmd in ['nmap', 'xmap'] 62 | execute printf('silent! %s %s %s (gf-user-%s)', 63 | \ map_cmd, 64 | \ modifier, 65 | \ gf_cmd, 66 | \ gf_cmd) 67 | endfor 68 | endfor 69 | endfunction 70 | 71 | if !exists('g:gf_user_no_default_key_mappings') 72 | GfUserDefaultKeyMappings 73 | endif 74 | 75 | 76 | 77 | 78 | let g:loaded_gf_user = 1 79 | 80 | " __END__ 81 | " vim: foldmethod=marker 82 | -------------------------------------------------------------------------------- /t/basics.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/gf/user.vim 2 | 3 | describe 'gf#user#extend()' 4 | before 5 | new 6 | let b:d = gf#user#get_ext_dict() 7 | end 8 | 9 | after 10 | call filter(b:d, '0') 11 | %bwipeout! 12 | end 13 | 14 | it 'registers a given extension' 15 | Expect b:d ==# {} 16 | 17 | call gf#user#extend('foo', 1000) 18 | Expect b:d['foo'] == 1000 19 | end 20 | 21 | it 'can override an existing extension with the same name' 22 | Expect b:d ==# {} 23 | 24 | call gf#user#extend('foo', 1000) 25 | Expect b:d['foo'] == 1000 26 | 27 | call gf#user#extend('foo', 2000) 28 | Expect b:d['foo'] == 2000 29 | end 30 | end 31 | 32 | describe 'gf#user#set_priority()' 33 | before 34 | new 35 | let b:d = gf#user#get_ext_dict() 36 | end 37 | 38 | after 39 | call filter(b:d, '0') 40 | %bwipeout! 41 | end 42 | 43 | it 'sets a priority to an existing extension' 44 | call gf#user#extend('foo', 1000) 45 | Expect b:d['foo'] == 1000 46 | 47 | call gf#user#set_priority('foo', 2000) 48 | Expect b:d['foo'] == 2000 49 | end 50 | 51 | it 'raises an error if the target extension is not registered' 52 | Expect b:d ==# {} 53 | 54 | let v:errmsg = '' 55 | silent! call gf#user#set_priority('bar', 3000) 56 | Expect v:errmsg ==# "gf-user: Extension 'bar' is not registered." 57 | Expect b:d ==# {} 58 | end 59 | end 60 | 61 | describe 'gf#user#get_sorted_ext_list()' 62 | before 63 | new 64 | let b:d = gf#user#get_ext_dict() 65 | end 66 | 67 | after 68 | call filter(b:d, '0') 69 | %bwipeout! 70 | end 71 | 72 | it 'returns a list of extensions sorted by priority then by name' 73 | call gf#user#extend('foo', 1000) 74 | call gf#user#extend('bar', 1000) 75 | call gf#user#extend('baz', 2000) 76 | call gf#user#extend('qux', 900) 77 | 78 | Expect gf#user#get_sorted_ext_list() ==# ['qux', 'bar', 'foo', 'baz'] 79 | end 80 | end 81 | 82 | describe 'gf#user#split()' 83 | it 'does not split anything for non-"gf" command' 84 | let status = s:check_split("xyzzy") 85 | Expect status.new_wn == status.old_wn 86 | Expect status.new_wc == status.old_wc 87 | Expect status.new_tn == status.old_tn 88 | Expect status.new_tc == status.old_tc 89 | end 90 | 91 | it 'does not split anything for "gf"' 92 | let status = s:check_split("gf") 93 | Expect status.new_wn == status.old_wn 94 | Expect status.new_wc == status.old_wc 95 | Expect status.new_tn == status.old_tn 96 | Expect status.new_tc == status.old_tc 97 | end 98 | 99 | it 'doesn not split anything for "gF"' 100 | let status = s:check_split("gF") 101 | Expect status.new_wn == status.old_wn 102 | Expect status.new_wc == status.old_wc 103 | Expect status.new_tn == status.old_tn 104 | Expect status.new_tc == status.old_tc 105 | end 106 | 107 | it 'splits a window to the current tabpage for "\f"' 108 | let status = s:check_split("\f") 109 | Expect status.new_wn == status.old_wn 110 | Expect status.new_wc == status.old_wc + 1 111 | Expect status.new_tn == status.old_tn 112 | Expect status.new_tc == status.old_tc 113 | end 114 | 115 | it 'splits a window to the current tabpage for "\\"' 116 | let status = s:check_split("\\") 117 | Expect status.new_wn == status.old_wn 118 | Expect status.new_wc == status.old_wc + 1 119 | Expect status.new_tn == status.old_tn 120 | Expect status.new_tc == status.old_tc 121 | end 122 | 123 | it 'splits a window to the current tabpage for "\F"' 124 | let status = s:check_split("\F") 125 | Expect status.new_wn == status.old_wn 126 | Expect status.new_wc == status.old_wc + 1 127 | Expect status.new_tn == status.old_tn 128 | Expect status.new_tc == status.old_tc 129 | end 130 | 131 | it 'splits a window to a new tabpage for "\gf"' 132 | let status = s:check_split("\gf") 133 | Expect status.new_wn == 1 134 | Expect status.new_wc == 1 135 | Expect status.new_tn != status.old_tn 136 | Expect status.new_tc == status.old_tc + 1 137 | end 138 | 139 | it 'splits a window to a new tabpage for "\gF"' 140 | let status = s:check_split("\gf") 141 | Expect status.new_wn == 1 142 | Expect status.new_wc == 1 143 | Expect status.new_tn != status.old_tn 144 | Expect status.new_tc == status.old_tc + 1 145 | end 146 | end 147 | 148 | function s:check_split(gf_cmd) 149 | let status = {} 150 | 151 | let status['old_wn'] = winnr() 152 | let status['old_wc'] = winnr('$') 153 | let status['old_tn'] = tabpagenr() 154 | let status['old_tc'] = tabpagenr('$') 155 | 156 | call gf#user#split(a:gf_cmd) 157 | 158 | let status['new_wn'] = winnr() 159 | let status['new_wc'] = winnr('$') 160 | let status['new_tn'] = tabpagenr() 161 | let status['new_tc'] = tabpagenr('$') 162 | 163 | silent tabonly! 164 | silent only! 165 | 166 | return status 167 | endfunction 168 | 169 | describe 'gf#user#try()' 170 | before 171 | new 172 | end 173 | 174 | after 175 | %bwipeout! 176 | end 177 | 178 | it 'fails if a given function is failed to find a file' 179 | Expect gf#user#try('GfAlwaysFailureFind', 'gf') to_be_false 180 | end 181 | 182 | it 'opens a path and move the cursor to a given position' 183 | silent let result = gf#user#try('GfSomePathL8C20Find', 'gf') 184 | Expect result to_be_true 185 | Expect bufname('') ==# 'doc/gf-user.txt' 186 | Expect line('.') == 8 187 | Expect col('.') == 20 188 | end 189 | 190 | it 'opens a path and move the cursor to the first non-blank character' 191 | silent let result = gf#user#try('GfSomePathL8C0Find', 'gf') 192 | Expect result to_be_true 193 | Expect bufname('') ==# 'doc/gf-user.txt' 194 | Expect line('.') == 8 195 | Expect col('.') == 5 196 | Expect getline(line('.')) =~# '^\s\s\s\s\S' 197 | end 198 | 199 | it 'opens a path which is neither a file nor a directory' 200 | Expect bufname('') ==# '' 201 | 202 | silent let result = gf#user#try('GfNonExistingPathFind', 'gf') 203 | Expect result to_be_true 204 | Expect bufname('') ==# 'foobarbaz' 205 | Expect line('.') == 1 206 | Expect col('.') == 1 207 | end 208 | 209 | it 'splits a window if necessary' 210 | let old_wn = winnr() 211 | let old_wc = winnr('$') 212 | let old_tn = tabpagenr() 213 | let old_tc = tabpagenr('$') 214 | 215 | silent let result = gf#user#try('GfNonExistingPathFind', "\f") 216 | 217 | let new_wn = winnr() 218 | let new_wc = winnr('$') 219 | let new_tn = tabpagenr() 220 | let new_tc = tabpagenr('$') 221 | Expect result to_be_true 222 | Expect bufname('') ==# 'foobarbaz' 223 | Expect line('.') == 1 224 | Expect col('.') == 1 225 | Expect new_wn == old_wn 226 | Expect new_wc == old_wc + 1 227 | Expect new_tn == old_tn 228 | Expect new_tc == old_tc 229 | end 230 | end 231 | 232 | function GfAlwaysFailureFind() 233 | return 0 234 | endfunction 235 | 236 | function GfSomePathL8C20Find() 237 | return {'path': 'doc/gf-user.txt', 'line': 8, 'col': 20} 238 | endfunction 239 | 240 | function GfSomePathL8C0Find() 241 | return {'path': 'doc/gf-user.txt', 'line': 8, 'col': 0} 242 | endfunction 243 | 244 | function GfNonExistingPathFind() 245 | return {'path': 'foobarbaz', 'line': 888, 'col': 888} 246 | endfunction 247 | 248 | describe 'gf#user#do()' 249 | before 250 | new 251 | call gf#user#extend('GfPathAFind', 1000) 252 | call gf#user#extend('GfPathBFind', 1000) 253 | end 254 | 255 | after 256 | let b:d = gf#user#get_ext_dict() 257 | call filter(b:d, '0') 258 | %bwipeout! 259 | end 260 | 261 | it 'uses the built-in command, then extensions' 262 | tabnew 263 | Expect bufname('') ==# '' 264 | 265 | silent call gf#user#do('gf', 'n') 266 | Expect bufname('') ==# 'test-path-a' 267 | 268 | call setline(1, 'doc/gf-user.txt') 269 | normal! 1gg$ 270 | silent call gf#user#do("\f", 'n') 271 | Expect bufname('') ==# 'doc/gf-user.txt' 272 | end 273 | 274 | it 'works also in Visual mode' 275 | tabnew 276 | Expect bufname('') ==# '' 277 | 278 | call setline(1, 'doc/gf-user.txt') 279 | normal! 1gg$vb 280 | silent call gf#user#do("\f", 'n') 281 | Expect bufname('') ==# 'test-path-a' 282 | end 283 | end 284 | 285 | function GfPathAFind() 286 | return {'path': 'test-path-a', 'line': 888, 'col': 888} 287 | endfunction 288 | 289 | function GfPathBFind() 290 | return {'path': 'test-path-b', 'line': 888, 'col': 888} 291 | endfunction 292 | 293 | describe 'UI key mappings' 294 | before 295 | new 296 | call gf#user#extend('GfPathAFind', 1000) 297 | call gf#user#extend('GfPathBFind', 1000) 298 | end 299 | 300 | after 301 | let b:d = gf#user#get_ext_dict() 302 | call filter(b:d, '0') 303 | %bwipeout! 304 | end 305 | 306 | it 'uses the built-in command, then extensions' 307 | tabnew 308 | Expect bufname('') ==# '' 309 | 310 | execute "silent normal \(gf-user-gf)" 311 | Expect bufname('') ==# 'test-path-a' 312 | 313 | call setline(1, 'doc/gf-user.txt') 314 | normal! 1gg$ 315 | execute "silent normal \(gf-user-\f)" 316 | Expect bufname('') ==# 'doc/gf-user.txt' 317 | end 318 | 319 | it 'works also in Visual mode' 320 | tabnew 321 | Expect bufname('') ==# '' 322 | 323 | call setline(1, 'doc/gf-user.txt') 324 | normal! 1gg$vb 325 | execute "silent normal \(gf-user-\f)" 326 | Expect bufname('') ==# 'test-path-a' 327 | end 328 | end 329 | 330 | describe 'Default key mappings' 331 | before 332 | new 333 | call gf#user#extend('GfPathAFind', 1000) 334 | call gf#user#extend('GfPathBFind', 1000) 335 | end 336 | 337 | after 338 | let b:d = gf#user#get_ext_dict() 339 | call filter(b:d, '0') 340 | %bwipeout! 341 | end 342 | 343 | it 'uses the built-in command, then extensions' 344 | tabnew 345 | Expect bufname('') ==# '' 346 | 347 | execute "silent normal gf" 348 | Expect bufname('') ==# 'test-path-a' 349 | 350 | call setline(1, 'doc/gf-user.txt') 351 | normal! 1gg$ 352 | execute "silent normal \f" 353 | Expect bufname('') ==# 'doc/gf-user.txt' 354 | end 355 | 356 | it 'works also in Visual mode' 357 | tabnew 358 | Expect bufname('') ==# '' 359 | 360 | call setline(1, 'doc/gf-user.txt') 361 | normal! 1gg$vb 362 | execute "silent normal \f" 363 | Expect bufname('') ==# 'test-path-a' 364 | end 365 | end 366 | 367 | describe 'GfUserDefaultKeyMappings' 368 | before 369 | new 370 | call gf#user#extend('GfPathAFind', 1000) 371 | call gf#user#extend('GfPathBFind', 1000) 372 | end 373 | 374 | after 375 | let b:d = gf#user#get_ext_dict() 376 | call filter(b:d, '0') 377 | %bwipeout! 378 | end 379 | 380 | it 'supports modifiers' 381 | tabnew 382 | Expect bufname('') ==# '' 383 | 384 | nnoremap gf gf 385 | execute "silent! normal gf" 386 | Expect bufname('') ==# '' 387 | Expect v:errmsg ==# 'E446: No file name under cursor' 388 | 389 | GfUserDefaultKeyMappings 390 | execute "silent! normal gf" 391 | Expect bufname('') ==# '' 392 | Expect v:errmsg ==# 'E446: No file name under cursor' 393 | 394 | GfUserDefaultKeyMappings! 395 | execute "silent normal gf" 396 | Expect bufname('') ==# 'test-path-a' 397 | end 398 | end 399 | -------------------------------------------------------------------------------- /t/mode.vim: -------------------------------------------------------------------------------- 1 | runtime! plugin/gf/user.vim 2 | 3 | " Dummy expected values to pass tests. 4 | " This test script is executed via vspec which runs Vim in Ex mode. 5 | " Therefore mode() always returns 'ce'. 6 | let g:EXPECTED_NORMAL_MODE = 'ce' " 'n' 7 | let g:EXPECTED_VISUAL_MODE = 'ce' " 'V' 8 | 9 | function GfSuccess() 10 | let t:mode = mode(1) 11 | return {'path': 'test-path-a', 'line': 888, 'col': 888} 12 | endfunction 13 | 14 | function GfFailure() 15 | let t:mode = mode(1) 16 | return 0 17 | endfunction 18 | 19 | describe 'Extension function' 20 | before 21 | new 22 | call gf#user#extend('GfSuccess', 1000) 23 | end 24 | 25 | after 26 | let b:d = gf#user#get_ext_dict() 27 | call filter(b:d, '0') 28 | %bwipeout! 29 | end 30 | 31 | it 'correctly detects the current mode (Normal mode)' 32 | tabnew doc/gf-user.txt 33 | let t:mode = 'not executed' 34 | Expect bufname('') ==# 'doc/gf-user.txt' 35 | 36 | execute 'silent normal gf' 37 | Expect bufname('') ==# 'test-path-a' 38 | Expect t:mode ==# g:EXPECTED_NORMAL_MODE 39 | Expect mode(1) ==# g:EXPECTED_NORMAL_MODE 40 | end 41 | 42 | it 'correctly detects the current mode (Visual mode)' 43 | tabnew doc/gf-user.txt 44 | let t:mode = 'not executed' 45 | Expect bufname('') ==# 'doc/gf-user.txt' 46 | 47 | execute 'silent normal Vgf' 48 | Expect bufname('') ==# 'test-path-a' 49 | Expect t:mode ==# g:EXPECTED_VISUAL_MODE 50 | Expect mode(1) ==# g:EXPECTED_NORMAL_MODE 51 | end 52 | end 53 | 54 | describe 'gf-user' 55 | before 56 | new 57 | call gf#user#extend('GfFailure', 1000) 58 | end 59 | 60 | after 61 | let b:d = gf#user#get_ext_dict() 62 | call filter(b:d, '0') 63 | %bwipeout! 64 | end 65 | 66 | it 'clears Visual mode if all tries are failed, to behave the same as built-in gf commands' 67 | tabnew doc/gf-user.txt 68 | let t:mode = 'not executed' 69 | Expect bufname('') ==# 'doc/gf-user.txt' 70 | 71 | execute 'silent normal Vgf' 72 | Expect bufname('') ==# 'doc/gf-user.txt' 73 | Expect t:mode ==# g:EXPECTED_VISUAL_MODE 74 | Expect mode(1) ==# g:EXPECTED_NORMAL_MODE 75 | end 76 | 77 | it 'clears Visual mode if unexpected error occurs' 78 | call gf#user#extend('GfSuccess', 1000) 79 | 80 | tabnew doc/gf-user.txt 81 | let t:mode = 'not executed' 82 | Expect bufname('') ==# 'doc/gf-user.txt' 83 | 84 | put ='xyzzy' 85 | try 86 | execute 'silent normal Vgf' 87 | catch 88 | Expect v:exception ==# 'Vim(edit):E37: No write since last change (add ! to override)' 89 | endtry 90 | Expect bufname('') ==# 'doc/gf-user.txt' 91 | Expect t:mode ==# g:EXPECTED_VISUAL_MODE 92 | Expect mode(1) ==# g:EXPECTED_NORMAL_MODE 93 | end 94 | end 95 | --------------------------------------------------------------------------------