-
Notifications
You must be signed in to change notification settings - Fork 386
/
base.vim
162 lines (148 loc) · 5.72 KB
/
base.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
function! test#base#test_file(runner, file) abort
return test#{a:runner}#test_file(a:file)
endfunction
function! test#base#build_position(runner, type, position) abort
return test#{a:runner}#build_position(a:type, a:position)
endfunction
function! test#base#options(runner, args, ...) abort
let options = get(g:, 'test#'.a:runner.'#options', [])
if empty(a:000) && type(options) == type('')
let options = split(options)
elseif !empty(a:000) && type(options) == type({})
let options = split(get(options, 'all', '')) + split(get(options, a:000[0], ''))
else
let options = []
endif
if exists('*test#'.a:runner.'#build_options')
return test#{a:runner}#build_options(a:args, options)
else
return options + a:args
endif
endfunction
function! test#base#executable(runner) abort
if exists('g:test#'.a:runner.'#executable')
return g:test#{a:runner}#executable
else
return test#{a:runner}#executable()
endif
endfunction
function! test#base#build_args(runner, args, strategy) abort
let no_color = has('gui_running') && a:strategy ==# 'basic'
try
" Before Vim 8.0.1423 exceptions thrown from return statement
" cannot be caught.
" https://github.com/vim/vim/pull/2483
let args = test#{a:runner}#build_args(a:args, !no_color)
return args
catch /^Vim\%((\a\+)\)\=:E118:/ " too many arguments
return test#{a:runner}#build_args(a:args)
endtry
endfunction
function! test#base#file_exists(file) abort
return !empty(glob(a:file)) || bufexists(a:file)
endfunction
function! test#base#escape_regex(string) abort
return escape(a:string, '?+*\^$.|{}[]()')
endfunction
" Takes a position and a dictionary of patterns and a optional configuration, and returns list of strings
" that were matched in the file by the patterns from the given position
" upwards. It can be used when a runner doesn't support running nearest tests
" with line numbers, but supports regexes.
"
" The "position" argument is a dictionary created by this plugin:
"
" {
" 'file': 'test/foo_test.rb',
" 'line': 11,
" 'col': 2,
" }
"
" The "patterns" argument is a dictionary where keys are either "test" or
" "namespace", and values are lists of regexes:
"
" {
" 'test': ['\v^\s*def (test_\w+)'],
" 'namespace': ['\v^\s*%(class|module) (\S+)'],
" }
"
" The optional configuration parameter is a dictionary which can contain next
" keys:
"
" {
" 'namespaces_with_same_indent': boolean // put namespace with same indent
" // in "namespace output.
" }
"
" If a line is matched, the substring corresponding to the 1st match group will
" be returned. So for the above patterns this function might return something
" like this:
"
" {
" 'test': ['test_calculates_time'],
" 'test_line': 54, " Line where 'test_calculates_time' was found
" 'namespace': ['CalculatorTest'],
" }
function! test#base#nearest_test(position, patterns, ...) abort
let configuration = a:0 > 0 ? a:1 : {}
return test#base#nearest_test_in_lines(a:position['file'], a:position['line'], 1, a:patterns, configuration)
endfunction
" This function is used internally by the test#base#nearest_test function
" So it behaves exactly like describe for test#base#nearest_test except that
" it can search forward or backward depending on the search range.
"
" Instead of taking a "position" argument, this function takes 3:
" - "filename" is the equivalent of "position['file']"
" - "from_line" the line number from where to start the search, is the
" equivalent of "position['line']"
" - "to_line" the line number where to end the search (it would be 1 in
" test#base#nearest_test)
"
" If "from_line" is greater than "to_line" or equals '$' then the search will
" be backward.
" Otherwise it will be forward.
function! test#base#nearest_test_in_lines(filename, from_line, to_line, patterns, ...) abort
let configuration = a:0 > 0 ? a:1 : {}
let test = []
let namespace = []
let last_indent = -1
let current_line = a:from_line + 1
let test_line = -1
let last_namespace_line = -1
let is_namespace_with_same_indent_allowed = get(configuration, 'namespaces_with_same_indent', 0)
let match_index = a:patterns->get('whole_match', 0) ? 0 : 1
let is_reverse = '$' == a:from_line ? 1 : a:from_line > a:to_line
let lines = is_reverse
\ ? reverse(getbufline(a:filename, a:to_line, a:from_line))
\ : getbufline(a:filename, a:from_line, a:to_line)
for line in lines
let current_line = current_line + (is_reverse ? -1 : 1)
let test_match = s:find_match(line, a:patterns['test'])
let namespace_match = s:find_match(line, a:patterns['namespace'])
let indent = len(matchstr(line, '^\s*'))
if !empty(test_match)
\ && (last_indent == -1
\ || (test_line == -1
\ && last_indent > indent
\ && last_namespace_line > current_line
\ && last_namespace_line != -1
\ )
\ )
if last_namespace_line > current_line
let namespace = []
let last_namespace_line = -1
endif
call add(test, filter(test_match[match_index:], '!empty(v:val)')[0])
let last_indent = indent
let test_line = current_line
elseif !empty(namespace_match) && (is_namespace_with_same_indent_allowed || (indent < last_indent || last_indent == -1))
call add(namespace, filter(namespace_match[match_index:], '!empty(v:val)')[0])
let last_indent = indent
let last_namespace_line = current_line
endif
endfor
return {'test': test, 'test_line': test_line, 'namespace': reverse(namespace)}
endfunction
function! s:find_match(line, patterns) abort
let matches = map(copy(a:patterns), 'matchlist(a:line, v:val)')
return get(filter(matches, '!empty(v:val)'), 0, [])
endfunction