示例#1
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:
                debug.log(self.args_for_action[0][0])
                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)
            debug.log("here again %s, %s"%(ch,k))
        elif self.keyhandler.is_displayable_key(ch):
            self.model.insert_char(ch)
        return k

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

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

    def finish(self, value=0, field=2):
        # save selected candidates and use them later (in execute_action)
        raise TerminateLoop(self.finish_with_exit_code_f(value,field=field))     # success

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

    def finish_with_exit_code(self, value):
        self.args_for_action = self.model_candidate.get_selected_results_with_index(sep=self.model.finder.sep)
        return value

    def finish_with_exit_code_f(self, value,field=None):
        self.args_for_action = self.model_candidate.get_selected_results_with_index_f(field=field,sep=self.model.finder.sep)
        return value

    def cancel_with_exit_code(self):
        return 1

    def finish_and_save(self):
        # self.model.query_mode = False # giving up on custom file names for now
        rerun = open('rerun.sh','w')
        for line in self.model.stack:
            rerun.writelines(line+'\n')
        rerun.close()
        raise TerminateLoop(0)
示例#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,
                 host = None, field_sep = 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

        finder.host = host
        finder.localhost = host
        finder.sep = field_sep

        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_f(sep=self.model.finder.sep)
        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:
                # debug.log(f'init.py 122, {self.args_for_action[0][0]}')
                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("init.py, 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, value=0):
    #     # save selected candidates and use them later (in execute_action)
    #     raise TerminateLoop(self.finish_with_exit_code(value))     # success

    def finish(self, value=0, field=2):
        # save selected candidates and use them later (in execute_action)
        raise TerminateLoop(self.finish_with_exit_code_f(value,field=field))     # success

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

    def finish_with_exit_code(self, value):
        self.args_for_action = self.model_candidate.get_selected_results_with_index(sep=self.model.finder.sep)
        return value

    def finish_with_exit_code_f(self, value,field=None):
        self.args_for_action = self.model_candidate.get_selected_results_with_index_f(field=field,sep=self.model.finder.sep)
        return value

    def cancel_with_exit_code(self):
        return 1

    def finish_and_save(self):
        # self.model.query_mode = False # giving up on custom file names for now
        rerun = open('rerun.sh','w')
        for line in self.model.stack:
            rerun.writelines(line+'\n')
        rerun.close()
        raise TerminateLoop(0)