[PYTHON] Set the output destination of the execution result to Vim started as a modeless window

modeless_demo.gif

Introduction

The execution result started on Vim is basically output to another buffer divided into windows, but

--The screen for source code becomes narrower --I want to output the result to the sub monitor

There was a personal dissatisfaction.

So, I thought that I wish I could get the result in the floating window like IDE, so I decided to implement it. The demo screen at the beginning shows what it looks like. Rust is executed with QuickRun, and the execution result that is originally output to the buffer is sent to another Vim via the Python server and output.

image.png

If you can communicate directly with other Vim processes from Vim, for example, if you can use the main editor side as a server, you can omit the Python server, but since I have not investigated it in detail, I modified the Python server enclosed as a sample in Vim and socketed it. I took the method of passing the execution result by communication.

These implementation sets are summarized as a plug-in, QuickRun is an example of actual use, and this plug-in provides a set of processing to send and receive the character string you want to pass by socket communication.

wordijp/vim-modeless-window

About QuickRun settings and mechanism

I will explain how to implement the demo at the beginning.

Send execution result

Since QuickRun will output the execution result to the outputter as it is, use the hook function to save the contents to the cache before output, and send the result to the Python server when the execution is completed. At this time, set outputter/buffer/close_on_empty to 1 so that the split window for output does not open on the main editor side.

.vimrc QuickRun settings
" .vimrc

let g:quickrun_config = {
\  'rust-run-modeless': {
\    'command' : 'cargo',
\    'cmdopt': 'run',
\    'args': '--quiet',
\    'exec' : '%c %o %a',
\    'outputter': 'buffer',
\    'outputter/buffer/close_on_empty': 1,
\  },
\ }

let s:hook = {
\ 'name': 'modeless',
\ 'kind': 'hook',
\ 'output_data': '',
\ }

function! s:hook.on_output(data, session)
  let self.output_data .= a:session.data
  let a:session.data = '' " empty output
endfunction

function! s:hook.on_exit(session, context)

  "Send execution result
  let l:txt = split(self.output_data, '\r\n\|\n')
  call modeless_window#send(l:txt)

  echohl MoreMsg
  echomsg 'output: to modeless'
  echohl NONE
endfunction

call quickrun#module#register(s:hook, 1)
unlet s:hook

Socket communication during transmission and reception

Socket communication uses Vim's channel, ch_open opens two for sending and receiving with a fixed port number, but the main editor opens only for sending and sends with ch_sendexpr.

Send processing
function! modeless_window#send(txt)
  let l:ch = ch_open('127.0.0.1:'.s:send_port)
  let l:st = ch_status(l:ch)
  if l:st ==# 'fail' || l:st ==# 'closed'
  else
    call ch_sendexpr(l:ch, a:txt)
  endif
endfunction

On the other hand, Vim started as a modeless window on the receiving side, so it also uses ch_sendexpr to execute asynchronously and waits for a reply from the server.

Receive processing
function! modeless_window#run() abort
  "Called from the main editor side and started
  call vimproc#system_bg(
    \ 'gvim' .
    \ ' +"set columns=80"' .
    \ ' +"set lines=20"' .
    \ ' +"set titlestring=[MODELESS_WINDOW]"' .
    \ ' +"set bufhidden=hide buftype=nofile noswapfile nobuflisted"' .
    \ ' +"call modeless_window#_recv()"')
endfunction

let s:ch = v:null

function! modeless_window#_recv()
  if s:ch == v:null
    let s:ch = ch_open('127.0.0.1:'.s:recv_port)
  endif
  let l:st = ch_status(s:ch)
  if l:st ==# 'fail' || l:st ==# 'closed'
    :q!
    return
  endif
  
  call ch_sendexpr(s:ch, 'message', {'callback': 'modeless_window#_recv_handler'})
endfunction


function! modeless_window#_recv_handler(handle, msg)
  if a:handle ==# 'fail' || a:handle ==# 'closed'
    :q!
    return
  endif

  "Helper function that replaces buffer contents with string arguments
  call modeless_window#utils#settext(a:msg)
  
  "next
  call modeless_window#_recv()
endfunction

Server send / receive processing

Even though it is a server, it is simple because it just opens two sockets and passes the transmitted contents as it is. Vim comes with a socket communication sample (tools/demoserver.py) made by Python. I am remodeling.

Send/receive process (all posted) (There is a bug for receiving)
#!/usr/bin/python
# vim-modeless-window/tools/modeless_server.py
#
# from) $VIM/tools/demoserver.Borrow py
#
# This requires Python 2.6 or later.

from __future__ import print_function
import json
import socket
import sys
import threading

import time

try:
    # Python 3
    import socketserver
except ImportError:
    # Python 2
    import SocketServer as socketserver


shared_message = None

lock = None

#For sending messages
class ThreadedTCPSendHandler(socketserver.BaseRequestHandler):

    def handle(self):
        print("=== [S] socket opened ===")
        global shared_message
        global lock
        while True:
            try:
                data = self.request.recv(40960).decode('utf-8') # NOTE:Appropriate size
            except socket.error:
                print("=== [S] socket error ===")
                break
            except IOError:
                print("=== [S] socket closed ===")
                break
            if data == '':
                print("=== [S] socket closed ===")
                break
            # print("received: {0}".format(data))
            try:
                decoded = json.loads(data)
            except ValueError:
                print("json decoding failed")
                decoded = [-1, '']

            #Acquisition of books
            response = ''
            if decoded[0] >= 0:
                with lock:
                    shared_message = decoded[1]

                response = 'sended!'
            else:
                response = 'what?'

            encoded = json.dumps([decoded[0], response])
            print("sending {0}".format(encoded))
            self.request.sendall(encoded.encode('utf-8'))


#For receiving messages
class ThreadedTCPReceiveHandler(socketserver.BaseRequestHandler):

    def handle(self):
        print("=== [R] socket opened ===")
        global shared_message
        global lock
        while True:
            try:
                data = self.request.recv(4096).decode('utf-8')
            except socket.error:
                print("=== [R] socket error ===")
                break
            except IOError:
                print("=== [R] socket closed ===")
                break
            if data == '':
                print("=== [R] socket closed ===")
                break
            print("received: {0}".format(data))
            try:
                decoded = json.loads(data)
            except ValueError:
                print("json decoding failed")
                decoded = [-1, '']

            if decoded[0] >= 0:
                if decoded[1] == 'message':
                    #Wait for the message
                    # FIXME:It won't end until it's received
                    while True:
                        message = None
                        with lock:
                            if shared_message != None:
                                message = shared_message
                                shared_message = None

                        if message != None:
                            encoded = json.dumps([decoded[0], message])
                            print('sending message text')
                            self.request.sendall(encoded.encode('utf-8'))
                            break

                        time.sleep(0.1)
                else:
                    #Request you don't know
                    print('unknown request {0}'.format(decoded[1]))
                    pass

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def send_server():
    HOST, PORT = "localhost", 13578
    return server(HOST, PORT, ThreadedTCPSendHandler)

def recv_server():
    HOST, PORT = "localhost", 13579
    return server(HOST, PORT, ThreadedTCPReceiveHandler)

def server(host, port, tcpHandler):
    server = ThreadedTCPServer((host, port), tcpHandler)
    ip, port = server.server_address

    # Start a thread with the server -- that thread will then start one
    # more thread for each request
    server_thread = threading.Thread(target=server.serve_forever)

    # Exit the server thread when the main thread terminates
    server_thread.daemon = True
    server_thread.start()
    print("Server loop running in thread: ", server_thread.name)

    print("Listening on port {0}".format(port))

    return server


if __name__ == "__main__":
    lock = threading.RLock()

    send_server = send_server()
    recv_server = recv_server()

    while True:
        typed = sys.stdin.readline()
        if "quit" in typed:
            print("Goodbye!")
            break

    send_server.shutdown()
    send_server.server_close()
    recv_server.shutdown()
    recv_server.server_close()

Recommended Posts

Set the output destination of the execution result to Vim started as a modeless window
Output the output result of sklearn.metrics.classification_report as a CSV file
How to output the output result of the Linux man command to a file
Setting to output the log of cron execution
How to pass the execution result of a shell command in a list in Python
A memorandum about the warning of the pylint output result
I want to grep the execution result of strace
Change the standard output destination to a file in Python
Output the result of gradient descent method as matplotlib animation
How to pass the execution result of a shell command in a list in Python (non-blocking version)
[sh] How to store the command execution result in a variable
[Python3] Define a decorator to measure the execution time of a function
Output the result of morphological analysis with Mecab to a WEB browser compatible with Sakura server / UTF-8
[python] A note that started to understand the behavior of matplotlib.pyplot
I tried to verify the result of A / B test by chi-square test
I made a tool to estimate the execution time of cron (+ PyPI debut)
Try to solve the traveling salesman problem with a genetic algorithm (execution result)
Set the number of elements in a NumPy one-dimensional array to a power of 2 (0 padded)
A programming beginner tried to find out the execution time of sorting etc.
To output a value even in the middle of a cell with Jupyter Notebook
[NNabla] How to get the output (variable) of the middle layer of a pre-built network
How to count the number of elements in Django and output to a template
I want to set a life cycle in the task definition of ECS
I made a script to record the active window using win32gui of Python
Python C / C ++ Extensions: Pass some of the data as np.array to Python (set stride)
How to calculate the volatility of a brand
Set the range of active strips to the preview range
Output in the form of a python array
From the introduction of pyethapp to the execution of contract
Find a guideline for the number of processes / threads to set in the application server
Don't take an instance of a Python exception class directly as an argument to the exception class!
Get the variable name of the variable as a character string.
Create a function to visualize / evaluate the clustering result
(Python) Treat integer values as a set of flags
[GoLang] Set a space at the beginning of the comment
A memo to visually understand the axis of pandas.Panel
[python] option to turn off the output of click.progressbar
Steps to calculate the likelihood of a normal distribution
[Blender] How to dynamically set the selection of EnumProperty
Set the specified column of QTableWidget to ReadOnly StyledItemDelegate
Python Note: The mystery of assigning a variable to a variable
How to display the regional mesh of the official statistics window (eStat) in a web browser
I tried to compare the accuracy of machine learning models using kaggle as a theme.
I want to output a beautifully customized heat map of the correlation matrix. matplotlib edition