pyDiffTools 0.1.47__tar.gz → 0.1.49__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.
- {pydifftools-0.1.47 → pydifftools-0.1.49}/MANIFEST.in +1 -0
- {pydifftools-0.1.47/pyDiffTools.egg-info → pydifftools-0.1.49}/PKG-INFO +1 -1
- {pydifftools-0.1.47 → pydifftools-0.1.49/pyDiffTools.egg-info}/PKG-INFO +1 -1
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/SOURCES.txt +4 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/browser_lifecycle.py +31 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags.lua +21 -3
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags_margin.lua +21 -3
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_toggle.js +1 -1
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comments.css +5 -4
- pydifftools-0.1.49/pydifftools/comments_author_colors.css +20 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/continuous.py +63 -20
- pydifftools-0.1.49/pydifftools/flowchart/source_jump.js +158 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/watch_graph.py +53 -3
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/wgrph_view.js +23 -0
- pydifftools-0.1.49/pydifftools/source_jump.py +128 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyproject.toml +3 -1
- pydifftools-0.1.49/tests/test_browser_lifecycle.py +115 -0
- pydifftools-0.1.49/tests/test_source_jump.py +85 -0
- pydifftools-0.1.47/tests/test_browser_lifecycle.py +0 -30
- {pydifftools-0.1.47 → pydifftools-0.1.49}/LICENSE.md +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/README.rst +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/_quarto.yml +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/example.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/example.tex +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/index.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/independent.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/index.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/tasks.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/test_include.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/test_include.qmd +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/dependency_links.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/entry_points.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/requires.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/top_level.txt +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/__init__.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/bibliography.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/check_numbers.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/citation_dialog.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/command_line.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/command_registry.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_functions.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_migration.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags_no_comments.lua +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/copy_files.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/diff-doc.js +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/doc_contents.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/__init__.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/comparison.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/dot_to_yaml.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/graph.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/forward_search.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_history.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_image.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_qt.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/html_comments.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/html_uncomments.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/log_example.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/match_spaces.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/__init__.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/display.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/fast_build.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/tex_to_qmd.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/onewordify.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/onewordify_undo.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/outline.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/rearrange_tex.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/searchacro.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/separate_comments.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/split_conflict.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/unseparate_comments.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/update_check.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/wrap_sentences.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/xml2xlsx.vbs +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/zotero.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/setup.cfg +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_bibliography.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_check_single_use.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_command_line_help.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_comment_migration.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_continuous_shutdown.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_forward_search.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_history.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_image.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_qt.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_rrng.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_tex_to_qmd.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_update_check.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_wrap_check.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_wrapping.py +0 -0
- {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_zotero.py +0 -0
|
@@ -4,6 +4,7 @@ include LICENSE.md
|
|
|
4
4
|
include pydifftools/diff-doc.js
|
|
5
5
|
include pydifftools/xml2xlsx.vbs
|
|
6
6
|
include pydifftools/comments.css
|
|
7
|
+
include pydifftools/comments_author_colors.css
|
|
7
8
|
include pydifftools/comment_tags.lua
|
|
8
9
|
include pydifftools/comment_tags_margin.lua
|
|
9
10
|
include pydifftools/comment_tags_no_comments.lua
|
|
@@ -58,6 +58,7 @@ pydifftools/comment_tags_margin.lua
|
|
|
58
58
|
pydifftools/comment_tags_no_comments.lua
|
|
59
59
|
pydifftools/comment_toggle.js
|
|
60
60
|
pydifftools/comments.css
|
|
61
|
+
pydifftools/comments_author_colors.css
|
|
61
62
|
pydifftools/continuous.py
|
|
62
63
|
pydifftools/copy_files.py
|
|
63
64
|
pydifftools/diff-doc.js
|
|
@@ -77,6 +78,7 @@ pydifftools/outline.py
|
|
|
77
78
|
pydifftools/rearrange_tex.py
|
|
78
79
|
pydifftools/searchacro.py
|
|
79
80
|
pydifftools/separate_comments.py
|
|
81
|
+
pydifftools/source_jump.py
|
|
80
82
|
pydifftools/split_conflict.py
|
|
81
83
|
pydifftools/unseparate_comments.py
|
|
82
84
|
pydifftools/update_check.py
|
|
@@ -87,6 +89,7 @@ pydifftools/flowchart/__init__.py
|
|
|
87
89
|
pydifftools/flowchart/comparison.py
|
|
88
90
|
pydifftools/flowchart/dot_to_yaml.py
|
|
89
91
|
pydifftools/flowchart/graph.py
|
|
92
|
+
pydifftools/flowchart/source_jump.js
|
|
90
93
|
pydifftools/flowchart/svg-pan-zoom.min.js
|
|
91
94
|
pydifftools/flowchart/watch_graph.py
|
|
92
95
|
pydifftools/flowchart/wgrph_view.js
|
|
@@ -105,6 +108,7 @@ tests/test_git_gd_history.py
|
|
|
105
108
|
tests/test_git_gd_image.py
|
|
106
109
|
tests/test_git_gd_qt.py
|
|
107
110
|
tests/test_rrng.py
|
|
111
|
+
tests/test_source_jump.py
|
|
108
112
|
tests/test_tex_to_qmd.py
|
|
109
113
|
tests/test_update_check.py
|
|
110
114
|
tests/test_wrap_check.py
|
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import shutil
|
|
3
4
|
import subprocess
|
|
5
|
+
import sys
|
|
4
6
|
|
|
5
7
|
# callbacks that run before any dialog needs the user; cpb registers one
|
|
6
8
|
# that closes its live preview, so issues are dealt with before it reopens
|
|
7
9
|
dialog_callbacks = []
|
|
8
10
|
|
|
9
11
|
|
|
12
|
+
def launch_chrome(webdriver, **kwargs):
|
|
13
|
+
"""Start Chrome, retrying version mismatches with a warning."""
|
|
14
|
+
from selenium.common.exceptions import SessionNotCreatedException
|
|
15
|
+
from selenium.webdriver.chrome.service import Service
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
return webdriver.Chrome(**kwargs)
|
|
19
|
+
except SessionNotCreatedException as exc:
|
|
20
|
+
# {{{ Retry only ChromeDriver's explicit browser-version rejection
|
|
21
|
+
details = str(exc)
|
|
22
|
+
driver_match = re.search(
|
|
23
|
+
r"ChromeDriver only supports Chrome version (\d+)", details
|
|
24
|
+
)
|
|
25
|
+
browser_match = re.search(
|
|
26
|
+
r"Current browser version is (\d+)", details
|
|
27
|
+
)
|
|
28
|
+
if driver_match is None or browser_match is None:
|
|
29
|
+
raise
|
|
30
|
+
print(
|
|
31
|
+
"pydifft: warning: ChromeDriver "
|
|
32
|
+
f"{driver_match.group(1)} and Chrome {browser_match.group(1)} "
|
|
33
|
+
"do not match; retrying with the driver version check disabled.",
|
|
34
|
+
file=sys.stderr,
|
|
35
|
+
)
|
|
36
|
+
kwargs["service"] = Service(service_args=["--disable-build-check"])
|
|
37
|
+
return webdriver.Chrome(**kwargs)
|
|
38
|
+
# }}}
|
|
39
|
+
|
|
40
|
+
|
|
10
41
|
def prepare_for_dialog():
|
|
11
42
|
"""Let watch commands close their preview before a dialog appears."""
|
|
12
43
|
for callback in list(dialog_callbacks):
|
|
@@ -34,6 +34,24 @@ local OPENERS = {
|
|
|
34
34
|
}
|
|
35
35
|
local AUTHOR_COLORS = {}
|
|
36
36
|
|
|
37
|
+
local function author_color_style(color)
|
|
38
|
+
local red = tonumber(color:sub(2, 3), 16)
|
|
39
|
+
local green = tonumber(color:sub(4, 5), 16)
|
|
40
|
+
local blue = tonumber(color:sub(6, 7), 16)
|
|
41
|
+
local function tint(component)
|
|
42
|
+
return math.floor(component * 0.13 + 255 * 0.87 + 0.5)
|
|
43
|
+
end
|
|
44
|
+
local background = string.format(
|
|
45
|
+
"#%02x%02x%02x", tint(red), tint(green), tint(blue)
|
|
46
|
+
)
|
|
47
|
+
return string.format(
|
|
48
|
+
"--comment-accent-color: %s; "
|
|
49
|
+
.. "--comment-accent-background-color: %s; "
|
|
50
|
+
.. "border-color: %s; background-color: %s;",
|
|
51
|
+
color, background, color, background
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
37
55
|
local comment_id = 0
|
|
38
56
|
local function next_id()
|
|
39
57
|
comment_id = comment_id + 1
|
|
@@ -43,7 +61,7 @@ end
|
|
|
43
61
|
local function make_inline_comment(side, content_inlines, spec)
|
|
44
62
|
local attributes = {}
|
|
45
63
|
if spec and spec.color then
|
|
46
|
-
attributes["style"] =
|
|
64
|
+
attributes["style"] = author_color_style(spec.color)
|
|
47
65
|
attributes["data-comment-author"] = spec.author
|
|
48
66
|
end
|
|
49
67
|
local inner = pandoc.Span(
|
|
@@ -66,7 +84,7 @@ local function make_block_overlay(side, id, content_blocks, spec)
|
|
|
66
84
|
["data-comment-id"] = id,
|
|
67
85
|
["data-comment-author"] = spec and spec.author or nil,
|
|
68
86
|
["style"] = spec and spec.color
|
|
69
|
-
and (
|
|
87
|
+
and author_color_style(spec.color) or nil,
|
|
70
88
|
})
|
|
71
89
|
)
|
|
72
90
|
end
|
|
@@ -402,7 +420,7 @@ function Div(el)
|
|
|
402
420
|
if #style > 0 and not style:match(";%s*$") then
|
|
403
421
|
style = style .. ";"
|
|
404
422
|
end
|
|
405
|
-
el.attributes.style = style ..
|
|
423
|
+
el.attributes.style = style .. author_color_style(color)
|
|
406
424
|
el.attributes["data-comment-author"] = author
|
|
407
425
|
end
|
|
408
426
|
end
|
|
@@ -35,6 +35,24 @@ local OPENERS = {
|
|
|
35
35
|
}
|
|
36
36
|
local AUTHOR_COLORS = {}
|
|
37
37
|
|
|
38
|
+
local function author_color_style(color)
|
|
39
|
+
local red = tonumber(color:sub(2, 3), 16)
|
|
40
|
+
local green = tonumber(color:sub(4, 5), 16)
|
|
41
|
+
local blue = tonumber(color:sub(6, 7), 16)
|
|
42
|
+
local function tint(component)
|
|
43
|
+
return math.floor(component * 0.13 + 255 * 0.87 + 0.5)
|
|
44
|
+
end
|
|
45
|
+
local background = string.format(
|
|
46
|
+
"#%02x%02x%02x", tint(red), tint(green), tint(blue)
|
|
47
|
+
)
|
|
48
|
+
return string.format(
|
|
49
|
+
"--comment-accent-color: %s; "
|
|
50
|
+
.. "--comment-accent-background-color: %s; "
|
|
51
|
+
.. "border-color: %s; background-color: %s;",
|
|
52
|
+
color, background, color, background
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
38
56
|
local comment_id = 0
|
|
39
57
|
local function next_id()
|
|
40
58
|
comment_id = comment_id + 1
|
|
@@ -69,7 +87,7 @@ local function make_inline_comment(side, content_inlines, spec)
|
|
|
69
87
|
["data-comment-author"] = spec and spec.author or nil,
|
|
70
88
|
["style"] = "font-size: 6pt;"
|
|
71
89
|
.. (spec and spec.color
|
|
72
|
-
and ("
|
|
90
|
+
and (" " .. author_color_style(spec.color)) or ""),
|
|
73
91
|
}
|
|
74
92
|
)
|
|
75
93
|
)
|
|
@@ -101,7 +119,7 @@ local function make_block_overlay(side, id, content_blocks, spec)
|
|
|
101
119
|
["data-comment-author"] = spec and spec.author or nil,
|
|
102
120
|
["style"] = "font-size: 6pt;"
|
|
103
121
|
.. (spec and spec.color
|
|
104
|
-
and ("
|
|
122
|
+
and (" " .. author_color_style(spec.color)) or ""),
|
|
105
123
|
}
|
|
106
124
|
)
|
|
107
125
|
)
|
|
@@ -441,7 +459,7 @@ function Div(el)
|
|
|
441
459
|
if #style > 0 and not style:match(";%s*$") then
|
|
442
460
|
style = style .. ";"
|
|
443
461
|
end
|
|
444
|
-
el.attributes.style = style ..
|
|
462
|
+
el.attributes.style = style .. author_color_style(color)
|
|
445
463
|
el.attributes["data-comment-author"] = author
|
|
446
464
|
end
|
|
447
465
|
end
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function positionComments() {
|
|
37
|
-
const useMobileFlow = window.matchMedia("(max-width:
|
|
37
|
+
const useMobileFlow = window.matchMedia("(max-width: 600px)").matches;
|
|
38
38
|
// Inline comment bubbles are absolutely positioned relative to a zero-width
|
|
39
39
|
// pin. We nudge overlapping bubbles so adjacent <comment> tags visibly
|
|
40
40
|
// separate, and we raise the bubbles so the pointer aims at the source point.
|
|
@@ -4,9 +4,10 @@ html {
|
|
|
4
4
|
}
|
|
5
5
|
body {
|
|
6
6
|
margin: 0 auto;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
padding-
|
|
7
|
+
/* Keep 75% of the previous side margins and give the rest to the text. */
|
|
8
|
+
max-width: calc(27em + 25%);
|
|
9
|
+
padding-left: 37.5px;
|
|
10
|
+
padding-right: 37.5px;
|
|
10
11
|
padding-top: 50px;
|
|
11
12
|
padding-bottom: 50px;
|
|
12
13
|
hyphens: auto;
|
|
@@ -311,7 +312,7 @@ span.comment-pin > span.comment-left::after {
|
|
|
311
312
|
}
|
|
312
313
|
|
|
313
314
|
/* ---------- Mobile fallback ---------- */
|
|
314
|
-
@media (max-width:
|
|
315
|
+
@media (max-width: 600px) {
|
|
315
316
|
div.comment-right,
|
|
316
317
|
div.comment-left,
|
|
317
318
|
span.comment-pin > span.comment-right,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Keep comment pointer triangles in sync with per-author bubble colors. */
|
|
2
|
+
span.comment-pin > span.comment-right::before,
|
|
3
|
+
.comment-overlay.comment-right::before {
|
|
4
|
+
border-color: transparent var(--comment-accent-color, #5aa0ff) transparent transparent;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
span.comment-pin > span.comment-right::after,
|
|
8
|
+
.comment-overlay.comment-right::after {
|
|
9
|
+
border-color: transparent var(--comment-accent-background-color, #eaf3ff) transparent transparent;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
span.comment-pin > span.comment-left::before,
|
|
13
|
+
.comment-overlay.comment-left::before {
|
|
14
|
+
border-color: transparent transparent transparent var(--comment-accent-color, #5aa0ff);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
span.comment-pin > span.comment-left::after,
|
|
18
|
+
.comment-overlay.comment-left::after {
|
|
19
|
+
border-color: transparent transparent transparent var(--comment-accent-background-color, #eaf3ff);
|
|
20
|
+
}
|
|
@@ -25,6 +25,7 @@ from .browser_lifecycle import (
|
|
|
25
25
|
close_browser_window,
|
|
26
26
|
dialog_callbacks,
|
|
27
27
|
forward_search_in_browser,
|
|
28
|
+
launch_chrome,
|
|
28
29
|
prepare_for_dialog,
|
|
29
30
|
)
|
|
30
31
|
from .forward_search import (
|
|
@@ -34,6 +35,7 @@ from .forward_search import (
|
|
|
34
35
|
drain_forward_search_queue,
|
|
35
36
|
serve_forward_search,
|
|
36
37
|
)
|
|
38
|
+
from .source_jump import SourceJumpServer
|
|
37
39
|
|
|
38
40
|
FORWARD_SEARCH_PORT = CPB_FORWARD_SEARCH_PORT
|
|
39
41
|
POLL_INTERVAL_SECONDS = 0.1
|
|
@@ -75,19 +77,24 @@ def confirm_restore_comment_filter(active_mode):
|
|
|
75
77
|
"but you ran without the --no-comments flag.\n\n"
|
|
76
78
|
"Note that you have not locally edited the filter relative to "
|
|
77
79
|
"the library default.\n\n"
|
|
78
|
-
"Do you want to
|
|
80
|
+
"Do you want to update the filter and comment styles to the "
|
|
81
|
+
"library versions and show comments, or keep no comments?"
|
|
79
82
|
)
|
|
80
83
|
default_choice = "restore"
|
|
84
|
+
update_label = "update filter and styles; show comments"
|
|
85
|
+
keep_label = "keep no comments"
|
|
81
86
|
elif active_mode == "custom":
|
|
82
87
|
message = (
|
|
83
|
-
"The current lua filter
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
88
|
+
"The current lua filter differs from the pyDiffTools library "
|
|
89
|
+
"filters, but you ran without the --no-comments flag. It may be "
|
|
90
|
+
"an older distributed filter or a local edit.\n\n"
|
|
91
|
+
"Do you want to update the filter and comment styles to the "
|
|
92
|
+
"library versions and show comments, or keep the current "
|
|
93
|
+
"filter and styles?"
|
|
89
94
|
)
|
|
90
95
|
default_choice = "keep"
|
|
96
|
+
update_label = "update filter and styles; show comments"
|
|
97
|
+
keep_label = "keep current filter"
|
|
91
98
|
else:
|
|
92
99
|
raise ValueError(
|
|
93
100
|
"Comment filter restore prompt is only valid for custom or "
|
|
@@ -102,12 +109,8 @@ box = QMessageBox()
|
|
|
102
109
|
box.setWindowTitle("pydifft cpb")
|
|
103
110
|
box.setIcon(QMessageBox.Icon.Question)
|
|
104
111
|
box.setText(sys.argv[1])
|
|
105
|
-
keep_button = box.addButton(
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
restore_button = box.addButton(
|
|
109
|
-
"show comments", QMessageBox.ButtonRole.AcceptRole
|
|
110
|
-
)
|
|
112
|
+
keep_button = box.addButton(sys.argv[3], QMessageBox.ButtonRole.RejectRole)
|
|
113
|
+
restore_button = box.addButton(sys.argv[4], QMessageBox.ButtonRole.AcceptRole)
|
|
111
114
|
if sys.argv[2] == "restore":
|
|
112
115
|
box.setDefaultButton(restore_button)
|
|
113
116
|
else:
|
|
@@ -119,7 +122,10 @@ sys.exit(1)
|
|
|
119
122
|
"""
|
|
120
123
|
prepare_for_dialog()
|
|
121
124
|
result = subprocess.run(
|
|
122
|
-
[
|
|
125
|
+
[
|
|
126
|
+
sys.executable, "-c", prompt_script, message, default_choice,
|
|
127
|
+
keep_label, update_label,
|
|
128
|
+
],
|
|
123
129
|
capture_output=True,
|
|
124
130
|
text=True,
|
|
125
131
|
)
|
|
@@ -530,6 +536,10 @@ def run_pandoc(
|
|
|
530
536
|
shutil.copy2(
|
|
531
537
|
packaged_filters["default"], active_filter
|
|
532
538
|
)
|
|
539
|
+
shutil.copy2(
|
|
540
|
+
os.path.join(package_dir, "comments.css"),
|
|
541
|
+
os.path.join(source_dir, "comments.css"),
|
|
542
|
+
)
|
|
533
543
|
effective_filter_mode = "default"
|
|
534
544
|
else:
|
|
535
545
|
effective_filter_mode = active_mode
|
|
@@ -581,7 +591,10 @@ def run_pandoc(
|
|
|
581
591
|
# }}}
|
|
582
592
|
# Only copy the comment UI assets when comments should be rendered.
|
|
583
593
|
if effective_filter_mode != "none":
|
|
584
|
-
for asset_name in [
|
|
594
|
+
for asset_name in [
|
|
595
|
+
"comments.css", "comments_author_colors.css",
|
|
596
|
+
"comment_toggle.js",
|
|
597
|
+
]:
|
|
585
598
|
target_path = os.path.join(source_dir, asset_name)
|
|
586
599
|
if not os.path.exists(target_path):
|
|
587
600
|
shutil.copy2(
|
|
@@ -791,7 +804,7 @@ def run_pandoc(
|
|
|
791
804
|
return
|
|
792
805
|
|
|
793
806
|
|
|
794
|
-
def append_autorefresh(html_file):
|
|
807
|
+
def append_autorefresh(html_file, source_jump_url=None):
|
|
795
808
|
with open(html_file, "r", encoding="utf-8") as fp:
|
|
796
809
|
all_data = fp.read()
|
|
797
810
|
all_data = all_data.replace(
|
|
@@ -856,6 +869,29 @@ position
|
|
|
856
869
|
</head>
|
|
857
870
|
""",
|
|
858
871
|
)
|
|
872
|
+
if source_jump_url:
|
|
873
|
+
all_data = re.sub(
|
|
874
|
+
r'\s*<script id="pydifft-source-jump-config">.*?</script>'
|
|
875
|
+
r'\s*<script id="pydifft-source-jump">.*?</script>',
|
|
876
|
+
"",
|
|
877
|
+
all_data,
|
|
878
|
+
flags=re.DOTALL,
|
|
879
|
+
)
|
|
880
|
+
source_jump_js = (
|
|
881
|
+
Path(__file__).parent / "flowchart" / "source_jump.js"
|
|
882
|
+
).read_text(encoding="utf-8")
|
|
883
|
+
source_jump_scripts = (
|
|
884
|
+
'<script id="pydifft-source-jump-config">'
|
|
885
|
+
"window.pydifftSourceJumpEndpoint = "
|
|
886
|
+
+ json.dumps(source_jump_url)
|
|
887
|
+
+ ";</script>"
|
|
888
|
+
'<script id="pydifft-source-jump">'
|
|
889
|
+
+ source_jump_js
|
|
890
|
+
+ "</script>"
|
|
891
|
+
)
|
|
892
|
+
all_data = all_data.replace(
|
|
893
|
+
"</head>", source_jump_scripts + "</head>", 1
|
|
894
|
+
)
|
|
859
895
|
with open(html_file, "w", encoding="utf-8") as fp:
|
|
860
896
|
fp.write(all_data)
|
|
861
897
|
|
|
@@ -928,6 +964,7 @@ def cpb(filename, comments_to_margin=False, no_comments=False, wrapnumber=79):
|
|
|
928
964
|
observer = None
|
|
929
965
|
observer_started = False
|
|
930
966
|
socket_thread_started = False
|
|
967
|
+
source_jump_server = None
|
|
931
968
|
|
|
932
969
|
try:
|
|
933
970
|
# Bind and serve before any build or browser work. This makes the fixed
|
|
@@ -957,14 +994,16 @@ def cpb(filename, comments_to_margin=False, no_comments=False, wrapnumber=79):
|
|
|
957
994
|
comment_filter_session=comment_filter_session,
|
|
958
995
|
wrapnumber=wrapnumber,
|
|
959
996
|
)
|
|
960
|
-
|
|
997
|
+
source_jump_server = SourceJumpServer(source_path)
|
|
998
|
+
source_jump_server.start()
|
|
999
|
+
append_autorefresh(html_file, source_jump_server.url)
|
|
961
1000
|
|
|
962
1001
|
# Selenium is deliberately initialized once, after the first
|
|
963
1002
|
# successful build. Nothing in recovery constructs a browser.
|
|
964
1003
|
from selenium import webdriver
|
|
965
1004
|
from selenium.common.exceptions import WebDriverException
|
|
966
1005
|
|
|
967
|
-
chrome = webdriver
|
|
1006
|
+
chrome = launch_chrome(webdriver)
|
|
968
1007
|
observer = Observer()
|
|
969
1008
|
change_queue = queue.Queue()
|
|
970
1009
|
event_handler = Handler(filename, change_queue)
|
|
@@ -1102,7 +1141,9 @@ def cpb(filename, comments_to_margin=False, no_comments=False, wrapnumber=79):
|
|
|
1102
1141
|
comment_filter_session=comment_filter_session,
|
|
1103
1142
|
wrapnumber=wrapnumber,
|
|
1104
1143
|
)
|
|
1105
|
-
append_autorefresh(
|
|
1144
|
+
append_autorefresh(
|
|
1145
|
+
html_file, source_jump_server.url
|
|
1146
|
+
)
|
|
1106
1147
|
except Exception as exc:
|
|
1107
1148
|
exception_filename = getattr(exc, "filename", None)
|
|
1108
1149
|
missing_source = isinstance(
|
|
@@ -1144,7 +1185,7 @@ def cpb(filename, comments_to_margin=False, no_comments=False, wrapnumber=79):
|
|
|
1144
1185
|
break
|
|
1145
1186
|
if chrome is None:
|
|
1146
1187
|
# {{{ reopen the preview a dialog closed
|
|
1147
|
-
chrome = webdriver
|
|
1188
|
+
chrome = launch_chrome(webdriver)
|
|
1148
1189
|
chrome.get(
|
|
1149
1190
|
"file://" + os.path.abspath(html_file)
|
|
1150
1191
|
)
|
|
@@ -1156,6 +1197,8 @@ def cpb(filename, comments_to_margin=False, no_comments=False, wrapnumber=79):
|
|
|
1156
1197
|
finally:
|
|
1157
1198
|
stop_event.set()
|
|
1158
1199
|
forward_search_server.close()
|
|
1200
|
+
if source_jump_server is not None:
|
|
1201
|
+
source_jump_server.stop()
|
|
1159
1202
|
if observer_started:
|
|
1160
1203
|
observer.stop()
|
|
1161
1204
|
observer.join()
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Add a small local-only source jump action to cpb and flowchart previews.
|
|
2
|
+
(function () {
|
|
3
|
+
const endpoint = window.pydifftSourceJumpEndpoint;
|
|
4
|
+
if (!endpoint) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const excludedSourceElements =
|
|
8
|
+
'.math,.MathJax,mjx-container,.katex,math,' +
|
|
9
|
+
'script[type^="math/tex"],' +
|
|
10
|
+
'a[href*="#fig:"],a[href*="#fig-"],a[href*="#fig%3A"]';
|
|
11
|
+
|
|
12
|
+
const menu = document.createElement('div');
|
|
13
|
+
menu.id = 'pydifft-source-jump-menu';
|
|
14
|
+
menu.style.cssText =
|
|
15
|
+
'display:none;position:fixed;z-index:2147483647;background:#fff;' +
|
|
16
|
+
'border:1px solid #777;border-radius:3px;padding:3px;' +
|
|
17
|
+
'box-shadow:0 2px 6px #555;font:14px sans-serif;';
|
|
18
|
+
const button = document.createElement('button');
|
|
19
|
+
button.type = 'button';
|
|
20
|
+
button.textContent = 'Jump to source';
|
|
21
|
+
button.style.cssText =
|
|
22
|
+
'border:0;background:transparent;padding:6px 10px;cursor:pointer;' +
|
|
23
|
+
'font:inherit;';
|
|
24
|
+
menu.appendChild(button);
|
|
25
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
26
|
+
document.body.appendChild(menu);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
let phrase = '';
|
|
30
|
+
|
|
31
|
+
function hideMenu() {
|
|
32
|
+
menu.style.display = 'none';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function showMenu(event, text) {
|
|
36
|
+
phrase = (text || '').replace(/\s+/g, ' ').trim();
|
|
37
|
+
if (!phrase) {
|
|
38
|
+
hideMenu();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (menu.parentNode !== document.body && document.body) {
|
|
42
|
+
document.body.appendChild(menu);
|
|
43
|
+
}
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
menu.style.left = Math.min(event.clientX, window.innerWidth - 170) + 'px';
|
|
46
|
+
menu.style.top = Math.min(event.clientY, window.innerHeight - 42) + 'px';
|
|
47
|
+
menu.style.display = 'block';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sentenceAtPoint(event) {
|
|
51
|
+
let range = null;
|
|
52
|
+
if (document.caretRangeFromPoint) {
|
|
53
|
+
range = document.caretRangeFromPoint(event.clientX, event.clientY);
|
|
54
|
+
} else if (document.caretPositionFromPoint) {
|
|
55
|
+
const position = document.caretPositionFromPoint(
|
|
56
|
+
event.clientX,
|
|
57
|
+
event.clientY
|
|
58
|
+
);
|
|
59
|
+
if (position) {
|
|
60
|
+
range = document.createRange();
|
|
61
|
+
range.setStart(position.offsetNode, position.offset);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const target = event.target.nodeType === Node.TEXT_NODE
|
|
65
|
+
? event.target.parentElement
|
|
66
|
+
: event.target;
|
|
67
|
+
const targetElement = target && target.closest ? target : document.body;
|
|
68
|
+
if (targetElement.closest(excludedSourceElements)) {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
const block = targetElement.closest(
|
|
72
|
+
'p,li,blockquote,h1,h2,h3,h4,h5,h6,dt,dd,td,th'
|
|
73
|
+
) || targetElement;
|
|
74
|
+
// Drop rendered math and cross-reference links so the phrase resembles
|
|
75
|
+
// the searchable Markdown source, where mfs removes those constructs.
|
|
76
|
+
let text = '';
|
|
77
|
+
let clickedOffset = 0;
|
|
78
|
+
let found = false;
|
|
79
|
+
function collectText(node) {
|
|
80
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
81
|
+
if (range && node === range.startContainer) {
|
|
82
|
+
clickedOffset = text.length + range.startOffset;
|
|
83
|
+
found = true;
|
|
84
|
+
}
|
|
85
|
+
text += node.textContent;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
90
|
+
node !== block &&
|
|
91
|
+
node.matches(excludedSourceElements)
|
|
92
|
+
) {
|
|
93
|
+
text += ' ';
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
node.childNodes.forEach(collectText);
|
|
97
|
+
}
|
|
98
|
+
collectText(block);
|
|
99
|
+
const rawText = text;
|
|
100
|
+
text = rawText.replace(/\s+/g, ' ').trim();
|
|
101
|
+
if (!text) {
|
|
102
|
+
return '';
|
|
103
|
+
}
|
|
104
|
+
if (!range || !found) {
|
|
105
|
+
return text;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// The raw DOM offset is used to find sentence bounds before whitespace is
|
|
109
|
+
// collapsed. Recompute its visible offset after normalization.
|
|
110
|
+
const normalizedPrefix = rawText
|
|
111
|
+
.slice(0, clickedOffset)
|
|
112
|
+
.replace(/\s+/g, ' ')
|
|
113
|
+
.replace(/^\s+/, '');
|
|
114
|
+
clickedOffset = normalizedPrefix.length;
|
|
115
|
+
const before = text.slice(0, clickedOffset);
|
|
116
|
+
const after = text.slice(clickedOffset);
|
|
117
|
+
const start = Math.max(
|
|
118
|
+
before.lastIndexOf('.'),
|
|
119
|
+
before.lastIndexOf('!'),
|
|
120
|
+
before.lastIndexOf('?')
|
|
121
|
+
) + 1;
|
|
122
|
+
const endMatch = after.match(/[.!?](?:\s|$)/);
|
|
123
|
+
const end = endMatch ? clickedOffset + endMatch.index + 1 : text.length;
|
|
124
|
+
return text.slice(start, end).trim() || text;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
button.addEventListener('click', function () {
|
|
128
|
+
hideMenu();
|
|
129
|
+
fetch(endpoint, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
mode: 'no-cors',
|
|
132
|
+
body: phrase,
|
|
133
|
+
}).catch(function () {
|
|
134
|
+
// The preview may be closing while the local editor request is sent.
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
document.addEventListener('click', hideMenu);
|
|
138
|
+
document.addEventListener('keydown', function (event) {
|
|
139
|
+
if (event.key === 'Escape') {
|
|
140
|
+
hideMenu();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
document.addEventListener('contextmenu', function (event) {
|
|
144
|
+
showMenu(event, sentenceAtPoint(event));
|
|
145
|
+
});
|
|
146
|
+
document.addEventListener('pydifft-source-context', function (event) {
|
|
147
|
+
showMenu(
|
|
148
|
+
{
|
|
149
|
+
clientX: event.detail.clientX,
|
|
150
|
+
clientY: event.detail.clientY,
|
|
151
|
+
preventDefault: function () {
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
event.detail.phrase
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
})();
|