Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

368 lines (329 sloc) 12.789 kB
" This file contains the code necessary to talk to the scion server
" -> haskellcomplete#EvalScion )
"
" This implementation requires has('python') support
" You can look up some use cases in the ftplugin file.
"
" This code is based on the initial implementation found in shim by Benedikt Schmidt
" The server side code can be found in src-scion-server/Scion/Server/ProtocolVim.hs
if exists('g:tovl')
fun! s:Log(level, msg)
call tovl#log#Log("haskellcomplete",a:level, type(a:msg) == type("") ? a:msg : string(a:msg))
endf
else
fun! s:Log(level, msg)
echoe a:msg
endf
endif
" require python or exit
if !has('python') | call s:Log(0, "Error: scion requires vim compiled with +python") | finish | endif
let g:vim_scion_protocol_version = "0"
fun! haskellcomplete#LoadComponent(set_cabal_project, component)
let result = haskellcomplete#EvalScion(0, 'load-component', { 'component' : a:component})
if has_key(result,'error')
if result['error']['message'] == "NoCurrentCabalProject" && a:set_cabal_project
let cabal_project = haskellcomplete#SetCurrentCabalProject()
return haskellcomplete#LoadComponent(0, a:component)
else
throw "can't handle this failure: ".string(result['error'])
endif
endif
return result['result']
endf
fun! haskellcomplete#SetCurrentCabalProject()
let configs = haskellcomplete#EvalScion(1,'list-cabal-configurations',
\ { 'cabal-file' : haskellcomplete#CabalFile()
\ , 'scion-default': json#IntToBool(get(g:scion_config, "use_default_scion_cabal_dist_dir", 1)) })
let config = haskellcomplete#ChooseFromList(configs, 'select a cabal configuration')
let result = haskellcomplete#EvalScion(1,'open-cabal-project'
\ ,{'root-dir' : getcwd()
\ ,'dist-dir' : config['dist-dir']
\ ,'extra-args' : config['extra-args'] }
\ )
endf
fun! haskellcomplete#ScionResultToErrorList(action, func, result)
let qflist = []
for dict in a:result['notes']
let loc = dict['location']
if has_key(loc, 'no-location')
" using no-location so that we have an item to jump to.
" ef we don't use that dummy file SaneHook won't see any errors!
call add(qflist, { 'filename' : 'no-location'
\ ,'lnum' : 0
\ ,'col' : 0
\ ,'text' : loc['no-location']
\ ,'type' : dict['kind'] == "error" ? "E" : "W"
\ })
else
call add(qflist, { 'filename' : loc['file']
\ ,'lnum' : loc['region'][0]
\ ,'col' : loc['region'][1]
\ ,'text' : ''
\ ,'type' : dict['kind'] == "error" ? "E" : "W"
\ })
endif
for msgline in split(dict['message'],"\n")
call add(qflist, {'text': msgline})
endfor
endfor
call call(a:func, [qflist])
if exists('g:haskell_qf_hook')
exec g:haskell_qf_hook
endif
if (len(qflist) == 0)
return printf(a:action." success. compilationTime: %s", string(a:result['duration']))
else
return printf(a:action." There are errors. compilationTime: %s", string(a:result['duration']))
endif
endfun
" if there is item take it, if there are more than one ask user which one to
" use.. -- don't think cabal allows multiple .cabal files.. At least the user
" is notified that there are more than one .cabal files
fun! haskellcomplete#ChooseFromList(list, ...)
let msg = a:0 > 0 ? a:1 : "choose from list"
if empty(a:list)
return
elseif len(a:list) == 1
return a:list[0]
else
let l = []
let i = 1
for line in a:list
let line2 = type(line) != type('') ? string(line) : line
call add(l, i.': '.line2)
let i = i + 1
unlet line
endfor
return a:list[inputlist(l)-1]
endif
endf
fun! haskellcomplete#CabalFile()
if !exists('g:cabal_file')
let list = split(glob('*.cabal'),"\n")
if empty(list)
throw "no cabal file found"
endif
let g:cabal_file = getcwd().'/'.haskellcomplete#ChooseFromList(list)
endif
return g:cabal_file
endf
fun! haskellcomplete#List(what)
return haskellcomplete#EvalScion(1,'list-'.a:what, {})
endf
fun! haskellcomplete#OpenCabalProject(method, ...)
return haskellcomplete#EvalScion(1,a:method
\ ,{'root-dir' : getcwd()
\ ,'dist-dir' : a:1
\ ,'extra-args' : a:000[1:] }
\ )
endf
fun! haskellcomplete#compToV(...)
let component = a:0 > 0 ? a:1 : 'file:'.expand('%:p')
let m = matchstr(component, '^executable:\zs.*')
if m != '' | return {'executable' : m} | endif
let m = matchstr(component, '^library$')
if m != '' | return {'library' : json#NULL()} | endif
let m = matchstr(component, '^file:\zs.*')
if m != '' | return {'file' : m} | endif
throw "invalid component".component
endfun
fun! haskellcomplete#WriteSampleConfig(...)
let file = a:0 > 0 ? a:1 : ".scion-config"
let file = fnamemodify(file, ":p")
return haskellcomplete#EvalScion(1, 'write-sample-config', {'file' : file})
endf
" if there are errors open quickfix and jump to first error (ignoring warning)
" if not close it
fun! haskellcomplete#SaneHook()
let list = getqflist()
let nr = 0
let open = 0
let firstError = 0
for i in getqflist()
let nr = nr +1
if i['bufnr'] == 0 | continue | endif
if i['type'] == "E" && firstError == 0
let firstError = nr
endif
let open = 1
endfor
if open
cope " open
" move to first error
if firstError > 0 | exec "crewind ".firstError | endif
else
cclose
endif
endf
" use this to connect to a socket
" connection settings: see strings in connectscion
" returns string part before and after cursor
function! haskellcomplete#BcAc()
let pos = col('.') -1
let line = getline('.')
return [strpart(line,0,pos), strpart(line, pos, len(line)-pos)]
endfunction
" completion functions
if !exists('g:haskellcompleteAll')
let g:haskellcompleteAll='' " '' or '-all' '-all' means complete from the set of all function exported by all modules found in all used packages
endif
fun! haskellcomplete#CompletModule(findstart, base)
if a:findstart
let [bc,ac] = haskellcomplete#BcAc()
return len(bc)-len(matchstr(bc,'\S*$'))
else
let [bc,ac] = haskellcomplete#BcAc()
let addImport = bc !~ 'import\s\+\S*$'
let matches = haskellcomplete#EvalScion(
\ { 'request' : 'cmdModuleCompletion'
\ , 'camelCase' : 'True'
\ , 'short' : a:base
\ })
if addImport
call map(matches, string('import ').'.v:val')
endif
return matches
endif
endf
let g:scion_request_id = 1
" name: method name
" params: params to method
" optional argument: continuation function (not yet implemented)
" returns: nothing when continuation function is given
" reply else
function! haskellcomplete#EvalScion(fail_on_error, method, params, ...)
if a:0 > 0
let continuation a:0
endif
let g:scion_request_id = g:scion_request_id + 1
let request = { 'method' : a:method, 'params' : a:params, 'id' : g:scion_request_id }
" the first string converts the vim object into a string, the second
" converts this string into a python string
let g:scion_arg = json#Encode(request)
py evalscionAssign(vim.eval('g:scion_arg'))
" warnings
for w in get(g:scion_result, 'warnings', [])
call s:Log(1, w) | echo w
endfor
" errors
if !a:fail_on_error
return g:scion_result
endif
if has_key(g:scion_result,'error')
call s:Log(0, g:scion_result['error'])
throw "There was a scion server error :".string(g:scion_result['error'])
else
return g:scion_result['result']
endif
endfunction
function! s:DefPython()
python << PYTHONEOF
import sys, tokenize, cStringIO, types, socket, string, vim, popen2, os
from subprocess import Popen, PIPE
scion_log_stdout = vim.eval('exists("g:scion_log_stdout") && g:scion_log_stdout')
scion_stdout = []
class ScionServerConnection:
"""base of a server connection. They all provide two methods: send and receive bothe sending or receiving a single line separated by \\n"""
def send(self, line):
self.scion_i.write("%s\n"%line)
self.scion_i.flush()
def receive(self):
s = self.scion_o.readline()
if s == "":
raise "EOF, stderr lines: \n%s"%self.scion_err.read()
else:
return s[:-1]
class ScionServerConnectionStdinOut(ScionServerConnection):
"""this connection launches the server and connects to its stdin and stdout streams"""
def __init__(self, scion_executable):
#self.scion_o,self.scion_i,e = popen2.popen3('%s -i -f /tmp/scion-log-%s' % (scion_executable, os.getcwd().replace('/','_').replace('\\','_'))
p = Popen([scion_executable,"-i","-f", "/tmp/scion-log-%s"%(os.getcwd().replace('/','_').replace('\\','_'))], \
shell = False, bufsize = 1, stdin = PIPE, stdout = PIPE, stderr = PIPE)
self.scion_o = p.stdout
self.scion_i = p.stdin
self.scion_err = p.stderr
def receive(self):
global scion_log_stdout, scion_stdout
s = ScionServerConnection.receive(self)
# ghc doesn't always use the ghc API to print statements.. so ignore all
# lines not marked by "scion:" at the beginning
# see README.markdown
while s[:6] != "scion:":
# throw away non "scion:" line and try again
if scion_log_stdout:
scion_stdout.append(s)
scion_stdout = scion_stdout[-200:]
# should this be printed? It doesn't hurt much but might be useful when
# trouble shooting..
print "ignoring line", s
s = ScionServerConnection.receive(self)
return s[6:]
class ScionServerConnectionSocket(ScionServerConnection):
"""connects to the scion server by either TCP/IP or socketfile"""
def __init__(self, connection):
if type(connection) == type([]):
# array [ host, port ]
# vim.eval always returns strings!
connection = (connection[0], string.atoi(connection[1]))
print "connection is now %s" % connection.__str__()
su = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
else: # must be path -> file socket
print "else "
su = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
su.settimeout(10)
su.connect(connection)
# making file to use readline()
self.scion_o = su.makefile('rw')
self.scion_i = self.scion_o
server_connection = None
told_user_about_missing_configuration = 0
lastScionResult = "";
def connectscion():
# check that connection method has been defined
global server_connection
global told_user_about_missing_configuration
if 0 == told_user_about_missing_configuration:
try:
# use connection form vim value so that python is used as lazily as possible
scionConnectionSetting = vim.eval('g:scion_connection_setting')
print "connecting to scion %s"%scionConnectionSetting.__str__()
except NameError:
vim.command("sp")
b = vim.current.buffer
b.append( "you haven't defined g:scion_connection_setting")
b.append( "Do so by adding one of the following lines to your .vimrc:")
b.append( "TCP/IP, socket, stdio")
b.append( "let g:scion_connection_setting = ['socket', \"socket file location\"] # socket connection")
b.append( "let g:scion_connection_setting = ['socket', ['localhost', 4005]] # host, port TCIP/IP connection")
b.append( "let g:scion_connection_setting = ['scion', \"scion_server location\"] # stdio connection ")
told_user_about_missing_configuration = 1
if scionConnectionSetting[0] == "socket":
server_connection = ScionServerConnectionSocket(scionConnectionSetting[1])
else:
server_connection = ScionServerConnectionStdinOut(scionConnectionSetting[1])
# tell server than vim doesn't like true, false, null
vim.command('call haskellcomplete#EvalScion(1, "client-identify", {"name":"vim"})')
# sends a command and returns the returned line
def evalscion(str):
global server_connection
try:
server_connection.send(str)
except:
vim.command('echom "%s"'% ("(re)connecting to scion"))
connectscion()
server_connection.send(str)
return server_connection.receive()
# str see EvalScion
def evalscionAssign(str):
global lastScionResult
"""assigns scion result to g:scion_result, result should either be
{ "result" : ..., "error" : [String] }"""
vim.command("silent! unlet g:scion_result")
lastScionResult = evalscion(str)
vim.command("silent! let g:scion_result = %s" % lastScionResult)
vim.command("if !exists('g:scion_result') | let g:scion_result = {'error' : \"couldn't parse scion result.\n%s\nTry :py print lastScionResult to see full server response\" } | endif " % str[:80])
# sys.path.extend(['.','..'])
PYTHONEOF
endfunction
call s:DefPython()
" vim: set et ts=4:
Jump to Line
Something went wrong with that request. Please try again.