@@ -290,6 +290,7 @@ type DocsWriteCmd struct {
290290 File string `name:"file" help:"Text file path ('-' for stdin)"`
291291 Append bool `name:"append" help:"Append instead of replacing the document body"`
292292 Pageless bool `name:"pageless" help:"Set document to pageless mode"`
293+ TabID string `name:"tab-id" help:"Target a specific tab by ID (see docs list-tabs)"`
293294}
294295
295296func (c * DocsWriteCmd ) Run (ctx context.Context , kctx * kong.Context , flags * RootFlags ) error {
@@ -320,21 +321,10 @@ func (c *DocsWriteCmd) Run(ctx context.Context, kctx *kong.Context, flags *RootF
320321 return err
321322 }
322323
323- doc , err := svc .Documents .Get (id ).
324- Fields ("documentId,body/content(startIndex,endIndex)" ).
325- Context (ctx ).
326- Do ()
324+ endIndex , err := docsTargetEndIndex (ctx , svc , id , c .TabID )
327325 if err != nil {
328- if isDocsNotFound (err ) {
329- return fmt .Errorf ("doc not found or not a Google Doc (id=%s)" , id )
330- }
331326 return err
332327 }
333- if doc == nil {
334- return errors .New ("doc not found" )
335- }
336-
337- endIndex := docsDocumentEndIndex (doc )
338328 insertIndex := int64 (1 )
339329 if c .Append {
340330 insertIndex = docsAppendIndex (endIndex )
@@ -349,6 +339,7 @@ func (c *DocsWriteCmd) Run(ctx context.Context, kctx *kong.Context, flags *RootF
349339 Range : & docs.Range {
350340 StartIndex : 1 ,
351341 EndIndex : deleteEnd ,
342+ TabId : c .TabID ,
352343 },
353344 },
354345 })
@@ -357,7 +348,7 @@ func (c *DocsWriteCmd) Run(ctx context.Context, kctx *kong.Context, flags *RootF
357348
358349 reqs = append (reqs , & docs.Request {
359350 InsertText : & docs.InsertTextRequest {
360- Location : & docs.Location {Index : insertIndex },
351+ Location : & docs.Location {Index : insertIndex , TabId : c . TabID },
361352 Text : text ,
362353 },
363354 })
@@ -384,6 +375,9 @@ func (c *DocsWriteCmd) Run(ctx context.Context, kctx *kong.Context, flags *RootF
384375 "append" : c .Append ,
385376 "index" : insertIndex ,
386377 }
378+ if c .TabID != "" {
379+ payload ["tabId" ] = c .TabID
380+ }
387381 if resp .WriteControl != nil {
388382 payload ["writeControl" ] = resp .WriteControl
389383 }
@@ -394,6 +388,9 @@ func (c *DocsWriteCmd) Run(ctx context.Context, kctx *kong.Context, flags *RootF
394388 u .Out ().Printf ("requests\t %d" , len (reqs ))
395389 u .Out ().Printf ("append\t %t" , c .Append )
396390 u .Out ().Printf ("index\t %d" , insertIndex )
391+ if c .TabID != "" {
392+ u .Out ().Printf ("tabId\t %s" , c .TabID )
393+ }
397394 if resp .WriteControl != nil && resp .WriteControl .RequiredRevisionId != "" {
398395 u .Out ().Printf ("revision\t %s" , resp .WriteControl .RequiredRevisionId )
399396 }
@@ -406,6 +403,7 @@ type DocsUpdateCmd struct {
406403 File string `name:"file" help:"Text file path ('-' for stdin)"`
407404 Index int64 `name:"index" help:"Insert index (default: end of document)"`
408405 Pageless bool `name:"pageless" help:"Set document to pageless mode"`
406+ TabID string `name:"tab-id" help:"Target a specific tab by ID (see docs list-tabs)"`
409407}
410408
411409func (c * DocsUpdateCmd ) Run (ctx context.Context , kctx * kong.Context , flags * RootFlags ) error {
@@ -442,27 +440,17 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
442440
443441 insertIndex := c .Index
444442 if insertIndex <= 0 {
445- var doc * docs.Document
446- doc , err = svc .Documents .Get (id ).
447- Fields ("documentId,body/content(startIndex,endIndex)" ).
448- Context (ctx ).
449- Do ()
450- if err != nil {
451- if isDocsNotFound (err ) {
452- return fmt .Errorf ("doc not found or not a Google Doc (id=%s)" , id )
453- }
454- return err
455- }
456- if doc == nil {
457- return errors .New ("doc not found" )
443+ endIndex , endErr := docsTargetEndIndex (ctx , svc , id , c .TabID )
444+ if endErr != nil {
445+ return endErr
458446 }
459- insertIndex = docsAppendIndex (docsDocumentEndIndex ( doc ) )
447+ insertIndex = docsAppendIndex (endIndex )
460448 }
461449
462450 reqs := []* docs.Request {
463451 {
464452 InsertText : & docs.InsertTextRequest {
465- Location : & docs.Location {Index : insertIndex },
453+ Location : & docs.Location {Index : insertIndex , TabId : c . TabID },
466454 Text : text ,
467455 },
468456 },
@@ -489,6 +477,9 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
489477 "requests" : len (reqs ),
490478 "index" : insertIndex ,
491479 }
480+ if c .TabID != "" {
481+ payload ["tabId" ] = c .TabID
482+ }
492483 if resp .WriteControl != nil {
493484 payload ["writeControl" ] = resp .WriteControl
494485 }
@@ -498,6 +489,9 @@ func (c *DocsUpdateCmd) Run(ctx context.Context, kctx *kong.Context, flags *Root
498489 u .Out ().Printf ("id\t %s" , resp .DocumentId )
499490 u .Out ().Printf ("requests\t %d" , len (reqs ))
500491 u .Out ().Printf ("index\t %d" , insertIndex )
492+ if c .TabID != "" {
493+ u .Out ().Printf ("tabId\t %s" , c .TabID )
494+ }
501495 if resp .WriteControl != nil && resp .WriteControl .RequiredRevisionId != "" {
502496 u .Out ().Printf ("revision\t %s" , resp .WriteControl .RequiredRevisionId )
503497 }
@@ -721,6 +715,7 @@ type DocsInsertCmd struct {
721715 Content string `arg:"" optional:"" name:"content" help:"Text to insert (or use --file / stdin)"`
722716 Index int64 `name:"index" help:"Character index to insert at (1 = beginning)" default:"1"`
723717 File string `name:"file" short:"f" help:"Read content from file (use - for stdin)"`
718+ TabID string `name:"tab-id" help:"Target a specific tab by ID (see docs list-tabs)"`
724719}
725720
726721func (c * DocsInsertCmd ) Run (ctx context.Context , flags * RootFlags ) error {
@@ -758,6 +753,7 @@ func (c *DocsInsertCmd) Run(ctx context.Context, flags *RootFlags) error {
758753 Text : content ,
759754 Location : & docs.Location {
760755 Index : c .Index ,
756+ TabId : c .TabID ,
761757 },
762758 },
763759 }},
@@ -767,23 +763,31 @@ func (c *DocsInsertCmd) Run(ctx context.Context, flags *RootFlags) error {
767763 }
768764
769765 if outfmt .IsJSON (ctx ) {
770- return outfmt . WriteJSON ( ctx , os . Stdout , map [string ]any {
766+ payload := map [string ]any {
771767 "documentId" : result .DocumentId ,
772768 "inserted" : len (content ),
773769 "atIndex" : c .Index ,
774- })
770+ }
771+ if c .TabID != "" {
772+ payload ["tabId" ] = c .TabID
773+ }
774+ return outfmt .WriteJSON (ctx , os .Stdout , payload )
775775 }
776776
777777 u .Out ().Printf ("documentId\t %s" , result .DocumentId )
778778 u .Out ().Printf ("inserted\t %d bytes" , len (content ))
779779 u .Out ().Printf ("atIndex\t %d" , c .Index )
780+ if c .TabID != "" {
781+ u .Out ().Printf ("tabId\t %s" , c .TabID )
782+ }
780783 return nil
781784}
782785
783786type DocsDeleteCmd struct {
784787 DocID string `arg:"" name:"docId" help:"Doc ID"`
785788 Start int64 `name:"start" required:"" help:"Start index (>= 1)"`
786789 End int64 `name:"end" required:"" help:"End index (> start)"`
790+ TabID string `name:"tab-id" help:"Target a specific tab by ID (see docs list-tabs)"`
787791}
788792
789793func (c * DocsDeleteCmd ) Run (ctx context.Context , flags * RootFlags ) error {
@@ -816,6 +820,7 @@ func (c *DocsDeleteCmd) Run(ctx context.Context, flags *RootFlags) error {
816820 Range : & docs.Range {
817821 StartIndex : c .Start ,
818822 EndIndex : c .End ,
823+ TabId : c .TabID ,
819824 },
820825 },
821826 }},
@@ -825,17 +830,24 @@ func (c *DocsDeleteCmd) Run(ctx context.Context, flags *RootFlags) error {
825830 }
826831
827832 if outfmt .IsJSON (ctx ) {
828- return outfmt . WriteJSON ( ctx , os . Stdout , map [string ]any {
833+ payload := map [string ]any {
829834 "documentId" : result .DocumentId ,
830835 "deleted" : c .End - c .Start ,
831836 "startIndex" : c .Start ,
832837 "endIndex" : c .End ,
833- })
838+ }
839+ if c .TabID != "" {
840+ payload ["tabId" ] = c .TabID
841+ }
842+ return outfmt .WriteJSON (ctx , os .Stdout , payload )
834843 }
835844
836845 u .Out ().Printf ("documentId\t %s" , result .DocumentId )
837846 u .Out ().Printf ("deleted\t %d characters" , c .End - c .Start )
838847 u .Out ().Printf ("range\t %d-%d" , c .Start , c .End )
848+ if c .TabID != "" {
849+ u .Out ().Printf ("tabId\t %s" , c .TabID )
850+ }
839851 return nil
840852}
841853
@@ -956,6 +968,7 @@ type DocsFindReplaceCmd struct {
956968 Find string `arg:"" name:"find" help:"Text to find"`
957969 ReplaceText string `arg:"" name:"replace" help:"Replacement text"`
958970 MatchCase bool `name:"match-case" help:"Case-sensitive matching"`
971+ TabID string `name:"tab-id" help:"Target a specific tab by ID (see docs list-tabs)"`
959972}
960973
961974func (c * DocsFindReplaceCmd ) Run (ctx context.Context , flags * RootFlags ) error {
@@ -978,16 +991,19 @@ func (c *DocsFindReplaceCmd) Run(ctx context.Context, flags *RootFlags) error {
978991 return err
979992 }
980993
994+ req := & docs.ReplaceAllTextRequest {
995+ ContainsText : & docs.SubstringMatchCriteria {
996+ Text : c .Find ,
997+ MatchCase : c .MatchCase ,
998+ },
999+ ReplaceText : c .ReplaceText ,
1000+ }
1001+ if c .TabID != "" {
1002+ req .TabsCriteria = & docs.TabsCriteria {TabIds : []string {c .TabID }}
1003+ }
1004+
9811005 result , err := svc .Documents .BatchUpdate (docID , & docs.BatchUpdateDocumentRequest {
982- Requests : []* docs.Request {{
983- ReplaceAllText : & docs.ReplaceAllTextRequest {
984- ContainsText : & docs.SubstringMatchCriteria {
985- Text : c .Find ,
986- MatchCase : c .MatchCase ,
987- },
988- ReplaceText : c .ReplaceText ,
989- },
990- }},
1006+ Requests : []* docs.Request {{ReplaceAllText : req }},
9911007 }).Context (ctx ).Do ()
9921008 if err != nil {
9931009 return fmt .Errorf ("find-replace: %w" , err )
@@ -999,18 +1015,25 @@ func (c *DocsFindReplaceCmd) Run(ctx context.Context, flags *RootFlags) error {
9991015 }
10001016
10011017 if outfmt .IsJSON (ctx ) {
1002- return outfmt . WriteJSON ( ctx , os . Stdout , map [string ]any {
1018+ payload := map [string ]any {
10031019 "documentId" : result .DocumentId ,
10041020 "find" : c .Find ,
10051021 "replace" : c .ReplaceText ,
10061022 "replacements" : replacements ,
1007- })
1023+ }
1024+ if c .TabID != "" {
1025+ payload ["tabId" ] = c .TabID
1026+ }
1027+ return outfmt .WriteJSON (ctx , os .Stdout , payload )
10081028 }
10091029
10101030 u .Out ().Printf ("documentId\t %s" , result .DocumentId )
10111031 u .Out ().Printf ("find\t %s" , c .Find )
10121032 u .Out ().Printf ("replace\t %s" , c .ReplaceText )
10131033 u .Out ().Printf ("replacements\t %d" , replacements )
1034+ if c .TabID != "" {
1035+ u .Out ().Printf ("tabId\t %s" , c .TabID )
1036+ }
10141037 return nil
10151038}
10161039
@@ -1280,6 +1303,61 @@ func docsDocumentEndIndex(doc *docs.Document) int64 {
12801303 return end
12811304}
12821305
1306+ func findTabByID (tabs []* docs.Tab , tabID string ) * docs.Tab {
1307+ tabID = strings .TrimSpace (tabID )
1308+ for _ , tab := range tabs {
1309+ if tab != nil && tab .TabProperties != nil && tab .TabProperties .TabId == tabID {
1310+ return tab
1311+ }
1312+ }
1313+ return nil
1314+ }
1315+
1316+ func docsTabEndIndex (tab * docs.Tab ) int64 {
1317+ if tab == nil || tab .DocumentTab == nil || tab .DocumentTab .Body == nil {
1318+ return 1
1319+ }
1320+ end := int64 (1 )
1321+ for _ , el := range tab .DocumentTab .Body .Content {
1322+ if el == nil {
1323+ continue
1324+ }
1325+ if el .EndIndex > end {
1326+ end = el .EndIndex
1327+ }
1328+ }
1329+ return end
1330+ }
1331+
1332+ func docsTargetEndIndex (ctx context.Context , svc * docs.Service , docID , tabID string ) (int64 , error ) {
1333+ getCall := svc .Documents .Get (docID ).Context (ctx )
1334+ if tabID != "" {
1335+ getCall = getCall .IncludeTabsContent (true )
1336+ } else {
1337+ getCall = getCall .Fields ("documentId,body/content(startIndex,endIndex)" )
1338+ }
1339+
1340+ doc , err := getCall .Do ()
1341+ if err != nil {
1342+ if isDocsNotFound (err ) {
1343+ return 0 , fmt .Errorf ("doc not found or not a Google Doc (id=%s)" , docID )
1344+ }
1345+ return 0 , err
1346+ }
1347+ if doc == nil {
1348+ return 0 , errors .New ("doc not found" )
1349+ }
1350+ if tabID == "" {
1351+ return docsDocumentEndIndex (doc ), nil
1352+ }
1353+
1354+ tab := findTabByID (flattenTabs (doc .Tabs ), tabID )
1355+ if tab == nil {
1356+ return 0 , fmt .Errorf ("tab not found: %s" , tabID )
1357+ }
1358+ return docsTabEndIndex (tab ), nil
1359+ }
1360+
12831361func docsAppendIndex (endIndex int64 ) int64 {
12841362 if endIndex > 1 {
12851363 return endIndex - 1
0 commit comments