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 non overlapping ranged annotations #7

Merged
merged 1 commit into from
Jun 27, 2024
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
42 changes: 27 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

# Annot

Annotate a string line with arrows leading to a description.
Annotate a string line leading to a description.

```
The quick brown fox jumps over the lazy dog
↑ ↑ ↑
│ └─ adjective └─ adjective
└─ adjective
The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.
↑ └──┬───┘ └───┬───┘ ↑
│ └─ adjective │ └─ comma
│ │
└─ article └─ facts, information, and skills acquired
through experience or education;
the theoretical or practical understanding
of a subject.
```

## Installation
Expand All @@ -30,21 +33,30 @@ import (
)

func main() {
fmt.Println("The quick brown fox jumps over the lazy dog")
fmt.Println("The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.")
fmt.Println(annot.String(
&annot.Annot{Col: 6, Lines: []string{"adjective"}},
&annot.Annot{Col: 12, Lines: []string{"adjective"}},
&annot.Annot{Col: 36, Lines: []string{"adjective"}},
&annot.Annot{Col: 1, Lines: []string{"article"}},
&annot.Annot{Col: 4, ColEnd: 11, Lines: []string{"adjective"}},
&annot.Annot{Col: 22, ColEnd: 30, Lines: []string{
"facts, information, and skills acquired",
"through experience or education;",
"the theoretical or practical understanding",
"of a subject.",
}},
&annot.Annot{Col: 48, Lines: []string{"comma"}},
))
}
```

Output:

```
The quick brown fox jumps over the lazy dog
↑ ↑ ↑
│ └─ adjective └─ adjective
└─ adjective
The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.
↑ └──┬───┘ └───┬───┘ ↑
│ └─ adjective │ └─ comma
│ │
└─ article └─ facts, information, and skills acquired
through experience or education;
the theoretical or practical understanding
of a subject.
```
73 changes: 57 additions & 16 deletions annot.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ type Annot struct {
// E.g. 0 draws an arrow to the first character in a line.
Col int

// ColEnd needs to be higher than Col. If ColEnd is set a
// range is annotated.
ColEnd int

// Lines is the text of the annotation represented in one or more lines.
Lines []string

pipeColIdx int

row int
lines []*line
pipeLeadingSpaces []int
Expand Down Expand Up @@ -109,7 +115,18 @@ func Write(w io.Writer, annots ...*Annot) error {
return a.Col - b.Col
})

for _, a := range annots {
for aIdx, a := range annots {
if a.ColEnd != 0 {
if a.Col >= a.ColEnd {
return newColExceedsColEndError(aIdx+1, a.Col, a.ColEnd)
}
a.pipeColIdx = (a.Col + a.ColEnd) / 2
} else {
a.pipeColIdx = a.Col
}
if aIdx > 0 && annots[aIdx-1].ColEnd != 0 && annots[aIdx-1].ColEnd >= a.Col {
return newOverlapError(annots[aIdx-1].ColEnd, aIdx, a.Col)
}
a.createLines()
}

Expand All @@ -127,13 +144,13 @@ func Write(w io.Writer, annots ...*Annot) error {
func (a *Annot) createLines() {
if len(a.Lines) == 0 {
a.lines = make([]*line, 1)
a.lines[0] = &line{}
a.lines[0] = &line{leadingSpaces: a.pipeColIdx}
return
}

a.lines = make([]*line, len(a.Lines))
for i := range a.Lines {
leadingSpaces := a.Col
leadingSpaces := a.pipeColIdx
if i > 0 {
leadingSpaces += 3
}
Expand Down Expand Up @@ -166,9 +183,9 @@ func setSpace(rowBefore int, a *Annot, rightAnnots []*Annot) {
closestA, s := closestAnnot(rowBefore, rightAnnots, 0)
switch s {
case above:
closestA.pipeLeadingSpaces[rowBefore] = closestA.Col + s.colPosShift() - a.Col - 1
closestA.pipeLeadingSpaces[rowBefore] = closestA.pipeColIdx + s.colPosShift() - a.pipeColIdx - 1
case lineOne, lineTwo, linesAfterSecond:
closestA.lines[rowBefore-closestA.row].leadingSpaces = closestA.Col + s.colPosShift() - a.Col - 1
closestA.lines[rowBefore-closestA.row].leadingSpaces = closestA.pipeColIdx + s.colPosShift() - a.pipeColIdx - 1
case noAnnot, trailingSpaceLines:
// Do nothing
}
Expand All @@ -195,11 +212,11 @@ func checkLineAndSetSpace(row, aLineIdx int, a *Annot, rightAnnots []*Annot) boo
// 3 for "└─ " or " " (indentation)
lineLength := 3 + a.lines[aLineIdx].length

remainingSpaces := closestA.Col + s.colPosShift() - a.Col - lineLength
remainingSpaces := closestA.pipeColIdx + s.colPosShift() - a.pipeColIdx - lineLength

if remainingSpaces-s.space() < 0 {
a.row++
a.pipeLeadingSpaces = append(a.pipeLeadingSpaces, a.Col)
a.pipeLeadingSpaces = append(a.pipeLeadingSpaces, a.pipeColIdx)
return false
}

Expand All @@ -213,7 +230,7 @@ func checkLineAndSetSpace(row, aLineIdx int, a *Annot, rightAnnots []*Annot) boo
if s2 == noAnnot {
return true
}
leadingSpaces2 := closestA2.Col + s2.colPosShift() - a.Col - lineLength
leadingSpaces2 := closestA2.pipeColIdx + s2.colPosShift() - a.pipeColIdx - lineLength
if s2 == above {
closestA2.pipeLeadingSpaces[rowPlusLineIdx] = leadingSpaces2
break
Expand Down Expand Up @@ -248,19 +265,15 @@ func closestAnnot(row int, rightAnnots []*Annot, trailingVerticalSpaceLinesCount
}

func write(writer io.Writer, annots []*Annot) error {
lastColPos := 0
rowCount := 0
for _, a := range annots {
rowCount = max(rowCount, a.row+len(a.lines))
}

b := &strings.Builder{}

for _, a := range annots {
b.WriteString(strings.Repeat(" ", a.Col-lastColPos))
b.WriteString("↑")
lastColPos = a.Col + 1
b.WriteString(arrowOrRangeString(annots))

// Also use this loop to calculate rowCount
rowCount = max(rowCount, a.row+len(a.lines))
}
b.WriteString("\n")
_, err := fmt.Fprint(writer, b.String())
if err != nil {
Expand Down Expand Up @@ -295,3 +308,31 @@ func write(writer io.Writer, annots []*Annot) error {

return nil
}

func arrowOrRangeString(annots []*Annot) string {
widthWritten := 0

b := &strings.Builder{}

for _, a := range annots {
if a.ColEnd == 0 {
b.WriteString(strings.Repeat(" ", a.pipeColIdx-widthWritten))
b.WriteString("↑")
widthWritten = a.pipeColIdx + 1
continue
}

b.WriteString(strings.Repeat(" ", a.Col-widthWritten))
if a.Col == a.pipeColIdx {
b.WriteString("├")
} else {
b.WriteString("└")
b.WriteString(strings.Repeat("─", a.pipeColIdx-a.Col-1))
b.WriteString("┬")
}
b.WriteString(strings.Repeat("─", a.ColEnd-a.pipeColIdx-1))
b.WriteString("┘")
widthWritten = a.ColEnd + 1
}
return b.String()
}
Loading