Skip to content

feat(docs): extend find-replace with markdown, images, and first-occurrence support#305

Closed
chrismdp wants to merge 5 commits intoopenclaw:mainfrom
chrismdp:feat/docs-replace-cmd
Closed

feat(docs): extend find-replace with markdown, images, and first-occurrence support#305
chrismdp wants to merge 5 commits intoopenclaw:mainfrom
chrismdp:feat/docs-replace-cmd

Conversation

@chrismdp
Copy link
Copy Markdown
Contributor

@chrismdp chrismdp commented Feb 17, 2026

Summary

Extends the existing docs find-replace command with three new flags, folding in the capabilities that were previously prototyped as a separate docs replace command:

  • --format=markdown — replacement text is parsed as markdown and applied with native Google Docs formatting (bold, italic, headings, lists, code blocks, tables, inline images via ![alt](url))
  • --first — replace only the first occurrence instead of all (uses revision-pinned delete + insert rather than ReplaceAllText API)
  • --content-file=PATH — read replacement text from a file instead of the positional argument

Backwards compatibility

gog docs find-replace <docId> <find> <replace> [--match-case] works identically to before — plain-text replace-all via the ReplaceAllText API. No flags changed meaning; replace positional arg is now optional (omit when using --content-file).

Four operational paths

--format --first Behaviour
plain (default) no ReplaceAllText API — unchanged from main
plain yes Find first occurrence → delete + insert (revision-pinned)
markdown yes Find first → delete + insert with formatting + image pipeline
markdown no Loop: find first, apply markdown replacement, re-read doc, repeat until no matches

Image pipeline

Reuses the shared two-pass image pipeline from docs_import.go:

  1. extractMarkdownImages strips ![alt](url)<<IMG_token_N>> placeholders
  2. Text + formatting inserted into document
  3. insertImagesIntoDocs reads doc back, finds placeholders, replaces with InsertInlineImage
  4. cleanupImagePlaceholders removes any leftover placeholders (belt-and-suspenders)

Custom image dimensions

Supports Pandoc-style {width=N height=N} attributes on markdown images:

![alt](url)                        → 468pt (default, full width)
![alt](url){width=200}             → 200pt wide, auto height
![alt](url){width=200 height=150}  → 200pt × 150pt
![alt](url){w=200 h=150}           → shorthand

The attribute parsing is shared between the import and sed image pipelines via parseImageDimAttrs.

Files changed

File Change
internal/cmd/docs.go Extended DocsFindReplaceCmd struct + Run(), added runReplaceAll/runPlain/runMarkdown/resolveReplaceText/insertImages/printFirstResult methods, removed DocsReplaceCmd entirely
internal/cmd/docs_find_replace_test.go New — 13 tests covering all paths (plain, markdown, images, case-sensitivity, content-file, error cases, revision pinning)
internal/cmd/docs_import.go Shared image pipeline — added widthPt/heightPt fields, extended regex for {…} attributes, shared parseImageDimAttrs helper, dimension-aware buildImageInsertRequests
internal/cmd/docs_import_test.go Image pipeline tests + 8 new dimension tests
internal/cmd/docs_import_security_test.go Minor test adjustment
internal/cmd/docs_sed_images.go Refactored parseImageSyntax to use shared parseImageDimAttrs

Test plan

  • go build ./...
  • go test ./internal/cmd/... -run "TestDocsFindReplace|TestFindTextInDoc" — 13/13 pass
  • go test ./internal/cmd/... -run "TestExtractMarkdownImages|TestBuildImageInsert" — all pass
  • go test ./internal/cmd/... -run "TestParseImageSyntax" — all pass (sed refactoring)
  • go vet ./internal/cmd/... — clean
  • Full test suite go test ./internal/cmd/... — pass
  • Manual testing against live Google Doc: plain replace-all, plain --first, --match-case, --content-file, markdown --first (bold), markdown replace-all (italic), zero matches, JSON output, all error paths

🤖 Generated with Claude Code

@chrismdp chrismdp changed the title feat(docs): add docs replace command with markdown support feat(docs): Full Google Docs editing with markdown / image support Feb 17, 2026
@chrismdp
Copy link
Copy Markdown
Contributor Author

chrismdp commented Feb 26, 2026

Hey! Noticed the great find-replace command landed in #225 — nice work.

Just wanted to flag why this PR is still relevant. The two commands solve different problems:

  • find-replace uses ReplaceAllText for bulk plain-text substitution — great for simple search-and-replace across the whole doc.
  • docs replace targets a single occurrence per invocation using index-based delete+insert, with RequiredRevisionId to prevent concurrent edit corruption. It also supports --format markdown for inserting formatted content (headings, bold, lists, images, tables) and --content-file for reading replacement text from a file.

The main use case is template-style workflows — find a placeholder like {{SECTION_CONTENT}}, replace it once with rich formatted content, and get back a count of remaining matches. All rebased on laster main if you'd like to take a look.

@chrismdp chrismdp force-pushed the feat/docs-replace-cmd branch 2 times, most recently from 87c0820 to 9f65638 Compare February 27, 2026 13:16
@chrismdp chrismdp changed the title feat(docs): Full Google Docs editing with markdown / image support feat(docs): extend find-replace with markdown, images, and first-occurrence support Feb 27, 2026
@chrismdp
Copy link
Copy Markdown
Contributor Author

@steipete — updated approach: instead of adding a separate docs replace command, this PR now extends the existing docs find-replace with three new flags:

  • --format=markdown — parses replacement text as markdown and applies native Google Docs formatting (bold, italic, headings, lists, code blocks, tables, inline images)
  • --first — replaces only the first occurrence (revision-pinned delete + insert) instead of all
  • --content-file=PATH — reads replacement from a file rather than a positional arg

The default behaviour (gog docs find-replace <docId> <find> <replace>) is unchanged — it still uses the ReplaceAllText API for plain-text replace-all, so this is fully backwards-compatible with what's on upstream/main.

The image pipeline reuses the shared functions from docs_import.go (extractMarkdownImages, insertImagesIntoDocs), so there's no duplication.

Single squashed commit, 13 tests, manually tested all paths against a live doc.

@chrismdp chrismdp force-pushed the feat/docs-replace-cmd branch from 9f65638 to 97bd9d2 Compare February 27, 2026 13:26
…rrence support

Adds --format=markdown, --first, and --content-file flags to the existing
`docs find-replace` command, giving it the capabilities that were previously
prototyped in a separate `docs replace` command.

Four operational paths:
- Plain replace-all (default): ReplaceAllText API — unchanged behaviour
- Plain --first: find first occurrence, delete + insert (revision-pinned)
- Markdown --first: find first, delete + insert with formatting + image pipeline
- Markdown replace-all: loop over occurrences, applying markdown each iteration

The shared image pipeline (extractMarkdownImages, insertImagesIntoDocs) from
docs_import.go is reused for inline image support (![alt](url)).

Inline images are now capped to 468pt width (US Letter content width) by
default, maintaining aspect ratio. Previously images inserted at native
resolution which could be far larger than the page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chrismdp chrismdp force-pushed the feat/docs-replace-cmd branch from 97bd9d2 to d963196 Compare February 27, 2026 13:29
chrismdp and others added 4 commits February 27, 2026 13:49
Add {width=N height=N} / {w=N h=N} syntax to the markdown image pipeline,
allowing control over inserted image size instead of always using 468pt.

Extract shared parseImageDimAttrs helper, reused by both the import and
sed image pipelines to eliminate duplicated attribute parsing logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add {width=N height=N} sizing syntax to --format and --file help
strings so the feature is discoverable from the CLI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ssaging

findPlaceholderIndices searched within individual TextRun elements, so
placeholders split across runs by Drive's markdown converter were never
found — causing silent image insertion failures.

Fix by concatenating all text runs within each paragraph before searching,
then mapping byte offsets back to absolute UTF-16 document indices.

Also improve the error message when local images resolve outside the
markdown file directory — now explains the constraint and suggests using
relative paths. Updated help text for --file and --format to document
the local image path requirement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Drive's markdown converter places table cell content inside
Table > TableRow > TableCell > Paragraph structures. The placeholder
search only walked top-level Body.Content paragraphs, so images inside
markdown tables were never found — causing silent insertion failures.

Fix by extracting searchElements/searchParagraph helpers that recurse
into table structures. Confirmed with manual test against a real
markdown file with images in table cells.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
steipete added a commit that referenced this pull request Mar 8, 2026
…rrence support (#305)

Co-authored-by: Chris Parsons <chris.p@rsons.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@steipete
Copy link
Copy Markdown
Collaborator

steipete commented Mar 8, 2026

Landed on main in c38fbb35921dd85447ed035c75e341b4f6e4eb29 after rebase/conflict resolution, review, and fixups.

Extra land fixes included:

  • kept the split docs command layout from main instead of reviving the old monolithic file
  • shared the image insertion path with docs create and markdown find-replace
  • fixed placeholder detection across split text runs and inside table cells
  • made markdown replace-all apply a stable match snapshot to avoid self-matching loops
  • tightened output/help text and added broader regression coverage for --first, --content-file, markdown images, and placeholder indexing

Verification: make ci

@steipete steipete closed this Mar 8, 2026
klodr pushed a commit to klodr/gogcli that referenced this pull request Apr 22, 2026
…rrence support (openclaw#305)

Co-authored-by: Chris Parsons <chris.p@rsons.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants