Advertisement
tarruda

Untitled

Jun 18th, 2014
386
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.28 KB | None | 0 0
  1. # This file is a basic example of how one can build a custom UI for neovim. It
  2. # uses python Tkinter module which is distributed with python
  3.  
  4. import sys, neovim, pprint
  5. from Tkinter import *
  6. import tkFont
  7. from threading import Thread
  8. from Queue import Queue
  9. # import cProfile, pstats, StringIO
  10.  
  11.  
  12.  
  13. class NvimTkUI(object):
  14.     def __init__(self, address):
  15.         self.address = address
  16.         # keep a variable to reference to the top-level frame to destroy when
  17.         # layout changes
  18.         self.toplevel = None
  19.         # windows_id -> text widget map
  20.         self.windows = None
  21.         # pending nvim events
  22.         self.nvim_events = Queue(maxsize=1)
  23.  
  24.     def on_tk_select(self, arg):
  25.         arg.widget.tag_remove('sel', '1.0', 'end')
  26.         # TODO: this should change nvim visual range
  27.  
  28.     def on_nvim_event(self, arg):
  29.         def handle(event):
  30.             event_type = event[0][7:]
  31.             event_arg = event[1]
  32.             handler = getattr(self, 'on_nvim_' + event_type)
  33.             handler(event_arg)
  34.         ev = self.nvim_events.get(block=False)
  35.         if len(ev) and isinstance(ev[0], basestring):
  36.             handle(ev)
  37.         else:
  38.             for e in ev:
  39.                 handle(e)
  40.  
  41.     def on_nvim_exit(self, arg):
  42.         self.root.destroy()
  43.  
  44.     def on_nvim_layout(self, arg):
  45.         windows = {}
  46.         # Recursion helper to build a tk frame graph from data received with
  47.         # the layout event
  48.         def build_widget_graph(parent, node, arrange='row'):
  49.             widget = None
  50.             if node['type'] in ['row', 'column']:
  51.                 widget = Frame(parent)
  52.             else:
  53.                 widget = Text(parent, width=node['width'],
  54.                               height=node['height'], state='normal',
  55.                               font=self.font, exportselection=False,
  56.                               wrap='none', undo=False)
  57.                 setattr(widget, 'added_tags', {})
  58.                 # fill the widget one linefeed per row to simplify updating
  59.                 widget.insert('1.0', '\n' * node['height'])
  60.                 # We don't want the user to edit
  61.                 widget['state'] = 'disabled'
  62.                 windows[node['window_id']] = widget
  63.             if 'children' in node:
  64.                 for child in node['children']:
  65.                     build_widget_graph(widget, child, arrange=node['type'])
  66.             if arrange == 'row':
  67.                 widget.pack(side=LEFT, anchor=NW)
  68.             else:
  69.                 widget.pack(side=TOP, anchor=NW)
  70.        
  71.         # build the new toplevel frame
  72.         toplevel = Frame(self.root, takefocus=True)
  73.         build_widget_graph(toplevel, arg)
  74.         # destroy the existing one if exists
  75.         if self.toplevel:
  76.             self.toplevel.destroy()
  77.         self.windows = windows
  78.         # save a reference for future removal when the layout changes again
  79.         self.toplevel = toplevel
  80.         # display the frame
  81.         self.toplevel.pack()
  82.  
  83.     def on_nvim_foreground_color(self, arg):
  84.         for widget in self.windows.values():
  85.             widget['fg'] = arg['color']
  86.  
  87.     def on_nvim_background_color(self, arg):
  88.         for widget in self.windows.values():
  89.             widget['bg'] = arg['color']
  90.  
  91.     def on_nvim_insert_line(self, arg):
  92.         widget = self.windows[arg['window_id']]
  93.         line = arg['row'] + 1
  94.         count = arg['count']
  95.         startpos = '%d.0' % line
  96.         widget['state'] = 'normal'
  97.         # insert
  98.         widget.insert(startpos, arg['count'] * '\n')
  99.         # delete at the end
  100.         widget.delete('end - %d lines' % count, 'end')
  101.         widget['state'] = 'disabled'
  102.  
  103.     def on_nvim_delete_line(self, arg):
  104.         widget = self.windows[arg['window_id']]
  105.         line = arg['row'] + 1
  106.         count = arg['count']
  107.         startpos = '%d.0' % line
  108.         endpos = '%d.0' % (line + count)
  109.         widget['state'] = 'normal'
  110.         # delete
  111.         widget.delete(startpos, endpos)
  112.         # insert at the end(they will be updated soon
  113.         widget.insert('end', '\n' * count)
  114.         widget['state'] = 'disabled'
  115.  
  116.     def on_nvim_win_end(self, arg):
  117.         widget = self.windows[arg['window_id']]
  118.         line = arg['row'] + 1
  119.         endline = arg['endrow'] + 1
  120.         marker = arg['marker']
  121.         fill = arg['fill']
  122.         startpos = '%d.0' % line
  123.         endpos = '%d.0' % endline
  124.         widget['state'] = 'normal'
  125.         # delete
  126.         widget.delete(startpos, endpos)
  127.         line_fill = '%s%s\n' % (marker, fill * (widget['width'] - 1))
  128.         # insert markers/fillers
  129.         widget.insert('end', line_fill * (endline - line))
  130.         widget['state'] = 'disabled'
  131.  
  132.     def on_nvim_update_line(self, arg):
  133.         widget = self.windows[arg['window_id']]
  134.         contents = ''.join(map(lambda c: c['content'], arg['line']))
  135.         line = arg['row'] + 1
  136.         startpos = '%d.0' % line
  137.         endpos = '%d.end' % line
  138.         widget['state'] = 'normal'
  139.         widget.delete(startpos, endpos)
  140.         widget.insert(startpos, contents)
  141.         widget['state'] = 'disabled'
  142.         if 'attributes' in arg:
  143.             for name, positions in arg['attributes'].items():
  144.                 for position in positions:
  145.                     self.apply_attribute(widget, name, line, position)
  146.  
  147.     def apply_attribute(self, widget, name, line, position):
  148.         # Ensure the attribute name is associated with a tag configured with
  149.         # the corresponding attribute format
  150.         if name not in widget.added_tags:
  151.             prefix = name[0:2]
  152.             if prefix in ['fg', 'bg']:
  153.                 color = name[3:]
  154.                 if prefix == 'fg':
  155.                     widget.tag_configure(name, foreground=color)
  156.                 else:
  157.                     widget.tag_configure(name, background=color)
  158.             widget.added_tags[name] = True
  159.         # Now clear occurences of the tags in the current line
  160.         ranges = widget.tag_ranges(name)
  161.         for i in range(0, len(ranges), 2):
  162.             start = ranges[i]
  163.             stop = ranges[i+1]
  164.             widget.tag_remove(start, stop)
  165.         if isinstance(position, list):
  166.             start = '%d.%d' % (line, position[0])
  167.             end = '%d.%d' % (line, position[1])
  168.             widget.tag_add(name, start, end)
  169.         else:
  170.             pos = '%d.%d' % (line, position)
  171.             widget.tag_add(name, pos)
  172.  
  173.     def run(self):
  174.         def get_nvim_events(queue, vim, root):
  175.             # Send the screen to ourselves
  176.             vim.request_screen()
  177.             buffered = None
  178.             event = None
  179.             while True:
  180.                 try:
  181.                     event = vim.next_event()
  182.                 except IOError:
  183.                     root.event_generate('<<nvim_exit>>', when='tail')
  184.                     break
  185.                 event_type = event[0]
  186.                 if event_type == 'redraw:start':
  187.                     buffered = []
  188.                 elif event_type == 'redraw:end':
  189.                     queue.put(buffered)
  190.                     buffered = None
  191.                     root.event_generate('<<nvim>>', when='tail')
  192.                 else:
  193.                     if buffered is None:
  194.                         # handle non-buffered events
  195.                         queue.put(event)
  196.                         root.event_generate('<<nvim>>', when='tail')
  197.                     else:
  198.                         buffered.append(event)
  199.         # Setup the root window
  200.         self.root = Tk()
  201.         self.root.bind('<<nvim>>', self.on_nvim_event.__get__(self, NvimTkUI))
  202.         self.root.bind('<<nvim_exit>>', self.on_nvim_exit.__get__(self, NvimTkUI))
  203.         self.root.bind('<<Selection>>', self.on_tk_select)
  204.         # setup font
  205.         self.font = tkFont.Font(family='Monospace', size=13)
  206.         # setup nvim connection
  207.         self.vim = neovim.connect(self.address)
  208.         # Subscribe to all redraw events
  209.         # self.vim.subscribe('redraw:tabs')
  210.         self.vim.subscribe('redraw:start')
  211.         self.vim.subscribe('redraw:end')
  212.         self.vim.subscribe('redraw:insert_line')
  213.         self.vim.subscribe('redraw:delete_line')
  214.         self.vim.subscribe('redraw:win_end')
  215.         self.vim.subscribe('redraw:update_line')
  216.         # self.vim.subscribe('redraw:status_line')
  217.         # self.vim.subscribe('redraw:ruler')
  218.         self.vim.subscribe('redraw:layout')
  219.         self.vim.subscribe('redraw:background_color')
  220.         self.vim.subscribe('redraw:foreground_color')
  221.         # self.vim.subscribe('redraw:cursor')
  222.         # Start a thread for receiving nvim events
  223.         t = Thread(target=get_nvim_events, args=(self.nvim_events,
  224.                                                  self.vim,
  225.                                                  self.root,))
  226.         t.daemon = True
  227.         t.start()
  228.         # Start handling tk events
  229.         self.root.mainloop()
  230.  
  231. if len(sys.argv) < 2:
  232.     print >> sys.stderr, 'Need neovim listen address as argument'
  233.  
  234. address = sys.argv[1]
  235. ui = NvimTkUI(address)
  236. # pr = cProfile.Profile()
  237. # pr.enable()
  238. try:
  239.     ui.run()
  240. except IOError:
  241.     print >> sys.stderr, 'Cannot connect to %s' % address
  242.     sys.exit(1)
  243. # pr.disable()
  244. # s = StringIO.StringIO()
  245. # ps = pstats.Stats(pr, stream=s)
  246. # ps.strip_dirs().sort_stats('cumtime').print_stats(10)
  247. # print s.getvalue()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement