metalines: use a 3-uple instead of dict

This commit is contained in:
dece 2021-06-28 02:09:59 +02:00
parent 016e4a49f9
commit f7b4607ed6
3 changed files with 37 additions and 40 deletions

View file

@ -552,19 +552,19 @@ class Browser:
line_pos = y + py line_pos = y + py
if line_pos >= len(self.current_page.metalines): if line_pos >= len(self.current_page.metalines):
return return
meta, line = self.current_page.metalines[line_pos] ltype, ltext, lextra = self.current_page.metalines[line_pos]
if meta["type"] != LineType.LINK: if ltype != LineType.LINK:
return return
# "url" key is contained only in the first line of the link if its text # "url" key is contained only in the first line of the link if its text
# is wrapped, so if the user did not click on the first line, rewind to # is wrapped, so if the user did not click on the first line, rewind to
# get the URL. # get the URL.
while "url" not in meta: while not lextra or "url" not in lextra:
line_pos -= 1 line_pos -= 1
meta, line = self.current_page.metalines[line_pos] _, ltext, lextra = self.current_page.metalines[line_pos]
url = meta["url"] url = lextra["url"]
# The click is valid if it is on the link itself or the dimmed preview. # The click is valid if it is on the link itself or the dimmed preview.
col_pos = x + px col_pos = x + px
if col_pos > len(line): if col_pos > len(ltext):
ch = self.page_pad.pad.instr(line_pos, col_pos, 1) ch = self.page_pad.pad.instr(line_pos, col_pos, 1)
if ch == b' ': if ch == b' ':
return return
@ -834,8 +834,8 @@ class Browser:
if not search: if not search:
return return
self.search_res_lines = [] self.search_res_lines = []
for index, (_, line) in enumerate(self.current_page.metalines): for index, (_, ltext, _) in enumerate(self.current_page.metalines):
if search in line: if search in ltext:
self.search_res_lines.append(index) self.search_res_lines.append(index)
if self.search_res_lines: if self.search_res_lines:
self.move_to_search_result(Browser.SEARCH_NEXT) self.move_to_search_result(Browser.SEARCH_NEXT)

View file

@ -6,6 +6,18 @@ displayed, along with associated meta-data such as its type or a link's URL.
Note that metalines can be generated by custom functions without relying on the Note that metalines can be generated by custom functions without relying on the
elements classes as they are quite coupled to Gemtext parsing/rendering. elements classes as they are quite coupled to Gemtext parsing/rendering.
The metalines are tuples (ltype, line, lextra):
- ltype is the LineType.
- line is the text content itself.
- lextra is either a dict of additional data, or None.
The lextra part is currently only used for links, and can contain the following
keys:
- url: the URL the link on this line refers to. Note that this key is present
only for the first line of the link, i.e. long link descriptions wrapped on
multiple lines will not have a this key except for the first line.
- link_id: only alongside "url" key, ID generated for this link.
""" """
import string import string
@ -53,22 +65,12 @@ class RenderOptions:
def generate_metalines(elements: list, options: RenderOptions) -> list: def generate_metalines(elements: list, options: RenderOptions) -> list:
"""Format elements into a list of lines with metadata. """Format elements into a list of lines with metadata.
The returned list ("metalines") are tuples (meta, line), meta being a
dict of metadata and a text line to display. Currently the only metadata
keys used are:
- type: one of the Renderer.TYPE constants.
- url: only for links, the URL the link on this line refers to. Note
that this key is present only for the first line of the link, i.e.
long link descriptions wrapped on multiple lines will not have a this
key except for the first line.
- link_id: only alongside "url" key, ID generated for this link.
Arguments: Arguments:
- elements: list of elements to use. - elements: list of elements to use.
- options: RenderOptions to respect when generating metalines. - options: RenderOptions to respect when generating metalines.
""" """
metalines = [] metalines = []
separator = ({"type": LineType.NONE}, "") separator = (LineType.NONE, "", None)
has_margins = False has_margins = False
thin_type = None thin_type = None
for index, element in enumerate(elements): for index, element in enumerate(elements):
@ -100,7 +102,7 @@ def generate_metalines(elements: list, options: RenderOptions) -> list:
# rendered as empty lines. # rendered as empty lines.
if options.mode == "dumb": if options.mode == "dumb":
if not element_metalines: if not element_metalines:
element_metalines = [({"type": LineType.PARAGRAPH}, "")] element_metalines = [(LineType.PARAGRAPH, "", None)]
# If current element requires margins and is not the first elements, # If current element requires margins and is not the first elements,
# separate from previous element. Also do it if the current element does # separate from previous element. Also do it if the current element does
# not require margins but follows an element that required it (e.g. link # not require margins but follows an element that required it (e.g. link
@ -119,7 +121,7 @@ def generate_metalines(elements: list, options: RenderOptions) -> list:
def generate_dumb_metalines(lines): def generate_dumb_metalines(lines):
"""Generate dumb metalines: all lines are given the PARAGRAPH line type.""" """Generate dumb metalines: all lines are given the PARAGRAPH line type."""
return [({"type": LineType.PARAGRAPH}, line) for line in lines] return [(LineType.PARAGRAPH, line, None) for line in lines]
def format_title(title: Title, options: RenderOptions): def format_title(title: Title, options: RenderOptions):
@ -135,13 +137,13 @@ def format_title(title: Title, options: RenderOptions):
else: else:
lines = wrap_words(title.text, width) lines = wrap_words(title.text, width)
# Title levels match the type constants of titles. # Title levels match the type constants of titles.
return [({"type": LineType(title.level)}, line) for line in lines] return [(LineType(title.level), line, None) for line in lines]
def format_paragraph(paragraph: Paragraph, options: RenderOptions): def format_paragraph(paragraph: Paragraph, options: RenderOptions):
"""Return metalines for this paragraph.""" """Return metalines for this paragraph."""
lines = wrap_words(paragraph.text, options.width) lines = wrap_words(paragraph.text, options.width)
return [({"type": LineType.PARAGRAPH}, line) for line in lines] return [(LineType.PARAGRAPH, line, None) for line in lines]
def format_link(link: Link, options: RenderOptions): def format_link(link: Link, options: RenderOptions):
@ -151,30 +153,26 @@ def format_link(link: Link, options: RenderOptions):
link_text = link.text or link.url link_text = link.text or link.url
# Wrap lines, indented by the link anchor length. # Wrap lines, indented by the link anchor length.
lines = wrap_words(link_text, options.width, indent=len(link_anchor)) lines = wrap_words(link_text, options.width, indent=len(link_anchor))
first_line_meta = { first_line_extra = {
"type": LineType.LINK,
"url": link.url, "url": link.url,
"link_id": link.ident "link_id": link.ident
} }
# Replace first line indentation with the anchor. # Replace first line indentation with the anchor.
first_line_text = link_anchor + lines[0][len(link_anchor):] first_line_text = link_anchor + lines[0][len(link_anchor):]
first_line = [(first_line_meta, first_line_text)] first_line = [(LineType.LINK, first_line_text, first_line_extra)]
other_lines = [({"type": LineType.LINK}, line) for line in lines[1:]] other_lines = [(LineType.LINK, line, None) for line in lines[1:]]
return first_line + other_lines return first_line + other_lines # type: ignore
def format_preformatted(preformatted: Preformatted, options: RenderOptions): def format_preformatted(preformatted: Preformatted, options: RenderOptions):
"""Return metalines for this preformatted block.""" """Return metalines for this preformatted block."""
return [ return [(LineType.PREFORMATTED, line, None) for line in preformatted.lines]
({"type": LineType.PREFORMATTED}, line)
for line in preformatted.lines
]
def format_blockquote(blockquote: Blockquote, options: RenderOptions): def format_blockquote(blockquote: Blockquote, options: RenderOptions):
"""Return metalines for this blockquote.""" """Return metalines for this blockquote."""
lines = wrap_words(blockquote.text, options.width, indent=2) lines = wrap_words(blockquote.text, options.width, indent=2)
return [({"type": LineType.BLOCKQUOTE}, line) for line in lines] return [(LineType.BLOCKQUOTE, line, None) for line in lines]
def format_list_item(item: ListItem, options: RenderOptions): def format_list_item(item: ListItem, options: RenderOptions):
@ -183,7 +181,7 @@ def format_list_item(item: ListItem, options: RenderOptions):
lines = wrap_words(item.text, options.width, indent=indent) lines = wrap_words(item.text, options.width, indent=indent)
first_line = options.bullet + lines[0][indent:] first_line = options.bullet + lines[0][indent:]
lines[0] = first_line lines[0] = first_line
return [({"type": LineType.LIST_ITEM}, line) for line in lines] return [(LineType.LIST_ITEM, line, None) for line in lines]
def wrap_words(text: str, width: int, indent: int =0) -> List[str]: def wrap_words(text: str, width: int, indent: int =0) -> List[str]:

View file

@ -37,13 +37,12 @@ def render_lines(metalines, window, max_width):
def render_line(metaline, window, max_width): def render_line(metaline, window, max_width):
"""Write a single line to the window.""" """Write a single line to the window."""
meta, line = metaline ltype, ltext, lextra = metaline
line_type = meta["type"] attributes = get_base_line_attributes(ltype)
attributes = get_base_line_attributes(line_type) line = ltext[:max_width - 1]
line = line[:max_width - 1]
window.addstr(line, attributes) window.addstr(line, attributes)
if meta["type"] == LineType.LINK and "url" in meta: if ltype == LineType.LINK and lextra and "url" in lextra:
url_text = f' {meta["url"]}' url_text = f' {lextra["url"]}'
attributes = ( attributes = (
curses.color_pair(ColorPair.LINK_PREVIEW) curses.color_pair(ColorPair.LINK_PREVIEW)
| curses.A_DIM | curses.A_DIM