#!/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 # 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()