Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a soft wrap option to handle Lines and ShowLineNumbers behavior #163

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type Config struct {
Language string `json:"language,omitempty" help:"Language of code file." short:"l" group:"Settings" placeholder:"go"`
Theme string `json:"theme" help:"Theme to use for syntax highlighting." short:"t" group:"Settings" placeholder:"charm"`
Wrap int `json:"wrap" help:"Wrap lines at a specific width." short:"w" group:"Settings" default:"0" placeholder:"80"`
SoftWrap bool `json:"soft-wrap" help:"Do not count wrapped lines (Lines & LineHeight)." group:"Settings"`

Output string `json:"output,omitempty" help:"Output location for {{.svg}}, {{.png}}, or {{.webp}}." short:"o" group:"Settings" default:"" placeholder:"freeze.svg"`
Execute string `json:"-" help:"Capture output of command execution." short:"x" group:"Settings" default:""`
Expand Down
33 changes: 31 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func main() {
strippedInput = cut(strippedInput, config.Lines)

// wrap to character limit.
if config.Wrap > 0 {
if config.Wrap > 0 && !config.SoftWrap {
strippedInput = wordwrap.String(strippedInput, config.Wrap)
input = wordwrap.String(input, config.Wrap)
}
Expand All @@ -195,6 +195,23 @@ func main() {
}
}

isRealLine := []bool{}
strippedIsRealLine := []bool{}
// wrap to character limit.
if config.Wrap > 0 && config.SoftWrap {
isRealLine = SoftWrap(input, config.Wrap)
strippedIsRealLine = SoftWrap(strippedInput, config.Wrap)
strippedInput = wordwrap.String(strippedInput, config.Wrap)
input = wordwrap.String(input, config.Wrap)
}

if config.Wrap <= 0 {
// If Wrap is disabled, but SoftWrap enabled, we force disable SoftWrap as it does not make sense
// to keep this option enabled.
printError("Wrap option disabled, but SoftWrap option enabled", fmt.Errorf("wrap option disabled"))
config.SoftWrap = false
}

s, ok := styles.Registry[strings.ToLower(config.Theme)]
if s == nil || !ok {
s = charmStyle
Expand Down Expand Up @@ -320,6 +337,7 @@ func main() {

config.LineHeight *= float64(scale)

softWrapOffset := 0
for i, line := range text {
if isAnsi {
line.SetText("")
Expand All @@ -330,9 +348,20 @@ func main() {
ln := etree.NewElement("tspan")
ln.CreateAttr("xml:space", "preserve")
ln.CreateAttr("fill", s.Get(chroma.LineNumbers).Colour.String())
ln.SetText(fmt.Sprintf("%3d ", i+1+offsetLine))
if config.SoftWrap {
if (isAnsi && strippedIsRealLine[i]) || (!isAnsi && isRealLine[i]) {
ln.SetText(fmt.Sprintf("%3d ", i+1+offsetLine-softWrapOffset))
} else {
ln.SetText(" ")
}
} else {
ln.SetText(fmt.Sprintf("%3d ", i+1+offsetLine))
}
line.InsertChildAt(0, ln)
}
if config.SoftWrap && !((isAnsi && strippedIsRealLine[i]) || (!isAnsi && isRealLine[i])) {
softWrapOffset++
}
x := float64(config.Padding[left] + config.Margin[left])
y := (float64(i+1))*(config.Font.Size*config.LineHeight) + float64(config.Padding[top]) + float64(config.Margin[top])

Expand Down
24 changes: 24 additions & 0 deletions soft_wrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"github.com/muesli/reflow/wordwrap"
"strings"
)

func SoftWrap(input string, wrapLength int) []bool {
var wrap []bool
for _, line := range strings.Split(input, "\n") {
wrappedLine := wordwrap.String(line, wrapLength)

for i := range strings.Split(wrappedLine, "\n") {
if i == 0 {
// We want line number on the original line
wrap = append(wrap, true)
} else {
// for wrapped line, we do not want line number
wrap = append(wrap, false)
}
}
}
return wrap
}
62 changes: 62 additions & 0 deletions soft_wrap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"reflect"
"testing"
)

// Mock the dependency if needed, assuming wordwrap.String works correctly.
func TestSoftWrap(t *testing.T) {
tests := []struct {
name string
input string
wrapLength int
expected []bool
}{
{
name: "Single short line, no wrapping",
input: "Hello",
wrapLength: 10,
expected: []bool{true},
},
{
name: "Single long line, wrapping",
input: "Hello World, this is a long line",
wrapLength: 10,
expected: []bool{true, false, false, false},
},
{
name: "Multiple lines, some wrapped",
input: "Short\nThis is a long line",
wrapLength: 10,
expected: []bool{true, true, false},
},
{
name: "Multiple lines, multiple wraps",
input: "This is an long line\nThis is an other long line\nThis is the last long line\nShort line",
wrapLength: 10,
expected: []bool{true, false, true, false, false, true, false, false, true},
},
{
name: "Empty input",
input: "",
wrapLength: 10,
expected: []bool{true},
},
{
name: "Lines with spaces only",
input: " \n ",
wrapLength: 5,
expected: []bool{true, true},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SoftWrap(tt.input, tt.wrapLength)
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("SoftWrap() = %v, expected %v", got, tt.expected)
}
})
}
}