dvm-mdview 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. dvm_mdview-0.1.0.dist-info/METADATA +170 -0
  2. dvm_mdview-0.1.0.dist-info/RECORD +43 -0
  3. dvm_mdview-0.1.0.dist-info/WHEEL +4 -0
  4. dvm_mdview-0.1.0.dist-info/entry_points.txt +3 -0
  5. dvm_mdview-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-katex +21 -0
  7. dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-mermaid +21 -0
  8. mdview/__init__.py +4 -0
  9. mdview/__main__.py +3 -0
  10. mdview/_version.py +24 -0
  11. mdview/cli.py +64 -0
  12. mdview/gui.py +105 -0
  13. mdview/render.py +242 -0
  14. mdview/server.py +298 -0
  15. mdview/tui.py +514 -0
  16. mdview/web/app.css +172 -0
  17. mdview/web/app.js +638 -0
  18. mdview/web/index.html +64 -0
  19. mdview/web/vendor/LICENSE-katex +21 -0
  20. mdview/web/vendor/LICENSE-mermaid +21 -0
  21. mdview/web/vendor/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  22. mdview/web/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  23. mdview/web/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  24. mdview/web/vendor/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  25. mdview/web/vendor/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  26. mdview/web/vendor/fonts/KaTeX_Main-Bold.woff2 +0 -0
  27. mdview/web/vendor/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  28. mdview/web/vendor/fonts/KaTeX_Main-Italic.woff2 +0 -0
  29. mdview/web/vendor/fonts/KaTeX_Main-Regular.woff2 +0 -0
  30. mdview/web/vendor/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  31. mdview/web/vendor/fonts/KaTeX_Math-Italic.woff2 +0 -0
  32. mdview/web/vendor/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  33. mdview/web/vendor/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  34. mdview/web/vendor/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  35. mdview/web/vendor/fonts/KaTeX_Script-Regular.woff2 +0 -0
  36. mdview/web/vendor/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  37. mdview/web/vendor/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  38. mdview/web/vendor/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  39. mdview/web/vendor/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  40. mdview/web/vendor/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  41. mdview/web/vendor/katex.min.css +1 -0
  42. mdview/web/vendor/katex.min.js +1 -0
  43. mdview/web/vendor/mermaid.min.js +4329 -0
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.5
2
+ Name: dvm-mdview
3
+ Version: 0.1.0
4
+ Summary: Markdown viewer and live-preview editor with a graphical UI and a terminal UI
5
+ Project-URL: Homepage, https://github.com/divyavanmahajan/mdview
6
+ Project-URL: Issues, https://github.com/divyavanmahajan/mdview/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ License-File: src/mdview/web/vendor/LICENSE-katex
10
+ License-File: src/mdview/web/vendor/LICENSE-mermaid
11
+ Keywords: editor,gui,markdown,preview,tui,viewer
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: MacOS X
14
+ Classifier: Environment :: Win32 (MS Windows)
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Text Editors
20
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: linkify-it-py>=2.0
23
+ Requires-Dist: markdown-it-py>=3.0
24
+ Requires-Dist: mdit-py-plugins>=0.4.1
25
+ Requires-Dist: pygments>=2.17
26
+ Requires-Dist: pywebview>=5.3
27
+ Requires-Dist: textual>=3.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # mdview
31
+
32
+ A Markdown viewer and editor with live preview, for macOS, Windows and Linux.
33
+ It has two interfaces that share one renderer:
34
+
35
+ - a **graphical UI** in a native window, and
36
+ - a **terminal UI** for use in a shell or over SSH.
37
+
38
+ Published on PyPI as **`dvm-mdview`**.
39
+
40
+ ## Run it
41
+
42
+ With [uv](https://docs.astral.sh/uv/) installed, nothing else needs installing:
43
+
44
+ ```bash
45
+ uvx dvm-mdview README.md
46
+ ```
47
+
48
+ ```bash
49
+ uvx dvm-mdview --tui README.md
50
+ ```
51
+
52
+ Or install it, which gives you the shorter `mdview` command:
53
+
54
+ ```bash
55
+ uv tool install dvm-mdview
56
+ ```
57
+
58
+ ```bash
59
+ mdview notes.md
60
+ ```
61
+
62
+ | Option | Effect |
63
+ |---|---|
64
+ | *(none)* | Graphical UI. Falls back to the terminal UI when there is no display. |
65
+ | `-t`, `--tui` | Terminal UI. |
66
+ | `-b`, `--browser` | Graphical UI in your default browser instead of a native window. |
67
+ | `-e`, `--edit` | Start with editor and preview side by side. |
68
+ | `-r`, `--read` | Start with the preview only. |
69
+
70
+ A file that does not exist yet is opened empty and created when you save.
71
+ Passing a folder opens its `README.md`.
72
+
73
+ ## Features
74
+
75
+ - **Live preview.** View, Split (editor and preview side by side) and Edit layouts. The preview
76
+ updates as you type.
77
+ - **Link navigation.** Links to other Markdown files open in place, with back and forward history.
78
+ Links to headings scroll to them. Web links open in your browser. A link to a file that does not
79
+ exist starts a new document at that path.
80
+ - **Markdown.** CommonMark with GitHub tables, task lists, strikethrough and autolinks, plus
81
+ footnotes, YAML front matter and heading anchors.
82
+ - **Code highlighting** in both interfaces.
83
+ - **Mermaid diagrams and LaTeX math** rendered in the graphical UI (shown as source in the terminal).
84
+ Both work offline; the libraries are bundled.
85
+ - **Safe saving.** Files are written atomically, you are asked before unsaved changes are lost, and
86
+ a file changed by another program is reloaded (or flagged, if you have edits of your own).
87
+ - Outline sidebar, light and dark themes, and relative images in the graphical UI.
88
+
89
+ ## Keys
90
+
91
+ ### Graphical UI
92
+
93
+ `Cmd` replaces `Ctrl` on macOS.
94
+
95
+ | Key | Action |
96
+ |---|---|
97
+ | `Ctrl+E` | Cycle View / Split / Edit |
98
+ | `Ctrl+S`, `Ctrl+Shift+S` | Save, Save as |
99
+ | `Ctrl+O`, `Ctrl+N` | Open, New |
100
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
101
+ | `Ctrl+B`, `Ctrl+I`, `Ctrl+K` | Bold, italic, link |
102
+
103
+ ### Terminal UI
104
+
105
+ | Key | Action |
106
+ |---|---|
107
+ | `Ctrl+E` | Cycle View / Split / Edit |
108
+ | `F6` | Switch between editor and preview |
109
+ | `Ctrl+S` | Save |
110
+ | `Ctrl+O` | Open |
111
+ | `Ctrl+L` | List the document's links and follow one |
112
+ | `Alt+Left`, `Alt+Right` | Back, Forward |
113
+ | `Ctrl+P` | Command palette (Save as, New, theme, ...) |
114
+ | `Ctrl+Q` | Quit |
115
+
116
+ Links can also be clicked with the mouse. While the preview has focus, single keys work too:
117
+ `e` edit, `l` links, `o` open, `b` back, `f` forward, `j`/`k` scroll, `q` quit.
118
+
119
+ ## How it is built
120
+
121
+ | Part | Library |
122
+ |---|---|
123
+ | Markdown parsing | [markdown-it-py](https://github.com/executablebooks/markdown-it-py) and [mdit-py-plugins](https://github.com/executablebooks/mdit-py-plugins) |
124
+ | Code highlighting | [Pygments](https://pygments.org/) |
125
+ | Native window | [pywebview](https://pywebview.flowrl.com/) (WebKit on macOS, WebView2 on Windows) |
126
+ | Terminal UI | [Textual](https://textual.textualize.io/) |
127
+ | Diagrams and math | [Mermaid](https://mermaid.js.org/) and [KaTeX](https://katex.org/), bundled |
128
+
129
+ The graphical UI is a small web app served by a local backend (`src/mdview/server.py`) that listens
130
+ only on `127.0.0.1` and requires a per-run secret token. Raw HTML in a document is displayed, but a
131
+ strict Content-Security-Policy stops any script in it from running.
132
+
133
+ On Linux a native window needs GTK or Qt bindings for pywebview; without them mdview uses the
134
+ browser automatically.
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ uv sync
140
+ ```
141
+
142
+ ```bash
143
+ uv run pytest
144
+ ```
145
+
146
+ ```bash
147
+ uv run mdview README.md
148
+ ```
149
+
150
+ ### Releasing
151
+
152
+ The version comes from the git tag, so the tag and the PyPI version always match.
153
+
154
+ 1. Once, on pypi.org, add this repository as a *trusted publisher* for `dvm-mdview`
155
+ (workflow `publish.yml`, environment `pypi`).
156
+ 2. Tag a commit on `main` and push the tag:
157
+
158
+ ```bash
159
+ git tag v1.2.3
160
+ ```
161
+
162
+ ```bash
163
+ git push origin v1.2.3
164
+ ```
165
+
166
+ The `Release` workflow builds version 1.2.3, creates the GitHub release and uploads to PyPI.
167
+
168
+ ## License
169
+
170
+ MIT. Bundled Mermaid and KaTeX are MIT licensed; their licences are in `src/mdview/web/vendor/`.
@@ -0,0 +1,43 @@
1
+ mdview/__init__.py,sha256=QNVQYqj8c4yWLj2uKX0TJ-04Vxdv9WZp75EGBCuela0,190
2
+ mdview/__main__.py,sha256=k1ocEWawweo1qCJWNFAAvyxz3tcY13dzvCenHszij30,48
3
+ mdview/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
4
+ mdview/cli.py,sha256=hnB3UwEahMve51MiRUABOtCO96OBuuoLQh95B8HPZoM,2499
5
+ mdview/gui.py,sha256=h3IN7PaaPfd0ILOfoosb-ZDUUbXG5foMM6FOfYIgEaw,3625
6
+ mdview/render.py,sha256=rHW64F4F_l_41TwF9NUyTEwCDaOQVGd8fAo9KueVKKg,8083
7
+ mdview/server.py,sha256=NMXjLBbTj6ntgm-93ObRiDBPHGYEq2oQFc5TU4jMaPw,11823
8
+ mdview/tui.py,sha256=rEXRTlsDHTqkOWAWQfaagCGfKijw5mqvtqWEmr9Un-k,19441
9
+ mdview/web/app.css,sha256=gN-msc2HWV_DF2h2spLZ-ahMz5aXFpqyAVkikEhB3JM,8482
10
+ mdview/web/app.js,sha256=mQ188a9oZV5yBk2HDhiuPvNTyjoTw8dqJ1pX6DAQeqY,25415
11
+ mdview/web/index.html,sha256=CNmPFSh8htTAj8gvnJD7dh039KZ-BB-IM0CyUFpf_Sc,2624
12
+ mdview/web/vendor/LICENSE-katex,sha256=dmzMHzBsiFqkVUKphGu9ClBbJ6A3TxRneBccIlTOGOM,1107
13
+ mdview/web/vendor/LICENSE-mermaid,sha256=7J-2fcsl7MxBbtVuGquBkiLIBaKkv-TLGedVa_L_3oA,1089
14
+ mdview/web/vendor/katex.min.css,sha256=AOCwlBcbB_WEIgdfRptP6sKy97zhruyUPAjaTWl_Tc8,24793
15
+ mdview/web/vendor/katex.min.js,sha256=-hQEmLjPyDAsvl58sb28H5Urw4s_oljt4J5z7b-adLw,273081
16
+ mdview/web/vendor/mermaid.min.js,sha256=M234A9h6OLcKAec25mbI92AdLOQvXkfI-v5dchB0uaM,5537838
17
+ mdview/web/vendor/fonts/KaTeX_AMS-Regular.woff2,sha256=DN04fJWQoan5eUVgAi27WWVKfYbxh6oMgUla1C06cwg,28076
18
+ mdview/web/vendor/fonts/KaTeX_Caligraphic-Bold.woff2,sha256=3ncB5Czx9M8LdmwD-yeXcgfu4vT9XXb6ghiEBtpD6kw,6912
19
+ mdview/web/vendor/fonts/KaTeX_Caligraphic-Regular.woff2,sha256=XVPnCtYHwjUhYt7J4JI_tU7Nr6zL9gTNjc99APrLmJs,6908
20
+ mdview/web/vendor/fonts/KaTeX_Fraktur-Bold.woff2,sha256=dERO_Vk8AF4_RXO0RSRwTArwqTf-kRzKnpQGjQ0UDT8,11348
21
+ mdview/web/vendor/fonts/KaTeX_Fraktur-Regular.woff2,sha256=UYFNJw0G_wJV26B5mZT6TYyE0R8JlR1HWV9Kux82Atw,11316
22
+ mdview/web/vendor/fonts/KaTeX_Main-Bold.woff2,sha256=D2DRuJeTjskYyM4HMJJBG6-UOPZzlGVpP_GLD50gsCE,25324
23
+ mdview/web/vendor/fonts/KaTeX_Main-BoldItalic.woff2,sha256=mc1Co8By2Rjy9EmEqAfPeqFuE1Rf0IdfwHxsZfmecVs,16780
24
+ mdview/web/vendor/fonts/KaTeX_Main-Italic.woff2,sha256=l0ecpszpBqvJYeyslvql-couYbjnZw1HWCa83umnwmc,16988
25
+ mdview/web/vendor/fonts/KaTeX_Main-Regular.woff2,sha256=wjQs2Lhp4BdSqTIdwXIT_EDU0Ex5aIwdQ_LPMWq9eGY,26272
26
+ mdview/web/vendor/fonts/KaTeX_Math-BoldItalic.woff2,sha256=3Ec0TbtstbZVyEYNVh9N9fUBuQyAStPGzsZf4yI1GrE,16400
27
+ mdview/web/vendor/fonts/KaTeX_Math-Italic.woff2,sha256=evWMXsjxMqLd3pAnxteBTezOTTuCKhEZKkKiDi6XMmQ,16440
28
+ mdview/web/vendor/fonts/KaTeX_SansSerif-Bold.woff2,sha256=6ZrlEUS_EjLvzBv-Wt02JixoZrD6qyT6dXQOG5hXemI,12216
29
+ mdview/web/vendor/fonts/KaTeX_SansSerif-Italic.woff2,sha256=ALJqyCXiCVBWOW4FU7isJtP4rRWMOCbii0xFs4XEcUo,12028
30
+ mdview/web/vendor/fonts/KaTeX_SansSerif-Regular.woff2,sha256=aOjHPvQq_TzOxYvw-6MCzORIk45_wCCl4x-KlS7uE0I,10344
31
+ mdview/web/vendor/fonts/KaTeX_Script-Regular.woff2,sha256=A21OlRSbaf-bzAzVV3Hv6yX_o5Ryk-aazXjVrDKMaEs,9644
32
+ mdview/web/vendor/fonts/KaTeX_Size1-Regular.woff2,sha256=a0fEAWa22-IaXfyncYQT8hR_0jmb4bpgXYrTnO3yXf4,5468
33
+ mdview/web/vendor/fonts/KaTeX_Size2-Regular.woff2,sha256=0ExUIZ-ersbU1P1C37KHhZdaR5TWsvxx5Wa5zW24Qt0,5208
34
+ mdview/web/vendor/fonts/KaTeX_Size3-Regular.woff2,sha256=c9WRJxsWBJYMsQu5D-4CFnCvcpcBfg6YSAszLRH1GZU,3624
35
+ mdview/web/vendor/fonts/KaTeX_Size4-Regular.woff2,sha256=pK99QURAocF5CCXPtwDPnPQ7DyxLBPDrxSMBGtmFPsA,4928
36
+ mdview/web/vendor/fonts/KaTeX_Typewriter-Regular.woff2,sha256=cdUX1ngneHz6vfGGkUzDNY7aU543kxlB8rL9SiH2jAs,13568
37
+ dvm_mdview-0.1.0.dist-info/METADATA,sha256=tDVMakAwb-tfGHgX7xMn6Jc7X5InM9njh1wJEO8vEhM,5561
38
+ dvm_mdview-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
39
+ dvm_mdview-0.1.0.dist-info/entry_points.txt,sha256=Qe5ZpLRorUw4blEDkewOV4sckD2RLVeocrJRcaVkgVg,72
40
+ dvm_mdview-0.1.0.dist-info/licenses/LICENSE,sha256=ChIUAQiTt_OLpli_wB1UNm0E2wivUyUCr6HvfvG0GrA,1072
41
+ dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-katex,sha256=dmzMHzBsiFqkVUKphGu9ClBbJ6A3TxRneBccIlTOGOM,1107
42
+ dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-mermaid,sha256=7J-2fcsl7MxBbtVuGquBkiLIBaKkv-TLGedVa_L_3oA,1089
43
+ dvm_mdview-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ dvm-mdview = mdview.cli:main
3
+ mdview = mdview.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 divyavanmahajan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-2020 Khan Academy and other contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 - 2022 Knut Sveidqvist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mdview/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ try:
2
+ from ._version import __version__ # written at build time from the git tag
3
+ except ImportError: # running from a source tree that was never built
4
+ __version__ = "0.0.0+unknown"
mdview/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
mdview/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
mdview/cli.py ADDED
@@ -0,0 +1,64 @@
1
+ """Command line entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import sys
8
+
9
+ from . import __version__
10
+
11
+
12
+ def build_parser() -> argparse.ArgumentParser:
13
+ parser = argparse.ArgumentParser(
14
+ prog="mdview",
15
+ description="Markdown viewer and live-preview editor (graphical and terminal UI).",
16
+ )
17
+ parser.add_argument("file", nargs="?", help="Markdown file to open (created on save if missing)")
18
+ ui = parser.add_mutually_exclusive_group()
19
+ ui.add_argument("-t", "--tui", action="store_true", help="use the terminal UI")
20
+ ui.add_argument("-g", "--gui", action="store_true", help="use the graphical UI (default)")
21
+ ui.add_argument(
22
+ "-b", "--browser", action="store_true",
23
+ help="graphical UI in the default web browser instead of a native window",
24
+ )
25
+ mode = parser.add_mutually_exclusive_group()
26
+ mode.add_argument("-e", "--edit", action="store_true", help="start with editor and preview side by side")
27
+ mode.add_argument("-r", "--read", action="store_true", help="start in read-only preview")
28
+ parser.add_argument("--port", type=int, default=0, help="port for --browser (default: any free port)")
29
+ parser.add_argument("--no-open", action="store_true", help="with --browser, do not launch the browser")
30
+ parser.add_argument("-V", "--version", action="version", version=f"mdview {__version__}")
31
+ return parser
32
+
33
+
34
+ def main(argv: list[str] | None = None) -> int:
35
+ args = build_parser().parse_args(argv)
36
+ path = os.path.abspath(os.path.expanduser(args.file)) if args.file else None
37
+ if path and os.path.isdir(path):
38
+ for name in ("README.md", "index.md", "readme.md"):
39
+ if os.path.isfile(os.path.join(path, name)):
40
+ path = os.path.join(path, name)
41
+ break
42
+ else:
43
+ print(f"mdview: {path} is a folder without a README.md", file=sys.stderr)
44
+ return 2
45
+ mode = "split" if args.edit else "view" if args.read else None
46
+
47
+ from .gui import has_display
48
+
49
+ use_tui = args.tui
50
+ if not (args.tui or args.gui or args.browser) and not has_display():
51
+ use_tui = True # e.g. an SSH session: no display to open a window on
52
+ if use_tui:
53
+ from .tui import run as run_tui
54
+
55
+ return run_tui(path, mode)
56
+
57
+ from .gui import run as run_gui
58
+ from .server import AppState
59
+
60
+ return run_gui(AppState(path, mode), args.browser, args.port, not args.no_open)
61
+
62
+
63
+ if __name__ == "__main__":
64
+ sys.exit(main())
mdview/gui.py ADDED
@@ -0,0 +1,105 @@
1
+ """Graphical UI: a native window (pywebview) around the local web app."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import threading
8
+ import time
9
+ import webbrowser
10
+
11
+ from .server import AppState, start_server
12
+
13
+ FILE_TYPES = ("Markdown (*.md;*.markdown;*.mdown;*.mkd;*.mdx;*.txt)", "All files (*.*)")
14
+
15
+
16
+ def has_display() -> bool:
17
+ if sys.platform in ("win32", "darwin"):
18
+ return True
19
+ return bool(os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"))
20
+
21
+
22
+ def run_browser(state: AppState, port: int = 0, open_browser: bool = True) -> int:
23
+ """Serve the UI and show it in the default browser until interrupted."""
24
+ server, url = start_server(state, port)
25
+ print(f"mdview is running at {url}\nPress Ctrl+C to stop.", flush=True)
26
+ if open_browser:
27
+ webbrowser.open(url)
28
+ try:
29
+ while True:
30
+ time.sleep(3600)
31
+ except KeyboardInterrupt:
32
+ print()
33
+ finally:
34
+ server.shutdown()
35
+ return 0
36
+
37
+
38
+ def run_window(state: AppState) -> int:
39
+ """Show the UI in a native window. Raises ImportError if pywebview is unusable."""
40
+ import logging
41
+
42
+ import webview
43
+
44
+ logging.getLogger("pywebview").setLevel(logging.CRITICAL) # we report failures ourselves
45
+ server, url = start_server(state)
46
+ window = webview.create_window(
47
+ "mdview", url, width=1200, height=820, min_size=(480, 320), text_select=True
48
+ )
49
+ closing_for_real = threading.Event()
50
+
51
+ def native_dialog(kind: str, directory: str, name: str) -> str | None:
52
+ try:
53
+ dialogs = webview.FileDialog # pywebview >= 6
54
+ open_kind, save_kind = dialogs.OPEN, dialogs.SAVE
55
+ except AttributeError:
56
+ open_kind, save_kind = webview.OPEN_DIALOG, webview.SAVE_DIALOG
57
+ if not os.path.isdir(directory):
58
+ directory = os.getcwd()
59
+ if kind == "save":
60
+ result = window.create_file_dialog(
61
+ save_kind, directory=directory, save_filename=name or "untitled.md",
62
+ file_types=FILE_TYPES,
63
+ )
64
+ else:
65
+ result = window.create_file_dialog(
66
+ open_kind, directory=directory, allow_multiple=False, file_types=FILE_TYPES
67
+ )
68
+ if not result:
69
+ return None
70
+ return result if isinstance(result, str) else result[0]
71
+
72
+ def on_closing():
73
+ if closing_for_real.is_set() or not state.dirty:
74
+ return True
75
+ # Unsaved changes: cancel the close and let the page ask Save / Discard.
76
+ # evaluate_js must not run on the GUI thread that is delivering this event.
77
+ threading.Thread(
78
+ target=lambda: window.evaluate_js("window.mdviewRequestClose()"), daemon=True
79
+ ).start()
80
+ return False
81
+
82
+ def close_window() -> None:
83
+ closing_for_real.set()
84
+ window.destroy()
85
+
86
+ state.native_dialog = native_dialog
87
+ state.set_title = window.set_title
88
+ state.close_window = close_window
89
+ window.events.closing += on_closing
90
+ try:
91
+ webview.start(private_mode=True)
92
+ finally:
93
+ server.shutdown()
94
+ return 0
95
+
96
+
97
+ def run(state: AppState, browser: bool = False, port: int = 0, open_browser: bool = True) -> int:
98
+ if browser:
99
+ return run_browser(state, port, open_browser)
100
+ try:
101
+ return run_window(state)
102
+ except Exception as exc: # no web engine, missing GTK/Qt, pythonnet trouble, ...
103
+ print(f"mdview: native window unavailable ({exc}); using the browser.", file=sys.stderr)
104
+ state.native_dialog = state.set_title = state.close_window = None
105
+ return run_browser(state, port, open_browser)