diff --git a/bebop/browser.py b/bebop/browser.py index 0074c79..6993bcc 100644 --- a/bebop/browser.py +++ b/bebop/browser.py @@ -13,7 +13,8 @@ from bebop.history import History from bebop.links import Links from bebop.mouse import ButtonState from bebop.navigation import * -from bebop.page import Page, PagePad +from bebop.page import Page +from bebop.page_pad import PagePad from bebop.protocol import Request, Response @@ -208,7 +209,8 @@ class Browser: if command in ("o", "open"): self.open_url(words[1], assume_absolute=True) - def open_url(self, url, base_url=None, redirects=0, assume_absolute=False): + def open_url(self, url, base_url=None, redirects=0, assume_absolute=False, + history=True, use_cache=True): """Try to open an URL. This function assumes that the URL can be from an user and thus tries a @@ -223,6 +225,8 @@ class Browser: - base_url: an URL string to use as base in case `url` is relative. - redirections: number of redirections we did yet for the same request. - assume_absolute: assume we intended to use an absolute URL if True. + - history: whether the URL should be pushed to history on success. + - use_cache: whether we should look for an already cached document. """ if redirects > 5: self.set_status_error(f"Too many redirections ({url}).") diff --git a/bebop/page.py b/bebop/page.py index 5a1f65f..d0fb2f2 100644 --- a/bebop/page.py +++ b/bebop/page.py @@ -1,11 +1,8 @@ -"""Single Gemini page curses management.""" - -import curses from dataclasses import dataclass, field -from bebop.gemtext import parse_gemtext +from bebop.gemtext import parse_gemtext, Title +from bebop.rendering import generate_metalines from bebop.links import Links -from bebop.rendering import generate_metalines, render_lines @dataclass @@ -13,6 +10,7 @@ class Page: """Page-related data.""" metalines: list = field(default_factory=list) links: Links = field(default_factory=Links) + title: str = "" @staticmethod def from_gemtext(gemtext: str): @@ -20,98 +18,10 @@ class Page: elements = parse_gemtext(gemtext) metalines = generate_metalines(elements, 80) links = Links.from_metalines(metalines) - return Page(metalines, links) - - -class PagePad: - """Window containing page content.""" - - MAX_COLS = 1000 - - def __init__(self, initial_num_lines): - self.dim = (initial_num_lines, PagePad.MAX_COLS) - self.pad = curses.newpad(*self.dim) - self.pad.scrollok(True) - self.pad.idlok(True) - self.current_line = 0 - self.current_column = 0 - self.current_page = None - - def show_page(self, page: Page): - """Render Gemtext data in the content pad.""" - self.current_page = page - self.pad.clear() - self.dim = render_lines(page.metalines, self.pad, PagePad.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 =0): - """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_h(self, num_columns: int, window_width: int =0): - """Make the content pad scroll left and right by num_columns. - - Arguments: - - num_columns: amount of columns to scroll, can be negative to scroll - left. - - window_width: total window width, used to limit scrolling right. - - Returns: - True if scrolling occured and the pad has to be refreshed. - """ - if num_columns < 0: - num_columns = -num_columns - min_column = 0 - if self.current_column > min_column: - new_column = self.current_column - num_columns - self.current_column = max(new_column, min_column) - return True - else: - max_column = self.dim[1] - window_width - if self.current_column < max_column: - new_column = self.current_column + num_columns - self.current_column = min(new_column, max_column) - return True - return False - - def go_to_beginning(self): - """Make the pad show its start; return True if a refresh is needed.""" - if self.current_line: - self.current_line = 0 - return True - return False - - def go_to_end(self, window_height): - """Make the pad show its bottom; return True if a refresh is needed.""" - max_line = self.dim[0] - window_height - if self.current_line != max_line: - self.current_line = max_line - return True - return False + # TODO this is horrible; merge parsing with page generation directly + title = "" + for element in elements: + if isinstance(element, Title) and element.level == 1: + title = element.text + break + return Page(metalines, links, title) diff --git a/bebop/page_pad.py b/bebop/page_pad.py new file mode 100644 index 0000000..ae1437f --- /dev/null +++ b/bebop/page_pad.py @@ -0,0 +1,100 @@ +"""Single Gemini page curses management.""" + +import curses + +from bebop.page import Page +from bebop.rendering import render_lines + + +class PagePad: + """Window containing page content.""" + + MAX_COLS = 1000 + + def __init__(self, initial_num_lines): + self.dim = (initial_num_lines, PagePad.MAX_COLS) + self.pad = curses.newpad(*self.dim) + self.pad.scrollok(True) + self.pad.idlok(True) + self.current_line = 0 + self.current_column = 0 + self.current_page = None + + def show_page(self, page: Page): + """Render Gemtext data in the content pad.""" + self.current_page = page + self.pad.clear() + self.dim = render_lines(page.metalines, self.pad, PagePad.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 =0): + """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_h(self, num_columns: int, window_width: int =0): + """Make the content pad scroll left and right by num_columns. + + Arguments: + - num_columns: amount of columns to scroll, can be negative to scroll + left. + - window_width: total window width, used to limit scrolling right. + + Returns: + True if scrolling occured and the pad has to be refreshed. + """ + if num_columns < 0: + num_columns = -num_columns + min_column = 0 + if self.current_column > min_column: + new_column = self.current_column - num_columns + self.current_column = max(new_column, min_column) + return True + else: + max_column = self.dim[1] - window_width + if self.current_column < max_column: + new_column = self.current_column + num_columns + self.current_column = min(new_column, max_column) + return True + return False + + def go_to_beginning(self): + """Make the pad show its start; return True if a refresh is needed.""" + if self.current_line: + self.current_line = 0 + return True + return False + + def go_to_end(self, window_height): + """Make the pad show its bottom; return True if a refresh is needed.""" + max_line = self.dim[0] - window_height + if self.current_line != max_line: + self.current_line = max_line + return True + return False