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.
Files changed (119) hide show
  1. {pydifftools-0.1.47 → pydifftools-0.1.49}/MANIFEST.in +1 -0
  2. {pydifftools-0.1.47/pyDiffTools.egg-info → pydifftools-0.1.49}/PKG-INFO +1 -1
  3. {pydifftools-0.1.47 → pydifftools-0.1.49/pyDiffTools.egg-info}/PKG-INFO +1 -1
  4. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/SOURCES.txt +4 -0
  5. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/browser_lifecycle.py +31 -0
  6. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags.lua +21 -3
  7. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags_margin.lua +21 -3
  8. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_toggle.js +1 -1
  9. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comments.css +5 -4
  10. pydifftools-0.1.49/pydifftools/comments_author_colors.css +20 -0
  11. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/continuous.py +63 -20
  12. pydifftools-0.1.49/pydifftools/flowchart/source_jump.js +158 -0
  13. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/watch_graph.py +53 -3
  14. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/wgrph_view.js +23 -0
  15. pydifftools-0.1.49/pydifftools/source_jump.py +128 -0
  16. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyproject.toml +3 -1
  17. pydifftools-0.1.49/tests/test_browser_lifecycle.py +115 -0
  18. pydifftools-0.1.49/tests/test_source_jump.py +85 -0
  19. pydifftools-0.1.47/tests/test_browser_lifecycle.py +0 -30
  20. {pydifftools-0.1.47 → pydifftools-0.1.49}/LICENSE.md +0 -0
  21. {pydifftools-0.1.47 → pydifftools-0.1.49}/README.rst +0 -0
  22. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/_quarto.yml +0 -0
  23. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/__version__.txt +0 -0
  24. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/1a724af72b16f5a9e607e12b1c721645/base.ipynb +0 -0
  25. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/1b28fc9daac9081847e5161b2c546f8a/base.ipynb +0 -0
  26. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/231f64eee282fa225d1104935cf80a24/base.ipynb +0 -0
  27. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/26e56f6b0ff54851a45145157f2f0dc4/base.ipynb +0 -0
  28. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/311fabd7029ffd050d056e2f316eb50f/base.ipynb +0 -0
  29. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/57da2021e5b156ac3adf01398201c723/base.ipynb +0 -0
  30. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/62b24ea7da75011d92b0f8924faa208d/base.ipynb +0 -0
  31. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/7f1b20d69d889514ab5d1cc92e3cb14f/base.ipynb +0 -0
  32. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/86f74c8c54a87ff892d9b15dd714e8f0/base.ipynb +0 -0
  33. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/88893b4234eac2945d9d6cb2e277f186/base.ipynb +0 -0
  34. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/9a40046ada6f582ee34af00fbdbfb417/base.ipynb +0 -0
  35. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/a1bf4d270d0641ff41faf1d7cce3439a/base.ipynb +0 -0
  36. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/a3789f7d9585a781f2a1c60ce95ff10d/base.ipynb +0 -0
  37. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/be46ecba858d39ad5f0c46902ddf1c02/base.ipynb +0 -0
  38. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/d0cbc57a12f2ccce710a5afd04cc05e7/base.ipynb +0 -0
  39. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/d3e12d320b14228f701231ae32ddd7dd/base.ipynb +0 -0
  40. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/eb434f61555438d020a6970a5dbf9ee8/base.ipynb +0 -0
  41. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/f6b0e73aa7fa029134665d4dde57e096/base.ipynb +0 -0
  42. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/f719a6e4ff09873cb0ffb06ec9d232f9/base.ipynb +0 -0
  43. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/executed/feeee244a7ce3d60e1a227eb604df823/base.ipynb +0 -0
  44. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/.jupyter_cache/global.db +0 -0
  45. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/example.qmd +0 -0
  46. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/example.tex +0 -0
  47. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/index.qmd +0 -0
  48. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/__version__.txt +0 -0
  49. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/executed/ca90e4df5f4f0583df6554156a68dc7f/base.ipynb +0 -0
  50. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/.jupyter_cache/global.db +0 -0
  51. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/end_vs_he_sketch.jpg +0 -0
  52. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/independent.qmd +0 -0
  53. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/index.qmd +0 -0
  54. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/tasks.qmd +0 -0
  55. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/test_include.qmd +0 -0
  56. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/subproject1/tryforerror.qmd +0 -0
  57. {pydifftools-0.1.47 → pydifftools-0.1.49}/example_notebook/project1/test_include.qmd +0 -0
  58. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/dependency_links.txt +0 -0
  59. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/entry_points.txt +0 -0
  60. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/requires.txt +0 -0
  61. {pydifftools-0.1.47 → pydifftools-0.1.49}/pyDiffTools.egg-info/top_level.txt +0 -0
  62. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/__init__.py +0 -0
  63. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/bibliography.py +0 -0
  64. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/check_numbers.py +0 -0
  65. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/citation_dialog.py +0 -0
  66. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/command_line.py +0 -0
  67. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/command_registry.py +0 -0
  68. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_functions.py +0 -0
  69. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_migration.py +0 -0
  70. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/comment_tags_no_comments.lua +0 -0
  71. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/copy_files.py +0 -0
  72. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/diff-doc.js +0 -0
  73. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/doc_contents.py +0 -0
  74. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/__init__.py +0 -0
  75. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/comparison.py +0 -0
  76. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/dot_to_yaml.py +0 -0
  77. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/graph.py +0 -0
  78. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/flowchart/svg-pan-zoom.min.js +0 -0
  79. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/forward_search.py +0 -0
  80. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd.py +0 -0
  81. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_history.py +0 -0
  82. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_image.py +0 -0
  83. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/git_gd_qt.py +0 -0
  84. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/html_comments.py +0 -0
  85. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/html_uncomments.py +0 -0
  86. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/log_example.py +0 -0
  87. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/match_spaces.py +0 -0
  88. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/__init__.py +0 -0
  89. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/display.py +0 -0
  90. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/fast_build.py +0 -0
  91. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/notebook/tex_to_qmd.py +0 -0
  92. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/onewordify.py +0 -0
  93. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/onewordify_undo.py +0 -0
  94. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/outline.py +0 -0
  95. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/rearrange_tex.py +0 -0
  96. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/searchacro.py +0 -0
  97. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/separate_comments.py +0 -0
  98. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/split_conflict.py +0 -0
  99. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/unseparate_comments.py +0 -0
  100. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/update_check.py +0 -0
  101. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/wrap_sentences.py +0 -0
  102. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/xml2xlsx.vbs +0 -0
  103. {pydifftools-0.1.47 → pydifftools-0.1.49}/pydifftools/zotero.py +0 -0
  104. {pydifftools-0.1.47 → pydifftools-0.1.49}/setup.cfg +0 -0
  105. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_bibliography.py +0 -0
  106. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_check_single_use.py +0 -0
  107. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_command_line_help.py +0 -0
  108. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_comment_migration.py +0 -0
  109. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_continuous_shutdown.py +0 -0
  110. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_forward_search.py +0 -0
  111. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_history.py +0 -0
  112. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_image.py +0 -0
  113. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_git_gd_qt.py +0 -0
  114. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_rrng.py +0 -0
  115. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_tex_to_qmd.py +0 -0
  116. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_update_check.py +0 -0
  117. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_wrap_check.py +0 -0
  118. {pydifftools-0.1.47 → pydifftools-0.1.49}/tests/test_wrapping.py +0 -0
  119. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyDiffTools
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: Diff tools
5
5
  Author: J M Franck
6
6
  License: Copyright (c) 2015, jmfranck
@@ -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"] = "--comment-accent-color: " .. spec.color .. ";"
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 ("--comment-accent-color: " .. spec.color .. ";") or nil,
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 .. "--comment-accent-color: " .. color .. ";"
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 (" --comment-accent-color: " .. spec.color .. ";") or ""),
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 (" --comment-accent-color: " .. spec.color .. ";") or ""),
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 .. "--comment-accent-color: " .. color .. ";"
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: 900px)").matches;
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
- max-width: 36em;
8
- padding-left: 50px;
9
- padding-right: 50px;
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: 900px) {
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 show comments, or continue with no comments?"
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 does not match the pyDiffTools library "
84
- "default or the no-comments filter, but you ran without the "
85
- "--no-comments flag.\n\n"
86
- "Note that this looks like a locally edited filter.\n\n"
87
- "Do you want to show comments using the library default, or "
88
- "continue with no comments?"
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
- "no comments", QMessageBox.ButtonRole.RejectRole
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
- [sys.executable, "-c", prompt_script, message, default_choice],
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 ["comments.css", "comment_toggle.js"]:
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
- append_autorefresh(html_file)
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.Chrome()
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(html_file)
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.Chrome()
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
+ })();