Source code for pipenv_pipes.picker

# -*-coding:utf-8-*-

"""
Modified from https://github.com/wong2/pick/
Pick - create curses based interactive selection list in the terminal
LICENSE MIT
"""

import re
import sys
import curses
import json
from itertools import cycle

from .colors import colors
from .elements import Line, EnvLine
from .keys import (
    KEYS_UP,
    KEYS_DOWN,
    KEYS_ENTER,
    KEYS_CLEAR,
    KEYS_BACKSPACE,
    KEYS_ESCAPE,
    KEYS_HOME,
    KEYS_END,
    KEYS_RIGHT,
    KEYS_LEFT,
)


__all__ = ['Picker']

OPTION_COLOR = 'WHITE'
SELECTED_OPTION = 'YELLOW'
TITLE_COLOR = 'BLUE'
IS_TESTING = 'pytest' in sys.modules


[docs]class Picker(object): def __init__(self, environments, query='', debug_mode=False): if not environments: raise ValueError('invalid environments value') self._environments = environments self.query = query self.index = 0 self.debug_mode = debug_mode self.CYCLES = range(4) self.expand_next()
[docs] def config_curses(self): curses.curs_set(0) # hide the cursor colors.initialize()
def _start(self, screen): self.screen = screen self.config_curses() return self.run_loop()
[docs] def start(self): if IS_TESTING: data = json.dumps({ 'query': self.query, 'envs': len(self.environments), }) raise SystemExit(data) return curses.wrapper(self._start)
[docs] def move_up(self, pos=1): self.index -= pos if self.index < 0: self.index = len(self.environments) - 1
# self.clear_query()
[docs] def move_down(self, pos=1): self.index += pos if self.index >= len(self.environments): self.index = 0
# self.clear_query()
[docs] def move_top(self): self.index = 0
# self.clear_query()
[docs] def move_bottom(self): self.index = len(self.environments) - 1
# self.clear_query()
[docs] def clear_query(self): self.query = ''
[docs] def expand_next(self): if not hasattr(self, '_cycle'): self._cycle = cycle(self.CYCLES) self.expanded = next(self._cycle)
[docs] def expand_prev(self): for _ in self.CYCLES[:-1]: self.expand_next()
[docs] def get_selected(self): return self.environments[self.index]
@property def environments(self): if self.query: return [e for e in self._environments if self.query in e.envname.lower()] else: return self._environments
[docs] def get_option_lines(self): envs = self.environments lines = [] for index, environment in enumerate(envs): is_selected = index == self.index if is_selected: color = SELECTED_OPTION else: color = OPTION_COLOR line = EnvLine( env=environment, color=color, selected=is_selected, expanded=self.expanded) lines.append(line) return lines
[docs] def get_title_lines(self): title = 'Pipenv Environments' title_line = Line(title, color=TITLE_COLOR, pad=2) bar_line = Line('-' * len(title), color=TITLE_COLOR, pad=2) blank_line = Line('') return [ blank_line, title_line, bar_line, blank_line]
[docs] def get_lines(self): title_lines = self.get_title_lines() environment_lines = self.get_option_lines() lines = title_lines + environment_lines current_line = self.index + len(title_lines) + 1 return lines, current_line
[docs] def draw(self, debug_info=None): self.screen.clear() pad_top = 0 pad_left = 1 pad_bottom = 4 pad_right = 1 x, y = pad_left, pad_top max_y, max_x = self.screen.getmaxyx() max_rows = max_y - pad_top - pad_bottom max_cols = max_x - pad_right if max_y < 5 or max_x < 10: self.screen.addnstr(0, 0, 'Help!', max_cols) return lines, current_line = self.get_lines() if current_line <= max_rows: visible_lines = lines[:max_rows] else: delta = current_line - max_rows visible_lines = lines[delta:][:max_rows] for n, line in enumerate(visible_lines): line.render(self.screen, x=x, y=y) y += 1 last_line = len(visible_lines) + 1 query = Line('$ {}'.format(self.query), color=SELECTED_OPTION) query.render(self.screen, x=pad_left, y=last_line) if debug_info: self.print_debug_info(debug_info) self.screen.refresh()
[docs] def print_debug_info(self, debug_info): key = debug_info.get('key') char = '' if not key else repr(chr(key)) query = repr(self.query) index = self.index text = "key: '{}' | char: '{}' | index: '{}' | query: '{}'".format( key, char, index, query) max_y, max_x = self.screen.getmaxyx() pos_y = max_y - 1 pos_x = 0 self.screen.addnstr(pos_y, pos_x, text, max_y, 0)
[docs] def run_loop(self): debug_info = None while True: self.draw(debug_info=debug_info) key = self.screen.getch() try: key_string = chr(key) except ValueError: continue if self.debug_mode and key > 0: # when stretching windows, key = -1 debug_info = {'key': key} if key in KEYS_ESCAPE: sys.exit(0) if key in KEYS_ENTER: if not self.environments: continue return self.get_selected() if re.search(r'[A-Za-z0-9\s\-_]', key_string): self.query += key_string self.expanded = 0 for n, environment in enumerate(self.environments): if environment.envname.startswith(self.query): self.index = n break elif key == curses.KEY_PPAGE: self.move_up(5) elif key == curses.KEY_NPAGE: self.move_down(5) elif key in KEYS_UP: self.move_up(1) elif key in KEYS_DOWN: self.move_down(1) elif key in KEYS_HOME: self.move_top() elif key in KEYS_END: self.move_bottom() elif key in KEYS_CLEAR: self.clear_query() elif key in KEYS_RIGHT: self.expand_next() elif key in KEYS_LEFT: self.expand_prev() elif key in KEYS_BACKSPACE: self.query = self.query[:-1]