panache-cli 2.57.0__tar.gz → 2.58.0__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 (280) hide show
  1. {panache_cli-2.57.0 → panache_cli-2.58.0}/Cargo.lock +47 -5
  2. {panache_cli-2.57.0 → panache_cli-2.58.0}/Cargo.toml +8 -3
  3. {panache_cli-2.57.0 → panache_cli-2.58.0}/PKG-INFO +1 -1
  4. {panache_cli-2.57.0 → panache_cli-2.58.0}/build.rs +24 -0
  5. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/CHANGELOG.md +12 -0
  6. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/Cargo.toml +2 -2
  7. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/config.rs +4 -0
  8. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/inline.rs +19 -7
  9. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/math/STYLE.md +26 -0
  10. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/math/operators.rs +41 -0
  11. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/math/render.rs +55 -0
  12. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/CHANGELOG.md +8 -0
  13. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/Cargo.toml +1 -1
  14. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/options.rs +9 -0
  15. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/definition_lists.rs +2 -5
  16. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/citations.rs +50 -0
  17. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/core.rs +5 -5
  18. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/inline_ir.rs +2 -0
  19. panache_cli-2.58.0/src/bin/distill_quarto_schema.rs +64 -0
  20. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/cache.rs +8 -1
  21. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/cli.rs +12 -0
  22. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/config/formatter_presets.rs +40 -0
  23. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/config/types.rs +82 -3
  24. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/config.rs +110 -0
  25. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/diagnostics.rs +35 -5
  26. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/eslint.rs +4 -4
  27. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/jarl.rs +4 -4
  28. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/ruff.rs +1 -4
  29. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/shellcheck.rs +4 -4
  30. panache_cli-2.58.0/src/linter/quarto_schema/distill.rs +123 -0
  31. panache_cli-2.58.0/src/linter/quarto_schema/interp.rs +864 -0
  32. panache_cli-2.58.0/src/linter/quarto_schema/model.rs +117 -0
  33. panache_cli-2.58.0/src/linter/quarto_schema/report.rs +216 -0
  34. panache_cli-2.58.0/src/linter/quarto_schema/value.rs +341 -0
  35. panache_cli-2.58.0/src/linter/quarto_schema.rs +85 -0
  36. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/adjacent_footnote_refs.rs +4 -4
  37. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/citation_keys.rs +74 -0
  38. panache_cli-2.58.0/src/linter/rules/consumer_divergence.rs +292 -0
  39. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/crossref_as_link_target.rs +4 -4
  40. panache_cli-2.58.0/src/linter/rules/empty_values.rs +238 -0
  41. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/heading_hierarchy.rs +6 -12
  42. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/link_text_is_url.rs +4 -4
  43. panache_cli-2.58.0/src/linter/rules/quarto_schema.rs +233 -0
  44. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/undefined_references.rs +32 -0
  45. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules.rs +6 -0
  46. panache_cli-2.58.0/src/linter/yaml_resolve.rs +223 -0
  47. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter.rs +7 -1
  48. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/dispatch.rs +60 -16
  49. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/documents.rs +25 -0
  50. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/global_state.rs +7 -0
  51. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/code_actions.rs +13 -2
  52. panache_cli-2.58.0/src/lsp/handlers/configuration.rs +31 -0
  53. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/diagnostics.rs +356 -31
  54. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/file_watcher.rs +14 -0
  55. panache_cli-2.58.0/src/lsp/handlers/linked_editing_range.rs +196 -0
  56. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers.rs +2 -0
  57. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/testing.rs +107 -3
  58. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/main.rs +157 -11
  59. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/salsa.rs +125 -0
  60. {panache_cli-2.57.0 → panache_cli-2.58.0}/LICENSE +0 -0
  61. {panache_cli-2.57.0 → panache_cli-2.58.0}/README.md +0 -0
  62. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/README.md +0 -0
  63. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/directives.rs +0 -0
  64. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/blockquotes.rs +0 -0
  65. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/code_blocks.rs +0 -0
  66. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/core.rs +0 -0
  67. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/fenced_divs.rs +0 -0
  68. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/hashpipe.rs +0 -0
  69. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/headings.rs +0 -0
  70. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/indent_utils.rs +0 -0
  71. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/inline_layout.rs +0 -0
  72. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/lists.rs +0 -0
  73. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/math/linebreak.rs +0 -0
  74. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/math.rs +0 -0
  75. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/metadata.rs +0 -0
  76. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/paragraphs.rs +0 -0
  77. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/sentence_wrap.rs +0 -0
  78. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/shortcodes.rs +0 -0
  79. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/smart.rs +0 -0
  80. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/tables.rs +0 -0
  81. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/utils.rs +0 -0
  82. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/STYLE.md +0 -0
  83. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/block_map.rs +0 -0
  84. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/block_sequence.rs +0 -0
  85. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/document.rs +0 -0
  86. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/flow.rs +0 -0
  87. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/options.rs +0 -0
  88. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml/scalar.rs +0 -0
  89. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter/yaml.rs +0 -0
  90. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/formatter.rs +0 -0
  91. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/lib.rs +0 -0
  92. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/parser.rs +0 -0
  93. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/syntax.rs +0 -0
  94. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/utils.rs +0 -0
  95. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-formatter/src/yaml_engine.rs +0 -0
  96. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/README.md +0 -0
  97. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/build.rs +0 -0
  98. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/grid_layout.rs +0 -0
  99. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/lib.rs +0 -0
  100. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/pandoc_ast.rs +0 -0
  101. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/block_dispatcher.rs +0 -0
  102. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/blockquotes.rs +0 -0
  103. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/code_blocks.rs +0 -0
  104. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/container_prefix.rs +0 -0
  105. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/fenced_divs.rs +0 -0
  106. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/figures.rs +0 -0
  107. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/headings.rs +0 -0
  108. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/horizontal_rules.rs +0 -0
  109. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/html_blocks.rs +0 -0
  110. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/indented_code.rs +0 -0
  111. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/latex_envs.rs +0 -0
  112. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/line_blocks.rs +0 -0
  113. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/lists.rs +0 -0
  114. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/metadata.rs +0 -0
  115. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/paragraphs.rs +0 -0
  116. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/raw_blocks.rs +0 -0
  117. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/reference_links.rs +0 -0
  118. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tables.rs +0 -0
  119. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/blanklines.rs +0 -0
  120. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/blockquotes.rs +0 -0
  121. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/code_blocks.rs +0 -0
  122. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/definition_lists.rs +0 -0
  123. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/headings.rs +0 -0
  124. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/helpers.rs +0 -0
  125. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/lists.rs +0 -0
  126. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/losslessness.rs +0 -0
  127. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks/tests/metadata_guards.rs +0 -0
  128. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/blocks.rs +0 -0
  129. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/core.rs +0 -0
  130. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/diagnostics.rs +0 -0
  131. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/bookdown.rs +0 -0
  132. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/bracketed_spans.rs +0 -0
  133. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/code_spans.rs +0 -0
  134. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/emoji.rs +0 -0
  135. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/escapes.rs +0 -0
  136. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/inline_executable.rs +0 -0
  137. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/inline_footnotes.rs +0 -0
  138. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/inline_html.rs +0 -0
  139. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/latex.rs +0 -0
  140. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/links.rs +0 -0
  141. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/mark.rs +0 -0
  142. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/math.rs +0 -0
  143. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/native_spans.rs +0 -0
  144. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/raw_inline.rs +0 -0
  145. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/refdef_map.rs +0 -0
  146. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/shortcodes.rs +0 -0
  147. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/sink.rs +0 -0
  148. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/strikeout.rs +0 -0
  149. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/subscript.rs +0 -0
  150. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/superscript.rs +0 -0
  151. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/tests.rs +0 -0
  152. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/uri-schemes.csv +0 -0
  153. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines/wikilinks.rs +0 -0
  154. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/inlines.rs +0 -0
  155. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/math.rs +0 -0
  156. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/attributes.rs +0 -0
  157. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/chunk_options.rs +0 -0
  158. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/container_stack.rs +0 -0
  159. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/continuation.rs +0 -0
  160. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/helpers.rs +0 -0
  161. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/inline_emission.rs +0 -0
  162. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/list_item_buffer.rs +0 -0
  163. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/marker_utils.rs +0 -0
  164. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/text_buffer.rs +0 -0
  165. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/tree_copy.rs +0 -0
  166. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils/yaml_regions.rs +0 -0
  167. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/utils.rs +0 -0
  168. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/cooking.rs +0 -0
  169. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/events.rs +0 -0
  170. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/model.rs +0 -0
  171. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/parser.rs +0 -0
  172. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/profile.rs +0 -0
  173. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/scanner.rs +0 -0
  174. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml/validator.rs +0 -0
  175. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser/yaml.rs +0 -0
  176. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/parser.rs +0 -0
  177. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/range_utils.rs +0 -0
  178. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/alerts.rs +0 -0
  179. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/ast.rs +0 -0
  180. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/attributes.rs +0 -0
  181. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/block_quotes.rs +0 -0
  182. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/blocks.rs +0 -0
  183. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/chunk_options.rs +0 -0
  184. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/citations.rs +0 -0
  185. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/code_blocks.rs +0 -0
  186. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/crossrefs.rs +0 -0
  187. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/definitions.rs +0 -0
  188. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/fenced_divs.rs +0 -0
  189. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/headings.rs +0 -0
  190. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/inlines.rs +0 -0
  191. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/kind.rs +0 -0
  192. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/links.rs +0 -0
  193. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/lists.rs +0 -0
  194. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/math.rs +0 -0
  195. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/raw_tex.rs +0 -0
  196. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/references.rs +0 -0
  197. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/shortcodes.rs +0 -0
  198. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/tables.rs +0 -0
  199. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/yaml.rs +0 -0
  200. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax/yaml_ast.rs +0 -0
  201. {panache_cli-2.57.0 → panache_cli-2.58.0}/crates/panache-parser/src/syntax.rs +0 -0
  202. {panache_cli-2.57.0 → panache_cli-2.58.0}/pyproject.toml +0 -0
  203. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib/bibtex.rs +0 -0
  204. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib/csl_json.rs +0 -0
  205. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib/csl_yaml.rs +0 -0
  206. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib/index.rs +0 -0
  207. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib/ris.rs +0 -0
  208. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/bib.rs +0 -0
  209. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/config/types/schema_helpers.rs +0 -0
  210. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/diagnostic_renderer.rs +0 -0
  211. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/directives.rs +0 -0
  212. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/external_formatters_common.rs +0 -0
  213. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/external_formatters_sync.rs +0 -0
  214. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/external_tools_common.rs +0 -0
  215. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/formatter.rs +0 -0
  216. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/includes.rs +0 -0
  217. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lib.rs +0 -0
  218. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/code_block_collector.rs +0 -0
  219. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/clippy.rs +0 -0
  220. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters/staticcheck.rs +0 -0
  221. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters.rs +0 -0
  222. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/external_linters_sync.rs +0 -0
  223. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/index.rs +0 -0
  224. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/metadata_diagnostics.rs +0 -0
  225. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/offsets.rs +0 -0
  226. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/chunk_label_spaces.rs +0 -0
  227. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/duplicate_references.rs +0 -0
  228. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/emoji_aliases.rs +0 -0
  229. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/empty_list_item.rs +0 -0
  230. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/figure_crossref_captions.rs +0 -0
  231. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/footnote_ref_in_footnote_def.rs +0 -0
  232. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/heading_eaten_attrs.rs +0 -0
  233. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/heading_strip_comments_residue.rs +0 -0
  234. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/html_entities.rs +0 -0
  235. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/math_content.rs +0 -0
  236. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/missing_chunk_labels.rs +0 -0
  237. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/stray_fenced_div_markers.rs +0 -0
  238. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/undefined_anchor.rs +0 -0
  239. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/rules/unused_definitions.rs +0 -0
  240. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/linter/runner.rs +0 -0
  241. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/config.rs +0 -0
  242. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/context.rs +0 -0
  243. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/conversions.rs +0 -0
  244. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/completion.rs +0 -0
  245. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/document_links.rs +0 -0
  246. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/document_symbols.rs +0 -0
  247. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/file_operations.rs +0 -0
  248. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/file_rename.rs +0 -0
  249. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/folding_ranges.rs +0 -0
  250. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/footnote_conversion.rs +0 -0
  251. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/formatting.rs +0 -0
  252. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/goto_definition.rs +0 -0
  253. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/heading_link_conversion.rs +0 -0
  254. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/hover.rs +0 -0
  255. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/link_conversion.rs +0 -0
  256. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/list_conversion.rs +0 -0
  257. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/prepare_rename.rs +0 -0
  258. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/references.rs +0 -0
  259. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/rename.rs +0 -0
  260. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/semantic_tokens.rs +0 -0
  261. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/shortcode_args.rs +0 -0
  262. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/handlers/workspace_symbols.rs +0 -0
  263. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/helpers.rs +0 -0
  264. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/navigation.rs +0 -0
  265. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/symbols.rs +0 -0
  266. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/task_pool.rs +0 -0
  267. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp/uri_ext.rs +0 -0
  268. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/lsp.rs +0 -0
  269. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata/bibliography.rs +0 -0
  270. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata/citations.rs +0 -0
  271. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata/project.rs +0 -0
  272. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata/references.rs +0 -0
  273. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata/yaml.rs +0 -0
  274. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/metadata.rs +0 -0
  275. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/parser.rs +0 -0
  276. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/range_utils.rs +0 -0
  277. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/syntax.rs +0 -0
  278. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/utils.rs +0 -0
  279. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/yaml_engine.rs +0 -0
  280. {panache_cli-2.57.0 → panache_cli-2.58.0}/src/yaml_regions.rs +0 -0
@@ -2,6 +2,12 @@
2
2
  # It is not intended for manual editing.
3
3
  version = 4
4
4
 
5
+ [[package]]
6
+ name = "adler2"
7
+ version = "2.0.1"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10
+
5
11
  [[package]]
6
12
  name = "ahash"
7
13
  version = "0.8.12"
@@ -272,6 +278,15 @@ version = "3.0.1"
272
278
  source = "registry+https://github.com/rust-lang/crates.io-index"
273
279
  checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
274
280
 
281
+ [[package]]
282
+ name = "crc32fast"
283
+ version = "1.5.0"
284
+ source = "registry+https://github.com/rust-lang/crates.io-index"
285
+ checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
286
+ dependencies = [
287
+ "cfg-if",
288
+ ]
289
+
275
290
  [[package]]
276
291
  name = "crossbeam-channel"
277
292
  version = "0.5.15"
@@ -523,6 +538,16 @@ version = "2.4.1"
523
538
  source = "registry+https://github.com/rust-lang/crates.io-index"
524
539
  checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
525
540
 
541
+ [[package]]
542
+ name = "flate2"
543
+ version = "1.1.9"
544
+ source = "registry+https://github.com/rust-lang/crates.io-index"
545
+ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
546
+ dependencies = [
547
+ "crc32fast",
548
+ "miniz_oxide",
549
+ ]
550
+
526
551
  [[package]]
527
552
  name = "float-cmp"
528
553
  version = "0.10.0"
@@ -1026,6 +1051,16 @@ version = "0.3.0"
1026
1051
  source = "registry+https://github.com/rust-lang/crates.io-index"
1027
1052
  checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74"
1028
1053
 
1054
+ [[package]]
1055
+ name = "miniz_oxide"
1056
+ version = "0.8.9"
1057
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1058
+ checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1059
+ dependencies = [
1060
+ "adler2",
1061
+ "simd-adler32",
1062
+ ]
1063
+
1029
1064
  [[package]]
1030
1065
  name = "normalize-line-endings"
1031
1066
  version = "0.3.0"
@@ -1147,7 +1182,7 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
1147
1182
 
1148
1183
  [[package]]
1149
1184
  name = "panache"
1150
- version = "2.57.0"
1185
+ version = "2.58.0"
1151
1186
  dependencies = [
1152
1187
  "annotate-snippets",
1153
1188
  "assert_cmd",
@@ -1159,6 +1194,7 @@ dependencies = [
1159
1194
  "dirs",
1160
1195
  "emojis",
1161
1196
  "env_logger",
1197
+ "flate2",
1162
1198
  "globset",
1163
1199
  "ignore",
1164
1200
  "insta",
@@ -1190,7 +1226,7 @@ dependencies = [
1190
1226
 
1191
1227
  [[package]]
1192
1228
  name = "panache-formatter"
1193
- version = "0.15.0"
1229
+ version = "0.16.0"
1194
1230
  dependencies = [
1195
1231
  "insta",
1196
1232
  "log",
@@ -1208,7 +1244,7 @@ dependencies = [
1208
1244
 
1209
1245
  [[package]]
1210
1246
  name = "panache-parser"
1211
- version = "0.18.0"
1247
+ version = "0.19.0"
1212
1248
  dependencies = [
1213
1249
  "entities",
1214
1250
  "insta",
@@ -1398,9 +1434,9 @@ dependencies = [
1398
1434
 
1399
1435
  [[package]]
1400
1436
  name = "quote"
1401
- version = "1.0.45"
1437
+ version = "1.0.46"
1402
1438
  source = "registry+https://github.com/rust-lang/crates.io-index"
1403
- checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
1439
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
1404
1440
  dependencies = [
1405
1441
  "proc-macro2",
1406
1442
  ]
@@ -1730,6 +1766,12 @@ dependencies = [
1730
1766
  "serde_core",
1731
1767
  ]
1732
1768
 
1769
+ [[package]]
1770
+ name = "simd-adler32"
1771
+ version = "0.3.9"
1772
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1773
+ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
1774
+
1733
1775
  [[package]]
1734
1776
  name = "similar"
1735
1777
  version = "2.7.0"
@@ -7,8 +7,11 @@ authors = ["Johan Larsson <johan@jolars.co>"]
7
7
 
8
8
  [package]
9
9
  name = "panache"
10
- version = "2.57.0"
10
+ version = "2.58.0"
11
11
  edition.workspace = true
12
+ # `distill_quarto_schema` is a dev-only vendoring bin (src/bin/); keep bare
13
+ # `cargo run` resolving to the CLI for tooling like the pre-commit format hook.
14
+ default-run = "panache"
12
15
  readme = "README.md"
13
16
  description = "An LSP, formatter, and linter for Markdown, Quarto, and R Markdown"
14
17
  license.workspace = true
@@ -41,8 +44,8 @@ path = "src/main.rs"
41
44
  required-features = ["cli"]
42
45
 
43
46
  [dependencies]
44
- panache-formatter = { path = "crates/panache-formatter", version = "0.15.0" }
45
- panache-parser = { path = "crates/panache-parser", version = "0.18.0", features = [
47
+ panache-formatter = { path = "crates/panache-formatter", version = "0.16.0" }
48
+ panache-parser = { path = "crates/panache-parser", version = "0.19.0", features = [
46
49
  "serde",
47
50
  "schema",
48
51
  ] }
@@ -51,6 +54,7 @@ clap = { version = "4.6.1", features = ["derive", "env"], optional = true }
51
54
  dirs = "6.0.0"
52
55
  env_logger = "0.11.10"
53
56
  emojis = "0.8.0"
57
+ flate2 = "1.0"
54
58
  crossbeam-channel = { version = "0.5.15", optional = true }
55
59
  globset = "0.4"
56
60
  ignore = { version = "0.4", optional = true }
@@ -99,6 +103,7 @@ clap = { version = "4.6.1", features = ["derive", "env"] }
99
103
  clap_complete = "4.6.0"
100
104
  clap_mangen = "0.3.0"
101
105
  clap-markdown = "0.1.5"
106
+ flate2 = "1.0"
102
107
 
103
108
  [package.metadata.deb]
104
109
  maintainer = "Johan Larsson <johan@jolars.co>"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: panache-cli
3
- Version: 2.57.0
3
+ Version: 2.58.0
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -456,10 +456,33 @@ fn generate_man_pages() -> Result<()> {
456
456
  Ok(())
457
457
  }
458
458
 
459
+ /// Gzip-compress the vendored Quarto schema into `OUT_DIR` so the binary (and
460
+ /// the wasm bundle) embeds the ~40 KB compressed blob instead of the ~2 MB
461
+ /// pretty-printed JSON. The reviewable pretty source stays committed; the
462
+ /// linter decompresses it once on first use (see `src/linter/quarto_schema.rs`).
463
+ fn compress_quarto_schema(outdir: &std::ffi::OsString) -> Result<()> {
464
+ use std::io::Write;
465
+
466
+ use flate2::Compression;
467
+ use flate2::write::GzEncoder;
468
+
469
+ let src = PathBuf::from("assets/quarto-schema/schema.json");
470
+ let json = fs::read(&src)?;
471
+ let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
472
+ encoder.write_all(&json)?;
473
+ let compressed = encoder.finish()?;
474
+ fs::write(
475
+ PathBuf::from(outdir).join("quarto-schema.json.gz"),
476
+ compressed,
477
+ )?;
478
+ Ok(())
479
+ }
480
+
459
481
  fn main() -> Result<()> {
460
482
  // Generate shell completions
461
483
  if let Some(outdir) = env::var_os("OUT_DIR") {
462
484
  generate_completions(&outdir)?;
485
+ compress_quarto_schema(&outdir)?;
463
486
  }
464
487
 
465
488
  // Generate man pages
@@ -473,6 +496,7 @@ fn main() -> Result<()> {
473
496
  println!("cargo:rerun-if-changed=src/cli.rs");
474
497
  println!("cargo:rerun-if-changed=src/config/formatter_presets.rs");
475
498
  println!("cargo:rerun-if-changed=src/linter/external_linters.rs");
499
+ println!("cargo:rerun-if-changed=assets/quarto-schema/schema.json");
476
500
  println!("cargo:rerun-if-changed=build.rs");
477
501
 
478
502
  Ok(())
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.0](https://github.com/jolars/panache/compare/panache-formatter-v0.15.0...panache-formatter-v0.16.0) (2026-06-23)
4
+
5
+ ### Features
6
+ - add `crossref-prefixes` for extension crossrefs ([`0b190cc`](https://github.com/jolars/panache/commit/0b190cc1ad00e1ca146c758226a325b0c7a16017))
7
+ - **formatter:** tighten spacing around scripts and groups ([`d0075c3`](https://github.com/jolars/panache/commit/d0075c39ebaa794c0b60e409e78277361b884773))
8
+
9
+ ### Bug Fixes
10
+ - **formatter:** drop leading blank line in display math ([`6700a54`](https://github.com/jolars/panache/commit/6700a54b489619040c4cea78e79e917105c9a403)), closes [#381](https://github.com/jolars/panache/issues/381), [#382](https://github.com/jolars/panache/issues/382), and [#383](https://github.com/jolars/panache/issues/383)
11
+
12
+ ### Dependencies
13
+ - updated crates/panache-parser to v0.19.0
14
+
3
15
  ## [0.15.0](https://github.com/jolars/panache/compare/panache-formatter-v0.14.0...panache-formatter-v0.15.0) (2026-06-21)
4
16
 
5
17
  ### Features
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "panache-formatter"
3
- version = "0.15.0"
3
+ version = "0.16.0"
4
4
  edition.workspace = true
5
5
  include = [
6
6
  "/src/**/*",
@@ -18,7 +18,7 @@ keywords = ["quarto", "pandoc", "markdown", "formatter"]
18
18
  categories = ["text-processing"]
19
19
 
20
20
  [dependencies]
21
- panache-parser = { path = "../panache-parser", version = "0.18.0" }
21
+ panache-parser = { path = "../panache-parser", version = "0.19.0" }
22
22
  log = { version = "0.4.31", features = ["release_max_level_debug"] }
23
23
  rowan = "0.16.1"
24
24
  serde = { version = "1.0", features = ["derive"], optional = true }
@@ -252,6 +252,10 @@ impl Config {
252
252
  dialect: self.dialect(),
253
253
  extensions: self.parser_extensions.clone(),
254
254
  pandoc_compat: self.parser,
255
+ // The formatter re-parses only for internal checks (idempotency,
256
+ // code-block handling) where citation-vs-crossref classification
257
+ // doesn't affect output, so the built-in prefix set suffices here.
258
+ crossref_prefixes: Vec::new(),
255
259
  refdef_labels: None,
256
260
  }
257
261
  }
@@ -615,13 +615,25 @@ pub(super) fn format_inline_node(node: &SyntaxNode, config: &Config) -> String {
615
615
  result.push_str(&math::format_math(&content, &opts));
616
616
  result.push('\n');
617
617
  } else {
618
- // Process content: trim surrounding newlines (NOT leading spaces)
619
- // and trailing whitespace, strip common leading whitespace, then
620
- // re-indent every line by `math_indent`. Trimming only leading
621
- // newlines keeps each line's true indent visible to `min_indent`,
622
- // so the re-indent is idempotent: a later pass strips the pad as
623
- // common indentation before re-applying it, rather than stacking.
624
- let trimmed_content = content.trim_start_matches(['\n', '\r']).trim_end();
618
+ // Process content: drop leading blank (whitespace-only) lines and
619
+ // trailing whitespace, strip common leading indentation, then
620
+ // re-indent every line by `math_indent`. Leading whitespace-only
621
+ // lines must be dropped rather than emitted: a blank line directly
622
+ // after the opening `$$` reparses as a paragraph break that splits
623
+ // the display math (pandoc ends `$$…$$` on any blank line), so
624
+ // emitting one is not lossless across passes. The opening marker's
625
+ // own trailing whitespace (`$$ `) surfaces here as exactly such a
626
+ // leading line. Stripping only full blank lines (not leading spaces
627
+ // of a content line) keeps each real line's indent visible to
628
+ // `min_indent`, so the re-indent stays idempotent.
629
+ let mut trimmed_content = content.trim_end();
630
+ while let Some((first, rest)) = trimmed_content.split_once('\n') {
631
+ if first.trim().is_empty() {
632
+ trimmed_content = rest;
633
+ } else {
634
+ break;
635
+ }
636
+ }
625
637
  if !trimmed_content.is_empty() {
626
638
  // Find minimum indentation across all non-empty lines
627
639
  let min_indent = trimmed_content
@@ -186,6 +186,27 @@ Returned unchanged, never reflowed:
186
186
  `\frac{…}{…}`) is left on one over-width line --- like an unbreakable long
187
187
  word in prose reflow. Inline and environment-body math are not line-broken.
188
188
 
189
+ 8. **Tight scripts and group interiors.** Whitespace that TeX ignores is
190
+ removed:
191
+ - **Sub/superscript markers** (`_`, `^`) bind tightly, so author whitespace
192
+ on either side is stripped: `H _{ 00}` → `H_{00}`, `x ^ 2` → `x^2`,
193
+ `{a} _ b` → `{a}_b`. The marker still presents an opening class, so a
194
+ directly following `+`/`-` coerces to unary (`x^{-1}` keeps its minus
195
+ tight).
196
+ - **Math-mode brace groups** have their *leading and trailing* interior
197
+ whitespace trimmed (`{ 00 }` → `{00}`, `{-1 }` → `{-1}`), since math mode
198
+ ignores it. The space *before* `{` and *after* `}` (between atoms) is left
199
+ alone (`{x} y` stays `{x} y`), and inter-atom spaces inside the group keep
200
+ the Rule 1/6 collapse, not removal. **Text-mode groups are exempt:** the
201
+ argument of a text-switching command (`\text`, `\mbox`, the `\text*` family
202
+ --- see `operators::is_text_mode_command`) keeps its interior spaces
203
+ verbatim (`\text{ a }` survives), and the exemption nests, so a group
204
+ inside a text argument (`\text{a {b} c}`) is also preserved. Whether a
205
+ group is text mode is tracked with a brace-mode stack in
206
+ `render::space_operators`. Math-mode font commands (`\mathrm`, `\mathbf`)
207
+ are **not** text mode --- spaces are already insignificant inside them ---
208
+ so their interiors are trimmed like any other math group.
209
+
189
210
  ## Idempotency
190
211
 
191
212
  `format(format(x)) == format(x)` for every well-formed input. The alignment
@@ -207,6 +228,11 @@ engine guarantees it by construction:
207
228
  token stream --- which round-trips --- so pass 2 makes the identical decision.
208
229
  Inserting at most one space per gap (then `collapse_spaces` + cell trim) and
209
230
  stripping spaces only beside *tight* runs both converge in one pass.
231
+ - **Tight scripts and trimmed group interiors are fixed points.** Once a script
232
+ is tight (`H_{00}`) or a math-mode group interior is trimmed (`{00}`), the
233
+ re-parse has no adjacent whitespace to strip, so pass 2 emits the same bytes.
234
+ The text-mode exemption keys on the command name, which round-trips, so the
235
+ same groups are spared each pass.
210
236
  - **Line-breaking is a fixed point.** The breaker emits continuations on soft
211
237
  newlines with leading alignment spaces. On pass 2 those soft newlines and
212
238
  spaces are insignificant whitespace that re-joins into the single logical row
@@ -113,6 +113,32 @@ pub fn command_class(name: &str) -> Option<AtomClass> {
113
113
  Some(class)
114
114
  }
115
115
 
116
+ /// Whether a command (name **without** the leading backslash) switches its
117
+ /// mandatory `{…}` argument into *text mode*, where whitespace is significant
118
+ /// and must be preserved verbatim. The curated set is the single-argument
119
+ /// text-switching family; math-mode font commands (`\mathrm`, `\mathbf`) are
120
+ /// **excluded** because spaces are already insignificant inside them, and
121
+ /// multi-argument commands (`\textcolor`) are excluded because their text
122
+ /// argument is not the first group.
123
+ pub fn is_text_mode_command(name: &str) -> bool {
124
+ matches!(
125
+ name,
126
+ "text"
127
+ | "textrm"
128
+ | "textbf"
129
+ | "textit"
130
+ | "texttt"
131
+ | "textsf"
132
+ | "textsc"
133
+ | "textnormal"
134
+ | "textup"
135
+ | "textsl"
136
+ | "textmd"
137
+ | "mbox"
138
+ | "hbox"
139
+ )
140
+ }
141
+
116
142
  /// Atom class of a delimiter/punctuation **token kind**. The parser tokenizes
117
143
  /// the unambiguous delimiters into dedicated kinds (`( [` → `MATH_OPEN`,
118
144
  /// `) ]` → `MATH_CLOSE`, `, ;` → `MATH_PUNCT`) because their TeX mathcode class
@@ -213,6 +239,21 @@ mod tests {
213
239
  assert_eq!(command_class("text"), None);
214
240
  }
215
241
 
242
+ #[test]
243
+ fn text_mode_command_set() {
244
+ // Text-switching commands → true (interior whitespace is significant).
245
+ assert!(is_text_mode_command("text"));
246
+ assert!(is_text_mode_command("textbf"));
247
+ assert!(is_text_mode_command("mbox"));
248
+ // Math-mode font commands and ordinary commands → false.
249
+ assert!(!is_text_mode_command("mathrm"));
250
+ assert!(!is_text_mode_command("mathbf"));
251
+ assert!(!is_text_mode_command("frac"));
252
+ assert!(!is_text_mode_command("alpha"));
253
+ // Multi-argument `\textcolor` is intentionally excluded.
254
+ assert!(!is_text_mode_command("textcolor"));
255
+ }
256
+
216
257
  #[test]
217
258
  fn delimiter_kind_classification() {
218
259
  assert_eq!(
@@ -534,6 +534,11 @@ fn space_operators(toks: &[(SyntaxKind, String)], seed: Option<AtomClass>) -> St
534
534
  let mut prev_class: Option<AtomClass> = seed;
535
535
  let mut prev_demand = Demand::Start;
536
536
  let mut pending_space = false;
537
+ // Brace-group mode stack (`true` = text mode, where interior whitespace is
538
+ // significant) and whether the last significant token was a text-mode
539
+ // command (so its `{…}` argument opens a text-mode group).
540
+ let mut group_stack: Vec<bool> = Vec::new();
541
+ let mut prev_sig_is_text_cmd = false;
537
542
 
538
543
  let mut i = 0;
539
544
  while i < toks.len() {
@@ -565,6 +570,7 @@ fn space_operators(toks: &[(SyntaxKind, String)], seed: Option<AtomClass>) -> St
565
570
  prev_demand = demand;
566
571
  prev_class = Some(class);
567
572
  }
573
+ prev_sig_is_text_cmd = false;
568
574
  }
569
575
  SyntaxKind::MATH_COMMAND => {
570
576
  let name = text.strip_prefix('\\').unwrap_or(text);
@@ -592,6 +598,7 @@ fn space_operators(toks: &[(SyntaxKind, String)], seed: Option<AtomClass>) -> St
592
598
  emit_atom(&mut out, prev_demand, demand, pending_space, text);
593
599
  pending_space = false;
594
600
  prev_demand = demand;
601
+ prev_sig_is_text_cmd = operators::is_text_mode_command(name);
595
602
  i += 1;
596
603
  }
597
604
  SyntaxKind::MATH_COMMENT => {
@@ -600,6 +607,53 @@ fn space_operators(toks: &[(SyntaxKind, String)], seed: Option<AtomClass>) -> St
600
607
  emit_atom(&mut out, prev_demand, Demand::Plain, pending_space, text);
601
608
  pending_space = false;
602
609
  prev_demand = Demand::Plain;
610
+ prev_sig_is_text_cmd = false;
611
+ i += 1;
612
+ }
613
+ SyntaxKind::MATH_SCRIPT => {
614
+ // `_`/`^` bind tightly: strip author whitespace on both sides
615
+ // (it is insignificant in TeX). Still present an `Open` class so
616
+ // a directly following `+`/`-` coerces to unary (`x^{-1}`).
617
+ emit_atom(&mut out, prev_demand, Demand::TightOp, pending_space, text);
618
+ pending_space = false;
619
+ prev_demand = Demand::TightOp;
620
+ prev_class = Some(AtomClass::Open);
621
+ prev_sig_is_text_cmd = false;
622
+ i += 1;
623
+ }
624
+ SyntaxKind::MATH_GROUP_OPEN => {
625
+ // A group is text mode if its `{` is a text-command argument or
626
+ // its parent group is already text mode (`\text{a {b} c}`).
627
+ let parent_text = group_stack.last().copied().unwrap_or(false);
628
+ let is_text = prev_sig_is_text_cmd || parent_text;
629
+ group_stack.push(is_text);
630
+ // Exterior (left) gap is preserved; the interior-leading space is
631
+ // stripped for a math-mode group (`{ 00}` → `{00}`), kept for text.
632
+ emit_atom(&mut out, prev_demand, Demand::Plain, pending_space, text);
633
+ pending_space = false;
634
+ prev_demand = if is_text {
635
+ Demand::Plain
636
+ } else {
637
+ Demand::TightOp
638
+ };
639
+ prev_class = Some(AtomClass::Open);
640
+ prev_sig_is_text_cmd = false;
641
+ i += 1;
642
+ }
643
+ SyntaxKind::MATH_GROUP_CLOSE => {
644
+ let is_text = group_stack.pop().unwrap_or(false);
645
+ // Interior-trailing space is stripped for a math-mode group
646
+ // (`{-1 }` → `{-1}`); the exterior space after `}` is preserved.
647
+ let cur = if is_text {
648
+ Demand::Plain
649
+ } else {
650
+ Demand::TightOp
651
+ };
652
+ emit_atom(&mut out, prev_demand, cur, pending_space, text);
653
+ pending_space = false;
654
+ prev_demand = Demand::Plain;
655
+ prev_class = Some(AtomClass::Close);
656
+ prev_sig_is_text_cmd = false;
603
657
  i += 1;
604
658
  }
605
659
  _ => {
@@ -607,6 +661,7 @@ fn space_operators(toks: &[(SyntaxKind, String)], seed: Option<AtomClass>) -> St
607
661
  pending_space = false;
608
662
  prev_demand = Demand::Plain;
609
663
  prev_class = atom_prev_class(*kind, text);
664
+ prev_sig_is_text_cmd = false;
610
665
  i += 1;
611
666
  }
612
667
  }
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.19.0](https://github.com/jolars/panache/compare/panache-parser-v0.18.0...panache-parser-v0.19.0) (2026-06-23)
4
+
5
+ ### Features
6
+ - add `crossref-prefixes` for extension crossrefs ([`0b190cc`](https://github.com/jolars/panache/commit/0b190cc1ad00e1ca146c758226a325b0c7a16017))
7
+
8
+ ### Performance Improvements
9
+ - **parser:** de-duplicate definition marker parsing ([`91a1f10`](https://github.com/jolars/panache/commit/91a1f10bc53d92294082a2acfc3442487f49ad2d))
10
+
3
11
  ## [0.18.0](https://github.com/jolars/panache/compare/panache-parser-v0.17.2...panache-parser-v0.18.0) (2026-06-21)
4
12
 
5
13
  ### Features
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "panache-parser"
3
- version = "0.18.0"
3
+ version = "0.19.0"
4
4
  edition.workspace = true
5
5
  readme = "README.md"
6
6
  include = [
@@ -881,6 +881,14 @@ pub struct ParserOptions {
881
881
  pub extensions: Extensions,
882
882
  /// Compatibility target for ambiguous Pandoc behavior.
883
883
  pub pandoc_compat: PandocCompat,
884
+ /// Additional cross-reference key prefixes (beyond the Quarto built-ins
885
+ /// recognized by [`crate::parser::inlines::citations::is_quarto_crossref_key`])
886
+ /// that should parse as cross-references rather than citations. Populated
887
+ /// from the top-level `crossref-prefixes` config key so documents relying on
888
+ /// crossref-injecting extensions (e.g. pseudocode's `@algo-`) don't have
889
+ /// those references misclassified as citations.
890
+ #[cfg_attr(feature = "serde", serde(default, alias = "crossref_prefixes"))]
891
+ pub crossref_prefixes: Vec<String>,
884
892
  /// Document-level reference link label set, populated by the
885
893
  /// top-level `parse()` function when running CommonMark dialect and
886
894
  /// consulted by inline parsing's bracket resolution pass. `None`
@@ -903,6 +911,7 @@ impl Default for ParserOptions {
903
911
  dialect: Dialect::for_flavor(flavor),
904
912
  extensions: Extensions::for_flavor(flavor),
905
913
  pandoc_compat: PandocCompat::default(),
914
+ crossref_prefixes: Vec::new(),
906
915
  refdef_labels: None,
907
916
  }
908
917
  }
@@ -115,16 +115,13 @@ pub(in crate::parser) fn next_line_is_definition_marker(
115
115
  check_pos += 1;
116
116
  continue;
117
117
  }
118
- if try_parse_definition_marker(line).is_some() {
118
+ if let Some((marker, ..)) = try_parse_definition_marker(line) {
119
119
  // Raw lines throughout: the marker detection above is itself raw, so
120
120
  // this only fires outside container prefixes (at top level raw ==
121
121
  // stripped). Inside a blockquote the raw `> :` never matches the
122
122
  // marker, so this caption gate is unreachable there — the
123
123
  // container-aware gate lives in `DefinitionListParser::detect_prepared`.
124
- if let Some((marker, ..)) = try_parse_definition_marker(line)
125
- && marker == ':'
126
- && is_caption_followed_by_table(lines, check_pos)
127
- {
124
+ if marker == ':' && is_caption_followed_by_table(lines, check_pos) {
128
125
  return None;
129
126
  }
130
127
  return Some(blank_count);
@@ -189,6 +189,36 @@ pub fn is_quarto_crossref_key(key: &str) -> bool {
189
189
  )
190
190
  }
191
191
 
192
+ /// Like [`is_quarto_crossref_key`], but also accepts any key whose prefix
193
+ /// appears in `custom_prefixes`. Used to recognize cross-reference prefixes
194
+ /// injected by Quarto extensions (e.g. pseudocode's `@algo-`) that aren't
195
+ /// built in. Matching is case-insensitive on the prefix, consistent with the
196
+ /// built-in check.
197
+ pub fn is_crossref_key(key: &str, custom_prefixes: &[String]) -> bool {
198
+ is_quarto_crossref_key(key) || has_custom_crossref_prefix(key, custom_prefixes)
199
+ }
200
+
201
+ /// Whether `key`'s prefix (the segment before the first `-`) appears in
202
+ /// `custom_prefixes`. Unlike [`is_quarto_crossref_key`], this matches *only*
203
+ /// the configured extension prefixes, so callers can tell an extension-injected
204
+ /// cross-reference (whose target panache can't resolve) apart from a built-in
205
+ /// one (whose target it can and should validate).
206
+ pub fn has_custom_crossref_prefix(key: &str, custom_prefixes: &[String]) -> bool {
207
+ if custom_prefixes.is_empty() {
208
+ return false;
209
+ }
210
+ let lower = key.to_ascii_lowercase();
211
+ let mut parts = lower.splitn(2, '-');
212
+ let prefix = parts.next().unwrap_or("");
213
+ let rest = parts.next().unwrap_or("");
214
+ if rest.is_empty() {
215
+ return false;
216
+ }
217
+ custom_prefixes
218
+ .iter()
219
+ .any(|candidate| candidate.eq_ignore_ascii_case(prefix))
220
+ }
221
+
192
222
  pub const BOOKDOWN_LABEL_PREFIXES: &[&str] = &[
193
223
  "eq", "fig", "tab", "thm", "lem", "cor", "prp", "cnj", "def", "exm", "exr", "sol", "rem",
194
224
  "alg", "sec", "hyp",
@@ -531,6 +561,26 @@ mod tests {
531
561
  assert_eq!(parse_citation_key("key rest"), Some(3));
532
562
  }
533
563
 
564
+ #[test]
565
+ fn is_crossref_key_accepts_builtin_without_custom() {
566
+ assert!(is_crossref_key("fig-plot", &[]));
567
+ assert!(!is_crossref_key("algo-cd", &[]));
568
+ }
569
+
570
+ #[test]
571
+ fn is_crossref_key_accepts_custom_prefix() {
572
+ let custom = vec!["algo".to_string()];
573
+ assert!(is_crossref_key("algo-cd", &custom));
574
+ // Case-insensitive on the prefix, consistent with the built-in check.
575
+ assert!(is_crossref_key("ALGO-cd", &custom));
576
+ // Built-ins still match alongside the custom set.
577
+ assert!(is_crossref_key("tbl-x", &custom));
578
+ // A bare prefix with no `-suffix` is not a crossref.
579
+ assert!(!is_crossref_key("algo", &custom));
580
+ // Unrelated prefixes remain citations.
581
+ assert!(!is_crossref_key("doe99", &custom));
582
+ }
583
+
534
584
  // Bare citation parsing tests
535
585
  #[test]
536
586
  fn test_parse_bare_citation_simple() {
@@ -452,7 +452,7 @@ fn parse_inline_range_impl(
452
452
  && pos + len == dispo_end
453
453
  {
454
454
  let is_crossref = config.extensions.quarto_crossrefs
455
- && super::citations::is_quarto_crossref_key(key);
455
+ && super::citations::is_crossref_key(key, &config.crossref_prefixes);
456
456
  if is_crossref || config.extensions.citations {
457
457
  if pos > text_start {
458
458
  builder.token(SyntaxKind::TEXT.into(), &text[text_start..pos]);
@@ -1380,8 +1380,8 @@ fn parse_inline_range_impl(
1380
1380
  && (config.extensions.citations || config.extensions.quarto_crossrefs)
1381
1381
  && let Some((len, key, has_suppress)) = try_parse_bare_citation(&text[pos..])
1382
1382
  {
1383
- let is_crossref =
1384
- config.extensions.quarto_crossrefs && super::citations::is_quarto_crossref_key(key);
1383
+ let is_crossref = config.extensions.quarto_crossrefs
1384
+ && super::citations::is_crossref_key(key, &config.crossref_prefixes);
1385
1385
  if is_crossref || config.extensions.citations {
1386
1386
  if pos > text_start {
1387
1387
  builder.token(SyntaxKind::TEXT.into(), &text[text_start..pos]);
@@ -1410,8 +1410,8 @@ fn parse_inline_range_impl(
1410
1410
  && (config.extensions.citations || config.extensions.quarto_crossrefs)
1411
1411
  && let Some((len, key, has_suppress)) = try_parse_bare_citation(&text[pos..])
1412
1412
  {
1413
- let is_crossref =
1414
- config.extensions.quarto_crossrefs && super::citations::is_quarto_crossref_key(key);
1413
+ let is_crossref = config.extensions.quarto_crossrefs
1414
+ && super::citations::is_crossref_key(key, &config.crossref_prefixes);
1415
1415
  if is_crossref || config.extensions.citations {
1416
1416
  if pos > text_start {
1417
1417
  builder.token(SyntaxKind::TEXT.into(), &text[text_start..pos]);
@@ -2915,6 +2915,7 @@ mod tests {
2915
2915
  dialect: crate::options::Dialect::for_flavor(flavor),
2916
2916
  extensions: crate::options::Extensions::for_flavor(flavor),
2917
2917
  pandoc_compat: crate::options::PandocCompat::default(),
2918
+ crossref_prefixes: Vec::new(),
2918
2919
  refdef_labels: None,
2919
2920
  }
2920
2921
  }
@@ -3132,6 +3133,7 @@ mod tests {
3132
3133
  dialect: crate::options::Dialect::for_flavor(flavor),
3133
3134
  extensions: crate::options::Extensions::for_flavor(flavor),
3134
3135
  pandoc_compat: crate::options::PandocCompat::default(),
3136
+ crossref_prefixes: Vec::new(),
3135
3137
  refdef_labels: None,
3136
3138
  }
3137
3139
  }