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.
- dvm_mdview-0.1.0.dist-info/METADATA +170 -0
- dvm_mdview-0.1.0.dist-info/RECORD +43 -0
- dvm_mdview-0.1.0.dist-info/WHEEL +4 -0
- dvm_mdview-0.1.0.dist-info/entry_points.txt +3 -0
- dvm_mdview-0.1.0.dist-info/licenses/LICENSE +21 -0
- dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-katex +21 -0
- dvm_mdview-0.1.0.dist-info/licenses/src/mdview/web/vendor/LICENSE-mermaid +21 -0
- mdview/__init__.py +4 -0
- mdview/__main__.py +3 -0
- mdview/_version.py +24 -0
- mdview/cli.py +64 -0
- mdview/gui.py +105 -0
- mdview/render.py +242 -0
- mdview/server.py +298 -0
- mdview/tui.py +514 -0
- mdview/web/app.css +172 -0
- mdview/web/app.js +638 -0
- mdview/web/index.html +64 -0
- mdview/web/vendor/LICENSE-katex +21 -0
- mdview/web/vendor/LICENSE-mermaid +21 -0
- mdview/web/vendor/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Main-Bold.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Main-Italic.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Main-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Math-Italic.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Script-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- mdview/web/vendor/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- mdview/web/vendor/katex.min.css +1 -0
- mdview/web/vendor/katex.min.js +1 -0
- 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,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
mdview/__main__.py
ADDED
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)
|