Skip to content

Commit

Permalink
added travis ci
Browse files Browse the repository at this point in the history
debug off
readme
  • Loading branch information
timeyyy committed May 23, 2016
1 parent 48288fe commit f107b39
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 95 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ __pycache__/
*.*~
.idea/
*.egg-info
build/
dist/
30 changes: 30 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
sudo: True

language: python
python:
- "3.5"

install:
# - "apt-get install software-properties-common"
- "sudo add-apt-repository ppa:neovim-ppa/unstable -y"
- "sudo apt-get update -y"
- "sudo apt-get install neovim -y"
- "pip install neovim"
- "pip install coveralls"
- "pip install coverage"

# For Testing with gui
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
- cd pytknvim

script:
- coverage run --source=tests -m py.test -k "not failing"

after_success:
- coveralls

notifications:
- email: false
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

### Python Tkinter gui for [Neovim](https://github.com/neovim/neovim)

[![Build Status](https://travis-ci.org/timeyyy/pytknvim.svg?branch=master)](https://travis-ci.org/timeyyy/pytknvim)
[![Coverage Status](https://coveralls.io/repos/github/timeyyy/pytknvim/badge.svg?branch=master)](https://coveralls.io/github/timeyyy/pytknvim?branch=master)

[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/timeyyy/pytknvim/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/timeyyy/pytknvim/?branch=master)
[![Code Climate](https://codeclimate.com/github/timeyyy/pytknvim/badges/gpa.svg)](https://codeclimate.com/github/timeyyy/pytknvim)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/9834369c3abe49fdaa4eebcc7239d17d)](https://www.codacy.com/app/timeyyy\_da\_man/pytknvim?utm\_source=github.com&utm\_medium=referral&utm\_content=timeyyy/pytknvim&utm\_campaign=Badge\_Grade)
[![Code Issues](https://www.quantifiedcode.com/api/v1/project/6c355ec860204274b165a15393aadacf/badge.svg)](https://www.quantifiedcode.com/app/project/6c355ec860204274b165a15393aadacf)

Simple nvim gui implemented using tkinter

#### Missing Features

Passing Ctrl/Alt/Meta through to neovim.
Python2 not supported (should be trivial to add)
Stability.. a bit flaky on startup

#### Installation

```sh
pip install pytknvim
```

#### Usage

```sh
pytknvim
```
134 changes: 134 additions & 0 deletions pytknvim/screen.py
Original file line number Diff line number Diff line change
@@ -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)
File renamed without changes.
9 changes: 6 additions & 3 deletions pytknvim/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
from itertools import count

import pytest
from neovim_gui.ui_bridge import UIBridge

from pytknvim.ui_bridge import UIBridge
from pytknvim.tk_ui import NvimTk
from pytknvim.util import attach_headless
from pytknvim.tests.util import compare_screens, send_tk_key
Expand Down Expand Up @@ -268,12 +268,13 @@ def test_big_file(self):
doesn't test anything that our other tests doesn't,
but just paves the way for testing a file
'''
test_file = 'weakref'
test_file = os.path.join(os.path.dirname(__file__),
'test_file')
self.v_normal_mode()
self.send_tk_key(*':e! '+test_file)
self.send_tk_key('Enter')
# Give time for actions to take place
time.sleep(0.5)
time.sleep(1)
self.compare_screens()
# TODO READ LEN OF FILE
old_max = self.nvimtk.max_scroll
Expand All @@ -287,6 +288,8 @@ def test_big_file(self):
self.max_sroll = old_max


# TODO Failing when used in combination with another test
@pytest.mark.failing
def test_number(self):
to_test = ('load', 'basic_insert', 'enter_key')
self.nvim.command("set nonumber")
Expand Down
2 changes: 1 addition & 1 deletion pytknvim/tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _parse(lines, line_length, mock_inst, eol_trim):
# Unfortunatley the code is a bit confusing
# I thought the handling was more similar than
# different for the two cases...
file_name = mock_inst.test_nvim.eval('expand("%")')
file_name = mock_inst.test_nvim.eval('expand("%:t")')
side_bar = mock_inst.test_nvim.eval('&relativenumber') \
or mock_inst.test_nvim.eval('&number')
all_rows = []
Expand Down
7 changes: 3 additions & 4 deletions pytknvim/tk_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
import sys
import math
import time

from neovim_gui.ui_bridge import UIBridge
from neovim import attach
from neovim_gui.screen import Screen

from pytknvim.ui_bridge import UIBridge
from pytknvim.screen import Screen
from pytknvim.util import _stringify_key, _stringify_color
from pytknvim.util import _split_color, _invert_color
from pytknvim.util import debug_echo, delay_call
Expand Down Expand Up @@ -537,7 +536,7 @@ def start(self, bridge):
bridge.attach(cols, rows, rgb=True)
self._bridge = bridge

self.debug_echo = True
self.debug_echo = False

self.root = tk.Tk()
self.root.protocol('WM_DELETE_WINDOW', self._tk_quit)
Expand Down
105 changes: 105 additions & 0 deletions pytknvim/ui_bridge.py
Original file line number Diff line number Diff line change
@@ -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.async_call(fn, *args)

def _ui_event_loop(self):
self._sem.acquire()
if self._profile:
import StringIO
import cProfile
import pstats
pr = cProfile.Profile()
pr.enable()
self._ui.start(self)
if self._profile:
pr.disable()
s = StringIO.StringIO()
ps = pstats.Stats(pr, stream=s)
ps.strip_dirs().sort_stats(self._profile).print_stats(30)
self._profile = s.getvalue()

def _nvim_event_loop(self):
def on_setup():
self._sem.release()

def on_request(method, args):
raise Exception('Not implemented')

def on_notification(method, updates):
def apply_updates():
if self._notify:
sys.stdout.write('attached\n')
sys.stdout.flush()
self._notify = False
try:
for update in updates:
# import sys
# l = [','.join([str(a) for a in args])
# for args in update[1:]]
# print >> sys.stderr, update[0], ' '.join(l)
try:
handler = getattr(self._ui, '_nvim_' + update[0])
except AttributeError:
pass
else:
for args in update[1:]:
handler(*args)
except:
self._error = format_exc()
self._call(self._nvim.quit)
if method == 'redraw':
self._ui.schedule_screen_update(apply_updates)

self._nvim.run_loop(on_request, on_notification, on_setup)
self._ui.quit()
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bdist_wheel]
universal = 0
Loading

0 comments on commit f107b39

Please sign in to comment.