Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions internal/app/reflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package app

import "strings"

// reflowBody joins indented continuation lines within list items so that
// goldmark does not render them as hard line breaks.
func reflowBody(body string) string {
lines := strings.Split(body, "\n")
out := make([]string, 0, len(lines))

prevBlank := false

for _, line := range lines {
blank := strings.TrimSpace(line) == ""
if len(out) > 0 && !prevBlank && !blank && strings.HasPrefix(line, " ") && !isListItem(line) {
out[len(out)-1] += " " + strings.TrimSpace(line)
} else {
out = append(out, line)
}

prevBlank = blank
}

return strings.Join(out, "\n")
}

func isListItem(s string) bool {
t := strings.TrimLeft(s, " \t")

return strings.HasPrefix(t, "- ") || strings.HasPrefix(t, "* ") || strings.HasPrefix(t, "+ ")
}
49 changes: 49 additions & 0 deletions internal/app/reflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package app

import "testing"

func TestReflowBody(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input string
want string
}{
{
name: "joins indented continuation lines",
input: "- item one with a long description that\n wraps onto the next line\n- item two\n",
want: "- item one with a long description that wraps onto the next line\n- item two\n",
},
{
name: "does not join across blank line",
input: "- item one\n \n indented after blank\n",
want: "- item one\n \n indented after blank\n",
},
{
name: "does not join across double newline",
input: "- item one\n\n indented after blank\n",
want: "- item one\n\n indented after blank\n",
},
{
name: "does not join paragraph soft breaks",
input: "A paragraph with\na manual break.\n",
want: "A paragraph with\na manual break.\n",
},
{
name: "multiple continuation lines",
input: "- The service's logic is mature\n and well-tested.\n Minimising changes reduces risk.\n- item two\n",
want: "- The service's logic is mature and well-tested. Minimising changes reduces risk.\n- item two\n",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

if got := reflowBody(tt.input); got != tt.want {
t.Errorf("got:\n%q\nwant:\n%q", got, tt.want)
}
})
}
}
2 changes: 1 addition & 1 deletion internal/app/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func Show(conf *config.Config, number int) error {
fmt.Sprintf("| Number | %04d |", found.Number),
fmt.Sprintf("| Date | %s |", found.Date),
fmt.Sprintf("| Status | %s |", found.Status),
}, "\n") + "\n\n" + string(found.Body)
}, "\n") + "\n\n" + reflowBody(string(found.Body))

renderer, err := glamour.NewTermRenderer(
glamour.WithEnvironmentConfig(),
Expand Down
Loading