Compare commits
No commits in common. "74764585213ad0205ea7bdde3a31c82e22f0e388" and "0c1924d40a634d914b568c7341ced6a82a5292d0" have entirely different histories.
7476458521
...
0c1924d40a
|
@ -1,5 +1,8 @@
|
||||||
TODO
|
TODO
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
opt. maintain history between sessions
|
||||||
|
directory view for file scheme
|
||||||
|
search in page (ugh)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,7 +31,6 @@ remember scroll pos in history
|
||||||
identity management
|
identity management
|
||||||
"previous/next" pages
|
"previous/next" pages
|
||||||
configurable keybinds
|
configurable keybinds
|
||||||
handle big files (e.g. gemini://tilde.team/~tomasino/irc/log.txt)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,5 +59,3 @@ home page
|
||||||
different rendering mode
|
different rendering mode
|
||||||
preferences per site
|
preferences per site
|
||||||
basic mouse support
|
basic mouse support
|
||||||
basic local browsing
|
|
||||||
search in page
|
|
||||||
|
|
|
@ -64,12 +64,8 @@ class Browser:
|
||||||
returning the page source path.
|
returning the page source path.
|
||||||
- last_download: tuple of MimeType and path, or None.
|
- last_download: tuple of MimeType and path, or None.
|
||||||
- identities: identities map.
|
- 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):
|
def __init__(self, config, cert_stash):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.stash = cert_stash
|
self.stash = cert_stash
|
||||||
|
@ -85,7 +81,6 @@ class Browser:
|
||||||
self.special_pages = self.setup_special_pages()
|
self.special_pages = self.setup_special_pages()
|
||||||
self.last_download: Optional[Tuple[MimeType, Path]] = None
|
self.last_download: Optional[Tuple[MimeType, Path]] = None
|
||||||
self.identities = {}
|
self.identities = {}
|
||||||
self.search_res_lines = []
|
|
||||||
self._current_url = ""
|
self._current_url = ""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -132,17 +127,16 @@ class Browser:
|
||||||
|
|
||||||
def _run(self, stdscr, start_url=None):
|
def _run(self, stdscr, start_url=None):
|
||||||
"""Start displaying content and handling events."""
|
"""Start displaying content and handling events."""
|
||||||
# Setup Curses.
|
|
||||||
self.screen = stdscr
|
self.screen = stdscr
|
||||||
self.screen.clear()
|
self.screen.clear()
|
||||||
self.screen.refresh()
|
self.screen.refresh()
|
||||||
|
|
||||||
mousemask = curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
mousemask = curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
||||||
if mousemask == 0:
|
if mousemask == 0:
|
||||||
logging.error("Could not enable mouse support.")
|
logging.error("Could not enable mouse support.")
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
init_colors()
|
init_colors()
|
||||||
|
|
||||||
# Setup windows and pads.
|
|
||||||
self.dim = self.screen.getmaxyx()
|
self.dim = self.screen.getmaxyx()
|
||||||
self.page_pad = PagePad(self.h - 2)
|
self.page_pad = PagePad(self.h - 2)
|
||||||
self.status_line = self.screen.subwin(
|
self.status_line = self.screen.subwin(
|
||||||
|
@ -158,7 +152,6 @@ class Browser:
|
||||||
self.config["command_editor"]
|
self.config["command_editor"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load user data files, record which failed to load to warn the user.
|
|
||||||
failed_to_load = []
|
failed_to_load = []
|
||||||
identities = load_identities(get_identities_list_path())
|
identities = load_identities(get_identities_list_path())
|
||||||
if identities is None:
|
if identities is None:
|
||||||
|
@ -171,15 +164,10 @@ class Browser:
|
||||||
else:
|
else:
|
||||||
self.capsule_prefs = capsule_prefs
|
self.capsule_prefs = capsule_prefs
|
||||||
|
|
||||||
# Load user data files that may not exist (no warning).
|
|
||||||
if self.config["persistent_history"]:
|
|
||||||
if not self.history.load():
|
|
||||||
logging.warning("Could not load history file.")
|
|
||||||
|
|
||||||
if failed_to_load:
|
if failed_to_load:
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"Failed to open some local data: {', '.join(failed_to_load)}. "
|
f"Failed to open some local data: {', '.join(failed_to_load)}. "
|
||||||
"These may be replaced if you continue."
|
"Some data may be lost if you continue."
|
||||||
)
|
)
|
||||||
self.set_status_error(error_msg)
|
self.set_status_error(error_msg)
|
||||||
elif start_url:
|
elif start_url:
|
||||||
|
@ -187,16 +175,12 @@ class Browser:
|
||||||
else:
|
else:
|
||||||
self.open_home()
|
self.open_home()
|
||||||
|
|
||||||
# Start listening for inputs.
|
|
||||||
while self.running:
|
while self.running:
|
||||||
try:
|
try:
|
||||||
self.handle_inputs()
|
self.handle_inputs()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.set_status("Cancelled.")
|
self.set_status("Cancelled.")
|
||||||
|
|
||||||
if self.config["persistent_history"]:
|
|
||||||
self.history.save()
|
|
||||||
|
|
||||||
def handle_inputs(self):
|
def handle_inputs(self):
|
||||||
char = self.screen.getch()
|
char = self.screen.getch()
|
||||||
if char == ord("?"):
|
if char == ord("?"):
|
||||||
|
@ -249,12 +233,6 @@ class Browser:
|
||||||
self.open_history()
|
self.open_history()
|
||||||
elif char == ord("§"):
|
elif char == ord("§"):
|
||||||
self.toggle_render_mode()
|
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):
|
elif curses.ascii.isdigit(char):
|
||||||
self.handle_digit_input(char)
|
self.handle_digit_input(char)
|
||||||
elif char == curses.KEY_MOUSE:
|
elif char == curses.KEY_MOUSE:
|
||||||
|
@ -505,9 +483,9 @@ class Browser:
|
||||||
|
|
||||||
If the click is on a link (appropriate line and columns), open it.
|
If the click is on a link (appropriate line and columns), open it.
|
||||||
"""
|
"""
|
||||||
page = self.page_pad.current_page
|
if not self.page_pad or not self.page_pad.current_page:
|
||||||
if not page:
|
|
||||||
return
|
return
|
||||||
|
page = self.page_pad.current_page
|
||||||
px, py = self.page_pad.current_column, self.page_pad.current_line
|
px, py = self.page_pad.current_column, self.page_pad.current_line
|
||||||
line_pos = y + py
|
line_pos = y + py
|
||||||
if line_pos >= len(page.metalines):
|
if line_pos >= len(page.metalines):
|
||||||
|
@ -686,10 +664,7 @@ class Browser:
|
||||||
return
|
return
|
||||||
|
|
||||||
command = self.config["source_editor"] + [source_filename]
|
command = self.config["source_editor"] + [source_filename]
|
||||||
success = open_external_program(command)
|
open_external_program(command)
|
||||||
if not success:
|
|
||||||
self.set_status_error("Could not open editor.")
|
|
||||||
|
|
||||||
if delete_source_after:
|
if delete_source_after:
|
||||||
os.unlink(source_filename)
|
os.unlink(source_filename)
|
||||||
self.refresh_windows()
|
self.refresh_windows()
|
||||||
|
@ -738,14 +713,13 @@ class Browser:
|
||||||
|
|
||||||
def show_page_info(self):
|
def show_page_info(self):
|
||||||
"""Show some page informations in the status bar."""
|
"""Show some page informations in the status bar."""
|
||||||
page = self.page_pad.current_page
|
if not self.page_pad or not self.page_pad.current_page:
|
||||||
if not page:
|
|
||||||
return
|
return
|
||||||
|
page = self.page_pad.current_page
|
||||||
mime = page.mime.short if page.mime else "(unknown MIME type)"
|
mime = page.mime.short if page.mime else "(unknown MIME type)"
|
||||||
encoding = page.encoding or "(unknown encoding)"
|
encoding = page.encoding or "(unknown encoding)"
|
||||||
size = f"{len(page.source)} chars"
|
size = f"{len(page.source)} chars"
|
||||||
lines = f"{len(page.metalines)} lines"
|
info = f"{mime} {encoding} {size}"
|
||||||
info = f"{mime} {encoding} {size} {lines}"
|
|
||||||
self.set_status(info)
|
self.set_status(info)
|
||||||
|
|
||||||
def set_render_mode(self, mode):
|
def set_render_mode(self, mode):
|
||||||
|
@ -770,9 +744,9 @@ class Browser:
|
||||||
|
|
||||||
def toggle_render_mode(self):
|
def toggle_render_mode(self):
|
||||||
"""Switch to the next render mode for the current page."""
|
"""Switch to the next render mode for the current page."""
|
||||||
page = self.page_pad.current_page
|
if not self.page_pad or not self.page_pad.current_page:
|
||||||
if not page:
|
|
||||||
return
|
return
|
||||||
|
page = self.page_pad.current_page
|
||||||
if page.render is None or page.render not in RENDER_MODES:
|
if page.render is None or page.render not in RENDER_MODES:
|
||||||
next_mode = RENDER_MODES[0]
|
next_mode = RENDER_MODES[0]
|
||||||
else:
|
else:
|
||||||
|
@ -785,46 +759,3 @@ class Browser:
|
||||||
)
|
)
|
||||||
self.load_page(new_page)
|
self.load_page(new_page)
|
||||||
self.set_status(f"Using render mode '{next_mode}'.")
|
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()
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
"""Local files browser."""
|
"""Local files browser."""
|
||||||
|
|
||||||
import logging
|
|
||||||
from pathlib import Path
|
|
||||||
from urllib.parse import quote, unquote
|
|
||||||
|
|
||||||
from bebop.browser.browser import Browser
|
from bebop.browser.browser import Browser
|
||||||
from bebop.page import Page
|
from bebop.page import Page
|
||||||
|
|
||||||
|
@ -11,9 +7,10 @@ from bebop.page import Page
|
||||||
def open_file(browser: Browser, filepath: str, encoding="utf-8"):
|
def open_file(browser: Browser, filepath: str, encoding="utf-8"):
|
||||||
"""Open a file and render it.
|
"""Open a file and render it.
|
||||||
|
|
||||||
This should be used only text files or directories. Anything else will
|
This should be used only on Gemtext files or at least text files.
|
||||||
produce garbage and may crash the program. In the future this should be able
|
Anything else will produce garbage and may crash the program. In the
|
||||||
to use a different parser according to a MIME type or something.
|
future this should be able to use a different parser according to a MIME
|
||||||
|
type or something.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- browser: Browser object making the request.
|
- browser: Browser object making the request.
|
||||||
|
@ -23,29 +20,13 @@ def open_file(browser: Browser, filepath: str, encoding="utf-8"):
|
||||||
Returns:
|
Returns:
|
||||||
The loaded file URI on success, None otherwise (e.g. file not found).
|
The loaded file URI on success, None otherwise (e.g. file not found).
|
||||||
"""
|
"""
|
||||||
path = Path(unquote(filepath))
|
|
||||||
if not path.exists():
|
|
||||||
logging.error(f"File {path} does not exist.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if path.is_file():
|
|
||||||
try:
|
try:
|
||||||
with open(path, "rt", encoding=encoding) as f:
|
with open(filepath, "rt", encoding=encoding) as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
except (OSError, ValueError) as exc:
|
except (OSError, ValueError) as exc:
|
||||||
browser.set_status_error(f"Failed to open file: {exc}")
|
browser.set_status_error(f"Failed to open file: {exc}")
|
||||||
return None
|
return None
|
||||||
browser.load_page(Page.from_text(text))
|
browser.load_page(Page.from_text(text))
|
||||||
elif path.is_dir():
|
file_url = "file://" + filepath
|
||||||
gemtext = str(path) + "\n\n"
|
|
||||||
for entry in sorted(path.iterdir()):
|
|
||||||
entry_path = quote(str(entry.absolute()))
|
|
||||||
name = entry.name
|
|
||||||
if entry.is_dir():
|
|
||||||
name += "/"
|
|
||||||
gemtext += f"=> {entry_path} {name}\n"
|
|
||||||
wrap_at = browser.config["text_width"]
|
|
||||||
browser.load_page(Page.from_gemtext(gemtext, wrap_at))
|
|
||||||
file_url = f"file://{path}"
|
|
||||||
browser.current_url = file_url
|
browser.current_url = file_url
|
||||||
return file_url
|
return file_url
|
||||||
|
|
|
@ -200,9 +200,7 @@ class CommandLine:
|
||||||
return
|
return
|
||||||
|
|
||||||
command = self.editor_command + [temp_filepath]
|
command = self.editor_command + [temp_filepath]
|
||||||
success = open_external_program(command)
|
open_external_program(command)
|
||||||
if not success:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(temp_filepath, "rt") as temp_file:
|
with open(temp_filepath, "rt") as temp_file:
|
||||||
|
|
|
@ -29,7 +29,6 @@ DEFAULT_CONFIG = {
|
||||||
"-subj", "/CN={common_name}",
|
"-subj", "/CN={common_name}",
|
||||||
],
|
],
|
||||||
"scroll_step": 3,
|
"scroll_step": 3,
|
||||||
"persistent_history": False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RENDER_MODES = ("fancy", "dumb")
|
RENDER_MODES = ("fancy", "dumb")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""Call external commands."""
|
"""Call external commands."""
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import logging
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,21 +9,12 @@ def open_external_program(command):
|
||||||
|
|
||||||
The caller has to refresh whatever windows it manages after calling this
|
The caller has to refresh whatever windows it manages after calling this
|
||||||
method or garbage may be left on the screen.
|
method or garbage may be left on the screen.
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if no exception occured.
|
|
||||||
"""
|
"""
|
||||||
curses.nocbreak()
|
curses.nocbreak()
|
||||||
curses.echo()
|
curses.echo()
|
||||||
curses.curs_set(1)
|
curses.curs_set(1)
|
||||||
result = True
|
|
||||||
try:
|
|
||||||
subprocess.run(command)
|
subprocess.run(command)
|
||||||
except OSError as exc:
|
|
||||||
logging.error(f"Failed to run '{command}': {exc}")
|
|
||||||
result = False
|
|
||||||
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
return result
|
|
||||||
|
|
12
bebop/fs.py
12
bebop/fs.py
|
@ -48,29 +48,23 @@ def get_downloads_path() -> Path:
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(None)
|
@lru_cache(None)
|
||||||
def get_identities_list_path() -> Path:
|
def get_identities_list_path():
|
||||||
"""Return the identities JSON file path."""
|
"""Return the identities JSON file path."""
|
||||||
return get_user_data_path() / "identities.json"
|
return get_user_data_path() / "identities.json"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(None)
|
@lru_cache(None)
|
||||||
def get_identities_path() -> Path:
|
def get_identities_path():
|
||||||
"""Return the directory where identities are stored."""
|
"""Return the directory where identities are stored."""
|
||||||
return get_user_data_path() / "identities"
|
return get_user_data_path() / "identities"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(None)
|
@lru_cache(None)
|
||||||
def get_capsule_prefs_path() -> Path:
|
def get_capsule_prefs_path():
|
||||||
"""Return the directory where identities are stored."""
|
"""Return the directory where identities are stored."""
|
||||||
return get_user_data_path() / "capsule_prefs.json"
|
return get_user_data_path() / "capsule_prefs.json"
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(None)
|
|
||||||
def get_history_path() -> Path:
|
|
||||||
"""Return the saved history path."""
|
|
||||||
return get_user_data_path() / "history.txt"
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_bebop_files_exist() -> Optional[str]:
|
def ensure_bebop_files_exist() -> Optional[str]:
|
||||||
"""Ensure various Bebop's files or directories are present.
|
"""Ensure various Bebop's files or directories are present.
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,6 @@ Keybinds using the SHIFT key are written uppercase. Keybinds using the ALT (or M
|
||||||
* digits: go to the corresponding link ID
|
* digits: go to the corresponding link ID
|
||||||
* escape: reset status line text
|
* escape: reset status line text
|
||||||
* section sign (§): toggle between render modes for the current page
|
* 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
|
* C-c: cancel current operation
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
@ -61,19 +58,18 @@ Bebop uses a JSON file (usually in ~/.config). It is created with default values
|
||||||
|
|
||||||
Here are the available options:
|
Here are the available options:
|
||||||
|
|
||||||
* command_editor (see note 1): command to use for editing cli input.
|
|
||||||
* connect_timeout (int): seconds before connection times out.
|
* connect_timeout (int): seconds before connection times out.
|
||||||
* download_path (string): download path.
|
|
||||||
* external_command_default (see note 1): default command to open files.
|
|
||||||
* external_commands (see note 2): commands to open various files.
|
|
||||||
* generate_client_cert_command (see note 3): command to generate a client cert.
|
|
||||||
* history_limit (int): maximum entries in history.
|
|
||||||
* home (string): home page.
|
|
||||||
* persistent_history (bool): save and reload history.
|
|
||||||
* render_mode (string): default render mode to use ("fancy" or "dumb").
|
|
||||||
* scroll_step (int): number of lines/columns to scroll in one step.
|
|
||||||
* source_editor (see note 1): command to use for editing sources.
|
|
||||||
* text_width (int): rendered line length.
|
* text_width (int): rendered line length.
|
||||||
|
* download_path (string): download path.
|
||||||
|
* source_editor (see note 1): command to use for editing sources.
|
||||||
|
* command_editor (see note 1): command to use for editing cli input.
|
||||||
|
* history_limit (int): maximum entries in history.
|
||||||
|
* external_commands (see note 2): commands to open various files.
|
||||||
|
* external_command_default (see note 1): default command to open files.
|
||||||
|
* home (string): home page.
|
||||||
|
* render_mode (string): default render mode to use ("fancy" or "dumb").
|
||||||
|
* generate_client_cert_command (see note 3): command to generate a client cert.
|
||||||
|
* scroll_step (int): number of lines/columns to scroll in one step.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
"""History management."""
|
"""History management."""
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from bebop.fs import get_history_path
|
|
||||||
|
|
||||||
|
|
||||||
class History:
|
class History:
|
||||||
"""Basic browsing history manager."""
|
"""Basic browsing history manager.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, limit):
|
def __init__(self, limit):
|
||||||
self.urls = []
|
self.urls = []
|
||||||
|
@ -57,28 +55,3 @@ class History:
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
seen.add(url)
|
seen.add(url)
|
||||||
return "# History\n\n" + "\n".join("=> " + url for url in urls)
|
return "# History\n\n" + "\n".join("=> " + url for url in urls)
|
||||||
|
|
||||||
def save(self):
|
|
||||||
"""Save current history to user data."""
|
|
||||||
history_path = get_history_path()
|
|
||||||
try:
|
|
||||||
with open(history_path, "wt") as history_file:
|
|
||||||
for url in self.urls:
|
|
||||||
history_file.write(url + "\n")
|
|
||||||
except OSError as exc:
|
|
||||||
logging.error(f"Failed to save history {history_path}: {exc}")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
"""Load saved history from user data."""
|
|
||||||
history_path = get_history_path()
|
|
||||||
self.urls = []
|
|
||||||
try:
|
|
||||||
with open(history_path, "rt") as history_file:
|
|
||||||
for url in history_file:
|
|
||||||
self.urls.append(url.rstrip())
|
|
||||||
except OSError as exc:
|
|
||||||
logging.error(f"Failed to load history {history_path}: {exc}")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
|
@ -19,8 +19,9 @@ def render_lines(metalines, window, max_width):
|
||||||
- max_width: line length limit for the pad.
|
- max_width: line length limit for the pad.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The tuple of integers (height, width), the new dimensions of the resized
|
The tuple of integers (error, height, width), error being a non-zero value
|
||||||
window.
|
if an error occured during rendering, and height and width being the new
|
||||||
|
dimensions of the resized window.
|
||||||
"""
|
"""
|
||||||
num_lines = len(metalines)
|
num_lines = len(metalines)
|
||||||
new_dimensions = max(num_lines, 1), max_width
|
new_dimensions = max(num_lines, 1), max_width
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[metadata]
|
[metadata]
|
||||||
name = bebop-browser
|
name = bebop-browser
|
||||||
version = 0.1.0
|
version = 0.0.3
|
||||||
description = Terminal browser for Gemini
|
description = Terminal browser for Gemini
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
license = GPLv3
|
license = GPLv3
|
||||||
|
|
Reference in a new issue