Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # This file is a basic example of how one can build a custom UI for neovim. It
- # uses python Tkinter module which is distributed with python
- import sys, neovim, pprint
- from Tkinter import *
- import tkFont
- from threading import Thread
- from Queue import Queue
- # import cProfile, pstats, StringIO
- class NvimTkUI(object):
- def __init__(self, address):
- self.address = address
- # keep a variable to reference to the top-level frame to destroy when
- # layout changes
- self.toplevel = None
- # windows_id -> text widget map
- self.windows = None
- # pending nvim events
- self.nvim_events = Queue(maxsize=1)
- # event name -> handler dict
- self.handlers = {}
- self.i = 0
- def on_tk_select(self, arg):
- arg.widget.tag_remove('sel', '1.0', 'end')
- # TODO: this should change nvim visual range
- def on_tk_configure(self, arg):
- if self.toplevel and arg.widget == self.root:
- self.root['width'] = self.toplevel['width']
- self.root['height'] = self.toplevel['height']
- def on_nvim_event(self, arg):
- print self.i
- self.i += 1
- event = self.nvim_events.get(block=False)
- event_arg = event[1]
- handler = self.handlers[event[0]]
- handler(event_arg)
- def on_nvim_exit(self, arg):
- self.root.destroy()
- def on_nvim_layout(self, arg):
- windows = {}
- # Recursion helper to build a tk frame graph from data received with
- # the layout event
- def build_widget_graph(parent, node, arrange='row'):
- widget = None
- if node['type'] in ['row', 'column']:
- widget = Frame(parent)
- else:
- widget = Text(parent, width=node['width'],
- height=node['height'], state='normal',
- font=self.fnormal, exportselection=False,
- wrap='none', undo=False)
- setattr(widget, 'added_tags', {})
- # fill the widget one linefeed per row to simplify updating
- widget.insert('1.0', '\n' * node['height'])
- # We don't want the user to edit
- widget['state'] = 'disabled'
- windows[node['window_id']] = widget
- if 'children' in node:
- for child in node['children']:
- build_widget_graph(widget, child, arrange=node['type'])
- if arrange == 'row':
- widget.pack(side=LEFT, anchor=NW)
- else:
- widget.pack(side=TOP, anchor=NW)
- # build the new toplevel frame
- toplevel = Frame(self.root, takefocus=True)
- build_widget_graph(toplevel, arg)
- # destroy the existing one if exists
- if self.toplevel:
- self.toplevel.destroy()
- self.windows = windows
- # save a reference for future removal when the layout changes again
- self.toplevel = toplevel
- # display the frame
- self.toplevel.pack()
- def on_nvim_foreground_color(self, arg):
- for widget in self.windows.values():
- widget['fg'] = arg['color']
- def on_nvim_background_color(self, arg):
- for widget in self.windows.values():
- widget['bg'] = arg['color']
- def on_nvim_insert_line(self, arg):
- widget = self.windows[arg['window_id']]
- line = arg['row'] + 1
- count = arg['count']
- startpos = '%d.0' % line
- widget['state'] = 'normal'
- # insert
- widget.insert(startpos, arg['count'] * '\n')
- # delete at the end
- widget.delete('end - %d lines' % count, 'end')
- widget['state'] = 'disabled'
- def on_nvim_delete_line(self, arg):
- widget = self.windows[arg['window_id']]
- line = arg['row'] + 1
- count = arg['count']
- startpos = '%d.0' % line
- endpos = '%d.0' % (line + count)
- widget['state'] = 'normal'
- # delete
- widget.delete(startpos, endpos)
- # insert at the end(they will be updated soon
- widget.insert('end', '\n' * count)
- widget['state'] = 'disabled'
- def on_nvim_win_end(self, arg):
- widget = self.windows[arg['window_id']]
- line = arg['row'] + 1
- endline = arg['endrow'] + 1
- marker = arg['marker']
- fill = arg['fill']
- startpos = '%d.0' % line
- endpos = '%d.0' % endline
- widget['state'] = 'normal'
- # delete
- widget.delete(startpos, endpos)
- line_fill = '%s%s\n' % (marker, fill * (widget['width'] - 1))
- # insert markers/fillers
- widget.insert('end', line_fill * (endline - line))
- widget['state'] = 'disabled'
- def on_nvim_update_line(self, arg):
- widget = self.windows[arg['window_id']]
- contents = ''.join(map(lambda c: c['content'], arg['line']))
- line = arg['row'] + 1
- startpos = '%d.0' % line
- endpos = '%d.end' % line
- widget['state'] = 'normal'
- widget.delete(startpos, endpos)
- widget.insert(startpos, contents)
- widget['state'] = 'disabled'
- # if 'attributes' in arg:
- # for name, positions in arg['attributes'].items():
- # for position in positions:
- # self.apply_attribute(widget, name, line, position)
- def apply_attribute(self, widget, name, line, position):
- # Ensure the attribute name is associated with a tag configured with
- # the corresponding attribute format
- if name not in widget.added_tags:
- prefix = name[0:2]
- if prefix in ['fg', 'bg']:
- color = name[3:]
- if prefix == 'fg':
- widget.tag_configure(name, foreground=color)
- else:
- widget.tag_configure(name, background=color)
- elif name == 'italic':
- widget.tag_configure(name, font=self.fitalic)
- elif name == 'bold':
- widget.tag_configure(name, font=self.fbold)
- else:
- print 'Unknown attribute %s' % name
- widget.added_tags[name] = True
- if isinstance(position, list):
- start = '%d.%d' % (line, position[0])
- end = '%d.%d' % (line, position[1])
- widget.tag_add(name, start, end)
- else:
- pos = '%d.%d' % (line, position)
- widget.tag_add(name, pos)
- def run(self):
- def get_nvim_events(queue, vim, root):
- # Send the screen to ourselves
- vim.request_screen()
- while True:
- try:
- queue.put(vim.next_event())
- except IOError:
- root.event_generate('<<nvim_exit>>', when='head')
- break
- root.event_generate('<<nvim>>', when='head')
- # Setup the root window
- self.root = Tk()
- self.root.bind('<<nvim>>', self.on_nvim_event.__get__(self,
- NvimTkUI))
- self.root.bind('<<nvim_exit>>', self.on_nvim_exit.__get__(self,
- NvimTkUI))
- self.root.bind('<<Selection>>', self.on_tk_select.__get__(self,
- NvimTkUI))
- self.root.bind('<Configure>', self.on_tk_configure.__get__(self,
- NvimTkUI))
- # setup fonts
- size = 13
- self.fnormal = tkFont.Font(family='Monospace', size=size)
- self.fitalic = tkFont.Font(family='Monospace', size=size,
- slant='italic')
- self.fbold = tkFont.Font(family='Monospace', size=size,
- weight='bold')
- # setup nvim connection
- self.vim = neovim.connect(self.address)
- # Subscribe to all redraw events
- for event in ['redraw:insert_line',
- 'redraw:delete_line',
- 'redraw:update_line',
- 'redraw:win_end',
- 'redraw:layout',
- 'redraw:background_color',
- 'redraw:foreground_color',
- # 'redraw:tabs',
- # 'redraw:status_line',
- # 'redraw:ruler',
- # 'redraw:cursor',
- ]:
- self.vim.subscribe(event)
- event_name = event[7:]
- self.handlers[event] = getattr(self, 'on_nvim_' + event_name)
- # Start a thread for receiving nvim events
- t = Thread(target=get_nvim_events, args=(self.nvim_events,
- self.vim,
- self.root,))
- t.daemon = True
- t.start()
- # Start handling tk events
- self.root.mainloop()
- if len(sys.argv) < 2:
- print >> sys.stderr, 'Need neovim listen address as argument'
- address = sys.argv[1]
- ui = NvimTkUI(address)
- # pr = cProfile.Profile()
- # pr.enable()
- try:
- ui.run()
- except IOError:
- print >> sys.stderr, 'Cannot connect to %s' % address
- sys.exit(1)
- # pr.disable()
- # s = StringIO.StringIO()
- # ps = pstats.Stats(pr, stream=s)
- # ps.strip_dirs().sort_stats('cumtime').print_stats(10)
- # print s.getvalue()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement