From bd8d4bbfb1022246492fba7fb7c142014a71e82d Mon Sep 17 00:00:00 2001 From: dece Date: Fri, 4 Jun 2021 02:25:15 +0200 Subject: [PATCH] browser: add text search --- BOARD.txt | 3 +- bebop/browser/browser.py | 69 ++++++++++++++++++++++++++++++++++++---- bebop/help.py | 3 ++ bebop/rendering.py | 5 ++- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/BOARD.txt b/BOARD.txt index af20618..20af543 100644 --- a/BOARD.txt +++ b/BOARD.txt @@ -1,6 +1,5 @@ TODO ---------------------------------------- -search in page (ugh) @@ -29,6 +28,7 @@ remember scroll pos in history identity management "previous/next" pages configurable keybinds +handle big files (e.g. gemini://tilde.team/~tomasino/irc/log.txt) @@ -58,3 +58,4 @@ different rendering mode preferences per site basic mouse support basic local browsing +search in page diff --git a/bebop/browser/browser.py b/bebop/browser/browser.py index 4b0851e..4d7d97f 100644 --- a/bebop/browser/browser.py +++ b/bebop/browser/browser.py @@ -64,8 +64,12 @@ class Browser: returning the page source path. - last_download: tuple of MimeType and path, or None. - identities: identities map. + - search_res_lines: list of lines containing results of the last search. """ + SEARCH_NEXT = 0 + SEARCH_PREVIOUS = 1 + def __init__(self, config, cert_stash): self.config = config self.stash = cert_stash @@ -81,6 +85,7 @@ class Browser: self.special_pages = self.setup_special_pages() self.last_download: Optional[Tuple[MimeType, Path]] = None self.identities = {} + self.search_res_lines = [] self._current_url = "" @property @@ -244,6 +249,12 @@ class Browser: self.open_history() elif char == ord("§"): self.toggle_render_mode() + elif char == ord("/"): + self.search_in_page() + elif char == ord("n"): + self.move_to_search_result(Browser.SEARCH_NEXT) + elif char == ord("N"): + self.move_to_search_result(Browser.SEARCH_PREVIOUS) elif curses.ascii.isdigit(char): self.handle_digit_input(char) elif char == curses.KEY_MOUSE: @@ -494,9 +505,9 @@ class Browser: If the click is on a link (appropriate line and columns), open it. """ - if not self.page_pad or not self.page_pad.current_page: - return page = self.page_pad.current_page + if not page: + return px, py = self.page_pad.current_column, self.page_pad.current_line line_pos = y + py if line_pos >= len(page.metalines): @@ -727,13 +738,14 @@ class Browser: def show_page_info(self): """Show some page informations in the status bar.""" - if not self.page_pad or not self.page_pad.current_page: - return page = self.page_pad.current_page + if not page: + return mime = page.mime.short if page.mime else "(unknown MIME type)" encoding = page.encoding or "(unknown encoding)" size = f"{len(page.source)} chars" - info = f"{mime} {encoding} {size}" + lines = f"{len(page.metalines)} lines" + info = f"{mime} {encoding} {size} {lines}" self.set_status(info) def set_render_mode(self, mode): @@ -758,9 +770,9 @@ class Browser: def toggle_render_mode(self): """Switch to the next render mode for the current page.""" - if not self.page_pad or not self.page_pad.current_page: - return page = self.page_pad.current_page + if not page: + return if page.render is None or page.render not in RENDER_MODES: next_mode = RENDER_MODES[0] else: @@ -773,3 +785,46 @@ class Browser: ) self.load_page(new_page) self.set_status(f"Using render mode '{next_mode}'.") + + def search_in_page(self): + """Search for words in the page.""" + page = self.page_pad.current_page + if not page: + return + search = self.get_user_text_input("Search", CommandLine.CHAR_TEXT) + if not search: + return + self.search_res_lines = [] + for index, (_, line) in enumerate(page.metalines): + if search in line: + self.search_res_lines.append(index) + if self.search_res_lines: + self.move_to_search_result(Browser.SEARCH_NEXT) + else: + self.set_status(f"'{search}' not found.") + + def move_to_search_result(self, prev_or_next: int): + """Move to the next or previous search result.""" + current_line = self.page_pad.current_line + next_line = None + index = 1 + max_index = len(self.search_res_lines) + if prev_or_next == Browser.SEARCH_NEXT: + for line in self.search_res_lines: + if line > current_line: + next_line = line + break + index += 1 + elif prev_or_next == Browser.SEARCH_PREVIOUS: + index = max_index + for line in reversed(self.search_res_lines): + if line < current_line: + next_line = line + break + index -= 1 + if next_line is None: + return + + self.set_status(f"Result {index}/{max_index}") + self.page_pad.current_line = next_line + self.refresh_windows() diff --git a/bebop/help.py b/bebop/help.py index 237d15f..f923d53 100644 --- a/bebop/help.py +++ b/bebop/help.py @@ -38,6 +38,9 @@ Keybinds using the SHIFT key are written uppercase. Keybinds using the ALT (or M * digits: go to the corresponding link ID * escape: reset status line text * section sign (§): toggle between render modes for the current page +* slash (/): search for some text +* n: go to next search result +* N: go to previous search result * C-c: cancel current operation ## Commands diff --git a/bebop/rendering.py b/bebop/rendering.py index 81252cd..bbb572a 100644 --- a/bebop/rendering.py +++ b/bebop/rendering.py @@ -19,9 +19,8 @@ def render_lines(metalines, window, max_width): - max_width: line length limit for the pad. Returns: - The tuple of integers (error, height, width), error being a non-zero value - if an error occured during rendering, and height and width being the new - dimensions of the resized window. + The tuple of integers (height, width), the new dimensions of the resized + window. """ num_lines = len(metalines) new_dimensions = max(num_lines, 1), max_width