# -*- coding: utf-8 -*-
# wasp_general/cli/curses.py
#
# Copyright (C) 2016 the wasp-general authors and contributors
# <see AUTHORS file>
#
# This file is part of wasp-general.
#
# Wasp-general is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Wasp-general is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with wasp-general. If not, see <http://www.gnu.org/licenses/>.
# TODO: document the code
# TODO: write tests for the code
# noinspection PyUnresolvedReferences
from wasp_general.version import __author__, __version__, __credits__, __license__, __copyright__, __email__
# noinspection PyUnresolvedReferences
from wasp_general.version import __status__
from abc import ABCMeta, abstractmethod
import curses
from wasp_general.verify import verify_type
from wasp_general.cli.cli import WConsoleWindowProto, WConsoleProto, WConsoleBase, WConsoleWindowBase
from wasp_general.cli.cli import WConsoleDrawerProto
from wasp_general.command.command import WCommandSet
[docs]class WCursesWindow(WConsoleWindowBase):
[docs] class EmptyWindowDrawer(WConsoleDrawerProto):
""" WConsoleWindowProto.DrawerProto implementation. Suites if there is nothing to display
"""
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def suitable(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.suitable` method implementation
"""
if len(window.list_data(previous_data=True, console_row=True)) == 0:
return True
return False
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def draw(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.draw` method implementation
"""
window.set_cursor(0, 0)
[docs] class SmallWindowDrawer(WConsoleDrawerProto):
""" WConsoleWindowProto.DrawerProto implementation. Suites if there is content and content fits window
width and height
"""
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def suitable(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.suitable` method implementation
"""
if prompt_show is True:
lines = len(window.list_data(previous_data=True, console_row=True))
else:
lines = len(window.list_data(previous_data=True))
if 1 <= lines < (window.height() - 1):
return True
return False
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def draw(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.draw` method implementation
"""
if prompt_show is True:
data_lines = window.list_data(previous_data=True, console_row=True)
else:
data_lines = window.list_data(previous_data=True)
window.write_data(data_lines)
if prompt_show is True:
data_lines_to_cursor = window.list_data(
previous_data=True, console_row_to_cursor=True
)
y = len(data_lines_to_cursor) - 1
line_length = len(window.console().prompt()) + window.cursor()
row_lines_to_cursor = window.list_data(console_row_to_cursor=True)
line_length += (len(row_lines_to_cursor) - 1) # append one char offset
x = line_length % window.width()
else:
y = 0
x = 0
window.set_cursor(y, x)
[docs] class BigWindowDrawer(WConsoleDrawerProto):
""" WConsoleWindowProto.DrawerProto implementation. Suites if content and even current row doesn't fit
window width and height
"""
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def suitable(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.suitable` method implementation
"""
if prompt_show is True:
console_row_lines = len(window.list_data(console_row=True))
if console_row_lines >= (window.height() - 1):
return True
return False
[docs] @verify_type('paranoid', window=WConsoleWindowProto, prompt_show=bool)
def draw(self, window, prompt_show=True):
""" :meth:`WConsoleWindowProto.DrawerProto.draw` method implementation
"""
assert(prompt_show is True)
height = window.height()
lines = window.list_data(console_row=True)
lines_to_cursor = window.list_data(console_row_to_cursor=True)
lines_from_cursor = window.list_data(console_row_from_cursor=True)
output_lines = []
if len(lines_from_cursor) == 0:
start = len(lines) - (height - 1)
output_lines.extend(lines[start:])
y = height - 2
elif len(lines_from_cursor) < (height - 1):
start = (len(lines)) - (height - 1) - (len(lines_from_cursor) - 1)
output_lines.extend(lines[start:start + (height - 1)])
y = (height - 1) - (len(lines) - len(lines_to_cursor)) - 1
else:
start = 0
if len(lines_to_cursor) > 0:
start = len(lines_to_cursor) - 1
output_lines.extend(lines[start:(start + (height - 1))])
y = 0
window.write_data(output_lines)
line_length = len(window.console().prompt()) + window.cursor()
line_length += (len(lines_to_cursor) - 1) # append one char offset
x = line_length % window.width()
window.set_cursor(y, x)
@verify_type('paranoid', console=WConsoleProto)
def __init__(self, console):
WConsoleWindowBase.__init__(
self, console, WCursesWindow.EmptyWindowDrawer(), WCursesWindow.SmallWindowDrawer(),
WCursesWindow.ScrolledWindowDrawer(), WCursesWindow.BigWindowDrawer()
)
[docs] def width(self):
return self.console().screen().getmaxyx()[1]
[docs] def height(self):
return self.console().screen().getmaxyx()[0]
[docs] def clear(self):
return self.console().screen().erase()
[docs] def write_line(self, line_index, line):
self.console().screen().addstr(line_index, 0, line)
[docs] @verify_type('paranoid', prompt_show=bool)
def refresh(self, prompt_show=True):
WConsoleWindowBase.refresh(self, prompt_show=prompt_show)
self.console().screen().refresh()
[docs] def set_cursor(self, y, x):
self.console().screen().move(y, x)
[docs]class WCursesKeyAction(metaclass=ABCMeta):
def __init__(self, key):
self.key = key
[docs] @abstractmethod
def action(self, console_meta):
raise NotImplementedError('This method is abstract')
def __call__(self, key, console_meta):
if self.key == key:
self.action(console_meta)
[docs]class WCursesKeyUp(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_UP)
[docs] def action(self, console_meta):
history = console_meta.history()
if history.size() == 0:
return
if not console_meta.history_mode():
console_meta.history_mode(True)
history.position(history.size() - 1)
console_meta.window().cursor(len(console_meta.row()))
elif history.position() > 0:
history.position(history.position() - 1)
console_meta.window().cursor(len(console_meta.row()))
else:
return
console_meta.refresh_window()
[docs]class WCursesKeyDown(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_DOWN)
[docs] def action(self, console_meta):
if not console_meta.history_mode():
return
history = console_meta.history()
history_size = history.size()
if history.position() < (history_size - 1):
history.position(history.position() + 1)
console_meta.window().cursor(len(console_meta.row()))
else:
console_meta.history_mode(False)
console_meta.window().cursor(len(console_meta.row()))
console_meta.refresh_window()
[docs]class WCursesKeyLeft(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_LEFT)
[docs] def action(self, console_meta):
cursor_position = console_meta.window().cursor()
if cursor_position > 0 and len(console_meta.row()):
cursor_position -= 1
console_meta.window().cursor(cursor_position)
console_meta.refresh_window()
[docs]class WCursesKeyRight(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_RIGHT)
[docs] def action(self, console_meta):
cursor_position = console_meta.window().cursor()
if cursor_position < len(console_meta.row()):
cursor_position += 1
console_meta.window().cursor(cursor_position)
console_meta.refresh_window()
[docs]class WCursesKeyBackspace(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_BACKSPACE)
[docs] def action(self, console_meta):
position = console_meta.window().cursor()
command = console_meta.row()
if 0 < position <= len(command):
command = command[:(position - 1)] + command[position:]
console_meta.window().cursor(position - 1)
console_meta.update_row(command)
console_meta.refresh_window()
[docs]class WCursesKeyDelete(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_DC)
[docs] def action(self, console_meta):
position = console_meta.window().cursor()
command = console_meta.row()
if 0 <= position < len(command):
command = command[:position] + command[(position + 1):]
console_meta.update_row(command)
console_meta.refresh_window()
[docs]class WCursesKeyResize(WCursesKeyAction):
def __init__(self):
WCursesKeyAction.__init__(self, curses.KEY_RESIZE)
[docs] def action(self, console_meta):
console_meta.refresh_window()
[docs]class WCursesConsole(WConsoleBase):
@verify_type('paranoid', command_set=(WCommandSet, None))
def __init__(self, command_set=None):
WConsoleBase.__init__(self, command_set=command_set)
self.__screen = curses.initscr()
self.__screen.keypad(True)
self.__window = WCursesWindow(self)
self.__key_actions = [
WCursesKeyUp(), WCursesKeyDown(), WCursesKeyLeft(), WCursesKeyRight(), WCursesKeyBackspace(),
WCursesKeyDelete(), WCursesKeyResize()
]
self.__stop_flag = True
[docs] def window(self):
return self.__window
[docs] def screen(self):
return self.__screen
[docs] def stop(self):
self.__stop_flag = True
[docs] def start(self):
self.__stop_flag = False
while self.__stop_flag is False:
self.start_session()
while True:
pressed_key = self.screen().get_wch()
if isinstance(pressed_key, str):
if pressed_key == '\n':
break
command = self.row()
position = self.window().cursor()
command = command[:position] + pressed_key + command[position:]
self.update_row(command)
self.window().cursor(position + 1)
self.refresh_window()
elif isinstance(pressed_key, int):
for action in self.__key_actions:
action(pressed_key, self)
self.fin_session()
def __del__(self):
curses.endwin()