Exemplo n.º 1
0
    def __enter__(self):
        # init curses and it's wrapper
        self.screen  = curses.initscr()
        self.display = Display(self.screen, self.encoding)

        # create keyhandler
        self.keyhandler = key.KeyHandler(self.screen)

        # create view
        self.view = SelectorView(percol = self)

        # create command
        self.command_candidate = SelectorCommand(self.model_candidate, self.view)
        self.command_action = SelectorCommand(self.model_action, self.view)

        # suppress SIGINT termination
        signal.signal(signal.SIGINT, lambda signum, frame: None)

        # handle special keys like <f1>, <down>, ...
        self.screen.keypad(True)

        curses.raw()
        curses.noecho()
        curses.cbreak()
        # Leave newline mode. Make percol distinguish between "C-m" and "C-j".
        curses.nonl()

        return self
Exemplo n.º 2
0
class Percol(object):
    def __init__(self, descriptors = None, encoding = "utf-8",
                 finder = None, action_finder = None,
                 candidates = None, actions = None,
                 query = None, caret = None, index = None):
        # initialization
        self.global_lock = threading.Lock()
        self.encoding = encoding

        if descriptors is None:
            self.stdin  = sys.stdin
            self.stdout = sys.stdout
            self.stderr = sys.stderr
        else:
            self.stdin  = descriptors["stdin"]
            self.stdout = descriptors["stdout"]
            self.stderr = descriptors["stderr"]

        if finder is None:
            finder = FinderMultiQueryString
        if action_finder is None:
            action_finder = FinderMultiQueryString

        self.actions = actions

        # wraps candidates (iterator)
        from percol.lazyarray import LazyArray
        self.candidates = LazyArray(candidates or [])

        # create model
        self.model_candidate = SelectorModel(percol = self,
                                             collection = self.candidates,
                                             finder = finder,
                                             query = query, caret = caret, index = index)
        self.model_action = SelectorModel(percol = self,
                                          collection = [action.desc for action in actions],
                                          finder = action_finder)
        self.model = self.model_candidate

    def has_no_candidate(self):
        return not self.candidates.has_nth_value(0)

    def has_only_one_candidate(self):
        return self.candidates.has_nth_value(0) and not self.candidates.has_nth_value(1)

    def __enter__(self):
        # init curses and it's wrapper
        self.screen  = curses.initscr()
        self.display = Display(self.screen, self.encoding)

        # create keyhandler
        self.keyhandler = key.KeyHandler(self.screen)

        # create view
        self.view = SelectorView(percol = self)

        # create command
        self.command_candidate = SelectorCommand(self.model_candidate, self.view)
        self.command_action = SelectorCommand(self.model_action, self.view)

        # suppress SIGINT termination
        signal.signal(signal.SIGINT, lambda signum, frame: None)

        # handle special keys like <f1>, <down>, ...
        self.screen.keypad(True)

        curses.raw()
        curses.noecho()
        curses.cbreak()
        # Leave newline mode. Make percol distinguish between "C-m" and "C-j".
        curses.nonl()

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # Back to newline mode (TODO: is it needed?).
        curses.nl()
        curses.endwin()
        self.execute_action()

    args_for_action = None

    def execute_action(self):
        selected_actions = self.model_action.get_selected_results_with_index()

        if selected_actions and self.args_for_action:
            for name, _, act_idx in selected_actions:
                try:
                    action = self.actions[act_idx]
                    if action:
                        action.act([arg for arg, _, _ in self.args_for_action], self)
                except Exception as e:
                    debug.log("execute_action", e)

    # ============================================================ #
    # Statuses
    # ============================================================ #

    @property
    def opposite_model(self):
        """
        Returns opposite model for self.model
        """
        if self.model is self.model_action:
            return self.model_candidate
        else:
            return self.model_action

    def switch_model(self):
        self.model = self.opposite_model

    @property
    def command(self):
        """
        Returns corresponding model wrapper which provides advanced commands
        """
        if self.model is self.model_action:
            return self.command_action
        else:
            return self.command_candidate

    # ============================================================ #
    # Main Loop
    # ============================================================ #

    SEARCH_DELAY = 0.05

    def loop(self):
        self.view.refresh_display()
        self.result_updating_timer = None

        def search_and_refresh_display():
            self.model.do_search(self.model.query)
            self.view.refresh_display()

        while True:
            try:
                self.handle_key(self.screen.getch())

                if self.model.should_search_again():
                    # search again
                    with self.global_lock:
                        # critical section
                        if not self.result_updating_timer is None:
                            # clear timer
                            self.result_updating_timer.cancel()
                            self.result_updating_timer = None

                        # with bounce
                        t = threading.Timer(self.SEARCH_DELAY, search_and_refresh_display)
                        self.result_updating_timer = t
                        t.start()

                self.view.refresh_display()
            except TerminateLoop as e:
                return e.value

    # ============================================================ #
    # Key Handling
    # ============================================================ #

    keymap = {
        "C-i"         : lambda percol: percol.switch_model(),
        # text
        "C-h"         : lambda percol: percol.command.delete_backward_char(),
        "<backspace>" : lambda percol: percol.command.delete_backward_char(),
        "C-w"         : lambda percol: percol.command.delete_backward_word(),
        "C-u"         : lambda percol: percol.command.clear_query(),
        "<dc>"        : lambda percol: percol.command.delete_forward_char(),
        # caret
        "<left>"      : lambda percol: percol.command.backward_char(),
        "<right>"     : lambda percol: percol.command.forward_char(),
        # line
        "<down>"      : lambda percol: percol.command.select_next(),
        "<up>"        : lambda percol: percol.command.select_previous(),
        # page
        "<npage>"     : lambda percol: percol.command.select_next_page(),
        "<ppage>"     : lambda percol: percol.command.select_previous_page(),
        # top / bottom
        "<home>"      : lambda percol: percol.command.select_top(),
        "<end>"       : lambda percol: percol.command.select_bottom(),
        # mark
        "C-SPC"       : lambda percol: percol.command.toggle_mark_and_next(),
        # finish
        "RET"         : lambda percol: percol.finish(), # Is RET never sent?
        "C-m"         : lambda percol: percol.finish(),
        "C-j"         : lambda percol: percol.finish(),
        "C-c"         : lambda percol: percol.cancel()
    }

    def import_keymap(self, keymap, reset = False):
        if reset:
            self.keymap = {}
        else:
            self.keymap = dict(self.keymap)
        for key, cmd in six.iteritems(keymap):
            self.keymap[key] = cmd

    # default
    last_key = None
    def handle_key(self, ch):
        if ch == curses.KEY_RESIZE:
            self.last_key = self.handle_resize(ch)
        elif ch != -1 and self.keyhandler.is_utf8_multibyte_key(ch):
            self.last_key = self.handle_utf8(ch)
        else:
            self.last_key = self.handle_normal_key(ch)

    def handle_resize(self, ch):
        self.display.update_screen_size()
        # XXX: trash -1 (it seems that resize key sends -1)
        self.keyhandler.get_key_for(self.screen.getch())
        return key.SPECIAL_KEYS[ch]

    def handle_utf8(self, ch):
        ukey = self.keyhandler.get_utf8_key_for(ch)
        self.model.insert_string(ukey)
        return ukey.encode(self.encoding)

    def handle_normal_key(self, ch):
        k = self.keyhandler.get_key_for(ch)
        if k in self.keymap:
            self.keymap[k](self)
        elif self.keyhandler.is_displayable_key(ch):
            self.model.insert_char(ch)
        return k

    # ------------------------------------------------------------ #
    # Finish / Cancel
    # ------------------------------------------------------------ #

    def finish(self):
        # save selected candidates and use them later (in execute_action)
        raise TerminateLoop(self.finish_with_exit_code())          # success

    def cancel(self):
        raise TerminateLoop(self.cancel_with_exit_code())          # failure

    def finish_with_exit_code(self):
        self.args_for_action = self.model_candidate.get_selected_results_with_index()
        return 0

    def cancel_with_exit_code(self):
        return 1