Advertisement
tarruda

Untitled

Jun 10th, 2014
355
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.45 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.         # event name -> handler dict
  24.         self.handlers = {}
  25.         self.i = 0
  26.  
  27.     def on_tk_select(self, arg):
  28.         arg.widget.tag_remove('sel', '1.0', 'end')
  29.         # TODO: this should change nvim visual range
  30.  
  31.     def on_tk_configure(self, arg):
  32.         if self.toplevel and arg.widget == self.root:
  33.             self.root['width'] = self.toplevel['width']
  34.             self.root['height'] = self.toplevel['height']
  35.  
  36.     def on_nvim_event(self, arg):
  37.         print self.i
  38.         self.i += 1
  39.         event = self.nvim_events.get(block=False)
  40.         event_arg = event[1]
  41.         handler = self.handlers[event[0]]
  42.         handler(event_arg)
  43.  
  44.     def on_nvim_exit(self, arg):
  45.         self.root.destroy()
  46.  
  47.     def on_nvim_layout(self, arg):
  48.         windows = {}
  49.         # Recursion helper to build a tk frame graph from data received with
  50.         # the layout event
  51.         def build_widget_graph(parent, node, arrange='row'):
  52.             widget = None
  53.             if node['type'] in ['row', 'column']:
  54.                 widget = Frame(parent)
  55.             else:
  56.                 widget = Text(parent, width=node['width'],
  57.                               height=node['height'], state='normal',
  58.                               font=self.fnormal, exportselection=False,
  59.                               wrap='none', undo=False)
  60.                 setattr(widget, 'added_tags', {})
  61.                 # fill the widget one linefeed per row to simplify updating
  62.                 widget.insert('1.0', '\n' * node['height'])
  63.                 # We don't want the user to edit
  64.                 widget['state'] = 'disabled'
  65.                 windows[node['window_id']] = widget
  66.             if 'children' in node:
  67.                 for child in node['children']:
  68.                     build_widget_graph(widget, child, arrange=node['type'])
  69.             if arrange == 'row':
  70.                 widget.pack(side=LEFT, anchor=NW)
  71.             else:
  72.                 widget.pack(side=TOP, anchor=NW)
  73.        
  74.         # build the new toplevel frame
  75.         toplevel = Frame(self.root, takefocus=True)
  76.         build_widget_graph(toplevel, arg)
  77.         # destroy the existing one if exists
  78.         if self.toplevel:
  79.             self.toplevel.destroy()
  80.         self.windows = windows
  81.         # save a reference for future removal when the layout changes again
  82.         self.toplevel = toplevel
  83.         # display the frame
  84.         self.toplevel.pack()
  85.  
  86.     def on_nvim_foreground_color(self, arg):
  87.         for widget in self.windows.values():
  88.             widget['fg'] = arg['color']
  89.  
  90.     def on_nvim_background_color(self, arg):
  91.         for widget in self.windows.values():
  92.             widget['bg'] = arg['color']
  93.  
  94.     def on_nvim_insert_line(self, arg):
  95.         widget = self.windows[arg['window_id']]
  96.         line = arg['row'] + 1
  97.         count = arg['count']
  98.         startpos = '%d.0' % line
  99.         widget['state'] = 'normal'
  100.         # insert
  101.         widget.insert(startpos, arg['count'] * '\n')
  102.         # delete at the end
  103.         widget.delete('end - %d lines' % count, 'end')
  104.         widget['state'] = 'disabled'
  105.  
  106.     def on_nvim_delete_line(self, arg):
  107.         widget = self.windows[arg['window_id']]
  108.         line = arg['row'] + 1
  109.         count = arg['count']
  110.         startpos = '%d.0' % line
  111.         endpos = '%d.0' % (line + count)
  112.         widget['state'] = 'normal'
  113.         # delete
  114.         widget.delete(startpos, endpos)
  115.         # insert at the end(they will be updated soon
  116.         widget.insert('end', '\n' * count)
  117.         widget['state'] = 'disabled'
  118.  
  119.     def on_nvim_win_end(self, arg):
  120.         widget = self.windows[arg['window_id']]
  121.         line = arg['row'] + 1
  122.         endline = arg['endrow'] + 1
  123.         marker = arg['marker']
  124.         fill = arg['fill']
  125.         startpos = '%d.0' % line
  126.         endpos = '%d.0' % endline
  127.         widget['state'] = 'normal'
  128.         # delete
  129.         widget.delete(startpos, endpos)
  130.         line_fill = '%s%s\n' % (marker, fill * (widget['width'] - 1))
  131.         # insert markers/fillers
  132.         widget.insert('end', line_fill * (endline - line))
  133.         widget['state'] = 'disabled'
  134.  
  135.     def on_nvim_update_line(self, arg):
  136.         widget = self.windows[arg['window_id']]
  137.         contents = ''.join(map(lambda c: c['content'], arg['line']))
  138.         line = arg['row'] + 1
  139.         startpos = '%d.0' % line
  140.         endpos = '%d.end' % line
  141.         widget['state'] = 'normal'
  142.         widget.delete(startpos, endpos)
  143.         widget.insert(startpos, contents)
  144.         widget['state'] = 'disabled'
  145.         # if 'attributes' in arg:
  146.         #     for name, positions in arg['attributes'].items():
  147.         #         for position in positions:
  148.         #             self.apply_attribute(widget, name, line, position)
  149.  
  150.     def apply_attribute(self, widget, name, line, position):
  151.         # Ensure the attribute name is associated with a tag configured with
  152.         # the corresponding attribute format
  153.         if name not in widget.added_tags:
  154.             prefix = name[0:2]
  155.             if prefix in ['fg', 'bg']:
  156.                 color = name[3:]
  157.                 if prefix == 'fg':
  158.                     widget.tag_configure(name, foreground=color)
  159.                 else:
  160.                     widget.tag_configure(name, background=color)
  161.             elif name == 'italic':
  162.                 widget.tag_configure(name, font=self.fitalic)
  163.             elif name == 'bold':
  164.                 widget.tag_configure(name, font=self.fbold)
  165.             else:
  166.                 print 'Unknown attribute %s' % name
  167.             widget.added_tags[name] = True
  168.         if isinstance(position, list):
  169.             start = '%d.%d' % (line, position[0])
  170.             end = '%d.%d' % (line, position[1])
  171.             widget.tag_add(name, start, end)
  172.         else:
  173.             pos = '%d.%d' % (line, position)
  174.             widget.tag_add(name, pos)
  175.  
  176.     def run(self):
  177.         def get_nvim_events(queue, vim, root):
  178.             # Send the screen to ourselves
  179.             vim.request_screen()
  180.             while True:
  181.                 try:
  182.                     queue.put(vim.next_event())
  183.                 except IOError:
  184.                     root.event_generate('<<nvim_exit>>', when='head')
  185.                     break
  186.                 root.event_generate('<<nvim>>', when='head')
  187.         # Setup the root window
  188.         self.root = Tk()
  189.         self.root.bind('<<nvim>>', self.on_nvim_event.__get__(self,
  190.                                                               NvimTkUI))
  191.         self.root.bind('<<nvim_exit>>', self.on_nvim_exit.__get__(self,
  192.                                                                   NvimTkUI))
  193.         self.root.bind('<<Selection>>', self.on_tk_select.__get__(self,
  194.                                                                   NvimTkUI))
  195.         self.root.bind('<Configure>', self.on_tk_configure.__get__(self,
  196.                                                                    NvimTkUI))
  197.         # setup fonts
  198.         size = 13
  199.         self.fnormal = tkFont.Font(family='Monospace', size=size)
  200.         self.fitalic = tkFont.Font(family='Monospace', size=size,
  201.                                    slant='italic')
  202.         self.fbold = tkFont.Font(family='Monospace', size=size,
  203.                                    weight='bold')
  204.         # setup nvim connection
  205.         self.vim = neovim.connect(self.address)
  206.         # Subscribe to all redraw events
  207.         for event in ['redraw:insert_line',
  208.                       'redraw:delete_line',
  209.                       'redraw:update_line',
  210.                       'redraw:win_end',
  211.                       'redraw:layout',
  212.                       'redraw:background_color',
  213.                       'redraw:foreground_color',
  214.                       # 'redraw:tabs',
  215.                       # 'redraw:status_line',
  216.                       # 'redraw:ruler',
  217.                       # 'redraw:cursor',
  218.                      ]:
  219.             self.vim.subscribe(event)
  220.             event_name = event[7:]
  221.             self.handlers[event] = getattr(self, 'on_nvim_' + event_name)
  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