Repos / mcross / 5e3834ce29
commit 5e3834ce29ab8333f3b7a061387ace0419c3f599
Author: Bùi Thành Nhân <hi@imnhan.com>
Date:   Fri May 15 20:34:58 2020 +0700

    implement list items & headings

diff --git a/README.md b/README.md
index 918ad98..166f7a2 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 
 It currently looks like this:
 
-![](https://p.caophim.net/85.png)
+![](https://p.caophim.net/87.png)
 
 Happy-path surfing and link-visiting already works.
 The UX is still terrible though (see feature checklist below).
@@ -45,7 +45,7 @@ # Feature checklist
 - [x] back-forward buttons
 - [ ] separate I/O thread to avoid blocking GUI
 - [ ] more visual indicators - maybe a status bar at the bottom
-- [ ] parse gemini's advanced line types
+- [x] parse gemini's advanced line types
 - [ ] configurable document styling
 - [ ] configurable TLS to accomodate self-signed sites?
 
diff --git a/pyproject.toml b/pyproject.toml
index 29d7446..93f8340 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "mcross"
-version = "0.3.0"
+version = "0.4.0"
 description = "Do you remember www?"
 authors = ["nhanb <hi@imnhan.com>"]
 license = "MIT"
diff --git a/src/mcross/document.py b/src/mcross/document.py
index 37af176..d5f6864 100644
--- a/src/mcross/document.py
+++ b/src/mcross/document.py
@@ -2,6 +2,7 @@
 
 NEWLINE = "\n"
 LINK_LINE_PATTERN = re.compile(r"^=>[ \t]+(\S+)([ \t]+(.+))?$")
+HEADING_LINE_PATTERN = re.compile(r"^(#{1,3})\s+(.+)$")
 
 
 class GeminiNode:
@@ -18,6 +19,22 @@ class TextNode(GeminiNode):
     pass
 
 
+class ListItemNode(GeminiNode):
+    pass
+
+
+class H1Node(GeminiNode):
+    pass
+
+
+class H2Node(GeminiNode):
+    pass
+
+
+class H3Node(GeminiNode):
+    pass
+
+
 class LinkNode(GeminiNode):
     url: str
     name: str
@@ -69,6 +86,24 @@ def parse(text):
             name = match.group(3)  # may be None
             nodes.append(LinkNode(text=line, url=url, name=name))
 
+        elif line.startswith("*"):
+            nodes.append(ListItemNode(line))
+
+        elif line.startswith("#"):
+            match = HEADING_LINE_PATTERN.match(line)
+            if not match:
+                nodes.append(TextNode(line))
+                continue
+            # heading_text = match.group(2)  # not used yet
+            hashes = match.group(1)
+            level = len(hashes)
+            if level == 1:
+                nodes.append(H1Node(line))
+            elif level == 2:
+                nodes.append(H2Node(line))
+            elif level == 3:
+                nodes.append(H3Node(line))
+
         else:
             nodes.append(TextNode(line))
 
diff --git a/src/mcross/gui/view.py b/src/mcross/gui/view.py
index 99ac122..c5c237d 100644
--- a/src/mcross/gui/view.py
+++ b/src/mcross/gui/view.py
@@ -1,7 +1,16 @@
 import sys
 from tkinter import Text, Tk, font, ttk
 
-from ..document import GeminiNode, LinkNode, PreformattedNode, TextNode
+from ..document import (
+    GeminiNode,
+    H1Node,
+    H2Node,
+    H3Node,
+    LinkNode,
+    ListItemNode,
+    PreformattedNode,
+    TextNode,
+)
 from .model import Model
 from .widgets import ReadOnlyText
 
@@ -102,12 +111,24 @@ def __init__(self, root: Tk, model: Model):
             # prevent verticle scrollbar from disappearing when window gets small:
             width=1,
         )
+        text.pack(side="left", fill="both", expand=True)
         text.tag_config("link", foreground="brown")
         text.tag_bind("link", "<Enter>", self._on_link_enter)
         text.tag_bind("link", "<Leave>", self._on_link_leave)
         text.tag_bind("link", "<Button-1>", self._on_link_click)
         text.tag_config("pre", font=(mono_font, 13))
-        text.pack(side="left", fill="both", expand=True)
+        text.tag_config("listitem", foreground="#044604")
+
+        base_heading_font = font.Font(font=text["font"])
+        base_heading_font.config(weight="bold")
+        h1_font = font.Font(font=base_heading_font)
+        h1_font.config(size=h1_font["size"] + 8)
+        text.tag_config("h1", font=h1_font)
+        h2_font = font.Font(font=base_heading_font)
+        h2_font.config(size=h2_font["size"] + 4)
+        text.tag_config("h2", font=h2_font)
+        h3_font = font.Font(font=base_heading_font)
+        text.tag_config("h3", font=h3_font)
 
         text_scrollbar = ttk.Scrollbar(row2, command=text.yview)
         text["yscrollcommand"] = text_scrollbar.set
@@ -162,17 +183,26 @@ def render_page(self):
 def render_node(node: GeminiNode, widget: Text):
     nodetype = type(node)
     if nodetype is TextNode:
-        widget.insert("end", node.text + "\n")
+        widget.insert("end", node.text)
     elif nodetype is LinkNode:
         widget.insert("end", "=> ")
-        widget.insert("end", f"{node.url}", ("link",))
+        widget.insert("end", node.url, ("link",))
         if node.name:
             widget.insert("end", f" {node.name}")
-        widget.insert("end", "\n")
     elif nodetype is PreformattedNode:
-        widget.insert("end", f"```\n{node.text}\n```\n", ("pre",))
+        widget.insert("end", f"```\n{node.text}\n```", ("pre",))
+    elif nodetype is ListItemNode:
+        widget.insert("end", node.text, ("listitem",))
+    elif nodetype is H1Node:
+        widget.insert("end", node.text, ("h1",))
+    elif nodetype is H2Node:
+        widget.insert("end", node.text, ("h2",))
+    elif nodetype is H3Node:
+        widget.insert("end", node.text, ("h3",))
     else:
-        widget.insert("end", node.text + "\n")
+        widget.insert("end", node.text)
+
+    widget.insert("end", "\n")
 
 
 def get_content_from_tag_click_event(event):