screen: move some stuff in separate modules
This commit is contained in:
parent
6d676d0471
commit
e6686265d7
61
bebop/command_line.py
Normal file
61
bebop/command_line.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
import curses
|
||||
import curses.textpad
|
||||
|
||||
|
||||
class CommandLine:
|
||||
|
||||
def __init__(self, window):
|
||||
self.window = window
|
||||
self.textbox = None
|
||||
|
||||
def clear(self):
|
||||
self.window.clear()
|
||||
self.window.refresh()
|
||||
|
||||
def focus(self, command_char, validator=None, prefix=""):
|
||||
"""Give user focus to the command bar.
|
||||
|
||||
Show the command char and give focus to the command textbox. The
|
||||
validator function is passed to the textbox.
|
||||
|
||||
Arguments:
|
||||
- command_char: char to display before the command line.
|
||||
- validator: function to use to validate the input chars.
|
||||
- prefix: string to insert before the cursor in the command line.
|
||||
|
||||
Returns:
|
||||
User input as string. The string will be empty if the validator raised
|
||||
an EscapeInterrupt.
|
||||
"""
|
||||
self.window.clear()
|
||||
self.window.refresh()
|
||||
self.textbox = curses.textpad.Textbox(self.window)
|
||||
self.window.addstr(command_char + prefix)
|
||||
curses.curs_set(1)
|
||||
try:
|
||||
command = self.textbox.edit(validator)[1:]
|
||||
except EscapeCommandInterrupt:
|
||||
command = ""
|
||||
except TerminateCommandInterrupt as exc:
|
||||
command = exc.command
|
||||
curses.curs_set(0)
|
||||
self.window.clear()
|
||||
self.window.refresh()
|
||||
return command
|
||||
|
||||
def gather(self):
|
||||
"""Return the string currently written by the user in command line."""
|
||||
return self.textbox.gather()[1:].rstrip()
|
||||
|
||||
|
||||
class EscapeCommandInterrupt(Exception):
|
||||
"""Signal that ESC has been pressed during command line."""
|
||||
pass
|
||||
|
||||
|
||||
class TerminateCommandInterrupt(Exception):
|
||||
"""Signal that validation ended command line input early. Use `command`."""
|
||||
|
||||
def __init__(self, command: str, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.command = command
|
|
@ -1,4 +1,3 @@
|
|||
import re
|
||||
import urllib.parse
|
||||
|
||||
|
||||
|
|
76
bebop/page.py
Normal file
76
bebop/page.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import curses
|
||||
|
||||
from bebop.gemtext import parse_gemtext
|
||||
from bebop.rendering import format_elements, render_lines
|
||||
|
||||
|
||||
class Page:
|
||||
"""Window containing page content."""
|
||||
|
||||
MAX_COLS = 1000
|
||||
|
||||
def __init__(self, initial_num_lines):
|
||||
self.dim = (initial_num_lines, Page.MAX_COLS)
|
||||
self.pad = curses.newpad(*self.dim)
|
||||
self.pad.scrollok(True)
|
||||
self.pad.idlok(True)
|
||||
self.metalines = []
|
||||
self.current_line = 0
|
||||
self.current_column = 0
|
||||
self.links = {}
|
||||
|
||||
def show_gemtext(self, gemtext: bytes):
|
||||
"""Render Gemtext data in the content pad."""
|
||||
elements = parse_gemtext(gemtext)
|
||||
self.metalines = format_elements(elements, 80)
|
||||
self.links = {
|
||||
meta["link_id"]: meta["url"]
|
||||
for meta, _ in self.metalines
|
||||
if "link_id" in meta and "url" in meta
|
||||
}
|
||||
self.pad.clear()
|
||||
self.dim = render_lines(self.metalines, self.pad, Page.MAX_COLS)
|
||||
self.current_line = 0
|
||||
self.current_column = 0
|
||||
|
||||
def refresh_content(self, x, y):
|
||||
"""Refresh content pad's view using the current line/column."""
|
||||
if x <= 0 or y <= 0:
|
||||
return
|
||||
content_position = self.current_line, self.current_column
|
||||
self.pad.refresh(*content_position, 0, 0, x, y)
|
||||
|
||||
def scroll_v(self, num_lines: int, window_height: int =None):
|
||||
"""Make the content pad scroll up and down by num_lines.
|
||||
|
||||
Arguments:
|
||||
- num_lines: amount of lines to scroll, can be negative to scroll up.
|
||||
- window_height: total window height, used to limit scrolling down.
|
||||
|
||||
Returns:
|
||||
True if scrolling occured and the pad has to be refreshed.
|
||||
"""
|
||||
if num_lines < 0:
|
||||
num_lines = -num_lines
|
||||
min_line = 0
|
||||
if self.current_line > min_line:
|
||||
self.current_line = max(self.current_line - num_lines, min_line)
|
||||
return True
|
||||
else:
|
||||
max_line = self.dim[0] - window_height
|
||||
if self.current_line < max_line:
|
||||
self.current_line = min(self.current_line + num_lines, max_line)
|
||||
return True
|
||||
return False
|
||||
|
||||
def scroll_left(self):
|
||||
if self.current_column > 0:
|
||||
self.current_column -= 1
|
||||
return True
|
||||
return False
|
||||
|
||||
def scroll_right(self, window_width):
|
||||
if self.current_column < Page.MAX_COLS - window_width:
|
||||
self.current_column += 1
|
||||
return True
|
||||
return False
|
|
@ -149,6 +149,10 @@ class Response:
|
|||
|
||||
HEADER_RE = re.compile(r"(\d{2}) (\S*)")
|
||||
|
||||
@property
|
||||
def generic_code(self):
|
||||
return Response.get_generic_code(self.code)
|
||||
|
||||
@staticmethod
|
||||
def parse(data):
|
||||
"""Parse a received response."""
|
||||
|
@ -161,7 +165,7 @@ class Response:
|
|||
if not match:
|
||||
return None
|
||||
code, meta = match.groups()
|
||||
response = Response(StatusCode(code), meta=meta)
|
||||
response = Response(StatusCode(int(code)), meta=meta)
|
||||
if Response.get_generic_code(response.code) == StatusCode.SUCCESS:
|
||||
content_offset = response_header_len + len(LINE_TERM)
|
||||
response.content = data[content_offset:]
|
||||
|
|
|
@ -2,7 +2,7 @@ import curses
|
|||
import string
|
||||
from enum import IntEnum
|
||||
|
||||
from bebop.colors import ColorPairs
|
||||
from bebop.colors import ColorPair
|
||||
from bebop.gemtext import Blockquote, Link, Paragraph, Preformatted, Title
|
||||
|
||||
|
||||
|
@ -83,9 +83,9 @@ def format_title(title: Title, context: dict):
|
|||
lines = (line_template.format(line) for line in wrapped)
|
||||
else:
|
||||
if title.level == 2:
|
||||
text = title.text
|
||||
else:
|
||||
text = " " + title.text
|
||||
else:
|
||||
text = title.text
|
||||
lines = wrap_words(text, context["width"])
|
||||
# Title levels match the type constants of titles.
|
||||
return [({"type": LineType(title.level)}, line) for line in lines]
|
||||
|
@ -205,20 +205,20 @@ def render_lines(metalines, window, max_width):
|
|||
line = line[:max_width - 1]
|
||||
line_type = meta["type"]
|
||||
if line_type == LineType.TITLE_1:
|
||||
attributes = curses.color_pair(ColorPairs.TITLE_1) | curses.A_BOLD
|
||||
attributes = curses.color_pair(ColorPair.TITLE_1) | curses.A_BOLD
|
||||
window.addstr(line, attributes)
|
||||
elif line_type == LineType.TITLE_2:
|
||||
attributes = curses.color_pair(ColorPairs.TITLE_2) | curses.A_BOLD
|
||||
attributes = curses.color_pair(ColorPair.TITLE_2) | curses.A_BOLD
|
||||
window.addstr(line, attributes)
|
||||
elif line_type == LineType.TITLE_3:
|
||||
window.addstr(line, curses.color_pair(ColorPairs.TITLE_3))
|
||||
window.addstr(line, curses.color_pair(ColorPair.TITLE_3))
|
||||
elif line_type == LineType.LINK:
|
||||
window.addstr(line, curses.color_pair(ColorPairs.LINK))
|
||||
window.addstr(line, curses.color_pair(ColorPair.LINK))
|
||||
elif line_type == LineType.PREFORMATTED:
|
||||
window.addstr(line, curses.color_pair(ColorPairs.PREFORMATTED))
|
||||
window.addstr(line, curses.color_pair(ColorPair.PREFORMATTED))
|
||||
elif line_type == LineType.BLOCKQUOTE:
|
||||
attributes = (
|
||||
curses.color_pair(ColorPairs.BLOCKQUOTE)
|
||||
curses.color_pair(ColorPair.BLOCKQUOTE)
|
||||
| curses.A_ITALIC
|
||||
)
|
||||
window.addstr(line, attributes)
|
||||
|
|
244
bebop/screen.py
244
bebop/screen.py
|
@ -4,32 +4,25 @@ import curses.textpad
|
|||
import os
|
||||
|
||||
from bebop.colors import ColorPair, init_colors
|
||||
from bebop.gemtext import parse_gemtext
|
||||
from bebop.command_line import (CommandLine, EscapeCommandInterrupt,
|
||||
TerminateCommandInterrupt)
|
||||
from bebop.mouse import ButtonState
|
||||
from bebop.navigation import join_url, parse_url
|
||||
from bebop.page import Page
|
||||
from bebop.protocol import Request, Response
|
||||
from bebop.rendering import format_elements, render_lines
|
||||
|
||||
|
||||
class Screen:
|
||||
|
||||
MAX_COLS = 1000
|
||||
|
||||
def __init__(self, cert_stash):
|
||||
self.stash = cert_stash
|
||||
self.screen = None
|
||||
self.dim = (0, 0)
|
||||
self.content_pad = None
|
||||
self.content_pad_dim = (0, 0)
|
||||
self.status_window = None
|
||||
self.command_window = None
|
||||
self.command_textbox = None
|
||||
self.metalines = []
|
||||
self.current_url = ""
|
||||
self.current_line = 0
|
||||
self.current_column = 0
|
||||
self.links = {}
|
||||
self.tab = None
|
||||
self.status_line = None
|
||||
self.command_line = None
|
||||
self.status_data = ("", 0)
|
||||
self.current_url = ""
|
||||
|
||||
@property
|
||||
def h(self):
|
||||
|
@ -49,23 +42,22 @@ class Screen:
|
|||
self.screen = stdscr
|
||||
self.screen.clear()
|
||||
self.screen.refresh()
|
||||
init_colors()
|
||||
|
||||
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
||||
curses.curs_set(0)
|
||||
init_colors()
|
||||
|
||||
self.dim = self.screen.getmaxyx()
|
||||
self.content_pad_dim = (self.h - 2, Screen.MAX_COLS)
|
||||
self.content_pad = curses.newpad(*self.content_pad_dim)
|
||||
self.content_pad.scrollok(True)
|
||||
self.content_pad.idlok(True)
|
||||
self.status_window = self.screen.subwin(
|
||||
self.page = Page(self.h - 2)
|
||||
self.status_line = self.screen.subwin(
|
||||
*self.line_dim,
|
||||
*self.status_window_pos,
|
||||
*self.status_line_pos,
|
||||
)
|
||||
self.command_window = self.screen.subwin(
|
||||
command_line_window = self.screen.subwin(
|
||||
*self.line_dim,
|
||||
*self.command_window_pos,
|
||||
*self.command_line_pos,
|
||||
)
|
||||
curses.curs_set(0)
|
||||
self.command_line = CommandLine(command_line_window)
|
||||
|
||||
pending_url = start_url
|
||||
running = True
|
||||
|
@ -81,21 +73,19 @@ class Screen:
|
|||
command = self.input_common_command()
|
||||
self.set_status(f"Command: {command}")
|
||||
elif char == ord("s"):
|
||||
self.set_status(f"h {self.h} w {self.w} cl {self.current_line} cc {self.current_column}")
|
||||
elif char == ord("r"):
|
||||
self.refresh_content()
|
||||
self.set_status(f"h {self.h} w {self.w}")
|
||||
elif char == ord("h"):
|
||||
if self.current_column > 0:
|
||||
self.current_column -= 1
|
||||
self.refresh_content()
|
||||
if self.page.scroll_left():
|
||||
self.refresh_page()
|
||||
elif char == ord("j"):
|
||||
self.scroll_content(1)
|
||||
if self.page.scroll_v(1, self.h - 2):
|
||||
self.refresh_page()
|
||||
elif char == ord("k"):
|
||||
self.scroll_content(1, scroll_up=True)
|
||||
if self.page.scroll_v(-1, self.h - 2):
|
||||
self.refresh_page()
|
||||
elif char == ord("l"):
|
||||
if self.current_column < Screen.MAX_COLS - self.w:
|
||||
self.current_column += 1
|
||||
self.refresh_content()
|
||||
if self.page.scroll_right(self.w):
|
||||
self.refresh_page()
|
||||
elif curses.ascii.isdigit(char):
|
||||
self.handle_digit_input(char)
|
||||
elif char == curses.KEY_MOUSE:
|
||||
|
@ -104,15 +94,15 @@ class Screen:
|
|||
self.handle_resize()
|
||||
|
||||
@property
|
||||
def content_window_refresh_size(self):
|
||||
def page_pad_size(self):
|
||||
return self.h - 3, self.w - 1
|
||||
|
||||
@property
|
||||
def status_window_pos(self):
|
||||
def status_line_pos(self):
|
||||
return self.h - 2, 0
|
||||
|
||||
@property
|
||||
def command_window_pos(self):
|
||||
def command_line_pos(self):
|
||||
return self.h - 1, 0
|
||||
|
||||
@property
|
||||
|
@ -120,49 +110,30 @@ class Screen:
|
|||
return 1, self.w
|
||||
|
||||
def refresh_windows(self):
|
||||
self.refresh_content()
|
||||
self.refresh_status()
|
||||
self.clear_command()
|
||||
self.refresh_page()
|
||||
self.refresh_status_line()
|
||||
self.command_line.clear()
|
||||
|
||||
def refresh_content(self):
|
||||
"""Refresh content pad's view using the current line/column."""
|
||||
refresh_size = self.content_window_refresh_size
|
||||
if refresh_size[0] <= 0 or refresh_size[1] <= 0:
|
||||
return
|
||||
self.content_pad.refresh(
|
||||
self.current_line, self.current_column, 0, 0, *refresh_size
|
||||
)
|
||||
def refresh_page(self):
|
||||
self.page.refresh_content(*self.page_pad_size)
|
||||
|
||||
def refresh_status(self):
|
||||
def refresh_status_line(self):
|
||||
"""Refresh status line contents."""
|
||||
text, pair = self.status_data
|
||||
text = text[:self.w - 1]
|
||||
self.status_window.addstr(0, 0, text, curses.color_pair(pair))
|
||||
self.status_window.clrtoeol()
|
||||
self.status_window.refresh()
|
||||
|
||||
def scroll_content(self, num_lines: int, scroll_up: bool =False):
|
||||
"""Make the content pad scroll up and down by *num_lines*."""
|
||||
if scroll_up:
|
||||
min_line = 0
|
||||
if self.current_line > min_line:
|
||||
self.current_line = max(self.current_line - num_lines, min_line)
|
||||
self.refresh_content()
|
||||
else:
|
||||
max_line = self.content_pad_dim[0] - self.h + 2
|
||||
if self.current_line < max_line:
|
||||
self.current_line = min(self.current_line + num_lines, max_line)
|
||||
self.refresh_content()
|
||||
self.status_line.addstr(0, 0, text, curses.color_pair(pair))
|
||||
self.status_line.clrtoeol()
|
||||
self.status_line.refresh()
|
||||
|
||||
def set_status(self, text):
|
||||
"""Set a regular message in the status bar."""
|
||||
self.status_data = text, ColorPair.NORMAL
|
||||
self.refresh_status()
|
||||
self.refresh_status_line()
|
||||
|
||||
def set_status_error(self, text):
|
||||
"""Set an error message in the status bar."""
|
||||
self.status_data = f"Error: {text}", ColorPair.ERROR
|
||||
self.refresh_status()
|
||||
self.refresh_status_line()
|
||||
|
||||
def open_url(self, url):
|
||||
"""Try to open an URL.
|
||||
|
@ -210,76 +181,27 @@ class Screen:
|
|||
self.set_status_error("server response parsing failed.")
|
||||
return
|
||||
|
||||
if response.code != 20:
|
||||
self.set_status_error(f"unknown response code {response.code}.")
|
||||
return
|
||||
|
||||
self.set_status(url)
|
||||
if response.code == 20:
|
||||
self.load_page(response.content)
|
||||
self.current_url = url
|
||||
self.show_gemtext(response.content)
|
||||
self.set_status(url)
|
||||
elif response.generic_code == 30 and response.meta:
|
||||
self.open_gemini_url(response.meta)
|
||||
|
||||
def show_gemtext(self, gemtext: bytes):
|
||||
"""Render Gemtext data in the content pad."""
|
||||
elements = parse_gemtext(gemtext)
|
||||
self.metalines = format_elements(elements, 80)
|
||||
self.links = {
|
||||
meta["link_id"]: meta["url"]
|
||||
for meta, _ in self.metalines
|
||||
if "link_id" in meta and "url" in meta
|
||||
}
|
||||
|
||||
self.content_pad.clear()
|
||||
h, w = render_lines(self.metalines, self.content_pad, Screen.MAX_COLS)
|
||||
self.content_pad_dim = (h, w)
|
||||
self.current_line = 0
|
||||
self.current_column = 0
|
||||
self.refresh_content()
|
||||
|
||||
def focus_command(self, command_char, validator=None, prefix=""):
|
||||
"""Give user focus to the command bar.
|
||||
|
||||
Show the command char and give focus to the command textbox. The
|
||||
validator function is passed to the textbox.
|
||||
|
||||
Arguments:
|
||||
- command_char: char to display before the command line.
|
||||
- validator: function to use to validate the input chars.
|
||||
- prefix: string to insert before the cursor in the command line.
|
||||
|
||||
Returns:
|
||||
User input as string. The string will be empty if the validator raised
|
||||
an EscapeInterrupt.
|
||||
"""
|
||||
assert self.command_window is not None
|
||||
self.command_window.clear()
|
||||
self.command_window.refresh()
|
||||
self.command_textbox = curses.textpad.Textbox(self.command_window)
|
||||
self.command_window.addstr(command_char + prefix)
|
||||
curses.curs_set(1)
|
||||
try:
|
||||
command = self.command_textbox.edit(validator)[1:]
|
||||
except EscapeCommandInterrupt:
|
||||
command = ""
|
||||
except TerminateCommandInterrupt as exc:
|
||||
command = exc.command
|
||||
curses.curs_set(0)
|
||||
self.clear_command()
|
||||
return command
|
||||
|
||||
def gather_current_command(self):
|
||||
"""Return the string currently written by the user in command line."""
|
||||
return self.command_textbox.gather()[1:].rstrip()
|
||||
|
||||
def clear_command(self):
|
||||
"""Clear the command line """
|
||||
self.command_window.clear()
|
||||
self.command_window.refresh()
|
||||
self.screen.delch(self.h - 1, 0)
|
||||
def load_page(self, gemtext: bytes):
|
||||
"""Load Gemtext data as the current page."""
|
||||
old_pad_height = self.page.dim[0]
|
||||
self.page.show_gemtext(gemtext)
|
||||
if self.page.dim[0] < old_pad_height:
|
||||
self.screen.clear()
|
||||
self.screen.refresh()
|
||||
self.refresh_windows()
|
||||
else:
|
||||
self.refresh_page()
|
||||
|
||||
def input_common_command(self):
|
||||
"""Focus command line to type a regular command. Currently useless."""
|
||||
return self.focus_command(":", self.validate_common_char)
|
||||
return self.command_line.focus(":", self.validate_common_char)
|
||||
|
||||
def validate_common_char(self, ch: int):
|
||||
"""Generic input validator, handles a few more cases than default.
|
||||
|
@ -294,7 +216,7 @@ class Screen:
|
|||
to handle the keys above.
|
||||
"""
|
||||
if ch == curses.KEY_BACKSPACE: # Cancel input if all line is cleaned.
|
||||
text = self.gather_current_command()
|
||||
text = self.command_line.gather()
|
||||
if len(text) == 0:
|
||||
raise EscapeCommandInterrupt()
|
||||
elif ch == curses.ascii.ESC: # Could be ESC or ALT
|
||||
|
@ -316,7 +238,7 @@ class Screen:
|
|||
- If it's higher than 10, the user either inputs as many digits required
|
||||
to disambiguate the link ID, or press enter to validate her input.
|
||||
|
||||
Examples
|
||||
Examples:
|
||||
- I have 3 links. Pressing "2" takes me to link 2.
|
||||
- I have 15 links. Pressing "3" and Enter takes me to link 2.
|
||||
- I have 15 links. Pressing "1" and "2" takes me to link 12 (no
|
||||
|
@ -326,25 +248,23 @@ class Screen:
|
|||
ambiguity as well).
|
||||
"""
|
||||
digit = init_char & 0xf
|
||||
num_links = len(self.links)
|
||||
links = self.page.links
|
||||
num_links = len(links)
|
||||
if num_links < 10:
|
||||
self.open_link(digit)
|
||||
self.open_link(links, digit)
|
||||
return
|
||||
required_digits = 0
|
||||
while num_links:
|
||||
required_digits += 1
|
||||
num_links //= 10
|
||||
link_input = self.focus_command(
|
||||
"~",
|
||||
validator=lambda ch: self._validate_link_digit(ch, required_digits),
|
||||
prefix=chr(init_char),
|
||||
)
|
||||
validator = lambda ch: self._validate_link_digit(ch, required_digits)
|
||||
link_input = self.command_line.focus("&", validator, chr(init_char))
|
||||
try:
|
||||
link_id = int(link_input)
|
||||
except ValueError:
|
||||
self.set_status_error("invalid link ID")
|
||||
return
|
||||
self.open_link(link_id)
|
||||
self.open_link(links, link_id)
|
||||
|
||||
def _validate_link_digit(self, ch: int, required_digits: int):
|
||||
"""Handle input chars to be used as link ID."""
|
||||
|
@ -353,7 +273,7 @@ class Screen:
|
|||
# Only accept digits. If we reach the amount of required digits, open
|
||||
# link now and leave command line. Else just process it.
|
||||
if curses.ascii.isdigit(ch):
|
||||
digits = self.gather_current_command()
|
||||
digits = self.command_line.gather()
|
||||
if len(digits) + 1 == required_digits:
|
||||
raise TerminateCommandInterrupt(digits + chr(ch))
|
||||
return ch
|
||||
|
@ -367,13 +287,12 @@ class Screen:
|
|||
if len(digits) == max_digits:
|
||||
return digits
|
||||
|
||||
|
||||
def open_link(self, link_id: int):
|
||||
def open_link(self, links, link_id: int):
|
||||
"""Open the link with this link ID."""
|
||||
if not link_id in self.links:
|
||||
if not link_id in links:
|
||||
self.set_status_error(f"unknown link ID {link_id}.")
|
||||
return
|
||||
self.open_url(self.links[link_id])
|
||||
self.open_url(links[link_id])
|
||||
|
||||
def handle_mouse(self, mouse_id: int, x: int, y: int, z: int, bstate: int):
|
||||
"""Handle mouse events.
|
||||
|
@ -381,9 +300,9 @@ class Screen:
|
|||
Right now, only vertical scrolling is handled.
|
||||
"""
|
||||
if bstate & ButtonState.SCROLL_UP:
|
||||
self.scroll_content(3, scroll_up=True)
|
||||
self.page.scroll_v(-3)
|
||||
elif bstate & ButtonState.SCROLL_DOWN:
|
||||
self.scroll_content(3)
|
||||
self.page.scroll_v(3, self.h - 2)
|
||||
|
||||
def handle_resize(self):
|
||||
"""Try to not make everything collapse on resizes."""
|
||||
|
@ -397,29 +316,16 @@ class Screen:
|
|||
return
|
||||
# Resize windows to fit the new dimensions. Content pad will be updated
|
||||
# on its own at the end of the function.
|
||||
self.status_window.resize(*self.line_dim)
|
||||
self.command_window.resize(*self.line_dim)
|
||||
self.status_line.resize(*self.line_dim)
|
||||
self.command_line.window.resize(*self.line_dim)
|
||||
# Move the windows to their new position if that's still possible.
|
||||
if self.status_window_pos[0] >= 0:
|
||||
self.status_window.mvwin(*self.status_window_pos)
|
||||
if self.command_window_pos[0] >= 0:
|
||||
self.command_window.mvwin(*self.command_window_pos)
|
||||
if self.status_line_pos[0] >= 0:
|
||||
self.status_line.mvwin(*self.status_line_pos)
|
||||
if self.command_line_pos[0] >= 0:
|
||||
self.command_line.window.mvwin(*self.command_line_pos)
|
||||
# If the content pad does not fit its whole place, we have to clean the
|
||||
# gap between it and the status line. Refresh all screen.
|
||||
if self.content_pad_dim[0] < self.h - 2:
|
||||
if self.page.dim[0] < self.h - 2:
|
||||
self.screen.clear()
|
||||
self.screen.refresh()
|
||||
self.refresh_windows()
|
||||
|
||||
|
||||
class EscapeCommandInterrupt(Exception):
|
||||
"""Signal that ESC has been pressed during command line."""
|
||||
pass
|
||||
|
||||
|
||||
class TerminateCommandInterrupt(Exception):
|
||||
"""Signal that validation ended command line input early. Use `command`."""
|
||||
|
||||
def __init__(self, command: str, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.command = command
|
||||
|
|
Reference in a new issue