Broken project to implement a cross-protocol browser in textual
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#!/usr/bin/env python3
from textual.app import App, ComposeResult
from textual.widgets import Input, Button, Static, Footer
from textual.containers import Container
from textual.binding import Binding
from textual.events import Resize
from mime.gemtext import Gemtext
from mime.plaintext import Plaintext
from mime.highlightedcode import HighlightedCode,mimetolexer
from protocol.gemini import GeminiProtocol
from protocol.data import DataProtocol
from protocol.http import HttpProtocol
from protocol.about import AboutProtocol
from subprocess import Popen, DEVNULL
from gemurllib.parse import urljoin

protocols = {
    "gemini": GeminiProtocol,
    "data": DataProtocol,
    "http": HttpProtocol,
    "https": HttpProtocol,
    "about": AboutProtocol,
}

class Browset(App):
    url = "about:blank"
    CSS_PATH = "browset.css"
    BINDINGS = [
        Binding("ctrl+q,ctrl+c", "app.quit", "Quit", show=True),
        Binding("ctrl+left", "back()", "Back", show=False),
        Binding("ctrl+right", "soon()", "Soon", show=False),
        Binding("ctrl+up", "top()", "Top", show=False),
        Binding("ctrl+o", "external_open()", "Open Externally"),
    ]
    history = []
    fistory = [] # forward history

    content = ["#h1", "## hey [b]Is this unformatted?[/b]", "```startpre","in preformatted text this hsould have a scrollbar if it is too wide oh yeah tonight is gonna be great.", "``` (ended pre)", "afterward"]
    def compose(self) -> ComposeResult:
        yield Footer()
        yield Container(
            Button("🔙", variant='primary', name='back', classes='mobile'), # ⏪
            Button("🔝", variant='primary', name='top'), # ⏫
            Button("🔜", variant='primary', name='soon'), # ⏩
            Button("🔄", variant='primary', name='refresh'), # 🔁
            
            Input(placeholder="Enter URI", id="url"),
            id="toolbar"
        )
        yield Gemtext(fp=self.content, id="content")
    async def on_input_submitted(self, message: Input.Submitted) -> None:
        self._do_url(message.value, setbar=False)

    async def on_button_pressed(self, event: Button.Pressed) -> None:
      if event.button.variant == 'primary':
        if event.button.name == 'refresh':
            self._do_url(self.url)
        elif event.button.name == 'back':
          self.action_back()
        elif event.button.name == 'soon':
          self.action_soon()
        elif event.button.name == 'top':
          self.action_top()
      else:
        url = event.button.name
        self._do_url(url)


    async def on_resize(self, event: Resize) -> None:
      toolbar = self.query_one("#toolbar")
      if event.size.width < 60:
        toolbar.add_class('mobile')
      else:
        toolbar.remove_class('mobile')

    def action_external_open(self):
      # Run this command in the background
      Popen(["xdg-open", self.url], stdout=DEVNULL, stderr=DEVNULL)

    def action_back(self):
      if len(self.history):
        self.fistory.append(self.url)
        url = self.history.pop()
        self._do_url(url, histore=False, clearF=False)
    def action_soon(self):
      if len(self.fistory):
        url = self.fistory.pop()
        self._do_url(url, clearF=False)
    def action_top(self):
      if self.url[-1] == '/':
        self._do_url('../')
      elif self.url:
        self._do_url('./')

    def _do_url(self, url, histore=True, setbar=True, clearF=True):
        if not ":" in url:
          url = urljoin(self.url,url)
        if clearF:
            self.fistory = []
        if setbar:
            input = self.query_one("#url")
            input.value = url
            input.action_end()
        if histore:
            self.history.append(self.url)
        self.url = url
        protocol = url.split(":")[0]
        if protocol in protocols:
          (mime, fp) = protocols[protocol].get(url)
        else:
          (mime, fp) = ("text/error", ["Unsupported protocol: " + protocol])

        self.query_one("#content").remove()

        if "text/gemini" in mime:
          content = Gemtext(fp=fp, id="content")
        elif HighlightedCode.can_handle_mime(mime):
          content = HighlightedCode(fp=fp, id="content", mime=mime)
        elif "text/" in mime:
          content = Plaintext(fp=fp, id="content")
        else:
          content = Plaintext(fp=["Unhandled mimetype: " + mime], id="content")
        self.mount(content)

if __name__ == "__main__":
    app = Browset()
    app.run()