2013-10-12 01:13:07 +08:00
|
|
|
##===-- cui.py -----------------------------------------------*- Python -*-===##
|
|
|
|
##
|
|
|
|
## The LLVM Compiler Infrastructure
|
|
|
|
##
|
|
|
|
## This file is distributed under the University of Illinois Open Source
|
|
|
|
## License. See LICENSE.TXT for details.
|
|
|
|
##
|
|
|
|
##===----------------------------------------------------------------------===##
|
|
|
|
|
2013-10-10 06:11:30 +08:00
|
|
|
import curses
|
|
|
|
import curses.ascii
|
|
|
|
import threading
|
|
|
|
|
|
|
|
class CursesWin(object):
|
|
|
|
def __init__(self, x, y, w, h):
|
|
|
|
self.win = curses.newwin(h, w, y, x)
|
|
|
|
self.focus = False
|
|
|
|
|
|
|
|
def setFocus(self, focus):
|
|
|
|
self.focus = focus
|
|
|
|
def getFocus(self):
|
|
|
|
return self.focus
|
|
|
|
def canFocus(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def handleEvent(self, event):
|
|
|
|
return
|
|
|
|
def draw(self):
|
|
|
|
return
|
|
|
|
|
|
|
|
class TextWin(CursesWin):
|
|
|
|
def __init__(self, x, y, w):
|
|
|
|
super(TextWin, self).__init__(x, y, w, 1)
|
|
|
|
self.win.bkgd(curses.color_pair(1))
|
|
|
|
self.text = ''
|
|
|
|
self.reverse = False
|
|
|
|
|
|
|
|
def canFocus(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def draw(self):
|
|
|
|
w = self.win.getmaxyx()[1]
|
|
|
|
text = self.text
|
|
|
|
if len(text) > w:
|
|
|
|
#trunc_length = len(text) - w
|
|
|
|
text = text[-w+1:]
|
|
|
|
if self.reverse:
|
|
|
|
self.win.addstr(0, 0, text, curses.A_REVERSE)
|
|
|
|
else:
|
|
|
|
self.win.addstr(0, 0, text)
|
|
|
|
self.win.noutrefresh()
|
|
|
|
|
|
|
|
def setReverse(self, reverse):
|
|
|
|
self.reverse = reverse
|
|
|
|
|
|
|
|
def setText(self, text):
|
|
|
|
self.text = text
|
|
|
|
|
|
|
|
class TitledWin(CursesWin):
|
|
|
|
def __init__(self, x, y, w, h, title):
|
|
|
|
super(TitledWin, self).__init__(x, y+1, w, h-1)
|
|
|
|
self.title = title
|
|
|
|
self.title_win = TextWin(x, y, w)
|
|
|
|
self.title_win.setText(title)
|
|
|
|
self.draw()
|
|
|
|
|
|
|
|
def setTitle(self, title):
|
|
|
|
self.title_win.setText(title)
|
|
|
|
|
|
|
|
def draw(self):
|
|
|
|
self.title_win.setReverse(self.getFocus())
|
|
|
|
self.title_win.draw()
|
|
|
|
self.win.noutrefresh()
|
|
|
|
|
|
|
|
class InputHandler(threading.Thread):
|
|
|
|
def __init__(self, screen, queue):
|
|
|
|
super(InputHandler, self).__init__()
|
|
|
|
self.screen = screen
|
|
|
|
self.queue = queue
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while True:
|
|
|
|
c = self.screen.getch()
|
|
|
|
self.queue.put(c)
|
|
|
|
|
|
|
|
|
|
|
|
class CursesUI(object):
|
|
|
|
""" Responsible for updating the console UI with curses. """
|
|
|
|
def __init__(self, screen, event_queue):
|
|
|
|
self.screen = screen
|
|
|
|
self.event_queue = event_queue
|
|
|
|
|
|
|
|
curses.start_color()
|
|
|
|
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLUE)
|
|
|
|
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
|
|
|
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
|
|
|
|
self.screen.bkgd(curses.color_pair(1))
|
|
|
|
self.screen.clear()
|
|
|
|
|
|
|
|
self.input_handler = InputHandler(self.screen, self.event_queue)
|
|
|
|
self.input_handler.daemon = True
|
|
|
|
|
|
|
|
self.focus = 0
|
|
|
|
|
|
|
|
self.screen.refresh()
|
|
|
|
|
|
|
|
def focusNext(self):
|
|
|
|
self.wins[self.focus].setFocus(False)
|
|
|
|
old = self.focus
|
|
|
|
while True:
|
|
|
|
self.focus += 1
|
|
|
|
if self.focus >= len(self.wins):
|
|
|
|
self.focus = 0
|
|
|
|
if self.wins[self.focus].canFocus():
|
|
|
|
break
|
|
|
|
self.wins[self.focus].setFocus(True)
|
|
|
|
|
|
|
|
def handleEvent(self, event):
|
|
|
|
if isinstance(event, int):
|
|
|
|
if event == curses.KEY_F3:
|
|
|
|
self.focusNext()
|
|
|
|
|
|
|
|
def eventLoop(self):
|
|
|
|
|
|
|
|
self.input_handler.start()
|
|
|
|
self.wins[self.focus].setFocus(True)
|
|
|
|
|
|
|
|
while True:
|
|
|
|
self.screen.noutrefresh()
|
|
|
|
|
|
|
|
for i, win in enumerate(self.wins):
|
|
|
|
if i != self.focus:
|
|
|
|
win.draw()
|
|
|
|
# Draw the focused window last so that the cursor shows up.
|
|
|
|
if self.wins:
|
|
|
|
self.wins[self.focus].draw()
|
|
|
|
curses.doupdate() # redraw the physical screen
|
|
|
|
|
|
|
|
event = self.event_queue.get()
|
|
|
|
|
|
|
|
for win in self.wins:
|
|
|
|
if isinstance(event, int):
|
|
|
|
if win.getFocus() or not win.canFocus():
|
|
|
|
win.handleEvent(event)
|
|
|
|
else:
|
|
|
|
win.handleEvent(event)
|
|
|
|
self.handleEvent(event)
|
|
|
|
|
|
|
|
class CursesEditLine(object):
|
|
|
|
""" Embed an 'editline'-compatible prompt inside a CursesWin. """
|
|
|
|
def __init__(self, win, history, enterCallback, tabCompleteCallback):
|
|
|
|
self.win = win
|
|
|
|
self.history = history
|
|
|
|
self.enterCallback = enterCallback
|
|
|
|
self.tabCompleteCallback = tabCompleteCallback
|
|
|
|
|
|
|
|
self.prompt = ''
|
|
|
|
self.content = ''
|
|
|
|
self.index = 0
|
|
|
|
self.startx = -1
|
|
|
|
self.starty = -1
|
|
|
|
|
|
|
|
def draw(self, prompt=None):
|
|
|
|
if not prompt:
|
|
|
|
prompt = self.prompt
|
|
|
|
(h, w) = self.win.getmaxyx()
|
|
|
|
if (len(prompt) + len(self.content)) / w + self.starty >= h-1:
|
|
|
|
self.win.scroll(1)
|
|
|
|
self.starty -= 1
|
|
|
|
if self.starty < 0:
|
|
|
|
raise RuntimeError('Input too long; aborting')
|
|
|
|
(y, x) = (self.starty, self.startx)
|
|
|
|
|
|
|
|
self.win.move(y, x)
|
|
|
|
self.win.clrtobot()
|
|
|
|
self.win.addstr(y, x, prompt)
|
|
|
|
remain = self.content
|
|
|
|
self.win.addstr(remain[:w-len(prompt)])
|
|
|
|
remain = remain[w-len(prompt):]
|
|
|
|
while remain != '':
|
|
|
|
y += 1
|
|
|
|
self.win.addstr(y, 0, remain[:w])
|
|
|
|
remain = remain[w:]
|
|
|
|
|
|
|
|
length = self.index + len(prompt)
|
|
|
|
self.win.move(self.starty + length / w, length % w)
|
|
|
|
|
|
|
|
def showPrompt(self, y, x, prompt=None):
|
|
|
|
self.content = ''
|
|
|
|
self.index = 0
|
|
|
|
self.startx = x
|
|
|
|
self.starty = y
|
|
|
|
self.draw(prompt)
|
|
|
|
|
|
|
|
def handleEvent(self, event):
|
|
|
|
if not isinstance(event, int):
|
|
|
|
return # not handled
|
|
|
|
key = event
|
|
|
|
|
|
|
|
if self.startx == -1:
|
|
|
|
raise RuntimeError('Trying to handle input without prompt')
|
|
|
|
|
|
|
|
if key == curses.ascii.NL:
|
|
|
|
self.enterCallback(self.content)
|
|
|
|
elif key == curses.ascii.TAB:
|
|
|
|
self.tabCompleteCallback(self.content)
|
|
|
|
elif curses.ascii.isprint(key):
|
|
|
|
self.content = self.content[:self.index] + chr(key) + self.content[self.index:]
|
|
|
|
self.index += 1
|
|
|
|
elif key == curses.KEY_BACKSPACE or key == curses.ascii.BS:
|
|
|
|
if self.index > 0:
|
|
|
|
self.index -= 1
|
|
|
|
self.content = self.content[:self.index] + self.content[self.index+1:]
|
|
|
|
elif key == curses.KEY_DC or key == curses.ascii.DEL or key == curses.ascii.EOT:
|
|
|
|
self.content = self.content[:self.index] + self.content[self.index+1:]
|
|
|
|
elif key == curses.ascii.VT: # CTRL-K
|
|
|
|
self.content = self.content[:self.index]
|
|
|
|
elif key == curses.KEY_LEFT or key == curses.ascii.STX: # left or CTRL-B
|
|
|
|
if self.index > 0:
|
|
|
|
self.index -= 1
|
|
|
|
elif key == curses.KEY_RIGHT or key == curses.ascii.ACK: # right or CTRL-F
|
|
|
|
if self.index < len(self.content):
|
|
|
|
self.index += 1
|
|
|
|
elif key == curses.ascii.SOH: # CTRL-A
|
|
|
|
self.index = 0
|
|
|
|
elif key == curses.ascii.ENQ: # CTRL-E
|
|
|
|
self.index = len(self.content)
|
|
|
|
elif key == curses.KEY_UP or key == curses.ascii.DLE: # up or CTRL-P
|
|
|
|
self.content = self.history.previous(self.content)
|
|
|
|
self.index = len(self.content)
|
|
|
|
elif key == curses.KEY_DOWN or key == curses.ascii.SO: # down or CTRL-N
|
|
|
|
self.content = self.history.next()
|
|
|
|
self.index = len(self.content)
|
|
|
|
self.draw()
|