nvim-doc-reader 0.1.0__tar.gz → 0.1.1__tar.gz

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 (26) hide show
  1. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/PKG-INFO +1 -1
  2. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/pyproject.toml +1 -1
  3. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/model.py +13 -0
  4. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/skills/doc-authoring/SKILL.md +13 -0
  5. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/static/reader.css +32 -2
  6. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/static/reader.js +10 -1
  7. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/PKG-INFO +1 -1
  8. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/tests/test_model.py +16 -2
  9. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/tests/test_nvim.py +16 -12
  10. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/tests/test_serve.py +10 -4
  11. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/tests/test_skill.py +9 -3
  12. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/tests/test_urls_config.py +2 -0
  13. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/README.md +0 -0
  14. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/setup.cfg +0 -0
  15. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/__init__.py +0 -0
  16. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/cli.py +0 -0
  17. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/config.py +0 -0
  18. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/nvim/lua/doc_reader.lua +0 -0
  19. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/nvim.py +0 -0
  20. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/serve.py +0 -0
  21. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/doc_reader/urls.py +0 -0
  22. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/SOURCES.txt +0 -0
  23. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/dependency_links.txt +0 -0
  24. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/entry_points.txt +0 -0
  25. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/requires.txt +0 -0
  26. {nvim_doc_reader-0.1.0 → nvim_doc_reader-0.1.1}/src/nvim_doc_reader.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvim-doc-reader
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Keyboard-driven HTML reader for Markdown docs, with jumps into running Neovim
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nvim-doc-reader"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Keyboard-driven HTML reader for Markdown docs, with jumps into running Neovim"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -83,6 +83,8 @@ def make_markdown():
83
83
  md.add_render_rule("link_open", _link_open)
84
84
  md.add_render_rule("image", _image)
85
85
  md.add_render_rule("list_item_open", _list_item_open)
86
+ md.add_render_rule("table_open", _table_open)
87
+ md.add_render_rule("table_close", _table_close)
86
88
  return md
87
89
 
88
90
 
@@ -277,5 +279,16 @@ def _list_item_open(self, tokens, idx, options, env):
277
279
  return self.renderToken(tokens, idx, options, env)
278
280
 
279
281
 
282
+ # The wrapper is what reader.css widens past the text column and scrolls when
283
+ # the table outgrows the screen; tabindex lets the arrow keys scroll it.
284
+ def _table_open(self, tokens, idx, options, env):
285
+ return ('<div class="dr-table" tabindex="0">'
286
+ + self.renderToken(tokens, idx, options, env))
287
+
288
+
289
+ def _table_close(self, tokens, idx, options, env):
290
+ return self.renderToken(tokens, idx, options, env) + "</div>\n"
291
+
292
+
280
293
  # After the render rules it refers to.
281
294
  _MD = make_markdown()
@@ -42,6 +42,19 @@ Add them where they help understanding, not for decoration.
42
42
  - Reference assets with relative paths, give images alt text, and say in the
43
43
  prose what the figure shows.
44
44
 
45
+ ## Tables
46
+
47
+ A table is for comparing a few things on a few measures; a dataframe dump is
48
+ not a table.
49
+
50
+ - About 8 columns at most. More than that: transpose (measures as rows), split
51
+ it by topic, or keep the full data as a CSV in `figures/` and show only the
52
+ columns that make the point.
53
+ - The first column names the row; the reader keeps it in view when a wide
54
+ table scrolls.
55
+ - Units in the header (`pnl $k`, `lat us`), not in every cell; round to the
56
+ digits that matter; right-align numeric columns with `---:`.
57
+
45
58
  ## Honesty
46
59
 
47
60
  - Never fabricate empirical results. Numbers come from a run you can point
@@ -106,8 +106,38 @@ pre { background: var(--code-bg); padding: 12px 14px; border-radius: 6px; overfl
106
106
  pre code { background: none; padding: 0; }
107
107
  pre.mermaid { background: none; text-align: center; }
108
108
  blockquote { border-left: 3px solid var(--rule); margin-left: 0; padding-left: 14px; color: var(--dim); }
109
- table { border-collapse: collapse; display: block; overflow-x: auto; }
110
- th, td { border: 1px solid var(--rule); padding: 5px 10px; text-align: left; }
109
+ /* A table as wide as it needs, up to the screen: narrow ones stay left in the
110
+ column, wide ones grow out of it centred on it. One wider than the screen or
111
+ taller than 80vh scrolls inside the box, which keeps its header row and
112
+ first column in view. Separate borders: collapsed ones scroll off sticky cells,
113
+ and backgrounds clipped to the padding so a sticky cell's cannot cover its
114
+ neighbour's border. */
115
+ .dr-table {
116
+ width: max-content;
117
+ min-width: 100%;
118
+ max-width: calc(100vw - 48px);
119
+ max-height: 80vh;
120
+ overflow: auto;
121
+ position: relative;
122
+ left: 50%;
123
+ transform: translateX(-50%);
124
+ margin: 0 0 1em;
125
+ }
126
+ @media (min-width: 1180px) {
127
+ body.show-outline .dr-table { max-width: calc(100vw - var(--outline-w) - 48px); }
128
+ }
129
+ table { border-collapse: separate; border-spacing: 0; font-size: 14px; }
130
+ .dr-table table { margin: 0; }
131
+ th, td { border: solid var(--rule); border-width: 0 1px 1px 0; padding: 5px 10px;
132
+ text-align: left; font-variant-numeric: tabular-nums;
133
+ background: var(--bg); background-clip: padding-box; }
134
+ tr > :first-child { border-left-width: 1px; }
135
+ thead tr:first-child > * { border-top-width: 1px; }
136
+ /* Row labels and numbers stay on one line; headers and prose cells wrap. */
137
+ td[style*="right"], .dr-table tr > :first-child { white-space: nowrap; }
138
+ .dr-table thead th { position: sticky; top: 0; z-index: 1; background: var(--code-bg); }
139
+ .dr-table tr > :first-child { position: sticky; left: 0; }
140
+ .dr-table thead th:first-child { z-index: 2; }
111
141
  img { max-width: 100%; height: auto; }
112
142
  div.math { overflow-x: auto; margin: 0 0 1em; }
113
143
  ul.listing { list-style: none; padding: 0; }
@@ -27,6 +27,7 @@
27
27
  return false;
28
28
  }
29
29
  const visible = () => sections().filter((s) => !hidden(s));
30
+ const tables = () => [...docEl.querySelectorAll(".dr-table")];
30
31
  // Section IDs come from the document, so look only among its sections: a
31
32
  // heading may be called anything, including what the page itself uses.
32
33
  const byId = (id) =>
@@ -378,7 +379,11 @@
378
379
  return { collapsed: sections().filter(collapsed).map((s) => s.id),
379
380
  cur: cur && cur.id,
380
381
  offset: cur ? heading(cur).getBoundingClientRect().top : null,
381
- scrollY: window.scrollY };
382
+ scrollY: window.scrollY,
383
+ // Matched back by position: an edit rarely adds a table above
384
+ // the one being read.
385
+ tables: tables().map((t) => [t.scrollLeft, t.scrollTop]),
386
+ focusedTable: tables().indexOf(document.activeElement) };
382
387
  }
383
388
  function applyState(state) {
384
389
  (state.collapsed || []).forEach((id) => { const s = byId(id); if (s && s.tagName === "SECTION") s.classList.add("collapsed"); });
@@ -438,6 +443,10 @@
438
443
  renderMath(docEl);
439
444
  await renderMermaid(docEl);
440
445
  if (my !== gen) return true;
446
+ tables().forEach((t, i) => {
447
+ if (snap.tables[i]) [t.scrollLeft, t.scrollTop] = snap.tables[i];
448
+ if (i === snap.focusedTable) t.focus({ preventScroll: true });
449
+ });
441
450
  if (cur && snap.offset !== null) {
442
451
  window.scrollTo({ top: window.scrollY + heading(cur).getBoundingClientRect().top - snap.offset });
443
452
  } else {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvim-doc-reader
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Keyboard-driven HTML reader for Markdown docs, with jumps into running Neovim
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -3,8 +3,15 @@
3
3
  The page, `docread url` and the Neovim jump all locate sections through this
4
4
  one parse, so these pin the cases where a naive heading scan would disagree
5
5
  with the rendered page."""
6
- from doc_reader import model
7
- import re
6
+ import pytest
7
+ import utils.doc_reader_src # noqa: F401 (puts doc_reader on sys.path)
8
+
9
+ # The parser needs mdit-py-plugins, which is newer than some container images.
10
+ pytest.importorskip("mdit_py_plugins")
11
+
12
+ from doc_reader import model # noqa: E402
13
+ import re # noqa: E402
14
+ from importlib import resources # noqa: E402
8
15
 
9
16
 
10
17
  def ids(text):
@@ -86,6 +93,13 @@ def test_raw_html_is_escaped_and_mermaid_kept_as_source():
86
93
  assert '<pre class="mermaid">A --&gt; B\n</pre>' in html
87
94
 
88
95
 
96
+ def test_the_table_wrapper_is_the_one_reader_css_styles():
97
+ html = model.render(model.parse("| a | n |\n|---|---:|\n| x | 1 |\n"))
98
+ cls = re.search(r'<div class="([\w-]+)"[^>]*><table>', html).group(1)
99
+ css = (resources.files("doc_reader") / "static" / "reader.css").read_text()
100
+ assert re.search(rf"^\.{cls} \{{", css, re.M)
101
+
102
+
89
103
  def test_project_task_rows_link_by_their_source_number():
90
104
  text = "# P\n\n3. [t] three\n7. [ ] seven\n9. [s] nine\n"
91
105
  html = model.render(model.parse(text),
@@ -1,18 +1,22 @@
1
1
  """`n` against real headless Neovim instances: which ones move, where they
2
2
  land, and that the target is found in the editor's text, not the disk's."""
3
- from doc_reader import config, nvim
4
- import asyncio
5
- import json
6
- import os
7
- import shutil
8
- import socket
9
- import subprocess
10
- import sys
11
- import tempfile
12
- import time
13
- from pathlib import Path
14
-
15
3
  import pytest
4
+ import utils.doc_reader_src # noqa: F401 (puts doc_reader on sys.path)
5
+
6
+ # The parser needs mdit-py-plugins, which is newer than some container images.
7
+ pytest.importorskip("mdit_py_plugins")
8
+
9
+ from doc_reader import config, nvim # noqa: E402
10
+ import asyncio # noqa: E402
11
+ import json # noqa: E402
12
+ import os # noqa: E402
13
+ import shutil # noqa: E402
14
+ import socket # noqa: E402
15
+ import subprocess # noqa: E402
16
+ import sys # noqa: E402
17
+ import tempfile # noqa: E402
18
+ import time # noqa: E402
19
+ from pathlib import Path # noqa: E402
16
20
 
17
21
 
18
22
  pytestmark = pytest.mark.skipif(shutil.which("nvim") is None, reason="no nvim")
@@ -1,9 +1,15 @@
1
1
  """The server's URL handling end to end: every file has one URL, and asking
2
2
  for any other spelling of it lands there in one hop."""
3
- from doc_reader import config, serve
4
- from aiohttp.test_utils import TestClient, TestServer
5
- import asyncio
6
- import json
3
+ import pytest
4
+ import utils.doc_reader_src # noqa: F401 (puts doc_reader on sys.path)
5
+
6
+ # The parser needs mdit-py-plugins, which is newer than some container images.
7
+ pytest.importorskip("mdit_py_plugins")
8
+
9
+ from doc_reader import config, serve # noqa: E402
10
+ from aiohttp.test_utils import TestClient, TestServer # noqa: E402
11
+ import asyncio # noqa: E402
12
+ import json # noqa: E402
7
13
 
8
14
 
9
15
  def fetch(cfg, *urls):
@@ -1,8 +1,14 @@
1
1
  """The bundled agent skill: what `install-skill` writes is what the README
2
2
  tells people to reference, under the name Claude Code will look it up by."""
3
- from doc_reader import cli
4
- import os
5
- import re
3
+ import pytest
4
+ import utils.doc_reader_src # noqa: F401 (puts doc_reader on sys.path)
5
+
6
+ # The parser needs mdit-py-plugins, which is newer than some container images.
7
+ pytest.importorskip("mdit_py_plugins")
8
+
9
+ from doc_reader import cli # noqa: E402
10
+ import os # noqa: E402
11
+ import re # noqa: E402
6
12
 
7
13
 
8
14
  README = os.path.join(os.path.dirname(__file__), "..", "README.md")
@@ -1,4 +1,6 @@
1
1
  """Path <-> URL over named roots, and the config that defines the roots."""
2
+ import utils.doc_reader_src # noqa: F401 (puts doc_reader on sys.path)
3
+
2
4
  import os
3
5
 
4
6
  from doc_reader import config