Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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.