diff options
Diffstat (limited to 'vim/bundle/slimv/ftplugin/swank.py')
-rw-r--r-- | vim/bundle/slimv/ftplugin/swank.py | 1373 |
1 files changed, 1373 insertions, 0 deletions
diff --git a/vim/bundle/slimv/ftplugin/swank.py b/vim/bundle/slimv/ftplugin/swank.py new file mode 100644 index 0000000..359cd68 --- /dev/null +++ b/vim/bundle/slimv/ftplugin/swank.py @@ -0,0 +1,1373 @@ +#!/usr/bin/env python) + +############################################################################### +# +# SWANK client for Slimv +# swank.py: SWANK client code for slimv.vim plugin +# Version: 0.9.13 +# Last Change: 16 Jan 2017 +# Maintainer: Tamas Kovacs <kovisoft at gmail dot com> +# License: This file is placed in the public domain. +# No warranty, express or implied. +# *** *** Use At-Your-Own-Risk! *** *** +# +############################################################################### + +from __future__ import print_function + +import socket +import time +import select +import string +import sys + +input_port = 4005 +output_port = 4006 +lenbytes = 6 # Message length is encoded in this number of bytes +maxmessages = 50 # Maximum number of messages to receive in one listening session +recv_timeout = 0.001 # socket recv timeout in seconds +listen_retries = 10 # number of retries if no response in swank_listen() +sock = None # Swank socket object +id = 0 # Message id +debug = False +log = False # Set this to True in order to enable logging +logfile = 'swank.log' # Logfile name in case logging is on +pid = '0' # Process id +current_thread = '0' +use_unicode = True # Use unicode message length counting +debug_active = False # Swank debugger is active +debug_activated = False # Swank debugger was activated +read_string = None # Thread and tag in Swank read string mode +empty_last_line = True # Swank output ended with a new line +prompt = 'SLIMV' # Command prompt +package = 'COMMON-LISP-USER' # Current package +actions = dict() # Swank actions (like ':write-string'), by message id +indent_info = dict() # Data of :indentation-update +frame_locals = dict() # Map frame variable names to their index +inspect_lines = 0 # Number of lines in the Inspector (excluding help text) +inspect_newline = True # Start a new line in the Inspector (for multi-part objects) +inspect_package = '' # Package used for the current Inspector +swank_version = '' # Swank version string in format YYYY-MM-DD +swank_param = '' # Additional parameter for the swank listener + + +############################################################################### +# Basic utility functions +############################################################################### + +def logprint(text): + if log: + f = open(logfile, "a") + f.write(text + '\n') + f.close() + +def logtime(text): + logprint(text + ' ' + str(time.clock())) + +############################################################################### +# Simple Lisp s-expression parser +############################################################################### + +# Possible error codes +PARSERR_NOSTARTBRACE = -1 # s-expression does not start with a '(' +PARSERR_NOCLOSEBRACE = -2 # s-expression does not end with a '(' +PARSERR_NOCLOSESTRING = -3 # string is not closed with double quote +PARSERR_MISSINGLITERAL = -4 # literal is missing after the escape character +PARSERR_EMPTY = -5 # s-expression is empty + + +def parse_comment( sexpr ): + """Parses a ';' Lisp comment till the end of line, returns comment length + """ + pos = sexpr.find( '\n' ) + if pos >= 0: + return pos + 1 + return len( sexpr ) + +def parse_keyword( sexpr ): + """Parses a Lisp keyword, returns keyword length + """ + for pos in range( len( sexpr ) ): + if sexpr[pos] in string.whitespace + ')]': + return pos + return pos + +def parse_sub_sexpr( sexpr, opening, closing ): + """Parses a Lisp sub -expression, returns parsed string length + and a Python list built from the s-expression, + expression can be a Clojure style list surrounded by braces + """ + result = [] + l = len( sexpr ) + for pos in range( l ): + # Find first opening '(' or '[' + if sexpr[pos] == opening: + break + if not sexpr[pos] in string.whitespace: + # S-expression does not start with '(' or '[' + return [PARSERR_NOSTARTBRACE, result] + else: + # Empty s-expression + return [PARSERR_EMPTY, result] + + pos = pos + 1 + quote_cnt = 0 + while pos < l: + literal = 0 + if sexpr[pos] == '\\': + literal = 1 + pos = pos + 1 + if pos == l: + return [PARSERR_MISSINGLITERAL, result] + if not literal and sexpr[pos] == '"': + # We toggle a string + quote_cnt = 1 - quote_cnt + if quote_cnt == 1: + quote_pos = pos + else: + result = result + [sexpr[quote_pos:pos+1]] + elif quote_cnt == 0: + # We are not in a string + if not literal and sexpr[pos] == '(': + # Parse sub expression + [slen, subresult] = parse_sub_sexpr( sexpr[pos:], '(', ')' ) + if slen < 0: + # Sub expression parsing error + return [slen, result] + result = result + [subresult] + pos = pos + slen - 1 + elif not literal and sexpr[pos] == '[': + # Parse sub expression + [slen, subresult] = parse_sub_sexpr( sexpr[pos:], '[', ']' ) + if slen < 0: + # Sub expression parsing error + return [slen, result] + result = result + [subresult] + pos = pos + slen - 1 + elif not literal and sexpr[pos] == closing: + # End of this sub expression + return [pos + 1, result] + elif not literal and sexpr[pos] != closing and sexpr[pos] in ')]': + # Wrong closing brace/bracket + return [PARSERR_NOCLOSEBRACE, result] + elif not literal and sexpr[pos] == ';': + # Skip coment + pos = pos + parse_comment( sexpr[pos:] ) - 1 + elif not literal and sexpr[pos] in "#'`@~,^": + # Skip prefix characters + while pos+1 < l and sexpr[pos+1] not in string.whitespace + '([': + pos = pos + 1 + elif not sexpr[pos] in string.whitespace + '\\': + # Parse keyword but ignore dot in dotted notation (a . b) + klen = parse_keyword( sexpr[pos:] ) + if klen > 1 or sexpr[pos] != '.': + result = result + [sexpr[pos:pos+klen]] + pos = pos + klen - 1 + pos = pos + 1 + + if quote_cnt != 0: + # Last string is not closed + return [PARSERR_NOCLOSESTRING, result] + # Closing ')' or ']' not found + return [PARSERR_NOCLOSEBRACE, result] + +def parse_sexpr( sexpr ): + """Parses a Lisp s-expression, returns parsed string length + and a Python list built from the s-expression + """ + return parse_sub_sexpr( sexpr, '(', ')' ) + + +############################################################################### +# Swank server interface +############################################################################### + +class swank_action: + def __init__ (self, id, name, data): + self.id = id + self.name = name + self.data = data + self.result = '' + self.pending = True + +def get_prompt(): + global prompt + if prompt.rstrip()[-1] == '>': + return prompt + ' ' + else: + return prompt + '> ' + +def unquote(s): + if len(s) < 2: + return s + if s[0] == '"' and s[-1] == '"': + slist = [] + esc = False + for c in s[1:-1]: + if not esc and c == '\\': + esc = True + elif esc and c == 'n': + esc = False + slist.append('\n') + else: + esc = False + slist.append(c) + return "".join(slist) + else: + return s + +def requote(s): + t = s.replace('\\', '\\\\') + t = t.replace('"', '\\"') + return '"' + t + '"' + +def new_line(new_text): + global empty_last_line + + if new_text != '': + if new_text[-1] != '\n': + return '\n' + elif not empty_last_line: + return '\n' + return '' + +def make_keys(lst): + keys = {} + for i in range(len(lst)): + if i < len(lst)-1 and lst[i][0] == ':': + keys[lst[i]] = unquote( lst[i+1] ) + return keys + +def parse_plist(lst, keyword): + for i in range(0, len(lst), 2): + if keyword == lst[i]: + return unquote(lst[i+1]) + return '' + +def parse_filepos(fname, loc): + lnum = 1 + cnum = 1 + pos = loc + try: + f = open(fname, "r") + except: + return [0, 0] + for line in f: + if pos < len(line): + cnum = pos + break + pos = pos - len(line) + lnum = lnum + 1 + f.close() + return [lnum, cnum] + +def format_filename(fname): + fname = vim.eval('fnamemodify(' + fname + ', ":~:.")') + if fname.find(' ') >= 0: + fname = '"' + fname + '"' + return fname + +def parse_location(lst): + fname = '' + line = '' + pos = '' + if lst[0] == ':location': + if type(lst[1]) == str: + return unquote(lst[1]) + for l in lst[1:]: + if l[0] == ':file': + fname = l[1] + if l[0] == ':line': + line = l[1] + if l[0] == ':position': + pos = l[1] + if fname == '': + fname = 'Unknown file' + if line != '': + return 'in ' + format_filename(fname) + ' line ' + line + if pos != '': + [lnum, cnum] = parse_filepos(unquote(fname), int(pos)) + if lnum > 0: + return 'in ' + format_filename(fname) + ' line ' + str(lnum) + else: + return 'in ' + format_filename(fname) + ' byte ' + pos + return 'no source line information' + +def unicode_len(text): + if use_unicode: + if sys.version_info[0] > 2: + return len(str(text)) + else: + return len(unicode(text, "utf-8")) + else: + if sys.version_info[0] > 2: + return len(text.encode('utf-8')) + else: + return len(text) + +def swank_send(text): + global sock + + logtime('[---Sent---]') + logprint(text) + l = "%06x" % unicode_len(text) + t = l + text + if debug: + print( 'Sending:', t) + try: + if sys.version_info[0] > 2: + sock.send(t.encode('utf-8')) + else: + sock.send(t) + except socket.error: + vim.command("let s:swank_result='Socket error when sending to SWANK server.\n'") + swank_disconnect() + +def swank_recv_len(timeout): + global sock + + rec = '' + sock.setblocking(0) + ready = select.select([sock], [], [], timeout) + if ready[0]: + l = lenbytes + sock.setblocking(1) + try: + data = sock.recv(l) + except socket.error: + vim.command("let s:swank_result='Socket error when receiving from SWANK server.\n'") + swank_disconnect() + return rec + while data and len(rec) < lenbytes: + if sys.version_info[0] > 2: + rec = rec + data.decode('utf-8') + else: + rec = rec + data + l = l - len(data) + if l > 0: + try: + data = sock.recv(l) + except socket.error: + vim.command("let s:swank_result='Socket error when receiving from SWANK server.\n'") + swank_disconnect() + return rec + return rec + +def swank_recv(msglen, timeout): + global sock + + if msglen > 0: + sock.setblocking(0) + ready = select.select([sock], [], [], timeout) + if ready[0]: + sock.setblocking(1) + rec = '' + while True: + # Each codepoint has at least 1 byte; so we start with the + # number of bytes, and read more if needed. + try: + needed = msglen - unicode_len(rec) + except UnicodeDecodeError: + # Add single bytes until we've got valid UTF-8 again + needed = max(msglen - len(rec), 1) + if needed == 0: + return rec + try: + data = sock.recv(needed) + except socket.error: + vim.command("let s:swank_result='Socket error when receiving from SWANK server.\n'") + swank_disconnect() + return rec + if len(data) == 0: + vim.command("let s:swank_result='Socket error when receiving from SWANK server.\n'") + swank_disconnect() + return rec + if sys.version_info[0] > 2: + rec = rec + data.decode('utf-8') + else: + rec = rec + data + rec = '' + +def swank_parse_inspect_content(pcont): + """ + Parse the swank inspector content + """ + global inspect_lines + global inspect_newline + + if type(pcont[0]) != list: + return + vim.command('setlocal modifiable') + buf = vim.current.buffer + help_lines = int( vim.eval('exists("b:help_shown") ? len(b:help) : 1') ) + pos = help_lines + inspect_lines + buf[pos:] = [] + istate = pcont[1] + start = pcont[2] + end = pcont[3] + lst = [] + for el in pcont[0]: + logprint(str(el)) + newline = False + if type(el) == list: + if el[0] == ':action': + text = '{<' + unquote(el[2]) + '> ' + unquote(el[1]) + ' <>}' + else: + text = '{[' + unquote(el[2]) + '] ' + unquote(el[1]) + ' []}' + lst.append(text) + else: + text = unquote(el) + lst.append(text) + if text == "\n": + newline = True + lines = "".join(lst).split("\n") + if inspect_newline or pos > len(buf): + buf.append(lines) + else: + buf[pos-1] = buf[pos-1] + lines[0] + buf.append(lines[1:]) + inspect_lines = len(buf) - help_lines + inspect_newline = newline + if int(istate) > int(end): + # Swank returns end+1000 if there are more entries to request + buf.append(['', "[--more--]", "[--all---]"]) + inspect_path = vim.eval('s:inspect_path') + if len(inspect_path) > 1: + buf.append(['', '[<<] Return to ' + ' -> '.join(inspect_path[:-1])]) + else: + buf.append(['', '[<<] Exit Inspector']) + if int(istate) > int(end): + # There are more entries to request + # Save current range for the next request + vim.command("let b:range_start=" + start) + vim.command("let b:range_end=" + end) + vim.command("let b:inspect_more=" + end) + else: + # No ore entries left + vim.command("let b:inspect_more=0") + vim.command('call SlimvEndUpdate()') + +def swank_parse_inspect(struct): + """ + Parse the swank inspector output + """ + global inspect_lines + global inspect_newline + + vim.command('call SlimvBeginUpdate()') + vim.command('call SlimvOpenInspectBuffer()') + vim.command('setlocal modifiable') + buf = vim.current.buffer + title = parse_plist(struct, ':title') + vim.command('let b:inspect_title="' + title + '"') + buf[:] = ['Inspecting ' + title, '--------------------', ''] + vim.command('normal! 3G0') + vim.command('call SlimvHelp(2)') + pcont = parse_plist(struct, ':content') + inspect_lines = 3 + inspect_newline = True + swank_parse_inspect_content(pcont) + vim.command('call SlimvSetInspectPos("' + title + '")') + +def swank_parse_debug(struct): + """ + Parse the SLDB output + """ + vim.command('call SlimvBeginUpdate()') + vim.command('call SlimvOpenSldbBuffer()') + vim.command('setlocal modifiable') + buf = vim.current.buffer + [thread, level, condition, restarts, frames, conts] = struct[1:7] + buf[:] = [l for l in (unquote(condition[0]) + "\n" + unquote(condition[1])).splitlines()] + buf.append(['', 'Restarts:']) + for i in range( len(restarts) ): + r0 = unquote( restarts[i][0] ) + r1 = unquote( restarts[i][1] ) + r1 = r1.replace("\n", " ") + buf.append([str(i).rjust(3) + ': [' + r0 + '] ' + r1]) + buf.append(['', 'Backtrace:']) + for f in frames: + frame = str(f[0]) + ftext = unquote( f[1] ) + ftext = ftext.replace('\n', '') + ftext = ftext.replace('\\\\n', '') + buf.append([frame.rjust(3) + ': ' + ftext]) + vim.command('call SlimvEndUpdate()') + vim.command("call search('^Restarts:', 'w')") + vim.command('stopinsert') + # This text will be printed into the REPL buffer + return unquote(condition[0]) + "\n" + unquote(condition[1]) + "\n" + +def swank_parse_xref(struct): + """ + Parse the swank xref output + """ + buf = '' + for e in struct: + buf = buf + unquote(e[0]) + ' - ' + parse_location(e[1]) + '\n' + return buf + +def swank_parse_compile(struct): + """ + Parse compiler output + """ + buf = '' + warnings = struct[1] + time = struct[3] + filename = '' + if len(struct) > 5: + filename = struct[5] + if filename == '' or filename[0] != '"': + filename = '"' + filename + '"' + vim.command('let s:compiled_file=' + filename + '') + vim.command("let qflist = []") + if type(warnings) == list: + buf = '\n' + str(len(warnings)) + ' compiler notes:\n\n' + for w in warnings: + msg = parse_plist(w, ':message') + severity = parse_plist(w, ':severity') + if severity[0] == ':': + severity = severity[1:] + location = parse_plist(w, ':location') + if location[0] == ':error': + # "no error location available" + buf = buf + ' ' + unquote(location[1]) + '\n' + buf = buf + ' ' + severity + ': ' + msg + '\n\n' + else: + fname = unquote(location[1][1]) + pos = location[2][1] + if location[3] != 'nil': + snippet = unquote(location[3][1]).replace('\r', '') + buf = buf + snippet + '\n' + buf = buf + fname + ':' + pos + '\n' + buf = buf + ' ' + severity + ': ' + msg + '\n\n' + if location[2][0] == ':line': + lnum = pos + cnum = 1 + else: + [lnum, cnum] = parse_filepos(fname, int(pos)) + msg = msg.replace("'", "' . \"'\" . '") + qfentry = "{'filename':'"+fname+"','lnum':'"+str(lnum)+"','col':'"+str(cnum)+"','text':'"+msg+"'}" + logprint(qfentry) + vim.command("call add(qflist, " + qfentry + ")") + else: + buf = '\nCompilation finished. (No warnings) [' + time + ' secs]\n\n' + vim.command("call setqflist(qflist)") + return buf + +def swank_parse_list_threads(tl): + vim.command('call SlimvBeginUpdate()') + vim.command('call SlimvOpenThreadsBuffer()') + vim.command('setlocal modifiable') + buf = vim.current.buffer + buf[:] = ['Threads in pid '+pid, '--------------------'] + vim.command('call SlimvHelp(2)') + buf.append(['', 'Idx ID Status Name Priority', \ + '---- ---- -------------------- -------------------- ---------']) + vim.command('normal! G0') + lst = tl[1] + headers = lst.pop(0) + logprint(str(lst)) + idx = 0 + for t in lst: + priority = '' + if len(t) > 3: + priority = unquote(t[3]) + buf.append(["%3d: %3d %-22s %-22s %s" % (idx, int(t[0]), unquote(t[2]), unquote(t[1]), priority)]) + idx = idx + 1 + vim.command('normal! j') + vim.command('call SlimvEndUpdate()') + +def swank_parse_frame_call(struct, action): + """ + Parse frame call output + """ + vim.command('call SlimvGotoFrame(' + action.data + ')') + vim.command('setlocal modifiable') + buf = vim.current.buffer + win = vim.current.window + line = win.cursor[0] + if type(struct) == list: + buf[line:line] = [struct[1][1]] + else: + buf[line:line] = ['No frame call information'] + vim.command('call SlimvEndUpdate()') + +def swank_parse_frame_source(struct, action): + """ + Parse frame source output + http://comments.gmane.org/gmane.lisp.slime.devel/9961 ;-( + 'Well, let's say a missing feature: source locations are currently not available for code loaded as source.' + """ + vim.command('call SlimvGotoFrame(' + action.data + ')') + vim.command('setlocal modifiable') + buf = vim.current.buffer + win = vim.current.window + line = win.cursor[0] + if type(struct) == list and len(struct) == 4: + if struct[1] == 'nil': + [lnum, cnum] = [int(struct[2][1]), 1] + fname = 'Unknown file' + else: + [lnum, cnum] = parse_filepos(unquote(struct[1][1]), int(struct[2][1])) + fname = format_filename(struct[1][1]) + if lnum > 0: + s = ' in ' + fname + ' line ' + str(lnum) + else: + s = ' in ' + fname + ' byte ' + struct[2][1] + slines = s.splitlines() + if len(slines) > 2: + # Make a fold (closed) if there are too many lines + slines[ 0] = slines[ 0] + '{{{' + slines[-1] = slines[-1] + '}}}' + buf[line:line] = slines + vim.command(str(line+1) + 'foldclose') + else: + buf[line:line] = slines + else: + buf[line:line] = [' No source line information'] + vim.command('call SlimvEndUpdate()') + +def swank_parse_locals(struct, action): + """ + Parse frame locals output + """ + frame_num = action.data + vim.command('call SlimvGotoFrame(' + frame_num + ')') + vim.command('setlocal modifiable') + buf = vim.current.buffer + win = vim.current.window + line = win.cursor[0] + if type(struct) == list: + lines = ' Locals:' + num = 0 + for f in struct: + name = parse_plist(f, ':name') + id = parse_plist(f, ':id') + value = parse_plist(f, ':value') + lines = lines + '\n ' + name + ' = ' + value + # Remember variable index in frame + frame_locals[str(frame_num) + " " + name] = num + num = num + 1 + else: + lines = ' No locals' + buf[line:line] = lines.split("\n") + vim.command('call SlimvEndUpdate()') + +def swank_listen(): + global output_port + global use_unicode + global debug_active + global debug_activated + global read_string + global empty_last_line + global current_thread + global prompt + global package + global pid + global swank_version + global swank_param + + retval = '' + msgcount = 0 + #logtime('[- Listen--]') + timeout = recv_timeout + while msgcount < maxmessages: + rec = swank_recv_len(timeout) + if rec == '': + break + timeout = 0.0 + msgcount = msgcount + 1 + if debug: + print('swank_recv_len received', rec) + msglen = int(rec, 16) + if debug: + print('Received length:', msglen) + if msglen > 0: + # length already received so it must be followed by data + # use a higher timeout + rec = swank_recv(msglen, 1.0) + logtime('[-Received-]') + logprint(rec) + [s, r] = parse_sexpr( rec ) + if debug: + print('Parsed:', r) + if len(r) > 0: + r_id = r[-1] + message = r[0].lower() + if debug: + print('Message:', message) + + if message == ':open-dedicated-output-stream': + output_port = int( r[1].lower(), 10 ) + if debug: + print(':open-dedicated-output-stream result:', output_port) + break + + elif message == ':presentation-start': + retval = retval + new_line(retval) + + elif message == ':write-string': + # REPL has new output to display + if len(r) > 2 and r[2] == ':repl-result': + retval = retval + new_line(retval) + retval = retval + unquote(r[1]) + add_prompt = True + for k,a in actions.items(): + if a.pending and a.name.find('eval') >= 0: + add_prompt = False + break + if add_prompt: + retval = retval + new_line(retval) + get_prompt() + + elif message == ':read-string': + # REPL requests entering a string + read_string = r[1:3] + vim.command('let s:read_string_mode=1') + + elif message == ':read-from-minibuffer': + # REPL requests entering a string in the command line + read_string = r[1:3] + vim.command('let s:read_string_mode=1') + vim.command("let s:input_prompt='%s'" % unquote(r[3]).replace("'", "''")) + + elif message == ':indentation-update': + for el in r[1]: + indent_info[ unquote(el[0]) ] = el[1] + + elif message == ':new-package': + package = unquote( r[1] ) + prompt = unquote( r[2] ) + + elif message == ':return': + read_string = None + vim.command('let s:read_string_mode=0') + if len(r) > 1: + result = r[1][0].lower() + else: + result = "" + if type(r_id) == str and r_id in actions: + action = actions[r_id] + action.pending = False + else: + action = None + if log: + logtime('[Actionlist]') + for k,a in sorted(actions.items()): + if a.pending: + pending = 'pending ' + else: + pending = 'finished' + logprint("%s: %s %s %s" % (k, str(pending), a.name, a.result)) + + if result == ':ok': + params = r[1][1] + logprint('params: ' + str(params)) + if params == []: + params = 'nil' + if type(params) == str: + element = params.lower() + to_ignore = [':frame-call', ':quit-inspector', ':kill-thread', ':debug-thread'] + to_nodisp = [':describe-symbol'] + to_prompt = [':undefine-function', ':swank-macroexpand-1', ':swank-macroexpand-all', ':disassemble-form', \ + ':load-file', ':toggle-profile-fdefinition', ':profile-by-substring', ':swank-toggle-trace', 'sldb-break'] + if action and action.name in to_ignore: + # Just ignore the output for this message + pass + elif element == 'nil' and action and action.name == ':inspector-pop': + # Quit inspector + vim.command('call SlimvQuitInspect(0)') + elif element != 'nil' and action and action.name in to_nodisp: + # Do not display output, just store it in actions + action.result = unquote(params) + else: + retval = retval + new_line(retval) + if element != 'nil': + retval = retval + unquote(params) + if action: + action.result = retval + vim.command("let s:swank_ok_result='%s'" % retval.replace("'", "''").replace("\0", "^@")) + if element == 'nil' or (action and action.name in to_prompt): + # No more output from REPL, write new prompt + retval = retval + new_line(retval) + get_prompt() + + elif type(params) == list and params: + element = '' + if type(params[0]) == str: + element = params[0].lower() + if element == ':present': + # No more output from REPL, write new prompt + retval = retval + new_line(retval) + unquote(params[1][0][0]) + '\n' + get_prompt() + elif element == ':values': + retval = retval + new_line(retval) + if type(params[1]) == list: + retval = retval + unquote(params[1][0]) + '\n' + else: + retval = retval + unquote(params[1]) + '\n' + get_prompt() + elif element == ':suppress-output': + pass + elif element == ':pid': + conn_info = make_keys(params) + pid = conn_info[':pid'] + swank_version = conn_info.get(':version', 'nil') + if len(swank_version) == 8: + # Convert version to YYYY-MM-DD format + swank_version = swank_version[0:4] + '-' + swank_version[4:6] + '-' + swank_version[6:8] + imp = make_keys( conn_info[':lisp-implementation'] ) + pkg = make_keys( conn_info[':package'] ) + package = pkg[':name'] + prompt = pkg[':prompt'] + vim.command('let s:swank_version="' + swank_version + '"') + if len(swank_version) < 8 or swank_version >= '2011-11-08': + # Recent swank servers count bytes instead of unicode characters + use_unicode = False + vim.command('let s:lisp_version="' + imp[':version'] + '"') + retval = retval + new_line(retval) + retval = retval + imp[':type'] + ' ' + imp[':version'] + ' Port: ' + str(input_port) + ' Pid: ' + pid + '\n; SWANK ' + swank_version + retval = retval + '\n' + get_prompt() + logprint(' Package:' + package + ' Prompt:' + prompt) + elif element == ':name': + keys = make_keys(params) + retval = retval + new_line(retval) + retval = retval + ' ' + keys[':name'] + ' = ' + keys[':value'] + '\n' + elif element == ':title': + swank_parse_inspect(params) + elif element == ':compilation-result': + retval = retval + new_line(retval) + swank_parse_compile(params) + get_prompt() + else: + if action.name == ':simple-completions': + if type(params[0]) == list and len(params[0]) > 0 and type(params[0][0]) == str and params[0][0] != 'nil': + compl = "\n".join(params[0]) + retval = retval + compl.replace('"', '') + elif action.name == ':fuzzy-completions': + if type(params[0]) == list and type(params[0][0]) == list: + compl = "\n".join(map(lambda x: x[0], params[0])) + retval = retval + compl.replace('"', '') + elif action.name == ':find-definitions-for-emacs': + if type(params[0]) == list and type(params[0][1]) == list and params[0][1][0] == ':location': + tags_file = vim.eval("g:slimv_tags_file") + temp = open(tags_file, 'w') + myitems = [[elem[1][1][1], elem[1][2][1]] for elem in params] + for i in myitems: + temp.write(swank_param) + temp.write('\t') + temp.write(i[0].replace('"', '')) + temp.write('\t') + temp.write(":go %s" % i[1]) + temp.write('\n') + temp.close() + retval = swank_param + elif action.name == ':list-threads': + swank_parse_list_threads(r[1]) + elif action.name == ':xref': + retval = retval + '\n' + swank_parse_xref(r[1][1]) + retval = retval + new_line(retval) + get_prompt() + elif action.name == ':set-package': + package = unquote(params[0]) + prompt = unquote(params[1]) + retval = retval + '\n' + get_prompt() + elif action.name == ':untrace-all': + retval = retval + '\nUntracing:' + for f in params: + retval = retval + '\n' + ' ' + f + retval = retval + '\n' + get_prompt() + elif action.name == ':frame-call': + swank_parse_frame_call(params, action) + elif action.name == ':frame-source-location': + swank_parse_frame_source(params, action) + elif action.name == ':frame-locals-and-catch-tags': + swank_parse_locals(params[0], action) + elif action.name == ':profiled-functions': + retval = retval + '\n' + 'Profiled functions:\n' + for f in params: + retval = retval + ' ' + f + '\n' + retval = retval + get_prompt() + elif action.name == ':inspector-range': + swank_parse_inspect_content(params) + if action: + action.result = retval + + elif result == ':abort': + debug_active = False + vim.command('let s:sldb_level=-1') + if len(r[1]) > 1: + retval = retval + '; Evaluation aborted on ' + unquote(r[1][1]).replace('\n', '\n;') + '\n' + get_prompt() + else: + retval = retval + '; Evaluation aborted\n' + get_prompt() + + elif message == ':inspect': + swank_parse_inspect(r[1]) + + elif message == ':debug': + retval = retval + swank_parse_debug(r) + + elif message == ':debug-activate': + debug_active = True + debug_activated = True + current_thread = r[1] + sldb_level = r[2] + vim.command('let s:sldb_level=' + sldb_level) + frame_locals.clear() + + elif message == ':debug-return': + debug_active = False + vim.command('let s:sldb_level=-1') + retval = retval + '; Quit to level ' + r[2] + '\n' + get_prompt() + + elif message == ':ping': + [thread, tag] = r[1:3] + swank_send('(:emacs-pong ' + thread + ' ' + tag + ')') + if retval != '': + empty_last_line = (retval[-1] == '\n') + return retval + +def swank_rex(action, cmd, package, thread, data=''): + """ + Send an :emacs-rex command to SWANK + """ + global id + id = id + 1 + key = str(id) + actions[key] = swank_action(key, action, data) + form = '(:emacs-rex ' + cmd + ' ' + package + ' ' + thread + ' ' + str(id) + ')\n' + swank_send(form) + +def get_package(): + """ + Package set by slimv.vim or nil + """ + pkg = vim.eval("s:swank_package") + if pkg == '': + return 'nil' + else: + return requote(pkg) + +def get_swank_package(): + """ + Package set by slimv.vim or current swank package + """ + pkg = vim.eval("s:swank_package") + if pkg == '': + return requote(package) + else: + return requote(pkg) + +def get_indent_info(name): + indent = '' + if name in indent_info: + indent = indent_info[name] + vc = ":let s:indent='" + indent + "'" + vim.command(vc) + +############################################################################### +# Various SWANK messages +############################################################################### + +def swank_connection_info(): + global log + actions.clear() + indent_info.clear() + frame_locals.clear() + debug_activated = False + if vim.eval('exists("g:swank_log") && g:swank_log') != '0': + log = True + swank_rex(':connection-info', '(swank:connection-info)', 'nil', 't') + +def swank_create_repl(): + global swank_version + if len(swank_version) < 8 or swank_version >= '2014-10-01': + swank_rex(':create-repl', '(swank-repl:create-repl nil)', get_swank_package(), 't') + else: + swank_rex(':create-repl', '(swank:create-repl nil)', get_swank_package(), 't') + +def swank_eval(exp): + if len(swank_version) < 8 or swank_version >= '2014-10-01': + cmd = '(swank-repl:listener-eval ' + requote(exp) + ')' + else: + cmd = '(swank:listener-eval ' + requote(exp) + ')' + swank_rex(':listener-eval', cmd, get_swank_package(), ':repl-thread') + +def swank_eval_in_frame(exp, n): + pkg = get_swank_package() + if len(swank_version) < 8 or swank_version >= '2011-11-21': + cmd = '(swank:eval-string-in-frame ' + requote(exp) + ' ' + str(n) + ' ' + pkg + ')' + else: + cmd = '(swank:eval-string-in-frame ' + requote(exp) + ' ' + str(n) + ')' + swank_rex(':eval-string-in-frame', cmd, pkg, current_thread, str(n)) + +def swank_pprint_eval(exp): + cmd = '(swank:pprint-eval ' + requote(exp) + ')' + swank_rex(':pprint-eval', cmd, get_swank_package(), ':repl-thread') + +def swank_interrupt(): + swank_send('(:emacs-interrupt :repl-thread)') + +def swank_invoke_restart(level, restart): + cmd = '(swank:invoke-nth-restart-for-emacs ' + level + ' ' + restart + ')' + swank_rex(':invoke-nth-restart-for-emacs', cmd, 'nil', current_thread, restart) + +def swank_throw_toplevel(): + swank_rex(':throw-to-toplevel', '(swank:throw-to-toplevel)', 'nil', current_thread) + +def swank_invoke_abort(): + swank_rex(':sldb-abort', '(swank:sldb-abort)', 'nil', current_thread) + +def swank_invoke_continue(): + swank_rex(':sldb-continue', '(swank:sldb-continue)', 'nil', current_thread) + +def swank_require(contrib): + cmd = "(swank:swank-require '" + contrib + ')' + swank_rex(':swank-require', cmd, 'nil', 't') + +def swank_frame_call(frame): + cmd = '(swank-backend:frame-call ' + frame + ')' + swank_rex(':frame-call', cmd, 'nil', current_thread, frame) + +def swank_frame_source_loc(frame): + cmd = '(swank:frame-source-location ' + frame + ')' + swank_rex(':frame-source-location', cmd, 'nil', current_thread, frame) + +def swank_frame_locals(frame): + cmd = '(swank:frame-locals-and-catch-tags ' + frame + ')' + swank_rex(':frame-locals-and-catch-tags', cmd, 'nil', current_thread, frame) + +def swank_restart_frame(frame): + cmd = '(swank-backend:restart-frame ' + frame + ')' + swank_rex(':restart-frame', cmd, 'nil', current_thread, frame) + +def swank_set_package(pkg): + cmd = '(swank:set-package "' + pkg + '")' + swank_rex(':set-package', cmd, get_package(), ':repl-thread') + +def swank_describe_symbol(fn): + cmd = '(swank:describe-symbol "' + fn + '")' + swank_rex(':describe-symbol', cmd, get_package(), 't') + +def swank_describe_function(fn): + cmd = '(swank:describe-function "' + fn + '")' + swank_rex(':describe-function', cmd, get_package(), 't') + +def swank_op_arglist(op): + pkg = get_swank_package() + cmd = '(swank:operator-arglist "' + op + '" ' + pkg + ')' + swank_rex(':operator-arglist', cmd, pkg, 't') + +def swank_completions(symbol): + cmd = '(swank:simple-completions "' + symbol + '" ' + get_swank_package() + ')' + swank_rex(':simple-completions', cmd, 'nil', 't') + +def swank_fuzzy_completions(symbol): + cmd = '(swank:fuzzy-completions "' + symbol + '" ' + get_swank_package() + ' :limit 2000 :time-limit-in-msec 2000)' + swank_rex(':fuzzy-completions', cmd, 'nil', 't') + +def swank_undefine_function(fn): + cmd = '(swank:undefine-function "' + fn + '")' + swank_rex(':undefine-function', cmd, get_package(), 't') + +def swank_return_string(s): + global read_string + swank_send('(:emacs-return-string ' + read_string[0] + ' ' + read_string[1] + ' ' + requote(s) + ')') + read_string = None + vim.command('let s:read_string_mode=0') + +def swank_return(s): + global read_string + if s != '': + swank_send('(:emacs-return ' + read_string[0] + ' ' + read_string[1] + ' "' + s + '")') + read_string = None + vim.command('let s:read_string_mode=0') + +def swank_inspect(symbol): + global inspect_package + cmd = '(swank:init-inspector "' + symbol + '")' + inspect_package = get_swank_package() + swank_rex(':init-inspector', cmd, inspect_package, 't') + +def swank_inspect_nth_part(n): + cmd = '(swank:inspect-nth-part ' + str(n) + ')' + swank_rex(':inspect-nth-part', cmd, get_swank_package(), 't', str(n)) + +def swank_inspector_nth_action(n): + cmd = '(swank:inspector-call-nth-action ' + str(n) + ')' + swank_rex(':inspector-call-nth-action', cmd, 'nil', 't', str(n)) + +def swank_inspector_pop(): + # Remove the last entry from the inspect path + vim.command('let s:inspect_path = s:inspect_path[:-2]') + swank_rex(':inspector-pop', '(swank:inspector-pop)', 'nil', 't') + +def swank_inspect_in_frame(symbol, n): + key = str(n) + " " + symbol + if key in frame_locals: + cmd = '(swank:inspect-frame-var ' + str(n) + " " + str(frame_locals[key]) + ')' + else: + cmd = '(swank:inspect-in-frame "' + symbol + '" ' + str(n) + ')' + swank_rex(':inspect-in-frame', cmd, get_swank_package(), current_thread, str(n)) + +def swank_inspector_range(): + start = int(vim.eval("b:range_start")) + end = int(vim.eval("b:range_end")) + cmd = '(swank:inspector-range ' + str(end) + " " + str(end+(end-start)) + ')' + swank_rex(':inspector-range', cmd, inspect_package, 't') + +def swank_quit_inspector(): + global inspect_package + swank_rex(':quit-inspector', '(swank:quit-inspector)', 'nil', 't') + inspect_package = '' + +def swank_break_on_exception(flag): + if flag: + swank_rex(':break-on-exception', '(swank:break-on-exception "true")', 'nil', current_thread) + else: + swank_rex(':break-on-exception', '(swank:break-on-exception "false")', 'nil', current_thread) + +def swank_set_break(symbol): + cmd = '(swank:sldb-break "' + symbol + '")' + swank_rex(':sldb-break', cmd, get_package(), 't') + +def swank_toggle_trace(symbol): + cmd = '(swank:swank-toggle-trace "' + symbol + '")' + swank_rex(':swank-toggle-trace', cmd, get_package(), 't') + +def swank_untrace_all(): + swank_rex(':untrace-all', '(swank:untrace-all)', 'nil', 't') + +def swank_macroexpand(formvar): + form = vim.eval(formvar) + cmd = '(swank:swank-macroexpand-1 ' + requote(form) + ')' + swank_rex(':swank-macroexpand-1', cmd, get_package(), 't') + +def swank_macroexpand_all(formvar): + form = vim.eval(formvar) + cmd = '(swank:swank-macroexpand-all ' + requote(form) + ')' + swank_rex(':swank-macroexpand-all', cmd, get_package(), 't') + +def swank_disassemble(symbol): + cmd = '(swank:disassemble-form "' + "'" + symbol + '")' + swank_rex(':disassemble-form', cmd, get_package(), 't') + +def swank_xref(fn, type): + cmd = "(swank:xref '" + type + " '" + '"' + fn + '")' + swank_rex(':xref', cmd, get_package(), 't') + +def swank_compile_string(formvar): + form = vim.eval(formvar) + filename = vim.eval("substitute( expand('%:p'), '\\', '/', 'g' )") + line = vim.eval("line('.')") + pos = vim.eval("line2byte(line('.'))") + if vim.eval("&fileformat") == 'dos': + # Remove 0x0D, keep 0x0A characters + pos = str(int(pos) - int(line) + 1) + cmd = '(swank:compile-string-for-emacs ' + requote(form) + ' nil ' + "'((:position " + str(pos) + ") (:line " + str(line) + " 1)) " + requote(filename) + ' nil)' + swank_rex(':compile-string-for-emacs', cmd, get_package(), 't') + +def swank_compile_file(name): + cmd = '(swank:compile-file-for-emacs ' + requote(name) + ' t)' + swank_rex(':compile-file-for-emacs', cmd, get_package(), 't') + +def swank_load_file(name): + cmd = '(swank:load-file ' + requote(name) + ')' + swank_rex(':load-file', cmd, get_package(), 't') + +def swank_toggle_profile(symbol): + cmd = '(swank:toggle-profile-fdefinition "' + symbol + '")' + swank_rex(':toggle-profile-fdefinition', cmd, get_package(), 't') + +def swank_profile_substring(s, package): + if package == '': + p = 'nil' + else: + p = requote(package) + cmd = '(swank:profile-by-substring ' + requote(s) + ' ' + p + ')' + swank_rex(':profile-by-substring', cmd, get_package(), 't') + +def swank_unprofile_all(): + swank_rex(':unprofile-all', '(swank:unprofile-all)', 'nil', 't') + +def swank_profiled_functions(): + swank_rex(':profiled-functions', '(swank:profiled-functions)', 'nil', 't') + +def swank_profile_report(): + swank_rex(':profile-report', '(swank:profile-report)', 'nil', 't') + +def swank_profile_reset(): + swank_rex(':profile-reset', '(swank:profile-reset)', 'nil', 't') + +def swank_list_threads(): + cmd = '(swank:list-threads)' + swank_rex(':list-threads', cmd, get_swank_package(), 't') + +def swank_kill_thread(index): + cmd = '(swank:kill-nth-thread ' + str(index) + ')' + swank_rex(':kill-thread', cmd, get_swank_package(), 't', str(index)) + +def swank_find_definitions_for_emacs(str): + global swank_param + swank_param = str + cmd = '(swank:find-definitions-for-emacs "' + str + '")' + swank_rex(':find-definitions-for-emacs', cmd, get_package(), ':repl-thread') + +def swank_debug_thread(index): + cmd = '(swank:debug-nth-thread ' + str(index) + ')' + swank_rex(':debug-thread', cmd, get_swank_package(), 't', str(index)) + +def swank_quit_lisp(): + swank_rex(':quit-lisp', '(swank:quit-lisp)', 'nil', 't') + swank_disconnect() + +############################################################################### +# Generic SWANK connection handling +############################################################################### + +def swank_connect(host, port, resultvar): + """ + Create socket to swank server and request connection info + """ + global sock + global input_port + + if not sock: + try: + input_port = port + swank_server = (host, input_port) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(swank_server) + swank_connection_info() + vim.command('let ' + resultvar + '=""') + return sock + except socket.error: + vim.command('let ' + resultvar + '="SWANK server is not running."') + sock = None + return sock + vim.command('let ' + resultvar + '=""') + +def swank_disconnect(): + """ + Disconnect from swank server + """ + global sock + try: + # Try to close socket but don't care if doesn't succeed + sock.close() + finally: + sock = None + vim.command('let s:swank_connected = 0') + vim.command("let s:swank_result='Connection to SWANK server is closed.\n'") + +def swank_input(formvar): + global empty_last_line + + empty_last_line = True + form = vim.eval(formvar) + if read_string: + # We are in :read-string mode, pass string entered to REPL + swank_return_string(form) + elif form[0] == '[': + if form[1] == '-': + swank_inspector_pop() + else: + swank_inspect_nth_part(form[1:-2]) + elif form[0] == '<': + swank_inspector_nth_action(form[1:-2]) + else: + # Normal s-expression evaluation + swank_eval(form) + +def actions_pending(): + count = 0 + for k,a in sorted(actions.items()): + if a.pending: + count = count + 1 + vc = ":let s:swank_actions_pending=" + str(count) + vim.command(vc) + return count + +def append_repl(text, varname_given): + """ + Append text at the end of the REPL buffer + Does not bring REPL buffer into focus if loaded but not displayed in any window + """ + repl_buf = int(vim.eval("s:repl_buf")) + if repl_buf < 0 or int(vim.eval("buflisted(%d) && bufloaded(%d)" % (repl_buf, repl_buf))) == 0: + # No REPL buffer exists + vim.command('call SlimvBeginUpdate()') + vim.command('call SlimvOpenReplBuffer()') + vim.command('call SlimvRestoreFocus(0)') + repl_buf = int(vim.eval("s:repl_buf")) + for buf in vim.buffers: + if buf.number == repl_buf: + break + if repl_buf > 0 and buf.number == repl_buf: + if varname_given: + lines = vim.eval(text).split("\n") + else: + lines = text.split("\n") + if lines[0] != '': + # Concatenate first line to the last line of the buffer + nlines = len(buf) + buf[nlines-1] = buf[nlines-1] + lines[0] + if len(lines) > 1: + # Append all subsequent lines + buf.append(lines[1:]) + + # Keep only the last g:slimv_repl_max_len lines + repl_max_len = int(vim.eval("g:slimv_repl_max_len")) + repl_prompt_line = int(vim.eval("getbufvar(%d, 'repl_prompt_line')" % repl_buf)) + lastline = len(buf) + prompt_offset = lastline - repl_prompt_line + if repl_max_len > 0 and lastline > repl_max_len: + form = "\n".join(buf[0:(lastline-repl_max_len)]) + ending = vim.eval("substitute(s:CloseForm('%s'), '\\n', '', 'g')" % form.replace("'", "''")) + # Delete extra lines + buf[0:(lastline - repl_max_len)] = [] + if ending.find(')') >= 0 or ending.find(']') >= 0 or ending.find(']') >= 0: + # Reverse the ending and replace matched characters with their pairs + start = ending[::-1] + start = start.replace(')', '(').replace(']', '[').replace('}', '{').replace("\n", '') + # Re-balance the beginning of the buffer + buf[0:0] = [start + " .... ; output shortened"] + vim.command("call setbufvar(%d, 'repl_prompt_line', %d)" % (repl_buf, len(buf) - prompt_offset)) + + # Move cursor at the end of REPL buffer in case it was originally after the prompt + vim.command('call SlimvReplSetCursorPos(0)') + +def swank_output(echo): + global sock + global debug_active + global debug_activated + + if not sock: + return "SWANK server is not connected." + count = 0 + #logtime('[- Output--]') + debug_activated = False + result = swank_listen() + pending = actions_pending() + while sock and result == '' and pending > 0 and count < listen_retries: + result = swank_listen() + pending = actions_pending() + count = count + 1 + if echo and result != '': + # Append SWANK output to REPL buffer + append_repl(result, 0) + if debug_activated and debug_active: + # Debugger was activated in this run + vim.command('call SlimvOpenSldbBuffer()') + vim.command('call SlimvEndUpdate()') + vim.command("call search('^Restarts:', 'w')") + +def swank_response(name): + #logtime('[-Response-]') + for k,a in sorted(actions.items()): + if not a.pending and (name == '' or name == a.name): + vc = ":let s:swank_action='" + a.name + "'" + vim.command(vc) + vim.command("let s:swank_result='%s'" % a.result.replace("'", "''")) + actions.pop(a.id) + actions_pending() + return + vc = ":let s:swank_action=''" + vc = ":let s:swank_result=''" + vim.command(vc) + actions_pending() + |