summaryrefslogtreecommitdiffstats
path: root/autoload/erlang_complete.vim
blob: 9d108fbcb15dc0e590b66019a18a3c060261ad9b (plain) (blame)
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
" Vim omni completion file
" Language:     Erlang
" Author:       Oscar Hellström <oscar@oscarh.net>
" Contributors: kTT (http://github.com/kTT)
"               Ricardo Catalinas Jiménez <jimenezrick@gmail.com>
"               Eduardo Lopez (http://github.com/tapichu)
"               Zhihui Jiao (http://github.com/onlychoice)
" License:      Vim license
" Version:      2012/11/26

if !exists('g:erlang_completion_cache')
	let g:erlang_completion_cache = 1
endif

" Completion program path
let s:erlang_complete_file = expand('<sfile>:p:h') . '/erlang_complete.erl'

" Modules cache used to speed up the completion
let s:modules_cache = {}

" File cache for persistence between Vim sessions
if filewritable(expand('<sfile>:p:h')) == 2
	let s:file_cache = expand('<sfile>:p:h') . '/vimerl_cache'
else
	let s:file_cache = '/tmp/vimerl_cache'
endif

" Patterns for completions
let s:erlang_local_func_beg    = '\(\<[0-9A-Za-z_-]*\|\s*\)$'
let s:erlang_external_func_beg = '\<[0-9A-Za-z_-]\+:[0-9A-Za-z_-]*$'
let s:erlang_blank_line        = '^\s*\(%.*\)\?$'

" Main function for completion
function erlang_complete#Complete(findstart, base)
	let lnum = line('.')
	let column = col('.')
	let line = strpart(getline('.'), 0, column - 1)

	" 1) Check if the char to the left of us are part of a function call
	"
	" Nothing interesting is written at the char just before the cursor
	" This means _anything_ could be started here
	" In this case, keyword completion should probably be used,
	" for now we'll only try and complete local functions.
	"
	" TODO: Examine if we can stare Identifiers end complete on them
	" Is this worth it? Is /completion/ of a "blank" wanted? Can we consider
	" `(' interesting and check if we are in a function call etc.?
	if line[column - 2] !~ '[0-9A-Za-z:_-]'
		if a:findstart
			return column
		else
			return s:ErlangFindLocalFunc(a:base)
		endif
	endif
	
	" 2) Function in external module
	if line =~ s:erlang_external_func_beg
		let delimiter = match(line, ':[0-9A-Za-z_-]*$') + 1
		if a:findstart
			return delimiter
		else
			let module = matchstr(line[:-2], '\<\k*\>$')
			return s:ErlangFindExternalFunc(module, a:base)
		endif
	endif

	" 3) Local function
	if line =~ s:erlang_local_func_beg
		let funcstart = match(line, ':\@<![0-9A-Za-z_-]*$')
		if a:findstart
			return funcstart
		else
			return s:ErlangFindLocalFunc(a:base)
		endif
	endif

	" 4) Unhandled situation
	if a:findstart
		return -1
	else
		return []
	endif
endfunction

" Find the next non-blank line
function s:ErlangFindNextNonBlank(lnum)
	let lnum = nextnonblank(a:lnum + 1)
	let line = getline(lnum)

	while line =~ s:erlang_blank_line && 0 != lnum
		let lnum = nextnonblank(lnum + 1)
		let line = getline(lnum)
	endwhile

	return lnum
endfunction

" Find external function names
function s:ErlangFindExternalFunc(module, base)
	" If the module is cached, load its functions
	if has_key(s:modules_cache, a:module)
		for field_cache in get(s:modules_cache, a:module)
			if match(field_cache.word, a:base) == 0
				call complete_add(field_cache)
			endif
		endfor

		return []
	endif

	let functions = system(s:erlang_complete_file . ' ' . a:module)
	for function_spec in split(functions, '\n')
		if match(function_spec, a:base) == 0
			let function_name = matchstr(function_spec, a:base . '\w*')
			let field = {'word': function_name . '(', 'abbr': function_spec,
				  \  'kind': 'f', 'dup': 1}
			call complete_add(field)

			" Populate the cache only when iterating over all the
			" module functions (i.e. no prefix for the completion)
			if g:erlang_completion_cache && a:base == ''
				if !has_key(s:modules_cache, a:module)
					let s:modules_cache[a:module] = [field]
				else
					let fields_cache = get(s:modules_cache, a:module)
					let s:modules_cache[a:module] = add(fields_cache, field)
				endif
			endif

			" The user entered some text, so stop the completion
			if complete_check()
				" The module couldn't be entirely cached
				if has_key(s:modules_cache, a:module)
					call remove(s:modules_cache, a:module)
				endif
				break
			endif
		endif
	endfor

	call s:ErlangWriteCache(a:module)

	return []
endfunction

" Find local function names
function s:ErlangFindLocalFunc(base)
	" Begin at line 1
	let lnum = s:ErlangFindNextNonBlank(1)

	if "" == a:base
		let base = '\w' " Used to match against word symbol
	else
		let base = a:base
	endif

	while 0 != lnum && !complete_check()
		let line = getline(lnum)
		let function_name = matchstr(line, '^' . base . '[0-9A-Za-z_-]\+(\@=')
		if function_name != ""
			call complete_add({'word': function_name, 'kind': 'f'})
		endif
		let lnum = s:ErlangFindNextNonBlank(lnum)
	endwhile

	return []
endfunction

function s:ErlangLoadCache()
	if filereadable(s:file_cache)
		for line in readfile(s:file_cache)
			let cache_entry = eval(line)
			" cache_entry is a dict with just one key with the
			" module name and the function list we are going to
			" add to the memory cache as the value of this key
			for mod_name in keys(cache_entry)
				let func_list = get(cache_entry, mod_name)
				let s:modules_cache[mod_name] = func_list
			endfor
		endfor
	endif
endfunction

function s:ErlangWriteCache(module)
	" Write all the module functions to the cache file
	if has_key(s:modules_cache, a:module)
		let func_list = get(s:modules_cache, a:module)
		if len(func_list) > 0
			let cache_entry = {a:module : func_list}
			execute 'redir >>' . s:file_cache
			silent echon cache_entry
			silent echon "\n"
			redir END
		endif
	endif
endfunction

function s:ErlangPurgeCache(...)
	for mod_name in a:000
		if has_key(s:modules_cache, mod_name)
			call remove(s:modules_cache, mod_name)
		endif
	endfor

	" Delete the old cache file
	call delete(s:file_cache)

	" Write a new one
	for mod_name in keys(s:modules_cache)
		call s:ErlangWriteCache(mod_name)
	endfor
endfunction

" Load the file cache when this script is autoloaded
call s:ErlangLoadCache()

" Command for removing modules from the cache
command -nargs=+ ErlangPurgeCache silent call s:ErlangPurgeCache(<f-args>)