[Python-modules-commits] [python-neovim-gui] 01/02: Imported Upstream version 0.1.1
Víctor Cuadrado Juan
viccuad-guest at moszumanska.debian.org
Thu Mar 3 22:58:26 UTC 2016
This is an automated email from the git hooks/post-receive script.
viccuad-guest pushed a commit to branch master
in repository python-neovim-gui.
commit 01d5a158d16017c697b474c59fceb0797afadc0d
Author: Víctor Cuadrado Juan <me at viccuad.me>
Date: Thu Mar 3 15:09:09 2016 +0100
Imported Upstream version 0.1.1
---
PKG-INFO | 11 +
neovim_gui.egg-info/PKG-INFO | 11 +
neovim_gui.egg-info/SOURCES.txt | 14 +
neovim_gui.egg-info/dependency_links.txt | 1 +
neovim_gui.egg-info/entry_points.txt | 3 +
neovim_gui.egg-info/not-zip-safe | 1 +
neovim_gui.egg-info/requires.txt | 3 +
neovim_gui.egg-info/top_level.txt | 1 +
neovim_gui/__init__.py | 1 +
neovim_gui/cli.py | 73 ++++
neovim_gui/gtk_ui.py | 587 +++++++++++++++++++++++++++++++
neovim_gui/screen.py | 134 +++++++
neovim_gui/ui_bridge.py | 105 ++++++
setup.cfg | 8 +
setup.py | 36 ++
15 files changed, 989 insertions(+)
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..78d3fbd
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,11 @@
+Metadata-Version: 1.1
+Name: neovim_gui
+Version: 0.1.1
+Summary: Gtk gui for neovim
+Home-page: http://github.com/neovim/python-gui
+Author: Thiago de Arruda
+Author-email: tpadilha84 at gmail.com
+License: Apache
+Download-URL: https://github.com/neovim/python-gui/archive/0.1.1.tar.gz
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/neovim_gui.egg-info/PKG-INFO b/neovim_gui.egg-info/PKG-INFO
new file mode 100644
index 0000000..1438ad6
--- /dev/null
+++ b/neovim_gui.egg-info/PKG-INFO
@@ -0,0 +1,11 @@
+Metadata-Version: 1.1
+Name: neovim-gui
+Version: 0.1.1
+Summary: Gtk gui for neovim
+Home-page: http://github.com/neovim/python-gui
+Author: Thiago de Arruda
+Author-email: tpadilha84 at gmail.com
+License: Apache
+Download-URL: https://github.com/neovim/python-gui/archive/0.1.1.tar.gz
+Description: UNKNOWN
+Platform: UNKNOWN
diff --git a/neovim_gui.egg-info/SOURCES.txt b/neovim_gui.egg-info/SOURCES.txt
new file mode 100644
index 0000000..a949789
--- /dev/null
+++ b/neovim_gui.egg-info/SOURCES.txt
@@ -0,0 +1,14 @@
+setup.cfg
+setup.py
+neovim_gui/__init__.py
+neovim_gui/cli.py
+neovim_gui/gtk_ui.py
+neovim_gui/screen.py
+neovim_gui/ui_bridge.py
+neovim_gui.egg-info/PKG-INFO
+neovim_gui.egg-info/SOURCES.txt
+neovim_gui.egg-info/dependency_links.txt
+neovim_gui.egg-info/entry_points.txt
+neovim_gui.egg-info/not-zip-safe
+neovim_gui.egg-info/requires.txt
+neovim_gui.egg-info/top_level.txt
\ No newline at end of file
diff --git a/neovim_gui.egg-info/dependency_links.txt b/neovim_gui.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/neovim_gui.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/neovim_gui.egg-info/entry_points.txt b/neovim_gui.egg-info/entry_points.txt
new file mode 100644
index 0000000..0c762fc
--- /dev/null
+++ b/neovim_gui.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+pynvim = neovim_gui.cli:main
+
diff --git a/neovim_gui.egg-info/not-zip-safe b/neovim_gui.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/neovim_gui.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/neovim_gui.egg-info/requires.txt b/neovim_gui.egg-info/requires.txt
new file mode 100644
index 0000000..bef00fd
--- /dev/null
+++ b/neovim_gui.egg-info/requires.txt
@@ -0,0 +1,3 @@
+neovim>=0.1.3
+click>=3.0
+pygobject
diff --git a/neovim_gui.egg-info/top_level.txt b/neovim_gui.egg-info/top_level.txt
new file mode 100644
index 0000000..98c5aa8
--- /dev/null
+++ b/neovim_gui.egg-info/top_level.txt
@@ -0,0 +1 @@
+neovim_gui
diff --git a/neovim_gui/__init__.py b/neovim_gui/__init__.py
new file mode 100644
index 0000000..74c52e0
--- /dev/null
+++ b/neovim_gui/__init__.py
@@ -0,0 +1 @@
+"""Neovim remote UI prototypes."""
diff --git a/neovim_gui/cli.py b/neovim_gui/cli.py
new file mode 100644
index 0000000..d82e74c
--- /dev/null
+++ b/neovim_gui/cli.py
@@ -0,0 +1,73 @@
+"""CLI for accessing the gtk/tickit UIs implemented by this package."""
+import shlex
+
+import click
+
+from .ui_bridge import UIBridge
+from neovim import attach
+from neovim.api import DecodeHook
+from neovim.compat import IS_PYTHON3
+
+
+ at click.command(context_settings=dict(allow_extra_args=True))
+ at click.option('--prog')
+ at click.option('--notify', '-n', default=False, is_flag=True)
+ at click.option('--listen', '-l')
+ at click.option('--connect', '-c')
+ at click.option('--profile',
+ default='disable',
+ type=click.Choice(['ncalls', 'tottime', 'percall', 'cumtime',
+ 'name', 'disable']))
+ at click.pass_context
+def main(ctx, prog, notify, listen, connect, profile):
+ """Entry point."""
+ address = connect or listen
+
+ if address:
+ import re
+ p = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?:\:\d{1,5})?$')
+
+ if p.match(address):
+ args = ('tcp',)
+ kwargs = {'address': address}
+ else:
+ args = ('socket',)
+ kwargs = {'path': address}
+
+ if connect:
+ # connect to existing instance listening on address
+ nvim = attach(*args, **kwargs)
+ elif listen:
+ # spawn detached instance listening on address and connect to it
+ import os
+ import time
+ from subprocess import Popen
+ os.environ['NVIM_LISTEN_ADDRESS'] = address
+ nvim_argv = shlex.split(prog or 'nvim --headless') + ctx.args
+ # spawn the nvim with stdio redirected to /dev/null.
+ dnull = open(os.devnull)
+ p = Popen(nvim_argv, stdin=dnull, stdout=dnull, stderr=dnull)
+ dnull.close()
+ while p.poll() or p.returncode is None:
+ try:
+ nvim = attach(*args, **kwargs)
+ break
+ except IOError:
+ # socket not ready yet
+ time.sleep(0.050)
+ else:
+ # spawn embedded instance
+ nvim_argv = shlex.split(prog or 'nvim --embed') + ctx.args
+ nvim = attach('child', argv=nvim_argv)
+
+ if IS_PYTHON3:
+ nvim = nvim.with_hook(DecodeHook())
+
+ from .gtk_ui import GtkUI
+ ui = GtkUI()
+ bridge = UIBridge()
+ bridge.connect(nvim, ui, profile if profile != 'disable' else None, notify)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/neovim_gui/gtk_ui.py b/neovim_gui/gtk_ui.py
new file mode 100644
index 0000000..0ac6696
--- /dev/null
+++ b/neovim_gui/gtk_ui.py
@@ -0,0 +1,587 @@
+"""Neovim Gtk+ UI."""
+import math
+
+import cairo
+
+from gi.repository import GLib, GObject, Gdk, Gtk, Pango, PangoCairo
+
+from .screen import Screen
+
+
+__all__ = ('GtkUI',)
+
+
+SHIFT = Gdk.ModifierType.SHIFT_MASK
+CTRL = Gdk.ModifierType.CONTROL_MASK
+ALT = Gdk.ModifierType.MOD1_MASK
+
+
+# Translation table for the names returned by Gdk.keyval_name that don't match
+# the corresponding nvim key names.
+KEY_TABLE = {
+ 'slash': '/',
+ 'backslash': '\\',
+ 'dead_circumflex': '^',
+ 'at': '@',
+ 'numbersign': '#',
+ 'dollar': '$',
+ 'percent': '%',
+ 'ampersand': '&',
+ 'asterisk': '*',
+ 'parenleft': '(',
+ 'parenright': ')',
+ 'underscore': '_',
+ 'plus': '+',
+ 'minus': '-',
+ 'bracketleft': '[',
+ 'bracketright': ']',
+ 'braceleft': '{',
+ 'braceright': '}',
+ 'dead_diaeresis': '"',
+ 'dead_acute': "'",
+ 'less': "<",
+ 'greater': ">",
+ 'comma': ",",
+ 'period': ".",
+ 'BackSpace': 'BS',
+ 'Return': 'CR',
+ 'Escape': 'Esc',
+ 'Delete': 'Del',
+ 'Page_Up': 'PageUp',
+ 'Page_Down': 'PageDown',
+ 'Enter': 'CR',
+ 'ISO_Left_Tab': 'Tab'
+}
+
+
+if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION,) <= (2, 32,):
+ GLib.threads_init()
+
+
+class GtkUI(object):
+
+ """Gtk+ UI class."""
+
+ def __init__(self):
+ """Initialize the UI instance."""
+ self._redraw_arg = None
+ self._foreground = -1
+ self._background = -1
+ self._font_size = 13
+ self._font_name = 'Monospace'
+ self._screen = None
+ self._attrs = None
+ self._busy = False
+ self._mouse_enabled = False
+ self._insert_cursor = False
+ self._blink = False
+ self._blink_timer_id = None
+ self._resize_timer_id = None
+ self._pressed = None
+ self._invalid = None
+ self._pending = [0, 0, 0]
+ self._reset_cache()
+
+ def start(self, bridge):
+ """Start the UI event loop."""
+ bridge.attach(80, 24, True)
+ drawing_area = Gtk.DrawingArea()
+ drawing_area.connect('draw', self._gtk_draw)
+ window = Gtk.Window()
+ window.add(drawing_area)
+ window.set_events(window.get_events() |
+ Gdk.EventMask.BUTTON_PRESS_MASK |
+ Gdk.EventMask.BUTTON_RELEASE_MASK |
+ Gdk.EventMask.POINTER_MOTION_MASK |
+ Gdk.EventMask.SCROLL_MASK)
+ window.connect('configure-event', self._gtk_configure)
+ window.connect('delete-event', self._gtk_quit)
+ window.connect('key-press-event', self._gtk_key)
+ window.connect('button-press-event', self._gtk_button_press)
+ window.connect('button-release-event', self._gtk_button_release)
+ window.connect('motion-notify-event', self._gtk_motion_notify)
+ window.connect('scroll-event', self._gtk_scroll)
+ window.show_all()
+ im_context = Gtk.IMContextSimple()
+ im_context.connect('commit', self._gtk_input)
+ self._pango_context = drawing_area.create_pango_context()
+ self._drawing_area = drawing_area
+ self._window = window
+ self._im_context = im_context
+ self._bridge = bridge
+ Gtk.main()
+
+ def quit(self):
+ """Exit the UI event loop."""
+ GObject.idle_add(Gtk.main_quit)
+
+ def schedule_screen_update(self, apply_updates):
+ """Schedule screen updates to run in the UI event loop."""
+ def wrapper():
+ apply_updates()
+ self._flush()
+ self._start_blinking()
+ self._screen_invalid()
+ GObject.idle_add(wrapper)
+
+ def _screen_invalid(self):
+ self._drawing_area.queue_draw()
+
+ def _nvim_resize(self, columns, rows):
+ da = self._drawing_area
+ # create FontDescription object for the selected font/size
+ font_str = '{0} {1}'.format(self._font_name, self._font_size)
+ self._font, pixels, normal_width, bold_width = _parse_font(font_str)
+ # calculate the letter_spacing required to make bold have the same
+ # width as normal
+ self._bold_spacing = normal_width - bold_width
+ cell_pixel_width, cell_pixel_height = pixels
+ # calculate the total pixel width/height of the drawing area
+ pixel_width = cell_pixel_width * columns
+ pixel_height = cell_pixel_height * rows
+ gdkwin = da.get_window()
+ content = cairo.CONTENT_COLOR
+ self._cairo_surface = gdkwin.create_similar_surface(content,
+ pixel_width,
+ pixel_height)
+ self._cairo_context = cairo.Context(self._cairo_surface)
+ self._pango_layout = PangoCairo.create_layout(self._cairo_context)
+ self._pango_layout.set_alignment(Pango.Alignment.LEFT)
+ self._pango_layout.set_font_description(self._font)
+ self._pixel_width, self._pixel_height = pixel_width, pixel_height
+ self._cell_pixel_width = cell_pixel_width
+ self._cell_pixel_height = cell_pixel_height
+ self._screen = Screen(columns, rows)
+ self._window.resize(pixel_width, pixel_height)
+
+ def _nvim_clear(self):
+ self._clear_region(self._screen.top, self._screen.bot + 1,
+ self._screen.left, self._screen.right + 1)
+ self._screen.clear()
+
+ def _nvim_eol_clear(self):
+ row, col = self._screen.row, self._screen.col
+ self._clear_region(row, row + 1, col, self._screen.right + 1)
+ self._screen.eol_clear()
+
+ def _nvim_cursor_goto(self, row, col):
+ self._screen.cursor_goto(row, col)
+
+ def _nvim_busy_start(self):
+ self._busy = True
+
+ def _nvim_busy_stop(self):
+ self._busy = False
+
+ def _nvim_mouse_on(self):
+ self._mouse_enabled = True
+
+ def _nvim_mouse_off(self):
+ self._mouse_enabled = False
+
+ def _nvim_mode_change(self, mode):
+ self._insert_cursor = mode == 'insert'
+
+ def _nvim_set_scroll_region(self, top, bot, left, right):
+ self._screen.set_scroll_region(top, bot, left, right)
+
+ def _nvim_scroll(self, count):
+ self._flush()
+ top, bot = self._screen.top, self._screen.bot + 1
+ left, right = self._screen.left, self._screen.right + 1
+ # The diagrams below illustrate what will happen, depending on the
+ # scroll direction. "=" is used to represent the SR(scroll region)
+ # boundaries and "-" the moved rectangles. note that dst and src share
+ # a common region
+ if count > 0:
+ # move an rectangle in the SR up, this can happen while scrolling
+ # down
+ # +-------------------------+
+ # | (clipped above SR) | ^
+ # |=========================| dst_top |
+ # | dst (still in SR) | |
+ # +-------------------------+ src_top |
+ # | src (moved up) and dst | |
+ # |-------------------------| dst_bot |
+ # | src (cleared) | |
+ # +=========================+ src_bot
+ src_top, src_bot = top + count, bot
+ dst_top, dst_bot = top, bot - count
+ clr_top, clr_bot = dst_bot, src_bot
+ else:
+ # move a rectangle in the SR down, this can happen while scrolling
+ # up
+ # +=========================+ src_top
+ # | src (cleared) | |
+ # |------------------------ | dst_top |
+ # | src (moved down) and dst| |
+ # +-------------------------+ src_bot |
+ # | dst (still in SR) | |
+ # |=========================| dst_bot |
+ # | (clipped below SR) | v
+ # +-------------------------+
+ src_top, src_bot = top, bot + count
+ dst_top, dst_bot = top - count, bot
+ clr_top, clr_bot = src_top, dst_top
+ self._cairo_surface.flush()
+ self._cairo_context.save()
+ # The move is performed by setting the source surface to itself, but
+ # with a coordinate transformation.
+ _, y = self._get_coords(dst_top - src_top, 0)
+ self._cairo_context.set_source_surface(self._cairo_surface, 0, y)
+ # Clip to ensure only dst is affected by the change
+ self._mask_region(dst_top, dst_bot, left, right)
+ # Do the move
+ self._cairo_context.paint()
+ self._cairo_context.restore()
+ # Clear the emptied region
+ self._clear_region(clr_top, clr_bot, left, right)
+ self._screen.scroll(count)
+
+ def _nvim_highlight_set(self, attrs):
+ self._attrs = self._get_pango_attrs(attrs)
+
+ def _nvim_put(self, text):
+ if self._screen.row != self._pending[0]:
+ # flush pending text if jumped to a different row
+ self._flush()
+ # work around some redraw glitches that can happen
+ self._redraw_glitch_fix()
+ # Update internal screen
+ self._screen.put(self._get_pango_text(text), self._attrs)
+ self._pending[1] = min(self._screen.col - 1, self._pending[1])
+ self._pending[2] = max(self._screen.col, self._pending[2])
+
+ def _nvim_bell(self):
+ self._window.get_window().beep()
+
+ def _nvim_visual_bell(self):
+ pass
+
+ def _nvim_update_fg(self, fg):
+ self._foreground = fg
+ self._reset_cache()
+
+ def _nvim_update_bg(self, bg):
+ self._background = bg
+ self._reset_cache()
+
+ def _nvim_suspend(self):
+ self._window.iconify()
+
+ def _nvim_set_title(self, title):
+ self._window.set_title(title)
+
+ def _nvim_set_icon(self, icon):
+ self._window.set_icon_name(icon)
+
+ def _gtk_draw(self, wid, cr):
+ if not self._screen:
+ return
+ # from random import random
+ # cr.rectangle(0, 0, self._pixel_width, self._pixel_height)
+ # cr.set_source_rgb(random(), random(), random())
+ # cr.fill()
+ self._cairo_surface.flush()
+ cr.save()
+ cr.rectangle(0, 0, self._pixel_width, self._pixel_height)
+ cr.clip()
+ cr.set_source_surface(self._cairo_surface, 0, 0)
+ cr.paint()
+ cr.restore()
+ if not self._busy and self._blink:
+ # Cursor is drawn separately in the window. This approach is
+ # simpler because it doesn't taint the internal cairo surface,
+ # which is used for scrolling
+ row, col = self._screen.row, self._screen.col
+ text, attrs = self._screen.get_cursor()
+ self._pango_draw(row, col, [(text, attrs,)], cr=cr, cursor=True)
+
+ def _gtk_configure(self, widget, event):
+ def resize(*args):
+ self._resize_timer_id = None
+ width, height = self._window.get_size()
+ columns = width / self._cell_pixel_width
+ rows = height / self._cell_pixel_height
+ if self._screen.columns == columns and self._screen.rows == rows:
+ return
+ self._bridge.resize(columns, rows)
+
+ if not self._screen:
+ return
+ if event.width == self._pixel_width and \
+ event.height == self._pixel_height:
+ return
+ if self._resize_timer_id is not None:
+ GLib.source_remove(self._resize_timer_id)
+ self._resize_timer_id = GLib.timeout_add(250, resize)
+
+ def _gtk_quit(self, *args):
+ self._bridge.exit()
+
+ def _gtk_key(self, widget, event, *args):
+ # This function was adapted from pangoterm source code
+ keyval = event.keyval
+ state = event.state
+ # GtkIMContext will eat a Shift-Space and not tell us about shift.
+ # Also don't let IME eat any GDK_KEY_KP_ events
+ done = (False if state & SHIFT and keyval == ord(' ') else
+ False if Gdk.KEY_KP_Space <= keyval <= Gdk.KEY_KP_Divide else
+ self._im_context.filter_keypress(event))
+ if done:
+ # input method handled keypress
+ return True
+ if event.is_modifier:
+ # We don't need to track the state of modifier bits
+ return
+ # translate keyval to nvim key
+ key_name = Gdk.keyval_name(keyval)
+ if key_name.startswith('KP_'):
+ key_name = key_name[3:]
+ input_str = _stringify_key(KEY_TABLE.get(key_name, key_name), state)
+ self._bridge.input(input_str)
+
+ def _gtk_button_press(self, widget, event, *args):
+ if not self._mouse_enabled or event.type != Gdk.EventType.BUTTON_PRESS:
+ return
+ button = 'Left'
+ if event.button == 2:
+ button = 'Middle'
+ elif event.button == 3:
+ button = 'Right'
+ col = int(math.floor(event.x / self._cell_pixel_width))
+ row = int(math.floor(event.y / self._cell_pixel_height))
+ input_str = _stringify_key(button + 'Mouse', event.state)
+ input_str += '<{0},{1}>'.format(col, row)
+ self._bridge.input(input_str)
+ self._pressed = button
+
+ def _gtk_button_release(self, widget, event, *args):
+ self._pressed = None
+
+ def _gtk_motion_notify(self, widget, event, *args):
+ if not self._mouse_enabled or not self._pressed:
+ return
+ col = int(math.floor(event.x / self._cell_pixel_width))
+ row = int(math.floor(event.y / self._cell_pixel_height))
+ input_str = _stringify_key(self._pressed + 'Drag', event.state)
+ input_str += '<{0},{1}>'.format(col, row)
+ self._bridge.input(input_str)
+
+ def _gtk_scroll(self, widget, event, *args):
+ if not self._mouse_enabled:
+ return
+ col = int(math.floor(event.x / self._cell_pixel_width))
+ row = int(math.floor(event.y / self._cell_pixel_height))
+ if event.direction == Gdk.ScrollDirection.UP:
+ key = 'ScrollWheelUp'
+ elif event.direction == Gdk.ScrollDirection.DOWN:
+ key = 'ScrollWheelDown'
+ else:
+ return
+ input_str = _stringify_key(key, event.state)
+ input_str += '<{0},{1}>'.format(col, row)
+ self._bridge.input(input_str)
+
+ def _gtk_input(self, widget, input_str, *args):
+ self._bridge.input(input_str.replace('<', '<lt>'))
+
+ def _start_blinking(self):
+ def blink(*args):
+ self._blink = not self._blink
+ self._screen_invalid()
+ self._blink_timer_id = GLib.timeout_add(500, blink)
+ if self._blink_timer_id:
+ GLib.source_remove(self._blink_timer_id)
+ self._blink = False
+ blink()
+
+ def _clear_region(self, top, bot, left, right):
+ self._flush()
+ self._cairo_context.save()
+ self._mask_region(top, bot, left, right)
+ r, g, b = _split_color(self._background)
+ r, g, b = r / 255.0, g / 255.0, b / 255.0
+ self._cairo_context.set_source_rgb(r, g, b)
+ self._cairo_context.paint()
+ self._cairo_context.restore()
+
+ def _mask_region(self, top, bot, left, right, cr=None):
+ if not cr:
+ cr = self._cairo_context
+ x1, y1, x2, y2 = self._get_rect(top, bot, left, right)
+ cr.rectangle(x1, y1, x2 - x1, y2 - y1)
+ cr.clip()
+
+ def _get_rect(self, top, bot, left, right):
+ x1, y1 = self._get_coords(top, left)
+ x2, y2 = self._get_coords(bot, right)
+ return x1, y1, x2, y2
+
+ def _get_coords(self, row, col):
+ x = col * self._cell_pixel_width
+ y = row * self._cell_pixel_height
+ return x, y
+
+ def _flush(self):
+ row, startcol, endcol = self._pending
+ self._pending[0] = self._screen.row
+ self._pending[1] = self._screen.col
+ self._pending[2] = self._screen.col
+ if startcol == endcol:
+ return
+ self._cairo_context.save()
+ ccol = startcol
+ buf = []
+ bold = False
+ for _, col, text, attrs in self._screen.iter(row, row, startcol,
+ endcol - 1):
+ newbold = attrs and 'bold' in attrs[0]
+ if newbold != bold or not text:
+ if buf:
+ self._pango_draw(row, ccol, buf)
+ bold = newbold
+ buf = [(text, attrs,)]
+ ccol = col
+ else:
+ buf.append((text, attrs,))
+ if buf:
+ self._pango_draw(row, ccol, buf)
+ self._cairo_context.restore()
+
+ def _pango_draw(self, row, col, data, cr=None, cursor=False):
+ markup = []
+ for text, attrs in data:
+ if not attrs:
+ attrs = self._get_pango_attrs(None)
+ attrs = attrs[1] if cursor else attrs[0]
+ markup.append('<span {0}>{1}</span>'.format(attrs, text))
+ markup = ''.join(markup)
+ self._pango_layout.set_markup(markup, -1)
+ # Draw the text
+ if not cr:
+ cr = self._cairo_context
+ x, y = self._get_coords(row, col)
+ if cursor and self._insert_cursor:
+ cr.rectangle(x, y, self._cell_pixel_width / 4,
+ self._cell_pixel_height)
+ cr.clip()
+ cr.move_to(x, y)
+ PangoCairo.update_layout(cr, self._pango_layout)
+ PangoCairo.show_layout(cr, self._pango_layout)
+ _, r = self._pango_layout.get_pixel_extents()
+
+ def _get_pango_text(self, text):
+ rv = self._pango_text_cache.get(text, None)
+ if rv is None:
+ rv = GLib.markup_escape_text(text or '')
+ self._pango_text_cache[text] = rv
+ return rv
+
+ def _get_pango_attrs(self, attrs):
+ key = tuple(sorted((k, v,) for k, v in (attrs or {}).items()))
+ rv = self._pango_attrs_cache.get(key, None)
+ if rv is None:
+ fg = self._foreground if self._foreground != -1 else 0
+ bg = self._background if self._background != -1 else 0xffffff
+ n = {
+ 'foreground': _split_color(fg),
+ 'background': _split_color(bg),
+ }
+ if attrs:
+ # make sure that foreground and background are assigned first
+ for k in ['foreground', 'background']:
+ if k in attrs:
+ n[k] = _split_color(attrs[k])
+ for k, v in attrs.items():
+ if k == 'reverse':
+ n['foreground'], n['background'] = \
+ n['background'], n['foreground']
+ elif k == 'italic':
+ n['font_style'] = 'italic'
+ elif k == 'bold':
+ n['font_weight'] = 'bold'
+ if self._bold_spacing:
+ n['letter_spacing'] = str(self._bold_spacing)
+ elif k == 'underline':
+ n['underline'] = 'single'
+ c = dict(n)
+ c['foreground'] = _invert_color(*_split_color(fg))
+ c['background'] = _invert_color(*_split_color(bg))
+ c['foreground'] = _stringify_color(*c['foreground'])
+ c['background'] = _stringify_color(*c['background'])
+ n['foreground'] = _stringify_color(*n['foreground'])
+ n['background'] = _stringify_color(*n['background'])
+ n = ' '.join(['{0}="{1}"'.format(k, v) for k, v in n.items()])
+ c = ' '.join(['{0}="{1}"'.format(k, v) for k, v in c.items()])
+ rv = (n, c,)
+ self._pango_attrs_cache[key] = rv
+ return rv
+
+ def _reset_cache(self):
+ self._pango_text_cache = {}
+ self._pango_attrs_cache = {}
+
+ def _redraw_glitch_fix(self):
+ row, col = self._screen.row, self._screen.col
+ text, attrs = self._screen.get_cursor()
+ # when updating cells in italic or bold words, the result can become
+ # messy(characters can be clipped or leave remains when removed). To
+ # prevent that, always update non empty sequences of cells and the
+ # surrounding space.
+ # find the start of the sequence
+ lcol = col - 1
+ while lcol >= 0:
+ text, _ = self._screen.get_cell(row, lcol)
+ lcol -= 1
+ if text == ' ':
+ break
+ self._pending[1] = min(lcol + 1, self._pending[1])
+ # find the end of the sequence
+ rcol = col + 1
+ while rcol < self._screen.columns:
+ text, _ = self._screen.get_cell(row, rcol)
+ rcol += 1
+ if text == ' ':
+ break
+ self._pending[2] = max(rcol, self._pending[2])
+
+
+def _split_color(n):
+ return ((n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff,)
+
+
+def _invert_color(r, g, b):
+ return (255 - r, 255 - g, 255 - b,)
+
+
+def _stringify_color(r, g, b):
+ return '#{0:0{1}x}'.format((r << 16) + (g << 8) + b, 6)
+
+
+def _stringify_key(key, state):
+ send = []
+ if state & SHIFT:
+ send.append('S')
+ if state & CTRL:
+ send.append('C')
+ if state & ALT:
+ send.append('A')
+ send.append(key)
+ return '<' + '-'.join(send) + '>'
+
+
+def _parse_font(font, cr=None):
+ if not cr:
+ ims = cairo.ImageSurface(cairo.FORMAT_RGB24, 300, 300)
+ cr = cairo.Context(ims)
+ fd = Pango.font_description_from_string(font)
+ layout = PangoCairo.create_layout(cr)
+ layout.set_font_description(fd)
+ layout.set_alignment(Pango.Alignment.LEFT)
+ layout.set_markup('<span font_weight="bold">A</span>')
+ bold_width, _ = layout.get_size()
+ layout.set_markup('<span>A</span>')
+ pixels = layout.get_pixel_size()
+ normal_width, _ = layout.get_size()
+ return fd, pixels, normal_width, bold_width
diff --git a/neovim_gui/screen.py b/neovim_gui/screen.py
new file mode 100644
index 0000000..6ebf99b
--- /dev/null
+++ b/neovim_gui/screen.py
@@ -0,0 +1,134 @@
+"""Common code for graphical and text UIs."""
+from neovim.compat import IS_PYTHON3
+
+
+__all__ = ('Screen',)
+
+
+if not IS_PYTHON3:
+ range = xrange # NOQA
+
+
+class Cell(object):
+ def __init__(self):
+ self.text = ' '
+ self.attrs = None
+
+ def __repr__(self):
+ return self.text
+
+ def get(self):
+ return self.text, self.attrs
+
+ def set(self, text, attrs):
+ self.text = text
+ self.attrs = attrs
+
+ def copy(self, other):
+ other.text = self.text
+ other.attrs = self.attrs
+
+
+class Screen(object):
+
+ """Store nvim screen state."""
+
+ def __init__(self, columns, rows):
+ """Initialize the Screen instance."""
+ self.columns = columns
+ self.rows = rows
+ self.row = 0
+ self.col = 0
+ self.top = 0
+ self.bot = rows - 1
+ self.left = 0
+ self.right = columns - 1
+ self._cells = [[Cell() for c in range(columns)] for r in range(rows)]
+
+ def clear(self):
+ """Clear the screen."""
+ self._clear_region(self.top, self.bot, self.left, self.right)
+
+ def eol_clear(self):
+ """Clear from the cursor position to the end of the scroll region."""
+ self._clear_region(self.row, self.row, self.col, self.right)
+
+ def cursor_goto(self, row, col):
+ """Change the virtual cursor position."""
+ self.row = row
+ self.col = col
+
+ def set_scroll_region(self, top, bot, left, right):
+ """Change scroll region."""
+ self.top = top
+ self.bot = bot
+ self.left = left
+ self.right = right
+
+ def scroll(self, count):
+ """Shift scroll region."""
+ top, bot = self.top, self.bot
+ left, right = self.left, self.right
+ if count > 0:
+ start = top
+ stop = bot - count + 1
+ step = 1
+ else:
+ start = bot
+ stop = top - count - 1
+ step = -1
+ # shift the cells
+ for row in range(start, stop, step):
+ target_row = self._cells[row]
+ source_row = self._cells[row + count]
+ for col in range(left, right + 1):
+ tgt = target_row[col]
+ source_row[col].copy(tgt)
+ # clear invalid cells
+ for row in range(stop, stop + count, step):
+ self._clear_region(row, row, left, right)
+
+ def put(self, text, attrs):
+ """Put character on virtual cursor position."""
+ cell = self._cells[self.row][self.col]
+ cell.set(text, attrs)
+ self.cursor_goto(self.row, self.col + 1)
+
+ def get_cell(self, row, col):
+ """Get text, attrs at row, col."""
+ return self._cells[row][col].get()
+
+ def get_cursor(self):
+ """Get text, attrs at the virtual cursor position."""
+ return self.get_cell(self.row, self.col)
+
+ def iter(self, startrow, endrow, startcol, endcol):
+ """Extract text/attrs at row, startcol-endcol."""
+ for row in range(startrow, endrow + 1):
+ r = self._cells[row]
+ cell = r[startcol]
+ curcol = startcol
+ attrs = cell.attrs
+ buf = [cell.text]
+ for col in range(startcol + 1, endcol + 1):
+ cell = r[col]
+ if cell.attrs != attrs or not cell.text:
+ yield row, curcol, ''.join(buf), attrs
+ attrs = cell.attrs
+ buf = [cell.text]
+ curcol = col
+ if not cell.text:
+ # glyph uses two cells, yield a separate entry
+ yield row, curcol, '', None
+ curcol += 1
+ else:
+ buf.append(cell.text)
+ if buf:
+ yield row, curcol, ''.join(buf), attrs
+
+ def _clear_region(self, top, bot, left, right):
+ for rownum in range(top, bot + 1):
+ row = self._cells[rownum]
+ for colnum in range(left, right + 1):
+ cell = row[colnum]
+ cell.set(' ', None)
diff --git a/neovim_gui/ui_bridge.py b/neovim_gui/ui_bridge.py
new file mode 100644
index 0000000..a4b5f1c
--- /dev/null
+++ b/neovim_gui/ui_bridge.py
@@ -0,0 +1,105 @@
+"""Bridge for connecting a UI instance to nvim."""
+import sys
+from threading import Semaphore, Thread
+from traceback import format_exc
+
+
+class UIBridge(object):
+
+ """UIBridge class. Connects a Nvim instance to a UI class."""
+
+ def connect(self, nvim, ui, profile=None, notify=False):
+ """Connect nvim and the ui.
+
+ This will start loops for handling the UI and nvim events while
+ also synchronizing both.
+ """
+ self._notify = notify
+ self._error = None
+ self._nvim = nvim
+ self._ui = ui
+ self._profile = profile
+ self._sem = Semaphore(0)
+ t = Thread(target=self._nvim_event_loop)
+ t.daemon = True
+ t.start()
+ self._ui_event_loop()
+ if self._error:
+ print(self._error)
+ if self._profile:
+ print(self._profile)
+
+ def exit(self):
+ """Disconnect by exiting nvim."""
+ self.detach()
+ self._call(self._nvim.quit)
+
+ def input(self, input_str):
+ """Send input to nvim."""
+ self._call(self._nvim.input, input_str)
+
+ def resize(self, columns, rows):
+ """Send a resize request to nvim."""
+ self._call(self._nvim.ui_try_resize, columns, rows)
+
+ def attach(self, columns, rows, rgb):
+ """Attach the UI to nvim."""
+ self._call(self._nvim.ui_attach, columns, rows, rgb)
+
+ def detach(self):
+ """Detach the UI from nvim."""
+ self._call(self._nvim.ui_detach)
+
+ def _call(self, fn, *args):
+ self._nvim.session.threadsafe_call(fn, *args)
+
+ def _ui_event_loop(self):
+ self._sem.acquire()
+ if self._profile:
... 103 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-neovim-gui.git
More information about the Python-modules-commits
mailing list