punctilio 4.3.0 → 4.3.1

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.
package/src/index.ts CHANGED
@@ -34,6 +34,7 @@ export {
34
34
  replaceAllInView,
35
35
  type ReplaceAllOptions,
36
36
  runLegacyPass,
37
+ withProseView,
37
38
  } from "./prose-view.js"
38
39
  export { classifyApostrophes, niceQuotes, PUNCTUATION_STYLES, type PunctuationStyle, type QuoteOptions } from "./quotes.js"
39
40
 
package/src/prose-view.ts CHANGED
@@ -247,6 +247,16 @@ function matchContainsBoundary(match: RegExpExecArray, view: ProseView): boolean
247
247
  return false
248
248
  }
249
249
 
250
+ /** Count of node boundaries that fall at exactly `offset` (empty nodes stack). */
251
+ export function boundaryCountAt(view: ProseView, offset: number): number {
252
+ let count = 0
253
+ for (const boundary of view.boundaries) {
254
+ if (boundary === offset) count++
255
+ else if (boundary > offset) break
256
+ }
257
+ return count
258
+ }
259
+
250
260
  export function replaceAllInView(
251
261
  view: ProseView,
252
262
  regex: RegExp,
@@ -281,6 +291,26 @@ export function replaceAllInView(
281
291
  }
282
292
  }
283
293
 
294
+ /**
295
+ * Splits sentinel-marked text on the separator, builds a ProseView over the
296
+ * fragments, runs `run` against the clean-text view, commits the queued edits,
297
+ * and rejoins the fragments with the separator. The separator count is
298
+ * preserved exactly: each fragment maps to one source node, and `commit()`
299
+ * never creates or destroys node boundaries.
300
+ */
301
+ export function withProseView(
302
+ markedText: string,
303
+ separator: string,
304
+ run: (view: ProseView) => void,
305
+ ): string {
306
+ const fragments = separator.length > 0 ? markedText.split(separator) : [markedText]
307
+ const nodes: ProseNode[] = fragments.map((value) => ({ value }))
308
+ const view = buildProseView(nodes)
309
+ run(view)
310
+ view.commit()
311
+ return nodes.map((node) => node.value).join(separator)
312
+ }
313
+
284
314
  export function runLegacyPass(
285
315
  view: ProseView,
286
316
  passFn: (markedText: string) => string,
@@ -1,5 +1,5 @@
1
1
  import { LATIN_LETTERS, TERMINAL_PUNCTUATION, UNICODE_SYMBOLS } from "./constants.js"
2
- import { buildProseView, type ProseNode, type ProseView } from "./prose-view.js"
2
+ import { type ProseView, withProseView } from "./prose-view.js"
3
3
 
4
4
  const {
5
5
  EM_DASH,
@@ -860,15 +860,6 @@ function queueRenderEdits(view: ProseView, items: Item[], table: Record<QuoteRol
860
860
  // Public engine entry points
861
861
  // ---------------------------------------------------------------------------
862
862
 
863
- function withProseView(markedText: string, separator: string, run: (view: ProseView) => void): string {
864
- const fragments = separator.length > 0 ? markedText.split(separator) : [markedText]
865
- const nodes: ProseNode[] = fragments.map((value) => ({ value }))
866
- const view = buildProseView(nodes)
867
- run(view)
868
- view.commit()
869
- return nodes.map((node) => node.value).join(separator)
870
- }
871
-
872
863
  /**
873
864
  * The quote/apostrophe role classifier: one boundary-aware scan over the
874
865
  * clean text replaces the v4 ordered regex pipeline.