├── .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 |
--------------------------------------------------------------------------------