分享一个 Python 命令行下列表选择

2020-11-25 17:56:25 +08:00
 lswang

可以通过上下键 + Enter 来选择,Ctrl+C 取消

[注意] 只能支持 linux + macos

超过 20 个数据,会出现 ==== ^ ======== v ==== 提示还有额外数据

# -*- coding: utf-8 -*-
import sys
import termios
import tty


left = u'\u001b[1000D'
right = u'\u001b[1000C'
clear_line = u'\u001b[2K'
up = u'\u001b[1A'
down = u'\u001b[1B'
clear_to_bottom = u'\u001b[J'

MAX_SHOW_COUNT = 20

class CommandSingleSelector:
    selectors = None
    cur_index = 0
    cursor_index = -1
    header_word = None
    is_cancel = False
    top_index = 0
    bottom_index = 0

    def __init__(self, selectors, header_word):
        self.selectors = selectors
        if len(selectors) > MAX_SHOW_COUNT:
            self.bottom_index = MAX_SHOW_COUNT
        else:
            self.bottom_index = len(selectors) - 1

        self.header_word = header_word

    def up(self):
        if self.cur_index > 0:
            self.cur_index = self.cur_index - 1
            if self.cur_index < self.top_index:
                self.top_index = self.top_index - 1
                self.bottom_index = self.bottom_index - 1
            self.print_multi_line()

    def down(self):
        if self.cur_index < len(self.selectors) - 1:
            self.cur_index = self.cur_index + 1
            if self.cur_index > self.bottom_index:
                self.top_index = self.top_index + 1
                self.bottom_index = self.bottom_index + 1
            self.print_multi_line()

    def exit(self):
        self.clear_multi_line()

    def get_selector(self):
        sys.stdout.write(self.header_word + '\n')
        self.print_multi_line()
        is_multi_first = False
        is_multi_second = False

        while True:
            fd = sys.stdin.fileno()
            old_settings = termios.tcgetattr(fd)
            try:
                tty.setraw(sys.stdin.fileno())
                ch = sys.stdin.read(1)
                as_code = ord(ch)

                # find the multi key for up/down
                if as_code == 27:
                    is_multi_first = True
                elif as_code == 91:
                    if is_multi_first:
                        is_multi_second = True
                elif as_code == 65:
                    if is_multi_first and is_multi_second:
                        self.up()
                    is_multi_second = False
                    is_multi_first = False
                elif as_code == 66:
                    if is_multi_first and is_multi_second:
                        self.down()
                    is_multi_second = False
                    is_multi_first = False
                elif as_code == 13:
                    is_multi_second = False
                    is_multi_first = False
                    self.exit()
                    break
                elif as_code == 3:
                    is_multi_second = False
                    is_multi_first = False
                    self.exit()
                    self.is_cancel = True
                    break
                else:
                    is_multi_second = False
                    is_multi_first = False
            finally:
                termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

        if self.is_cancel:
            raise Exception('Cancel this operation.')

        return self.selectors[self.cur_index]

    def print_multi_line(self):
        sys.stdout.write((self.cursor_index + 1) * up + left)
        sys.stdout.write(clear_to_bottom)

        up_size = self.bottom_index - self.cur_index + 1 + 1 + 1 # first and last word

        has_first_sign = '          '
        if self.top_index > 0:
            has_first_sign = '   ==== ^ ===='

        sys.stdout.write(left + clear_line + has_first_sign + '\n')


        for index, item in enumerate(self.selectors):
            if self.top_index <= index <= self.bottom_index:
                first_word = '   '
                if index == self.cur_index:
                    first_word = ' > '
                    sys.stdout.write(left + clear_line + first_word + item + '\n')
                else:
                    sys.stdout.write(left + clear_line + first_word + item + '\n')
        has_last_sign = '          '
        if len(self.selectors) - 1 > self.bottom_index:
            has_last_sign = '   ==== v ===='
        sys.stdout.write(left + clear_line + has_last_sign + '\n')

        back_to_cur_index = up * (up_size -1)
        self.cursor_index = self.cur_index - self.top_index
        sys.stdout.write(back_to_cur_index)
        sys.stdout.write(left)

    def clear_multi_line(self):
        back_to_top = up * (self.cur_index - self.top_index + 2)
        sys.stdout.write(back_to_top)
        sys.stdout.write(left)
        sys.stdout.write(clear_to_bottom)


if __name__ == '__main__':
    selectors = ['HP Printer', 'XiaoMi Printer','DELL Printer']
    command_selector = CommandSingleSelector(selectors=selectors, header_word='Please Select One Printer: ')
    try:
        sel = command_selector.get_selector()
        print sel
    except Exception, e:
        print e.message

2239 次点击
所在节点    Python
4 条回复
lisniuse
2020-11-25 21:11:24 +08:00
可以来这里分享: https://www.kuxai.com/forum
ClericPy
2020-11-25 22:11:21 +08:00
以前倒是折腾过类似 terminal UI 的, 有三个库印象深刻 PyInquirer, console-menu, questionary

还好我只 star 最好的, 所以还能找到
renmu123
2020-11-25 22:40:45 +08:00
我最近再用一个 poetry 出品的 celo 工具,也是类似的交互式命令行工具,相比楼上的几个,弱化了交互式组件的部分,增强了作为命令行的部分(其实完全可以拼一起造个融合怪)
frostming
2020-11-26 09:42:05 +08:00
Paging 嘛,rich 有现成的支持,prompt_toolkit 也有

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/729199

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX