Skip to content

Commit

Permalink
Add SoftWrap to handle Lines and ShowLineNumbers behavior
Browse files Browse the repository at this point in the history
This commit introduces a `SoftWrap` option that adjusts the behavior
of the `Lines` option and `ShowLineNumbers` when used with the `Wrap` option.

- `Lines`:
Currently, when both `Lines` and `Wrap` options are used, the line count
is computed after wrapping occurs. This can lead to discrepancies where
wrapped lines count as multiple lines, causing the final output to exclude
some original input lines.
With the new `SoftWrap` option, wrapped lines will count as a single line,
ensuring that all lines specified in the `Lines` option are correctly included.

- `ShowLineNumbers`:
When `SoftWrap` is enabled, line numbers are not added to wrapped lines.
This preserves the original line numbering, ensuring consistency with the input file.
  • Loading branch information
Hazegard committed Dec 15, 2024
1 parent e505682 commit af350e3
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 2 deletions.
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
26 changes: 24 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,16 @@ 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)
}

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

config.LineHeight *= float64(scale)

softWrapOffset := 0
for i, line := range text {
if isAnsi {
line.SetText("")
Expand All @@ -330,9 +341,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)
}
})
}
}

0 comments on commit af350e3

Please sign in to comment.