learnlytics-go/templ/parser/v2/sourcemap.go

135 lines
3.8 KiB
Go
Raw Normal View History

2025-03-20 12:35:13 +01:00
package parser
import (
"strings"
"unicode/utf8"
)
// NewSourceMap creates a new lookup to map templ source code to items in the
// parsed template.
func NewSourceMap() *SourceMap {
return &SourceMap{
SourceLinesToTarget: make(map[uint32]map[uint32]Position),
TargetLinesToSource: make(map[uint32]map[uint32]Position),
SourceSymbolRangeToTarget: make(map[uint32]map[uint32]Range),
TargetSymbolRangeToSource: make(map[uint32]map[uint32]Range),
}
}
type SourceMap struct {
Expressions []string
SourceLinesToTarget map[uint32]map[uint32]Position
TargetLinesToSource map[uint32]map[uint32]Position
SourceSymbolRangeToTarget map[uint32]map[uint32]Range
TargetSymbolRangeToSource map[uint32]map[uint32]Range
}
func (sm *SourceMap) AddSymbolRange(src Range, tgt Range) {
sm.SourceSymbolRangeToTarget[src.From.Line] = make(map[uint32]Range)
sm.SourceSymbolRangeToTarget[src.From.Line][src.From.Col] = tgt
sm.TargetSymbolRangeToSource[tgt.From.Line] = make(map[uint32]Range)
sm.TargetSymbolRangeToSource[tgt.From.Line][tgt.From.Col] = src
}
func (sm *SourceMap) SymbolTargetRangeFromSource(line, col uint32) (tgt Range, ok bool) {
lm, ok := sm.SourceSymbolRangeToTarget[line]
if !ok {
return
}
tgt, ok = lm[col]
return
}
func (sm *SourceMap) SymbolSourceRangeFromTarget(line, col uint32) (src Range, ok bool) {
lm, ok := sm.TargetSymbolRangeToSource[line]
if !ok {
return
}
src, ok = lm[col]
return
}
// Add an item to the lookup.
func (sm *SourceMap) Add(src Expression, tgt Range) (updatedFrom Position) {
sm.Expressions = append(sm.Expressions, src.Value)
srcIndex := src.Range.From.Index
tgtIndex := tgt.From.Index
lines := strings.Split(src.Value, "\n")
for lineIndex, line := range lines {
srcLine := src.Range.From.Line + uint32(lineIndex)
tgtLine := tgt.From.Line + uint32(lineIndex)
var srcCol, tgtCol uint32
if lineIndex == 0 {
// First line can have an offset.
srcCol += src.Range.From.Col
tgtCol += tgt.From.Col
}
// Process the cols.
for _, r := range line {
if _, ok := sm.SourceLinesToTarget[srcLine]; !ok {
sm.SourceLinesToTarget[srcLine] = make(map[uint32]Position)
}
sm.SourceLinesToTarget[srcLine][srcCol] = NewPosition(tgtIndex, tgtLine, tgtCol)
if _, ok := sm.TargetLinesToSource[tgtLine]; !ok {
sm.TargetLinesToSource[tgtLine] = make(map[uint32]Position)
}
sm.TargetLinesToSource[tgtLine][tgtCol] = NewPosition(srcIndex, srcLine, srcCol)
// Ignore invalid runes.
rlen := utf8.RuneLen(r)
if rlen < 0 {
rlen = 1
}
srcCol += uint32(rlen)
tgtCol += uint32(rlen)
srcIndex += int64(rlen)
tgtIndex += int64(rlen)
}
// LSPs include the newline char as a col.
if _, ok := sm.SourceLinesToTarget[srcLine]; !ok {
sm.SourceLinesToTarget[srcLine] = make(map[uint32]Position)
}
sm.SourceLinesToTarget[srcLine][srcCol] = NewPosition(tgtIndex, tgtLine, tgtCol)
if _, ok := sm.TargetLinesToSource[tgtLine]; !ok {
sm.TargetLinesToSource[tgtLine] = make(map[uint32]Position)
}
sm.TargetLinesToSource[tgtLine][tgtCol] = NewPosition(srcIndex, srcLine, srcCol)
srcIndex++
tgtIndex++
}
return src.Range.From
}
// TargetPositionFromSource looks up the target position using the source position.
func (sm *SourceMap) TargetPositionFromSource(line, col uint32) (tgt Position, ok bool) {
lm, ok := sm.SourceLinesToTarget[line]
if !ok {
return
}
tgt, ok = lm[col]
return
}
// SourcePositionFromTarget looks the source position using the target position.
// If a source exists on the line but not the col, the function will search backwards.
func (sm *SourceMap) SourcePositionFromTarget(line, col uint32) (src Position, ok bool) {
lm, ok := sm.TargetLinesToSource[line]
if !ok {
return
}
for {
src, ok = lm[col]
if ok || col == 0 {
return
}
col--
}
}