本文来自依云's Blog,转载请注明。
有一天,我发现了一个很好用的 Python shell——bpython。它使用了 ncurses 来做界面,使用了 pygments 来高亮代码,怎么看都比 ipython 漂亮,更不用说 Python 自己的了。不过既然它使用了 ncurses,麻烦也就来了——ncurses 不支持 readline!虽然有些模拟,但终究是不好用,M-f M-b不起使用,M-数字也不能用。于是我再次去 google 同时使用 ncurses 和 readline 这两个库的办法。
功夫不负有心人,这次终于 google 到了点有用的东西:
The basic idea is to use call rl_callback_read_char() when input from the user is available (determined with select, or similar), then print 'rl_line_buffer' as you would any other string in ncurses, and optionally set A_REVERSE on the position indicated by rl_point. (or just reposition the cursor I guess, either works...)
不过可惜的是,这封邮件给出的代码在我这里并没有跑起来。其实跑起来了也用处不大,因为我需要的那部分代码独立性太差了,还是得重写。
花了一个下午,我终于弄出了一个雏形。
首先,这个rl_callback_read_char()
是这么用的(文档):
#include<stdio.h> #include<readline/readline.h> int main(void){ int cont = 1; void callback(char *text){ if(text == NULL){ rl_callback_handler_remove(); putchar('\n'); cont = 0; }else{ printf("%s.\n", text); } } rl_callback_handler_install(">> ", callback); while(cont){ rl_callback_read_char(); } return 0; }
首先安装个回调函数,它将在 readline 读取到一行内容时调用。当标准输入可用的时候,调用rl_callback_read_char()
来读取字符。另外注意,这里我用了 gcc 的嵌套函数支持,免得弄出不少全局变量。
知道怎么用rl_callback_read_char()
之后,就可以按那封邮件所说的,把它和 ncurses 联合起来了。代码修改自NCURSES Programming HOWTO。思路很简单,readline 负责读取并处理用户输入,显示是自己处理的。不过作为中文用户,纠结了半天的中文问题。最开始是有了 ncurses 之后,中文显示异常。这个是通过setlocale
解决的。然后又是光标放的位置不对。于是又用上了我同样不熟悉的 wchar,自己计算光标应该放在哪里。
#define _XOPEN_SOURCE 700 /* for wcswidth and 700 is for mbsnrtowcs */ #include<wchar.h> #include<ncurses.h> /* ncurses.h includes stdio.h */ #include<stdlib.h> #include<string.h> #include<readline/readline.h> #include<locale.h> int mygetstr(char *str, int y, int x){ WINDOW *win; int size, col; int ok = 0; int width; wchar_t wstr[80]; char *p; getmaxyx(stdscr, size, col); void getaline(char *s){ str = s; rl_callback_handler_remove(); ok = 1; } rl_callback_handler_install("", getaline); win = newwin(1, col-x, y, x); while(1){ rl_callback_read_char(); if(ok) break; werase(win); strncpy(str, rl_line_buffer, 80); p = str; /* how many column chars before cursor occupies? */ size = mbsnrtowcs(wstr, (const char**)&p, rl_point, 80, NULL); width = wcswidth(wstr, size); mvwprintw(win, 0, 0, "%s", str); /* put the cursor at right column */ wmove(win, 0, width); wrefresh(win); } delwin(win); return 0; } int main(){ char mesg[] = "Enter a string: "; char str[80]; int row, col; setlocale(LC_ALL, ""); /* make ncurses handle Chinese correctly */ initscr(); getmaxyx(stdscr, row, col); mvprintw(row / 2, (col - strlen(mesg)) / 2, "%s", mesg); refresh(); mygetstr(str, row / 2, (col + strlen(mesg)) / 2); mvprintw(LINES - 2, 0, "You Entered: %s", str); getch(); endwin(); return 0; }
注意:此代码只是演示用,缓冲区溢出什么的我都没处理。
终于搞定了 C 下结合两者的使用,接下来 Python 版思路是有了,但因为其标准库 readline 中没有提供rl_callback_read_char()
函数,所以只能用 ctypes 了。下面是在 Python 里使用rl_callback_read_char()
的示例,ncurses 部分我暂时不想折腾了。
#!/usr/bin/env python3 # vim:fileencoding=utf-8 import sys import readline import ctypes import ctypes.util rllib_path = ctypes.util.find_library('readline') rllib = ctypes.CDLL(rllib_path) def callback(s): if s is None: rllib.rl_callback_handler_remove() sys.stdout.write('\n') sys.exit() elif not s: pass else: print('%s.' % s.decode()) # 这样也可以 # print(readline.get_line_buffer()) cbfunc = ctypes.CFUNCTYPE(None, ctypes.c_char_p) rllib.rl_callback_handler_install.restype = None rllib.rl_callback_handler_install(ctypes.c_char_p(b">> "), cbfunc(callback)) while True: rllib.rl_callback_read_char()
2011年8月4日更新:今天终于完成了个 quick and dirty 的 Python 版:
#!/usr/bin/env python3 # vim:fileencoding=utf-8 import sys import readline import ctypes import ctypes.util import curses import struct from 字符集 import width rllib_path = ctypes.util.find_library('readline') rllib = ctypes.CDLL(rllib_path) def getstr(win, y, x): _, col = win.getmaxyx() inputbox = curses.newwin(1, col-x, y, x) ret = '' ok = False def callback(s): nonlocal ok, ret if s is None: rllib.rl_callback_handler_remove() raise EOFError elif not s: ok = True else: ret = s.decode() ok = True cbfunc = ctypes.CFUNCTYPE(None, ctypes.c_char_p) rllib.rl_callback_handler_install.restype = None rllib.rl_callback_handler_install(ctypes.c_char_p(b""), cbfunc(callback)) while True: rllib.rl_callback_read_char() if ok: break inputbox.erase() # 这样获取的值不对。。。 # bbuf = ctypes.string_at(rllib.rl_line_buffer) buf = readline.get_line_buffer() bbuf = buf.encode() inputbox.addstr(0, 0, buf) rl_point = struct.unpack('I', ctypes.string_at(rllib.rl_point, 4))[0] w = width(bbuf[:rl_point].decode()) inputbox.move(0, w) inputbox.refresh() del inputbox return ret msg = '输入字符串:' win = curses.initscr() curses.noecho() row, col = win.getmaxyx() win.addstr(row // 2, (col - width(msg)) // 2, msg) win.refresh() s = getstr(win, row // 2, (col + width(msg)) // 2) win.addstr(row - 2, 0, '你输入了: ' + s) win.getch() curses.endwin()
Apr 24, 2012 11:09:25 AM
博主是做什么工作的? 这些文章看不懂。
Apr 26, 2012 03:21:58 PM
我也好奇许久,依云仙子活跃在 vim / linux / python 各大社区,实乃热心同志,关于页面却没有什么实质性内容…
Aug 06, 2017 03:18:07 PM
博主你好,我想问下使用ncurses的getstr(),我输入的汉字,删除的话要按三下,该怎么办呢。
Aug 06, 2017 10:42:50 PM
是支持 UTF-8 的 ncurses 吗?(ncursesw)
Aug 06, 2017 11:51:01 PM
不是ncursesw吧,我是通过setlocale()来显示中文的。