[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