Repository: wong2/pick

Issue:
Long lists issue
I have an issue with `pick` while passing a long list, here is an example you can try it:

```python
from pick import pick

title = 'Select: '
options = ['foo.bar1.baz', 'foo.bar2.baz', 'foo.bar3.baz', 'foo.bar4.baz', 'foo.bar5.baz', 'foo.bar6.baz', 'foo.bar7.baz','foo.bar8.baz', 'foo.bar9.baz', 'foo.bar10.baz','foo.bar11.baz', 'foo.bar12.baz', 'foo.bar13.baz', 'foo.bar14.baz', 'foo.bar15.baz', 'foo.bar16.baz', 'foo.bar17.baz','foo.bar18.baz', 'foo.bar19.baz', 'foo.bar20.baz','foo.bar21.baz', 'foo.bar22.baz', 'foo.bar23.baz', 'foo.bar24.baz', 'foo.bar25.baz', 'foo.bar26.baz', 'foo.bar27.baz','foo.bar28.baz', 'foo.bar29.baz', 'foo.bar30.baz','foo.bar31.baz', 'foo.bar32.baz', 'foo.bar33.baz', 'foo.bar34.baz', 'foo.bar35.baz', 'foo.bar36.baz', 'foo.bar37.baz','foo.bar38.baz', 'foo.bar39.baz', 'foo.bar40.baz','foo.bar41.baz', 'foo.bar42.baz', 'foo.bar43.baz', 'foo.bar44.baz', 'foo.bar45.baz', 'foo.bar46.baz', 'foo.bar47.baz','foo.bar48.baz', 'foo.bar49.baz', 'foo.bar50.baz','foo.bar51.baz', 'foo.bar52.baz', 'foo.bar53.baz', 'foo.bar54.baz', 'foo.bar55.baz', 'foo.bar56.baz', 'foo.bar57.baz','foo.bar58.baz', 'foo.bar59.baz', 'foo.bar60.baz']
option, index = pick(options, title)
``` 

the result will be:
```
Traceback (most recent call last):
  File "pick_test.py", line 5, in <module>
    option, index = pick(options, title)
  File "/usr/lib/python2.7/site-packages/pick/__init__.py", line 109, in pick
    return picker.start()
  File "/usr/lib/python2.7/site-packages/pick/__init__.py", line 73, in start
    return curses.wrapper(self.run_loop)
  File "/usr/lib64/python2.7/curses/wrapper.py", line 43, in wrapper
    return func(stdscr, *args, **kwds)
  File "/usr/lib/python2.7/site-packages/pick/__init__.py", line 84, in run_loop
    self.draw()
  File "/usr/lib/python2.7/site-packages/pick/__init__.py", line 67, in draw
    self.screen.addstr(y, x, line)
_curses.error: addstr() returned ERR
```

Discussion:

Patch:
diff --git a/example/scroll.py b/example/scroll.py
new file mode 100644
index 0000000..6f34b22
--- /dev/null
+++ b/example/scroll.py
@@ -0,0 +1,10 @@
+#-*-coding:utf-8-*-
+
+from __future__ import print_function
+
+from pick import pick
+
+title = 'Select:'
+options = ['foo.bar%s.baz' % x for x in range(1, 71)]
+option, index = pick(options, title)
+print(option, index)
diff --git a/pick/__init__.py b/pick/__init__.py
index 1f8ccfc..f004b84 100644
--- a/pick/__init__.py
+++ b/pick/__init__.py
@@ -49,14 +49,13 @@ class Picker(object):
         """
         return self.options[self.index], self.index
 
-    def draw(self):
-        """draw the curses ui on the screen"""
-        self.screen.clear()
-
-        x, y = 1, 1
+    def get_title_lines(self):
         if self.title:
-            self.screen.addstr(y, x, self.title)
-            y += 2
+            return self.title.split('\n') + ['']
+        return []
+
+    def get_option_lines(self):
+        lines = []
 
         for index, option in enumerate(self.options):
             if index == self.index:
@@ -64,13 +63,53 @@ class Picker(object):
             else:
                 prefix = len(self.indicator) * ' '
             line = '{0} {1}'.format(prefix, option)
+            lines.append(line)
+
+        return lines
+
+    def get_lines(self):
+        title_lines = self.get_title_lines()
+        option_lines = self.get_option_lines()
+        lines = title_lines + option_lines
+        current_line = self.index + len(title_lines) + 1
+        return lines, current_line
+
+    def draw(self):
+        """draw the curses ui on the screen, handle scroll if needed"""
+        self.screen.clear()
+
+        x, y = 1, 1  # start point
+        max_y, max_x = self.screen.getmaxyx()
+        max_rows = max_y - y  # the max rows we can draw
+
+        lines, current_line = self.get_lines()
+
+        # calculate how many lines we should scroll, relative to the top
+        scroll_top = getattr(self, 'scroll_top', 0)
+        if current_line <= scroll_top:
+            scroll_top = 0
+        elif current_line - scroll_top > max_rows:
+            scroll_top = current_line - max_rows
+        self.scroll_top = scroll_top
+
+        lines_to_draw = lines[scroll_top:scroll_top+max_rows]
+
+        for line in lines_to_draw:
             self.screen.addstr(y, x, line)
             y += 1
 
         self.screen.refresh()
 
-    def start(self):
-        return curses.wrapper(self.run_loop)
+    def run_loop(self):
+        while True:
+            self.draw()
+            c = self.screen.getch()
+            if c in KEYS_UP:
+                self.move_up()
+            elif c in KEYS_DOWN:
+                self.move_down()
+            elif c in KEYS_ENTER:
+                return self.get_selected()
 
     def config_curses(self):
         # use the default colors of the terminal
@@ -78,21 +117,13 @@ class Picker(object):
         # hide the cursor
         curses.curs_set(0)
 
-    def run_loop(self, screen):
-        self.config_curses()
+    def _start(self, screen):
         self.screen = screen
-        self.draw()
+        self.config_curses()
+        return self.run_loop()
 
-        while True:
-            c = self.screen.getch()
-            if c in KEYS_UP:
-                self.move_up()
-                self.draw()
-            elif c in KEYS_DOWN:
-                self.move_down()
-                self.draw()
-            elif c in KEYS_ENTER:
-                return self.get_selected()
+    def start(self):
+        return curses.wrapper(self._start)
 
 
 def pick(options, title=None, indicator='*', default_index=0):


Test patch:
diff --git a/tests/test_pick.py b/tests/test_pick.py
index f5b7dd4..21eecc4 100644
--- a/tests/test_pick.py
+++ b/tests/test_pick.py
@@ -1,12 +1,12 @@
 #-*-coding:utf-8-*-
 
 import unittest
-from pick import pick, Picker
+from pick import Picker
 
 
 class TestPick(unittest.TestCase):
 
-    def test_pick(self):
+    def test_move_up_down(self):
         title = 'Please choose an option: '
         options = ['option1', 'option2', 'option3']
         picker = Picker(options, title)
@@ -16,6 +16,26 @@ class TestPick(unittest.TestCase):
         picker.move_down()
         assert picker.get_selected() == ('option2', 1)
 
+    def test_default_index(self):
+        title = 'Please choose an option: '
+        options = ['option1', 'option2', 'option3']
+        picker = Picker(options, title, default_index=1)
+        assert picker.get_selected() == ('option2', 1)
+
+    def test_get_lines(self):
+        title = 'Please choose an option: '
+        options = ['option1', 'option2', 'option3']
+        picker = Picker(options, title, indicator='*')
+        lines, current_line = picker.get_lines()
+        assert lines == [title, '', '* option1', '  option2', '  option3']
+        assert current_line == 3
+
+    def test_no_title(self):
+        options = ['option1', 'option2', 'option3']
+        picker = Picker(options)
+        lines, current_line = picker.get_lines()
+        assert current_line == 1
+
 
 if __name__ == '__main__':
     unittest.main()