Compare commits
4 commits
62619f29fb
...
84764644df
Author | SHA1 | Date | |
---|---|---|---|
dece | 84764644df | ||
dece | d7a94650cb | ||
dece | 524bda5b39 | ||
dece | 72111bce70 |
45
README.md
Normal file
45
README.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
Bebop
|
||||
=====
|
||||
|
||||
Bebop is a [Gemini][gemini] browser for the terminal, focusing on practicality
|
||||
and speed. It is a personal project to learn how to use ncurses and try new
|
||||
ways to explore the Geminispace. It borrows some ideas from [Amfora][amfora],
|
||||
another great terminal browser, Vim for interactivity and tries to support mouse
|
||||
usage decently.
|
||||
|
||||
[gemini]: https://gemini.circumlunar.space/
|
||||
[amfora]: https://github.com/makeworld-the-better-one/amfora
|
||||
|
||||
If you are interested in Gemini and looking for a client, I recommend trying a
|
||||
graphical one like the excellent [Lagrange][lagrange] or [Kristall][kristall],
|
||||
or Amfora if you're feeling more at home in the terminal. Bebop won't attempt
|
||||
to support every feature other clients might have.
|
||||
|
||||
[lagrange]: https://git.skyjake.fi/skyjake/lagrange
|
||||
[kristall]: https://kristall.random-projects.net/
|
||||
|
||||
It passes the Conman's client test but not Egsam's for now.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
### What works
|
||||
|
||||
- Basic browsing: scrolling, follow links, redirections, Web links.
|
||||
|
||||
### What is planned
|
||||
|
||||
- Handle more content types.
|
||||
- Great config options.
|
||||
- Identity management with temporary and managed certificates.
|
||||
- Buffers (or tabs if you prefer).
|
||||
- Home page.
|
||||
- Bookmarks.
|
||||
|
||||
### What is not planned for now
|
||||
|
||||
- 256-colors mode and themes.
|
||||
- Subscriptions. I have no need for them as I prefer to use a browser-agnostic
|
||||
aggregator at the moment.
|
|
@ -222,7 +222,11 @@ class Browser:
|
|||
else:
|
||||
pass # TODO
|
||||
|
||||
response = Response.parse(req.proceed())
|
||||
data = req.proceed()
|
||||
if not data:
|
||||
self.set_status_error(f"Server did not respond in time ({url}).")
|
||||
return
|
||||
response = Response.parse(data)
|
||||
if not response:
|
||||
self.set_status_error(f"Server response parsing failed ({url}).")
|
||||
return
|
||||
|
|
|
@ -81,7 +81,7 @@ class Request:
|
|||
self.payload += LINE_TERM
|
||||
|
||||
try:
|
||||
sock = socket.create_connection((hostname, port))
|
||||
sock = socket.create_connection((hostname, port), timeout=10)
|
||||
except OSError as exc:
|
||||
self.state = Request.STATE_CONNECTION_FAILED
|
||||
self.error = exc.strerror
|
||||
|
@ -124,7 +124,10 @@ class Request:
|
|||
self.ssock.sendall(self.payload)
|
||||
response = b""
|
||||
while True:
|
||||
buf = self.ssock.recv(4096)
|
||||
try:
|
||||
buf = self.ssock.recv(4096)
|
||||
except socket.timeout:
|
||||
buf = None
|
||||
if not buf:
|
||||
return response
|
||||
response += buf
|
||||
|
@ -172,6 +175,7 @@ class Response:
|
|||
content: bytes = b""
|
||||
|
||||
HEADER_RE = re.compile(r"(\d{2}) (.*)")
|
||||
MAX_META_LEN = 1024
|
||||
|
||||
@property
|
||||
def generic_code(self):
|
||||
|
@ -189,6 +193,8 @@ class Response:
|
|||
if not match:
|
||||
return None
|
||||
code, meta = match.groups()
|
||||
if len(meta) > Response.MAX_META_LEN:
|
||||
return None
|
||||
response = Response(StatusCode(int(code)), meta=meta)
|
||||
if response.generic_code == StatusCode.SUCCESS:
|
||||
content_offset = response_header_len + len(LINE_TERM)
|
||||
|
|
|
@ -215,10 +215,13 @@ def render_lines(metalines, window, max_width):
|
|||
- max_width: line length limit for the pad.
|
||||
|
||||
Return:
|
||||
The tuple (height, width) of the resized window.
|
||||
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.
|
||||
"""
|
||||
num_lines = len(metalines)
|
||||
window.resize(num_lines, max_width)
|
||||
new_dimensions = num_lines, max_width
|
||||
window.resize(*new_dimensions)
|
||||
for line_index, metaline in enumerate(metalines):
|
||||
meta, line = metaline
|
||||
line = line[:max_width - 1]
|
||||
|
@ -237,7 +240,10 @@ def render_lines(metalines, window, max_width):
|
|||
attr = curses.color_pair(ColorPair.BLOCKQUOTE) | curses.A_ITALIC
|
||||
else: # includes LineType.PARAGRAPH
|
||||
attr = curses.color_pair(ColorPair.NORMAL)
|
||||
window.addstr(line, attr)
|
||||
try:
|
||||
window.addstr(line, attr)
|
||||
except ValueError:
|
||||
return new_dimensions
|
||||
if line_index < num_lines - 1:
|
||||
window.addstr("\n")
|
||||
return num_lines, max_width
|
||||
return new_dimensions
|
||||
|
|
Reference in a new issue