Skip to content

Commit cc86bfe

Browse files
clawsweeper[bot]Peter Steinberger
andauthored
feat(docs): add spacing mode to docs format (#886)
* feat(docs): add spacing mode to docs format * feat(docs): add spacing mode to docs format * feat(docs): add spacing mode to docs format * docs: restore spacing mode changelog --------- Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: Peter Steinberger <[email protected]>
1 parent e0007e3 commit cc86bfe

11 files changed

Lines changed: 74 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 0.31.2 - Unreleased
44

5+
- Docs: add `docs format --spacing-mode` for setting paragraph spacing collapse behavior alongside `--space-above` and `--space-below`. (#885) — thanks @odyssey4me.
6+
57
## 0.31.1 - 2026-06-26
68

79
- Calendar: add `calendar changed` for listing recently modified events, including cancellations, across one or more calendars. (#875) — thanks @sorenisanerd.

docs/commands/gog-docs-format.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ gog docs (doc) format <docId> [flags]
6868
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
6969
| `--space-above` | `*float64` | | Space above the paragraph in points |
7070
| `--space-below` | `*float64` | | Space below the paragraph in points |
71+
| `--spacing-mode` | `string` | | Paragraph spacing mode: NEVER_COLLAPSE or COLLAPSE_LISTS |
7172
| `--strikethrough`<br>`--strike` | `bool` | | Set strikethrough |
7273
| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) |
7374
| `--text-color` | `string` | | Text color as #RRGGBB or #RGB |

docs/commands/gog-docs-write.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ gog docs (doc) write <docId> [flags]
7575
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
7676
| `--space-above` | `*float64` | | Space above the paragraph in points |
7777
| `--space-below` | `*float64` | | Space below the paragraph in points |
78+
| `--spacing-mode` | `string` | | Paragraph spacing mode: NEVER_COLLAPSE or COLLAPSE_LISTS |
7879
| `--strikethrough`<br>`--strike` | `bool` | | Set strikethrough |
7980
| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) |
8081
| `--text` | `string` | | Text to write |

docs/docs-editing.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,13 @@ gog docs format <docId> --match "Action item" --no-bullets
7777
```
7878

7979
Paragraph layout controls use points and accept explicit zero values. Boolean
80-
keep controls have matching `--no-...` forms:
80+
keep controls have matching `--no-...` forms. `--spacing-mode` accepts the
81+
Docs API values `NEVER_COLLAPSE` and `COLLAPSE_LISTS` case-insensitively:
8182

8283
```bash
8384
gog docs format <docId> --match "Summary" --indent-start 24 --indent-first-line 12
8485
gog docs format <docId> --match "Summary" --space-above 6 --space-below 12 --keep-with-next
86+
gog docs format <docId> --match "Action item" --space-below 6 --spacing-mode NEVER_COLLAPSE
8587
gog docs format <docId> --match "Summary" --no-keep-with-next --keep-lines-together
8688
```
8789

internal/cmd/docs_format.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type DocsFormatFlags struct {
4444
NoStrike bool `name:"no-strikethrough" aliases:"no-strike" help:"Clear strikethrough"`
4545
Alignment string `name:"alignment" help:"Paragraph alignment: left, center, right, justify, start, end, justified"`
4646
LineSpacing float64 `name:"line-spacing" help:"Paragraph line spacing percentage, for example 100 or 150"`
47+
SpacingMode string `name:"spacing-mode" help:"Paragraph spacing mode: NEVER_COLLAPSE or COLLAPSE_LISTS"`
4748
HeadingLevel *int `name:"heading-level" help:"Set paragraph named style to HEADING_1..HEADING_6 (shortcut for --named-style=HEADING_N)"`
4849
NamedStyle string `name:"named-style" help:"Set paragraph named style: NORMAL_TEXT, TITLE, SUBTITLE, HEADING_1..HEADING_6"`
4950
Bullets bool `name:"bullets" help:"Create a bulleted list with the default disc preset"`
@@ -123,6 +124,7 @@ func (c *DocsFormatCmd) Run(ctx context.Context, flags *RootFlags) error {
123124
"no_strike": c.Format.NoStrike,
124125
"alignment": c.Format.Alignment,
125126
"line_spacing": c.Format.LineSpacing,
127+
"spacing_mode": c.Format.SpacingMode,
126128
"heading_level": c.Format.HeadingLevel,
127129
"named_style": c.Format.NamedStyle,
128130
"bullets": c.Format.Bullets,
@@ -399,7 +401,8 @@ func (f DocsFormatFlags) createsBullets() bool {
399401

400402
func (f DocsFormatFlags) hasParagraphStyle() bool {
401403
return strings.TrimSpace(f.Alignment) != "" || f.LineSpacing != 0 || f.HeadingLevel != nil ||
402-
strings.TrimSpace(f.NamedStyle) != "" || f.IndentStart != nil || f.IndentFirstLine != nil ||
404+
strings.TrimSpace(f.SpacingMode) != "" || strings.TrimSpace(f.NamedStyle) != "" ||
405+
f.IndentStart != nil || f.IndentFirstLine != nil ||
403406
f.IndentEnd != nil || f.SpaceAbove != nil || f.SpaceBelow != nil ||
404407
f.KeepWithNext != nil || f.KeepLinesTogether != nil
405408
}
@@ -429,6 +432,7 @@ func (f DocsFormatFlags) paragraphOnly() DocsFormatFlags {
429432
return DocsFormatFlags{
430433
Alignment: f.Alignment,
431434
LineSpacing: f.LineSpacing,
435+
SpacingMode: f.SpacingMode,
432436
HeadingLevel: f.HeadingLevel,
433437
NamedStyle: f.NamedStyle,
434438
Bullets: f.Bullets,
@@ -483,6 +487,7 @@ func (f DocsFormatFlags) options() docsformat.Options {
483487
ClearStrike: f.NoStrike,
484488
Alignment: f.Alignment,
485489
LineSpacing: f.LineSpacing,
490+
SpacingMode: f.SpacingMode,
486491
HeadingLevel: f.HeadingLevel,
487492
NamedStyle: f.NamedStyle,
488493
Bullets: f.Bullets,

internal/cmd/docs_format_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func TestDocsFormatFlagsBuildRequests(t *testing.T) {
2626
Italic: true,
2727
Alignment: "center",
2828
LineSpacing: 150,
29+
SpacingMode: "collapse_lists",
2930
}).buildRequests(3, 9, "t.second")
3031
if err != nil {
3132
t.Fatalf("buildRequests: %v", err)
@@ -65,7 +66,8 @@ func TestDocsFormatFlagsBuildRequests(t *testing.T) {
6566
t.Fatalf("missing paragraph request: %#v", reqs[1])
6667
return
6768
}
68-
if paraReq.ParagraphStyle.Alignment != "CENTER" || paraReq.ParagraphStyle.LineSpacing != 150 {
69+
if paraReq.ParagraphStyle.Alignment != "CENTER" || paraReq.ParagraphStyle.LineSpacing != 150 ||
70+
paraReq.ParagraphStyle.SpacingMode != docsformat.SpacingModeCollapseLists {
6971
t.Fatalf("unexpected paragraph style: %#v", paraReq.ParagraphStyle)
7072
}
7173
if got := paraReq.Range; got.TabId != "t.second" {
@@ -118,6 +120,9 @@ func TestDocsFormatFlagsValidation(t *testing.T) {
118120
if _, err := (DocsFormatFlags{Alignment: "sideways"}).buildRequests(1, 2, ""); err == nil {
119121
t.Fatalf("expected invalid alignment error")
120122
}
123+
if _, err := (DocsFormatFlags{SpacingMode: "sometimes"}).buildRequests(1, 2, ""); err == nil {
124+
t.Fatalf("expected invalid spacing-mode error")
125+
}
121126
if _, err := (DocsFormatFlags{Code: true, FontFamily: "Arial"}).buildRequests(1, 2, ""); err == nil {
122127
t.Fatalf("expected conflicting code/font-family flags error")
123128
}
@@ -330,6 +335,7 @@ func TestDocsFormatCmdParagraphControls(t *testing.T) {
330335
err := runKong(t, &DocsFormatCmd{}, []string{
331336
"doc1", "--match", "Alpha", "--match-all", "--bold", "--ordered",
332337
"--indent-start", "24", "--space-below", "6",
338+
"--spacing-mode", "never_collapse",
333339
"--keep-with-next", "--no-keep-lines-together",
334340
}, ctx, flags)
335341
if err != nil {
@@ -351,6 +357,7 @@ func TestDocsFormatCmdParagraphControls(t *testing.T) {
351357
t.Fatalf("grouped paragraph requests: %#v", batchRequests[0])
352358
}
353359
if paragraph.ParagraphStyle.IndentStart.Magnitude != 24 || paragraph.ParagraphStyle.SpaceBelow.Magnitude != 6 ||
360+
paragraph.ParagraphStyle.SpacingMode != docsformat.SpacingModeNeverCollapse ||
354361
!paragraph.ParagraphStyle.KeepWithNext || paragraph.ParagraphStyle.KeepLinesTogether {
355362
t.Fatalf("unexpected paragraph style: %#v", paragraph.ParagraphStyle)
356363
}

internal/cmd/dryrun_e2e_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func TestDryRunE2E_CommandsSkipAuthAPIAndFileWrites(t *testing.T) {
8989
},
9090
{
9191
name: "docs format",
92-
args: []string{"docs", "format", "doc123", "--bold"},
92+
args: []string{"docs", "format", "doc123", "--spacing-mode", "collapse_lists"},
9393
op: "docs.format",
9494
},
9595
{
@@ -894,10 +894,18 @@ func TestDryRunE2E_ValidatesFormsAndSheetsLocalInputs(t *testing.T) {
894894
name: "docs write validates format flags before dry-run",
895895
args: []string{"docs", "write", "doc123", "--text", "hello", "--font-size", "-1"},
896896
},
897+
{
898+
name: "docs write validates spacing mode before dry-run",
899+
args: []string{"docs", "write", "doc123", "--text", "hello", "--spacing-mode", "sometimes"},
900+
},
897901
{
898902
name: "docs format validates colors before dry-run",
899903
args: []string{"docs", "format", "doc123", "--text-color", "nope"},
900904
},
905+
{
906+
name: "docs format validates spacing mode before dry-run",
907+
args: []string{"docs", "format", "doc123", "--spacing-mode", "sometimes"},
908+
},
901909
}
902910

903911
for _, tc := range cases {

internal/docsedit/planner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ func createsParagraphBullets(options docsformat.Options) bool {
7171

7272
func hasParagraphStyle(options docsformat.Options) bool {
7373
return strings.TrimSpace(options.Alignment) != "" || options.LineSpacing != 0 || options.HeadingLevel != nil ||
74-
strings.TrimSpace(options.NamedStyle) != "" || options.IndentStart != nil || options.IndentFirstLine != nil ||
74+
strings.TrimSpace(options.SpacingMode) != "" || strings.TrimSpace(options.NamedStyle) != "" ||
75+
options.IndentStart != nil || options.IndentFirstLine != nil ||
7576
options.IndentEnd != nil || options.SpaceAbove != nil || options.SpaceBelow != nil ||
7677
options.KeepWithNext != nil || options.KeepLinesTogether != nil
7778
}

internal/docsedit/planner_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,13 @@ func TestBuildWriteRequestsAppendSkipsDelete(t *testing.T) {
6464
}
6565

6666
func TestBuildWriteRequestsBulletsAdjustParagraphRangeAfterTabs(t *testing.T) {
67-
indent := 54.0
68-
6967
requests, err := BuildWriteRequests(WriteOptions{
7068
EndIndex: 1,
7169
InsertIndex: 1,
7270
Text: "\tFirst\n\t\tSecond\n",
7371
Format: docsformat.Options{
7472
Ordered: true,
75-
IndentStart: &indent,
73+
SpacingMode: docsformat.SpacingModeCollapseLists,
7674
},
7775
})
7876
if err != nil {
@@ -86,6 +84,10 @@ func TestBuildWriteRequestsBulletsAdjustParagraphRangeAfterTabs(t *testing.T) {
8684
if got := requests[2].UpdateParagraphStyle.Range; got.StartIndex != 1 || got.EndIndex != 14 {
8785
t.Fatalf("post-bullet paragraph range = %#v, want 1..14", got)
8886
}
87+
88+
if got := requests[2].UpdateParagraphStyle.ParagraphStyle.SpacingMode; got != docsformat.SpacingModeCollapseLists {
89+
t.Fatalf("spacing mode = %q, want %q", got, docsformat.SpacingModeCollapseLists)
90+
}
8991
}
9092

9193
func TestBuildWriteRequestsEmptyBodySkipsDelete(t *testing.T) {

internal/docsformat/format.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const (
2727
namedStyleHeading6 = "HEADING_6"
2828
)
2929

30+
const (
31+
SpacingModeNeverCollapse = "NEVER_COLLAPSE"
32+
SpacingModeCollapseLists = "COLLAPSE_LISTS"
33+
)
34+
3035
type ValidationError string
3136

3237
func (e ValidationError) Error() string {
@@ -52,6 +57,7 @@ type Options struct {
5257
ClearStrike bool
5358
Alignment string
5459
LineSpacing float64
60+
SpacingMode string
5561
HeadingLevel *int
5662
NamedStyle string
5763
Bullets bool
@@ -85,6 +91,7 @@ func (o Options) Any() bool {
8591
o.Strikethrough || o.ClearStrike ||
8692
strings.TrimSpace(o.Alignment) != "" ||
8793
o.LineSpacing != 0 ||
94+
strings.TrimSpace(o.SpacingMode) != "" ||
8895
o.HeadingLevel != nil ||
8996
strings.TrimSpace(o.NamedStyle) != "" ||
9097
o.Bullets || o.Ordered || strings.TrimSpace(o.BulletPreset) != "" || o.ClearBullets ||
@@ -292,6 +299,16 @@ func buildParagraphStyleRequest(options Options, start, end int64, tabID string)
292299
fields = append(fields, "lineSpacing")
293300
}
294301

302+
if spacingMode := strings.TrimSpace(options.SpacingMode); spacingMode != "" {
303+
resolved, err := formatSpacingMode(spacingMode)
304+
if err != nil {
305+
return nil, false, err
306+
}
307+
style.SpacingMode = resolved
308+
309+
fields = append(fields, "spacingMode")
310+
}
311+
295312
namedStyle, err := formatNamedStyle(options.HeadingLevel, options.NamedStyle)
296313
if err != nil {
297314
return nil, false, err
@@ -442,6 +459,17 @@ func validBulletPreset(value string) bool {
442459
}
443460
}
444461

462+
func formatSpacingMode(value string) (string, error) {
463+
switch strings.ToUpper(strings.TrimSpace(value)) {
464+
case SpacingModeNeverCollapse:
465+
return SpacingModeNeverCollapse, nil
466+
case SpacingModeCollapseLists:
467+
return SpacingModeCollapseLists, nil
468+
default:
469+
return "", ValidationError("--spacing-mode must be NEVER_COLLAPSE or COLLAPSE_LISTS")
470+
}
471+
}
472+
445473
func formatLink(value string) (*docs.Link, error) {
446474
link := strings.TrimSpace(value)
447475
if link == "" {

0 commit comments

Comments
 (0)