diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 0a4ed08..237c947 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -3,6 +3,8 @@ monitor_subchannel = True # update vim-ipython 'shell' on every send? run_flags= "-i" # flags to for IPython's run magic when using current_line = '' +allow_stdin = True # whether or not to accept stdin requests +current_stdin_prompt = {} try: from queue import Empty # python3 convention @@ -355,7 +357,16 @@ def update_subchannel_msgs(debug=False, force=False): """ if kc is None or (not vim_ipython_is_open() and not force): return False + + if allow_stdin: + stdin_msgs = kc.stdin_channel.get_msgs() + else: + stdin_msgs = [] + msgs = kc.iopub_channel.get_msgs() + msgs += stdin_msgs + + global current_stdin_prompt b = vim.current.buffer startedin_vimipython = vim.eval('@%')=='vim-ipython' if not startedin_vimipython: @@ -407,6 +418,9 @@ def update_subchannel_msgs(debug=False, force=False): b = vim.current.buffer update_occured = False for m in msgs: + # if we received a message it means the kernel is not waiting for input + vim.command('autocmd! InsertEnter ') + current_stdin_prompt.clear() s = '' if 'msg_type' not in m['header']: # debug information @@ -446,6 +460,13 @@ def update_subchannel_msgs(debug=False, force=False): c = m['content'] s = "\n".join(map(strip_color_escapes,c['traceback'])) s += c['ename'] + ":" + c['evalue'] + elif header == 'input_request': + current_stdin_prompt['prompt'] = m['content']['prompt'] + current_stdin_prompt['is_password'] = m['content']['password'] + current_stdin_prompt['parent_msg_id'] = m['parent_header']['msg_id'] + s += m['content']['prompt'] + vim.command('autocmd InsertEnter :py InputPrompt()') + echo('Awaiting input. Answer by editing last vim-ipython line') if s.find('\n') == -1: # somewhat ugly unicode workaround from @@ -460,11 +481,14 @@ def update_subchannel_msgs(debug=False, force=False): b.append([l.encode(vim_encoding) for l in s.splitlines()]) update_occured = True # make a newline so we can just start typing there - if status_blank_lines: + if status_blank_lines and not current_stdin_prompt: if b[-1] != '': b.append(['']) if update_occured or force: vim.command('normal! G') # go to the end of the file + if current_stdin_prompt: + vim.command('normal! $') # also go to the end of the line + if not startedin_vimipython: vim.command('normal! p') # go back to where you were return update_occured @@ -491,7 +515,9 @@ def print_prompt(prompt,msg_id=None): count = child['content']['execution_count'] echo("In[%d]: %s" %(count,prompt)) except Empty: - echo("In[]: %s (no reply from IPython kernel)" % prompt) + # show empty prompt only if the kernel is not waiting for input + if not kc.stdin_channel.msg_ready(): + echo("In[]: %s (no reply from IPython kernel)" % prompt) else: echo("In[]: %s" % prompt) @@ -534,12 +560,12 @@ def run_this_line(dedent=False): vim.command('stopi') w.cursor = original_pos return - msg_id = send(line) + msg_id = send(line, allow_stdin=allow_stdin) print_prompt(line, msg_id) @with_subchannel def run_command(cmd): - msg_id = send(cmd) + msg_id = send(cmd, allow_stdin=allow_stdin) print_prompt(cmd, msg_id) @with_subchannel @@ -555,7 +581,7 @@ def run_these_lines(dedent=False): lines = "\n".join(x[leading:] for x in lines) else: lines = "\n".join(vim.current.buffer[r.start:r.end+1]) - msg_id = send(lines) + msg_id = send(lines, allow_stdin=allow_stdin) #alternative way of doing this in more recent versions of ipython #but %paste only works on the local machine #vim.command("\"*yy") @@ -571,6 +597,40 @@ def run_these_lines(dedent=False): print_prompt(prompt,msg_id) +@with_subchannel +def InputPrompt(): + # If there is a pending input and we are in the last line + if current_stdin_prompt and vim.eval('line(".")')==vim.eval('line("$")'): + vim.command('call inputsave()') + input_command = ( + "try" + "|let user_input = {input_command}('{prompt}')" + "|catch /^Vim:Interrupt$/" + "|silent! unlet user_input" + "|endtry" + ).format(input_command='inputsecret' if current_stdin_prompt['is_password'] else 'input', + prompt=current_stdin_prompt['prompt']) + vim.command(input_command) + vim.command('call inputrestore()') + + if vim.eval('exists("user_input")'): + reply = vim.eval('user_input') + vim.command("silent! unlet user_input") + b = vim.current.buffer + last_line = b[-1] + del b[-1] + b.append((last_line+reply).splitlines()) + vim.command('autocmd InsertLeave :normal! G$') + vim.command('autocmd InsertLeave :autocmd! InsertLeave ') + vim.command('call feedkeys("\\", "n")') + kc.input(reply) + try: + child = get_child_msg(current_stdin_prompt['parent_msg_id']) + except Empty: + pass + current_stdin_prompt.clear() + + def set_pid(): """ Explicitly ask the ipython kernel for its pid