-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsml_polyml.vim
463 lines (421 loc) · 15.9 KB
/
sml_polyml.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
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
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
" Poly/ML integration
" Maintainer: Alex Merry <dev@randomguy3.me.uk>
" Last Change: 2012 Jun 21
" Version: 0.1
" Contributors: Alex Merry <dev@randomguy3.me.uk>
"
" Installation:
" Drop sml_polyml.vim and the poly/ directory into ~/.vim/ftplugin
"
" rlwrap is needed for :PolymlConsoleHere
"
" Usage:
" Note that for :Polyml and :PolymlConsoleHere, if there is a subdirectory
" called '.polysave' in the same directory as the file being edited, and
" this contains a file with the same name as the file being edited, plus the
" extension '.save', this will be loaded as a Poly/ML saved state before
" running the file. These files are created automatically by
" PolyML.Project.
"
" :Polyml [timeout]
" Compile the current file. There is no need to save first, although
" QuickFix lists don't work well with unnamed buffers. The timeout is
" in seconds.
" Auto-opening the QuickFix window on errors can be disabled with
" let g:polyml_cwindow = 0
" Default shortcuts: <LocalLeader>pc and <F5>
"
"
" :PolymlGetType
" Gets the type of the expression under the cursor. If you have edited the
" file since the last compile, you should re-compile it with :Polyml first.
" Default shortcut: <LocalLeader>pt
"
"
" :PolymlFindDeclaration
" Goes to the declaration of the expression under the cursor. If you have
" edited the file since the last compile, you should re-compile it with
" :Polyml first. Due to a shortcoming of Poly/ML, it will fail to find ML
" files that are not in the current directory.
" Default shortcut: <LocalLeader>pd
"
"
" :[range]PolymlAccessors
" Generates accessor implementations for a record datatype, as selected by
" [range]. The usual use is to visually highlight (with V) the datatype
" declaration, and then call :'<,'>PolymlAccessors. If the file has been
" compiled, it will try to find a record datatype under the cursor.
" Default shortcut: <LocalLeader>pa
"
"
" :[range]PolymlAccessorSigs
" Generates accessor declarations that correspond with the implementations
" provided by PolymlAccessors. Usage is the same as for that command.
" Default shortcut: <LocalLeader>ps
"
"
" :PolymlConsoleHere
" Opens a Poly/ML instance in a terminal window (wrapped using rlwrap) with
" the current file pre-loaded.
" Default shortcut: <LocalLeader>pC
"
if exists(':Polyml') == 2
finish
endif
if !has('python')
echoerr "Error: Required vim compiled with +python"
finish
endif
if !exists('g:polyml_cwindow')
let g:polyml_cwindow = 1
endif
if !exists('g:poly_bin')
let g:poly_bin = 'poly'
endif
if !exists('g:polyml_terminal')
let g:polyml_terminal = 'xterm'
endif
if !exists('g:polyml_accessor_buffer_name')
let g:polyml_accessor_buffer_name = '__poly_accessors__'
endif
command -nargs=? Polyml :call Polyml(<args>)
command PolymlGetType :python PolymlGetType()
command -range PolymlAccessors :<line1>,<line2>python PolymlCreateAccessors()
command -range PolymlAccessorSigs :<line1>,<line2>python PolymlCreateAccessorSigs()
command PolymlFindDeclaration python PolymlFindDeclaration()
command PolymlConsoleHere python poly.console.ConsoleThread(vim.current.buffer.name,vim.eval('g:poly_bin'),vim.eval('g:polyml_terminal')).start()
python <<EOP
import vim
import os
sys.path.append(os.path.dirname(vim.eval('expand("<sfile>")')))
import poly
# The main reason this is a function is to scope poly_inst properly.
# Otherwise, the Poly object won't be garbage collected, and vim will
# hang at exit waiting for the various Python threads to return.
def poly_do_compile(path, ml, timeout):
poly_bin = vim.eval('g:poly_bin')
poly_inst = poly.global_instance(poly_bin)
preamble = ''
if (path):
working_dir = os.path.dirname(path)
file_name = os.path.basename(path)
preamble = "OS.FileSys.chDir \"" + working_dir + "\";\n"
polysave = working_dir + "/.polysave/" + file_name + ".save"
if os.path.exists(polysave):
preamble += "PolyML.SaveState.loadState(\"" + polysave + "\");\n"
preamble += "PolyML.fullGC ();\n"
else:
path = '--scratch--'
return poly_inst.compile_sync(path, preamble, ml, timeout)
def rowcol(lines,offset):
"""Get the row and column of an offset in a list of lines
Every value is zero-indexed
"""
i = 0
while (i < len(lines) and offset > len(lines[i])):
offset -= len(lines[i]) + 1
i += 1
return i,offset
class PolymlProcessedNode:
def __init__(self, node, start_row, start_col, end_row, end_col, text):
self.node = node
self.start_row = start_row
self.start_col = start_col
self.end_row = end_row
self.end_col = end_col
self.text = text
def poly_get_node(poly_inst=None):
path = vim.current.buffer.name
if not path:
vim.command('echoerr "You must save and compile the file first!"')
return None
lines = vim.current.buffer[:]
row,col = vim.current.window.cursor
line_start_pos = 0
for i in range(row-1):
line_start_pos += len(lines[i]) + 1
if not poly_inst:
poly_bin = vim.eval('g:poly_bin')
poly_inst = poly.global_instance(poly_bin)
if not poly_inst.has_built(path):
vim.command('echoerr "You must compile the file first!"')
return None
node = poly_inst.node_for_position(path, line_start_pos + col)
if not node:
vim.command('echoerr "Failed to find parse tree location"')
return None
start_col = node.start - line_start_pos
start_row = row
while start_col < 0:
start_row -= 1
start_col += len(lines[start_row-1]) + 1
end_col = node.end - line_start_pos
end_row = row
while end_col > len(lines[end_row-1]):
end_col -= len(lines[end_row-1]) + 1
end_row += 1
if end_row > start_row:
text = lines[start_row-1][start_col:]
for i in range(start_row,end_row-1):
text += '\n'
text += lines[i]
text += '\n'
text += lines[end_row-1][:end_col]
else:
text = lines[start_row-1][start_col:end_col]
return PolymlProcessedNode(node, start_row, start_col, end_row, end_col, text)
def poly_format_message(msg):
if msg.message_code == 'E':
mtype = 'Error: '
elif msg.message_code == 'W':
mtype = 'Warning: '
elif msg.message_code == 'X':
mtype = 'Exception: '
else:
mtype = ''
if not msg.location:
return mtype + msg.text
else:
if msg.location.file_name == '--scratch--':
file_name = ''
else:
file_name = msg.location.file_name
if msg.location.line:
line = msg.location.line
start_col = msg.location.start
end_col = msg.location.end
else:
# try to find the right buffer
buff = vim.current.buffer
for b in vim.buffers:
if b.name == file_name:
buff = b
lines = buff[:]
line,start_col = rowcol(lines,msg.location.start)
end_col = rowcol(lines,msg.location.end)[1]
return "{0}:{1}:{2}-{3}: {5}{4}".format(
file_name,
line + 1,
start_col + 1,
end_col + 1,
msg.text,
mtype)
def poly_loc_is_in_buffer(location, buff=None):
if not buff:
buff = vim.current.buffer
if not buff.name or buff.name == '':
return location.file_name == '-scratch-'
return location.file_name == buff.name
def poly_open_buffer(file_name):
return None
def poly_find_buffer(location):
if len(location.file_name) == 0 or location.file_name == '-scratch-':
# only allow the current buffer to be unnamed
return None
for b in vim.buffers:
if poly_loc_is_in_buffer(location, b):
return b
def poly_go_to_location(location):
# FIXME: can we specify a search path for files that don't have an
# absolute path?
if not poly_loc_is_in_buffer(location):
buff = poly_find_buffer(location)
if buff:
vim.command('hide buffer! \'{0}\''.format(buff.name))
else:
if len(location.file_name) == 0 or location.file_name == '-scratch-':
vim.command('echoerr "Could not go to location with no file name"')
return
vim.command('hide edit! {0}'.format(location.file_name))
if vim.current.window.buffer.name != os.path.abspath(location.file_name):
print("Expected to go to '{0}', went to '{1}'".format(
os.path.abspath(location.file_name),
vim.current.window.buffer.name))
return
line,col = location.line,location.start
if not line:
line,col = rowcol(vim.current.window.buffer[:], location.start)
line += 1
if line <= len(vim.current.window.buffer) and col < len(vim.current.window.buffer[line-1]):
vim.current.window.cursor = line,col
EOP
function! Polyml(...)
let l:output = []
let l:complete = 0
let l:success = 0
let l:timeout = 10
if a:0 > 0
let l:timeout = a:000[0]
endif
redraw
echo "Compiling code with Poly/ML..."
python <<EOP
try:
(result,messages) = poly_do_compile(vim.current.buffer.name,
"\n".join(vim.current.buffer[:]),
int(vim.eval('l:timeout')))
hr_result = poly.translate_result_code(result)
vim.command("let l:complete = 1")
if result == 'S':
vim.command("let l:success = 1")
vim.command("let l:result = '{0}'".format(hr_result.replace("'","''")))
vim.command("let l:output = [l:result]".format(hr_result))
for msg in messages:
vim.command("call add(l:output,'{0}')".format(
poly_format_message(msg).replace("'","''")))
del result
del messages
except poly.process.Timeout:
vim.command('echoerr "Communication with Poly/ML timed out"')
except poly.process.ProtocolError as e:
vim.command("echoerr 'Communication with Poly/ML failed: {0}'".format(str(e).replace("'","''")))
except Exception as e:
vim.command("echoerr 'Caught exception: {0}'".format(repr(e).replace("'","''")))
EOP
if l:complete
redraw
echom 'Poly/ML compilation result was: ' . l:result
" Vim uses the global errorformat for cexpr, not the local one
let l:efm_save = &g:errorformat
" the second part matches results from unnamed buffers, but in
" this case vim cannot jump to the correct position in the file
setglobal errorformat=%f:%l:%c-%*[0-9]:\ %m,:%l:%c-%*[0-9]:\ %m
if l:success
silent cgetexpr l:output
else
silent hide cexpr! l:output
endif
let &g:errorformat = l:efm_save
if g:polyml_cwindow && len(l:output) > 1
copen
else
cclose
endif
else
redraw
echo ''
endif
endfunction
function! s:get_visual_selection()
let [lnum1, col1] = getpos("'<")[1:2]
let [lnum2, col2] = getpos("'>")[1:2]
let lines = getline(lnum1, lnum2)
let lines[-1] = lines[-1][: col2 - 2]
let lines[0] = lines[0][col1 - 1:]
return join(lines, "\n")
endfunction
function! Poly_get_accessor_buffer()
let l:scr_bufnum = bufnr(g:polyml_accessor_buffer_name)
if l:scr_bufnum == -1
" open a new scratch buffer
exe "new " . g:polyml_accessor_buffer_name
let l:scr_bufnum = bufnr(g:polyml_accessor_buffer_name)
else
" Scratch buffer is already created. Check whether it is open
" in one of the windows
let l:scr_winnum = bufwinnr(l:scr_bufnum)
if l:scr_winnum != -1
" Jump to the window which has the scratch buffer if we are not
" already in that window
if winnr() != l:scr_winnum
exe l:scr_winnum . "wincmd w"
endif
else
" Create a new scratch buffer
exe "split +buffer" . l:scr_bufnum
endif
endif
setlocal buftype=nofile
setlocal bufhidden=delete
setlocal noswapfile
setlocal nobuflisted
return g:polyml_accessor_buffer_name
endfunction
python <<EOP
def poly_fill_accessor_buffer(generator):
try:
if len(vim.current.range) == 0:
vim.command("echoerr 'No range given'")
return
accessors = generator("\n".join(vim.current.range[:]))
# default range length will be 1
if not accessors:
# try finding if the cursor is on a datatype
pnode = poly_get_node()
if not pnode:
return
accessors = generator(pnode.text)
if not accessors:
vim.command("echoerr 'Range is not a record'")
return
vim.command('call Poly_get_accessor_buffer()')
# abspath is needed because vim names buffers as though they were
# files, even when they're not
abufname = os.path.abspath(vim.eval('g:polyml_accessor_buffer_name'))
for b in vim.buffers:
if b.name == abufname:
b[:] = accessors.split('\n')
break
except poly.process.Timeout:
vim.command('echoerr "Request timed out"')
except poly.process.ProtocolError:
vim.command('echoerr "Communication with Poly/ML failed"')
except Exception as e:
vim.command("echoerr 'Caught exception: {0}'".format(repr(e).replace("'","''")))
def PolymlCreateAccessorSigs():
poly_fill_accessor_buffer(poly.accessors.sig_for_record)
def PolymlCreateAccessors():
poly_fill_accessor_buffer(poly.accessors.struct_for_record)
def PolymlGetType():
try:
poly_bin = vim.eval('g:poly_bin')
poly_inst = poly.global_instance(poly_bin)
pnode = poly_get_node(poly_inst)
if pnode:
ml_type = poly_inst.type_for_node(pnode.node)
if (pnode.end_row > pnode.start_row):
if ml_type:
vim.command('echom "Expression spans multiple lines, and has type {0}"'.format(ml_type.replace("'","''")))
else:
vim.command('echom "Expression spans multiple lines, and has no type"')
else:
if ml_type:
vim.command('echom "val {0} : {1}"'.format(pnode.text.replace("'","''"), ml_type.replace("'","''")))
else:
vim.command("echom 'Expression \"{0}\" has no type'".format(pnode.text.replace("'","''")))
except poly.process.Timeout:
vim.command('echoerr "Request timed out"')
except poly.process.ProtocolError:
vim.command('echoerr "Communication with Poly/ML failed"')
except Exception as e:
vim.command("echoerr 'Caught exception: {0}'".format(repr(e).replace("'","''")))
def PolymlFindDeclaration():
try:
poly_bin = vim.eval('g:poly_bin')
poly_inst = poly.global_instance(poly_bin)
pnode = poly_get_node(poly_inst)
if not pnode:
return
loc = poly_inst.declaration_for_node(pnode.node)
if not loc:
vim.command('echoerr "Could not find location of declaration"')
return
poly_go_to_location(loc)
except poly.process.Timeout:
vim.command('echoerr "Request timed out"')
except poly.process.ProtocolError:
vim.command('echoerr "Communication with Poly/ML failed"')
except Exception as e:
vim.command("echoerr 'Caught exception: {0}'".format(repr(e).replace("'","''")))
def poly_cleanup():
poly.kill_global_instance()
EOP
autocmd VimLeave * python poly_cleanup()
map <silent> <F5> :Polyml<CR>
map <silent> <LocalLeader>pc :Polyml<CR>
map <silent> <LocalLeader>pt :PolymlGetType<CR>
map <silent> <LocalLeader>pd :PolymlFindDeclaration<CR>
map <silent> <LocalLeader>pa :PolymlAccessors<CR>
map <silent> <LocalLeader>ps :PolymlAccessorSigs<CR>
map <silent> <LocalLeader>pC :PolymlConsoleHere<CR>
" vim:sts=4:sw=4:et