punctilio 5.5.2 → 5.5.4
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/README.md +1 -1
- package/dist/constants.d.ts +13 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +19 -0
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts +1 -0
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +38 -24
- package/dist/dashes.js.map +1 -1
- package/dist/markdown.d.ts +6 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +25 -12
- package/dist/markdown.js.map +1 -1
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +14 -4
- package/dist/nbsp.js.map +1 -1
- package/dist/quote-classifier.d.ts.map +1 -1
- package/dist/quote-classifier.js +203 -114
- package/dist/quote-classifier.js.map +1 -1
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +14 -11
- package/dist/symbols.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +22 -0
- package/src/dashes.ts +37 -26
- package/src/markdown.ts +36 -18
- package/src/nbsp.ts +14 -4
- package/src/quote-classifier.ts +210 -107
- package/src/symbols.ts +15 -13
package/src/dashes.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { asCharClassContent, cachedRegExp, FOLDED_WORD_CHARS, isWordLike, LATIN_LETTER_RE, LATIN_LETTERS, LATIN_OR_DIGIT, LATIN_OR_DIGIT_RE,
|
|
1
|
+
import { asCharClassContent, cachedRegExp, FOLDED_TERMINALS, FOLDED_WORD_CHARS, FRACTION_GLYPHS, isWordLike, LATIN_LETTER_RE, LATIN_LETTERS, LATIN_OR_DIGIT, LATIN_OR_DIGIT_RE, MAX_BOUNDARY_SEPARATORS, NBSP_CHARS, OPENING_BRACKETS, OPERATOR_SPACE, QUOTE_GLYPHS, SPACE_CHAR_RE, UNICODE_SYMBOLS } from "./constants.js"
|
|
2
2
|
import { boundaryCountAt, exceedsSingleBoundary, firstInteriorBoundary, lastBoundaryBefore, makeProsePass, overInput, type ProseView, replaceAllInView, type ReplaceAllOptions } from "./prose-view.js"
|
|
3
3
|
|
|
4
4
|
export const DASH_STYLES = ["american", "british", "none"] as const
|
|
@@ -9,10 +9,7 @@ export interface DashOptions {
|
|
|
9
9
|
dashStyle?: DashStyle
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const {
|
|
13
|
-
EN_DASH, EM_DASH, MINUS, MULTIPLICATION, PLUS_MINUS,
|
|
14
|
-
LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE, LEFT_SINGLE_QUOTE, RIGHT_SINGLE_QUOTE,
|
|
15
|
-
} = UNICODE_SYMBOLS
|
|
12
|
+
const { EN_DASH, EM_DASH, MINUS, MULTIPLICATION, PLUS_MINUS } = UNICODE_SYMBOLS
|
|
16
13
|
|
|
17
14
|
// Prevents false-positive ranges in model names like "Llama-2-7B".
|
|
18
15
|
export const numberRangeDisallowedPrefixes = ["-", EN_DASH, EM_DASH, MINUS] as const
|
|
@@ -391,7 +388,7 @@ interface ResolvedTail {
|
|
|
391
388
|
function resolvePositiveTail(view: ProseView, endPos: number): ResolvedTail | null {
|
|
392
389
|
const candidates = followingCandidates(view, endPos, true)
|
|
393
390
|
for (let i = candidates.length - 1; i >= 0; i--) {
|
|
394
|
-
const suffixEnd = suffixWbeEnd(view, candidates[i], true)
|
|
391
|
+
const suffixEnd = suffixWbeEnd(view, candidates[i], true, i === 0)
|
|
395
392
|
if (suffixEnd !== null) {
|
|
396
393
|
return { followingEmpty: i === 0, consumedEnd: suffixEnd }
|
|
397
394
|
}
|
|
@@ -466,7 +463,7 @@ function matchNegativeRangeAt(view: ProseView, i: number): number | null {
|
|
|
466
463
|
const endPos = end.firstDigitStart + len
|
|
467
464
|
const candidates = followingCandidates(view, endPos, false)
|
|
468
465
|
for (let c = candidates.length - 1; c >= 0; c--) {
|
|
469
|
-
const suffixEnd = suffixWbeEnd(view, candidates[c], false)
|
|
466
|
+
const suffixEnd = suffixWbeEnd(view, candidates[c], false, c === 0)
|
|
470
467
|
if (suffixEnd === null) continue
|
|
471
468
|
if (c === 0) {
|
|
472
469
|
view.replace(startEnd, negEnd, `${EN_DASH}${hasNeg ? MINUS : ""}`)
|
|
@@ -529,8 +526,15 @@ const RANGE_SUFFIX_RE = /[xKBTMCF]/
|
|
|
529
526
|
* The `(?:[AaPp][Mm]|[xKBTMCF])?${wbe}` suffix at `pos`, with one tolerated
|
|
530
527
|
* boundary before the suffix. Returns the end offset after the optional suffix
|
|
531
528
|
* when the tail completes (wbe passes), or null when it does not.
|
|
529
|
+
*
|
|
530
|
+
* `allowMultiplier` admits the lowercase `x`. It folds to `×`, which is no
|
|
531
|
+
* suffix, so after a non-empty `following` a re-run reads the tail as failing
|
|
532
|
+
* and converts the leading range: `1-2-3x` must convert like `1-2-3×`. An
|
|
533
|
+
* operator the multiplication pass folds in place (`1-2-3*4`, `1-2-3 x 4`)
|
|
534
|
+
* fails the tail the same way.
|
|
532
535
|
*/
|
|
533
|
-
function suffixWbeEnd(view: ProseView, pos: number, allowAmPm: boolean): number | null {
|
|
536
|
+
function suffixWbeEnd(view: ProseView, pos: number, allowAmPm: boolean, allowMultiplier: boolean): number | null {
|
|
537
|
+
if (!allowMultiplier && foldsToMultiplication(view.text, pos)) return null
|
|
534
538
|
if (wordBoundaryEndOk(view, pos)) return pos
|
|
535
539
|
// An optional suffix: one tolerated boundary then am/pm or one of
|
|
536
540
|
// {@link RANGE_SUFFIX_RE}'s letters, followed by wbe. The suffix letters sit
|
|
@@ -542,12 +546,19 @@ function suffixWbeEnd(view: ProseView, pos: number, allowAmPm: boolean): number
|
|
|
542
546
|
if (allowAmPm && /[ap]/i.test(text[pos] ?? "") && /m/i.test(text[pos + 1] ?? "") && !view.hasBoundary(pos + 1)) {
|
|
543
547
|
return wordBoundaryEndOk(view, pos + 2) ? pos + 2 : null
|
|
544
548
|
}
|
|
545
|
-
if (RANGE_SUFFIX_RE.test(text[pos] ?? "")) {
|
|
549
|
+
if (RANGE_SUFFIX_RE.test(text[pos] ?? "") && (allowMultiplier || text[pos] !== "x")) {
|
|
546
550
|
return wordBoundaryEndOk(view, pos + 1) ? pos + 1 : null
|
|
547
551
|
}
|
|
548
552
|
return null
|
|
549
553
|
}
|
|
550
554
|
|
|
555
|
+
/** True iff an `x`, `X` or `*` operator with an operand digit after it starts at `pos`. */
|
|
556
|
+
function foldsToMultiplication(text: string, pos: number): boolean {
|
|
557
|
+
const operator = cachedRegExp(`${OPERATOR_SPACE}[xX*]${OPERATOR_SPACE}\\d`, "y")
|
|
558
|
+
operator.lastIndex = pos
|
|
559
|
+
return operator.test(text)
|
|
560
|
+
}
|
|
561
|
+
|
|
551
562
|
const NOT_AFTER_DASH_RE = new RegExp(`[${DISALLOWED_PREFIX_CLASS_FRAGMENT}${LATIN_LETTERS}${MULTIPLICATION}${PLUS_MINUS}.+]`)
|
|
552
563
|
|
|
553
564
|
/**
|
|
@@ -591,14 +602,14 @@ function endsFoldableEllipsisDots(view: ProseView, dotIndex: number): boolean {
|
|
|
591
602
|
*/
|
|
592
603
|
function notAfterDashOk(view: ProseView, startOffset: number): boolean {
|
|
593
604
|
if (startOffset === 0) return true
|
|
594
|
-
if (boundaryCountAt(view, startOffset) > 0) return true
|
|
595
605
|
const prev = view.text[startOffset - 1]
|
|
596
606
|
// A multiplication sign one space slot further left blocks like the `x` it
|
|
597
|
-
// folds from: the multiplication pass renders
|
|
598
|
-
// blocked at `x8-6` must stay blocked at `× 8-6`.
|
|
607
|
+
// folds from, even across a node boundary: the multiplication pass renders
|
|
608
|
+
// `5x8` as `5 × 8`, so a range blocked at `x8-6` must stay blocked at `× 8-6`.
|
|
599
609
|
if (prev !== undefined && SPACE_CHAR_RE.test(prev) && view.text[startOffset - 2] === MULTIPLICATION) {
|
|
600
610
|
return false
|
|
601
611
|
}
|
|
612
|
+
if (boundaryCountAt(view, startOffset) > 0) return true
|
|
602
613
|
if (prev === undefined || !NOT_AFTER_DASH_RE.test(prev)) return true
|
|
603
614
|
if (prev === "." && endsFoldableEllipsisDots(view, startOffset - 1)) return true
|
|
604
615
|
const dashIndex = startOffset - 1
|
|
@@ -781,7 +792,7 @@ function minusSubtractionPrefixOk(match: RegExpExecArray, view: ProseView, prefi
|
|
|
781
792
|
return NUM_BODY_RE.test(view.text.slice(numStart, truncatedEnd))
|
|
782
793
|
}
|
|
783
794
|
|
|
784
|
-
const MINUS_BEFORE_CLASS_RE = new RegExp(`[\\s${asCharClassContent(OPENING_BRACKETS)}
|
|
795
|
+
const MINUS_BEFORE_CLASS_RE = new RegExp(`[\\s${asCharClassContent(OPENING_BRACKETS)}${QUOTE_GLYPHS}]`)
|
|
785
796
|
// The folded word glyphs block like the word chars they fold from ("1x|-0"
|
|
786
797
|
// and "1×|-0" must agree), the folded math operators block like the operator
|
|
787
798
|
// chars they fold from (the `!=` → `≠` fold consumes the `=` that blocked
|
|
@@ -790,10 +801,7 @@ const MINUS_BEFORE_CLASS_RE = new RegExp(`[\\s${asCharClassContent(OPENING_BRACK
|
|
|
790
801
|
// ("?|!-0" folds to "⁈|-0" with the hyphen stranded boundary-adjacent).
|
|
791
802
|
const FOLDED_WORD_CLASS_FRAGMENT = [...FOLDED_WORD_CHARS].join("")
|
|
792
803
|
const FOLDED_MATH_CLASS_FRAGMENT = [...FOLDED_MATH_OPERATORS].join("")
|
|
793
|
-
const FOLDED_TERMINAL_CLASS_FRAGMENT = [
|
|
794
|
-
UNICODE_SYMBOLS.ELLIPSIS, UNICODE_SYMBOLS.DOUBLE_QUESTION, UNICODE_SYMBOLS.QUESTION_EXCLAMATION,
|
|
795
|
-
UNICODE_SYMBOLS.EXCLAMATION_QUESTION, UNICODE_SYMBOLS.DOUBLE_EXCLAMATION, UNICODE_SYMBOLS.INTERROBANG, "!",
|
|
796
|
-
].join("")
|
|
804
|
+
const FOLDED_TERMINAL_CLASS_FRAGMENT = [UNICODE_SYMBOLS.ELLIPSIS, ...FOLDED_TERMINALS, "!"].join("")
|
|
797
805
|
const NUM_BEFORE_2B_BLOCK_RE = new RegExp(`[\\d.,${LATIN_LETTERS}${FOLDED_WORD_CLASS_FRAGMENT}${FOLDED_MATH_CLASS_FRAGMENT}${FOLDED_TERMINAL_CLASS_FRAGMENT}]`)
|
|
798
806
|
|
|
799
807
|
/**
|
|
@@ -862,7 +870,7 @@ function convertParentheticalDashes(view: ProseView, style: DashStyle): void {
|
|
|
862
870
|
|
|
863
871
|
const DASH_RUN_CHARS = new Set(["-", EN_DASH, EM_DASH])
|
|
864
872
|
|
|
865
|
-
function isDashChar(ch: string | undefined): boolean {
|
|
873
|
+
export function isDashChar(ch: string | undefined): boolean {
|
|
866
874
|
return ch !== undefined && DASH_RUN_CHARS.has(ch)
|
|
867
875
|
}
|
|
868
876
|
|
|
@@ -1072,14 +1080,13 @@ function convertBoundaryLedDashes(view: ProseView, rendered: string): void {
|
|
|
1072
1080
|
if (b < lastProcessedEnd) continue
|
|
1073
1081
|
if (!isDashChar(text[b])) continue
|
|
1074
1082
|
// Non-space before the boundary: ≥2 stacked boundaries, or a non-space
|
|
1075
|
-
// clean char immediately left.
|
|
1076
|
-
//
|
|
1077
|
-
//
|
|
1078
|
-
//
|
|
1079
|
-
// would diverge from that blocked first pass.
|
|
1083
|
+
// clean char immediately left. Blocked there: a folded math operator (the
|
|
1084
|
+
// math pass consumes the `=` that blocked this rule: `=|~—` folds to
|
|
1085
|
+
// `≈|—`), and an en/em dash, the head of an already-converted run whose
|
|
1086
|
+
// tail this is.
|
|
1080
1087
|
if (stackedHere < 2) {
|
|
1081
1088
|
const before = text[b - 1]
|
|
1082
|
-
if (before === undefined || /\s/.test(before) || FOLDED_MATH_OPERATORS.has(before)) continue
|
|
1089
|
+
if (before === undefined || /\s/.test(before) || FOLDED_MATH_OPERATORS.has(before) || before === EN_DASH || before === EM_DASH) continue
|
|
1083
1090
|
}
|
|
1084
1091
|
// Dash run from the boundary, not crossing a further boundary.
|
|
1085
1092
|
let runEnd = b + 1
|
|
@@ -1237,11 +1244,15 @@ function removeSpacesAroundEmDash(view: ProseView): void {
|
|
|
1237
1244
|
)
|
|
1238
1245
|
}
|
|
1239
1246
|
|
|
1240
|
-
/**
|
|
1247
|
+
/**
|
|
1248
|
+
* Preserve a space after a line-leading em-dash before an uppercase/digit. A
|
|
1249
|
+
* vulgar fraction glyph counts as the digit it folds from (`—2/5` and `—⅖`
|
|
1250
|
+
* must agree).
|
|
1251
|
+
*/
|
|
1241
1252
|
function preserveLineLeadingEmDashSpace(view: ProseView): void {
|
|
1242
1253
|
pass(
|
|
1243
1254
|
view,
|
|
1244
|
-
cachedRegExp(`^${EM_DASH}(?<after>[A-Z0-9])`, "gm"),
|
|
1255
|
+
cachedRegExp(`^${EM_DASH}(?<after>[A-Z0-9${FRACTION_GLYPHS}])`, "gm"),
|
|
1245
1256
|
(match, v) => {
|
|
1246
1257
|
// One boundary is tolerated between the line start and the em-dash; two or
|
|
1247
1258
|
// more exceed that slot and block the match.
|
package/src/markdown.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { unified } from "unified"
|
|
2
2
|
import remarkParse from "remark-parse"
|
|
3
3
|
import remarkGfm from "remark-gfm"
|
|
4
|
-
import remarkStringify from "remark-stringify"
|
|
4
|
+
import remarkStringify, { type Options as StringifyOptions } from "remark-stringify"
|
|
5
5
|
import QuickLRU from "quick-lru"
|
|
6
6
|
|
|
7
7
|
import { MARKDOWN_OPTION_KEYS } from "./sink-option-keys.js"
|
|
@@ -35,6 +35,23 @@ export interface MarkdownOptions extends RemarkPunctilioOptions {
|
|
|
35
35
|
ruleMarker?: "-" | "*" | "_"
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Stringify settings shared by {@link createProcessor} and {@link remarkRoundTrip}. */
|
|
39
|
+
function stringifyOptions({ emphasisMarker, strongMarker, bulletMarker, ruleMarker }: MarkdownOptions): StringifyOptions {
|
|
40
|
+
return {
|
|
41
|
+
emphasis: emphasisMarker ?? "*",
|
|
42
|
+
strong: strongMarker ?? "*",
|
|
43
|
+
bullet: bulletMarker ?? "-",
|
|
44
|
+
rule: ruleMarker ?? "-",
|
|
45
|
+
// Use 4 dashes (----) for thematic breaks to avoid ambiguity with
|
|
46
|
+
// YAML front matter delimiters (---) during re-parsing
|
|
47
|
+
...(ruleMarker === "-" || ruleMarker === undefined ? { ruleRepetition: 4 } : {}),
|
|
48
|
+
// remark-stringify escapes opening brackets but not closing brackets,
|
|
49
|
+
// producing `\[1]` instead of `\[1\]`. This can cause issues when the
|
|
50
|
+
// output is re-parsed by markdown renderers.
|
|
51
|
+
unsafe: [{ character: "]", inConstruct: "phrasing" }],
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
function createProcessor(options: MarkdownOptions) {
|
|
39
56
|
const {
|
|
40
57
|
emphasisMarker,
|
|
@@ -48,19 +65,7 @@ function createProcessor(options: MarkdownOptions) {
|
|
|
48
65
|
.use(remarkParse)
|
|
49
66
|
.use(remarkGfm)
|
|
50
67
|
.use(remarkPunctilio, punctilioOptions)
|
|
51
|
-
.use(remarkStringify, {
|
|
52
|
-
emphasis: emphasisMarker ?? "*",
|
|
53
|
-
strong: strongMarker ?? "*",
|
|
54
|
-
bullet: bulletMarker ?? "-",
|
|
55
|
-
rule: ruleMarker ?? "-",
|
|
56
|
-
// Use 4 dashes (----) for thematic breaks to avoid ambiguity with
|
|
57
|
-
// YAML front matter delimiters (---) during re-parsing
|
|
58
|
-
...(ruleMarker === "-" || ruleMarker === undefined ? { ruleRepetition: 4 } : {}),
|
|
59
|
-
// remark-stringify escapes opening brackets but not closing brackets,
|
|
60
|
-
// producing `\[1]` instead of `\[1\]`. This can cause issues when the
|
|
61
|
-
// output is re-parsed by markdown renderers.
|
|
62
|
-
unsafe: [{ character: "]", inConstruct: "phrasing" }],
|
|
63
|
-
})
|
|
68
|
+
.use(remarkStringify, stringifyOptions({ emphasisMarker, strongMarker, bulletMarker, ruleMarker }))
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
const MAX_PROCESSOR_CACHE_SIZE = 8
|
|
@@ -126,17 +131,30 @@ export async function transformMarkdown(
|
|
|
126
131
|
options: MarkdownOptions = {}
|
|
127
132
|
): Promise<string> {
|
|
128
133
|
assertKnownOptionKeys(options, MARKDOWN_OPTION_KEYS, "transformMarkdown")
|
|
129
|
-
return transformPreservingBom(input, (text) => transformWithFrontmatter(text, options))
|
|
134
|
+
return transformPreservingBom(input, (text) => transformWithFrontmatter(text, (body) => processMarkdown(body, options)))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @internal Parse, then stringify, with no punctilio pass: the output
|
|
139
|
+
* `transformMarkdown` is held to on a second run (remark's own round trip is
|
|
140
|
+
* not always a fixed point).
|
|
141
|
+
*/
|
|
142
|
+
export async function remarkRoundTrip(
|
|
143
|
+
input: string,
|
|
144
|
+
options: MarkdownOptions = {}
|
|
145
|
+
): Promise<string> {
|
|
146
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkStringify, stringifyOptions(options))
|
|
147
|
+
return transformPreservingBom(input, (text) => transformWithFrontmatter(text, async (body) => String(await processor.process(body))))
|
|
130
148
|
}
|
|
131
149
|
|
|
132
|
-
async function transformWithFrontmatter(input: string,
|
|
150
|
+
async function transformWithFrontmatter(input: string, processBody: (body: string) => Promise<string>): Promise<string> {
|
|
133
151
|
const frontmatterEnd = frontmatterLength(input)
|
|
134
|
-
if (frontmatterEnd === 0) return
|
|
152
|
+
if (frontmatterEnd === 0) return processBody(input)
|
|
135
153
|
|
|
136
154
|
// Front matter is data (YAML/TOML), not prose; remark would parse it as a
|
|
137
155
|
// thematic break plus a heading and rewrite it.
|
|
138
156
|
const body = input.slice(frontmatterEnd)
|
|
139
157
|
const blankLines = LEADING_BLANK_LINES_RE.exec(body)![0]
|
|
140
|
-
const transformedBody = await
|
|
158
|
+
const transformedBody = await processBody(body.slice(blankLines.length))
|
|
141
159
|
return input.slice(0, frontmatterEnd) + blankLines + transformedBody
|
|
142
160
|
}
|
package/src/nbsp.ts
CHANGED
|
@@ -14,6 +14,16 @@ const {
|
|
|
14
14
|
|
|
15
15
|
const SPACE = `[${SPACE_CHARS}]`
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Glues the space at `offset` with an NBSP. A space that is already
|
|
19
|
+
* non-breaking (NBSP, or the NNBSP French quote padding) stays as written: an
|
|
20
|
+
* NBSP in its place would be re-padded to NNBSP by the next quote pass.
|
|
21
|
+
*/
|
|
22
|
+
function glueSpace(view: ProseView, offset: number): void {
|
|
23
|
+
if (NBSP_CHARS.includes(view.text[offset])) return
|
|
24
|
+
view.replace(offset, offset + 1, NBSP)
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
const UNICODE_UPPERCASE = "\\p{Lu}"
|
|
18
28
|
|
|
19
29
|
// A word-forming character (any Unicode letter or number). Used for the
|
|
@@ -197,7 +207,7 @@ function nbspAfterShortWordsOverView(view: ProseView): ReadonlySet<number> {
|
|
|
197
207
|
// Edit only the space so any boundary keeps its side of the NBSP.
|
|
198
208
|
const spaceOffset = offset + match[0].length - 1
|
|
199
209
|
if (!NBSP_CHARS.includes(clean[spaceOffset])) insertedGlue.add(spaceOffset)
|
|
200
|
-
v
|
|
210
|
+
glueSpace(v, spaceOffset)
|
|
201
211
|
return null
|
|
202
212
|
},
|
|
203
213
|
{
|
|
@@ -226,7 +236,7 @@ function nbspBetweenNumberAndUnitOverView(view: ProseView): void {
|
|
|
226
236
|
(match, v) => {
|
|
227
237
|
// Edit only the space (after the single digit) so any boundaries keep
|
|
228
238
|
// their sides.
|
|
229
|
-
v
|
|
239
|
+
glueSpace(v, match.index + 1)
|
|
230
240
|
return null
|
|
231
241
|
},
|
|
232
242
|
{
|
|
@@ -304,7 +314,7 @@ function nbspBeforeLastWordOverView(view: ProseView, insertedShortWordGlue: Read
|
|
|
304
314
|
if (exceedsSingleBoundary(v, end - match.groups!.ending.length)) return null
|
|
305
315
|
// Edit only the leading space so the lastWord/ending (and any boundary
|
|
306
316
|
// before \n\n) keep their positions.
|
|
307
|
-
v
|
|
317
|
+
glueSpace(v, start)
|
|
308
318
|
return null
|
|
309
319
|
},
|
|
310
320
|
{
|
|
@@ -375,7 +385,7 @@ function nbspBeforeContext(
|
|
|
375
385
|
}
|
|
376
386
|
// Edit only the trailing space so the slot/lookahead boundaries keep their
|
|
377
387
|
// sides.
|
|
378
|
-
view
|
|
388
|
+
glueSpace(view, end - 1)
|
|
379
389
|
}
|
|
380
390
|
}
|
|
381
391
|
view.commit()
|