EditorAPI currently asks hosts to find text: selectPhrase(phrase), and applyEdit({type: 'str_replace', oldStr, …}). Every host therefore implements its own search, with its own leniency:
| Host |
How it searches |
Leniency |
| Lexical |
indexOf per TextNode, via the fold ladder |
tiered (#587) |
| Word |
body.search / Range.search |
ignorePunct, ignoreSpace, matchCase — all at once, untiered |
| Google Docs |
Apps Script findText (regex) |
exact, then retry with a word trimmed off each end |
Three policies for one question. They disagree in ways that produce real failures: Word's selectPhrase is lenient while its applyEdit is not, so the reveal highlights a phrase the apply then fails to find. Google Docs' trimming can't repair an interior mismatch (a curly apostrophe mid-phrase) and, when it does succeed, selects less text than was asked for. And the shared ladder in @/utilities/textMatching can't reach Apps Script at all: Code.gs is deployed separately from the bundle and drifts from it.
Proposal
Invert the seam. The browser already holds the document text (getDocText / getParagraphs), which is what the ladder needs. Match there, and give hosts two primitives that require no searching:
selectRange(start, end)
replaceRange(start, end, text) (or keep applySplice and lower it to range replaces)
Then matching has one implementation, in the module that has the tests, and hosts shrink to what they're uniquely able to do.
Falls out of it:
Cost
Needs an Apps Script redeploy for the new bridge functions. The version-drift fallback pattern already exists in googleDocsEditorAPI.ts (documentPropertyBridge treats the bridge methods as optional and falls back when the installed deployment predates them), so this can ship without assuming a synchronized deploy.
Depends on #588: matching by offset requires the document text the browser holds to correspond to the document Apps Script will edit.
EditorAPIcurrently asks hosts to find text:selectPhrase(phrase), andapplyEdit({type: 'str_replace', oldStr, …}). Every host therefore implements its own search, with its own leniency:indexOfper TextNode, via the fold ladderbody.search/Range.searchignorePunct,ignoreSpace,matchCase— all at once, untieredfindText(regex)Three policies for one question. They disagree in ways that produce real failures: Word's
selectPhraseis lenient while itsapplyEditis not, so the reveal highlights a phrase the apply then fails to find. Google Docs' trimming can't repair an interior mismatch (a curly apostrophe mid-phrase) and, when it does succeed, selects less text than was asked for. And the shared ladder in@/utilities/textMatchingcan't reach Apps Script at all:Code.gsis deployed separately from the bundle and drifts from it.Proposal
Invert the seam. The browser already holds the document text (
getDocText/getParagraphs), which is what the ladder needs. Match there, and give hosts two primitives that require no searching:selectRange(start, end)replaceRange(start, end, text)(or keepapplySpliceand lower it to range replaces)Then matching has one implementation, in the module that has the tests, and hosts shrink to what they're uniquely able to do.
Falls out of it:
applyEditscopes to a paragraph today), as doesescapeForFindTextinCode.gs.minimalReplacebug in minimalReplace assumes exact first-occurrence semantics, but Word searches case-insensitively #592 disappears: no needle, no first-occurrence assumption.Cost
Needs an Apps Script redeploy for the new bridge functions. The version-drift fallback pattern already exists in
googleDocsEditorAPI.ts(documentPropertyBridgetreats the bridge methods as optional and falls back when the installed deployment predates them), so this can ship without assuming a synchronized deploy.Depends on #588: matching by offset requires the document text the browser holds to correspond to the document Apps Script will edit.