Comments (1)
I ported the node one over and added a basic benchmark.
The Node.js port was slower, and resulted in more allocations.
func BenchmarkDocumentContents(b *testing.B) {
start := `package testcall
templ personTemplate(p person) {
<div>
<h1>{ p.name }</h1>
</div>
}
`
operations := func(d *Document) {
d.Apply(&lsp.Range{
Start: lsp.Position{
Line: 4,
Character: 21,
},
End: lsp.Position{
Line: 4,
Character: 21,
},
}, "\n\t\t")
}
expected := `package testcall
templ personTemplate(p person) {
<div>
<h1>{ p.name }</h1>
</div>
}
`
log := zap.NewNop()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
d := NewDocument(log, start)
operations(d)
if d.String() != expected {
b.Fatalf("comparison failed: %v", d.String())
}
}
}
1 146 /Users/adrian/github.com/a-h/templ/cmd/templ/lspcmd/proxy % go test -bench .
goos: darwin
goarch: arm64
pkg: github.com/a-h/templ/cmd/templ/lspcmd/proxy
BenchmarkDocumentNodeContents-10 2208548 546.1 ns/op 440 B/op 15 allocs/op
BenchmarkDocumentContents-10 3266124 364.3 ns/op 656 B/op 7 allocs/op
PASS
ok github.com/a-h/templ/cmd/templ/lspcmd/proxy 3.532s
The Node.js port was:
package proxy
import (
lsp "github.com/a-h/protocol"
)
type FullTextDocument struct {
version int
content string
lineOffsets []uint32
}
func NewFullTextDocument(version int, content string) (this *FullTextDocument) {
return &FullTextDocument{
version: version,
content: content,
lineOffsets: nil,
}
}
func (this *FullTextDocument) Apply(r *lsp.Range, text string) {
this.update([]lsp.TextDocumentContentChangeEvent{
{
Range: r,
Text: text,
},
}, this.version)
}
func (this *FullTextDocument) String() string {
return this.content
}
func (this *FullTextDocument) normalizeRange(r *lsp.Range) {
if r == nil {
return
}
idxs := computeLineOffsets(this.String(), true, 0)
var lens []uint32
for i := 0; i < len(idxs)-1; i++ {
lens = append(lens, idxs[i+1]-idxs[i])
}
if len(lens) == 0 {
lens = append(lens, uint32(len(this.String())))
}
if r.Start.Line >= uint32(len(lens)) {
r.Start.Line = uint32(len(lens) - 1)
r.Start.Character = uint32(lens[r.Start.Line])
}
if r.Start.Character > uint32(lens[r.Start.Line]) {
r.Start.Character = uint32(lens[r.Start.Line])
}
if r.End.Line >= uint32(len(lens)) {
r.End.Line = uint32(len(lens) - 1)
r.End.Character = uint32(lens[r.End.Line])
}
if r.End.Character > uint32(lens[r.End.Line]) {
r.End.Character = uint32(lens[r.End.Line])
}
}
func (this *FullTextDocument) update(changes []lsp.TextDocumentContentChangeEvent, version int) {
for _, change := range changes {
if this.isIncremental(change) {
// makes sure start is before end
r := getWellformedRange(*change.Range)
// Normalize.
this.normalizeRange(&r)
// update content
startOffset := this.offsetAt(r.Start)
endOffset := this.offsetAt(r.End)
this.content = this.content[0:startOffset] + change.Text + this.content[endOffset:len(this.content)]
// update the offsets
startLine := max(r.Start.Line, 0)
endLine := max(r.End.Line, 0)
lineOffsets := this.lineOffsets
addedLineOffsets := computeLineOffsets(change.Text, false, startOffset)
if endLine-startLine == uint32(len(addedLineOffsets)) {
length := len(addedLineOffsets)
for i := 0; i < length; i++ {
lineOffsets[uint32(i)+startLine+1] = addedLineOffsets[i]
}
} else {
lineOffsets = append(lineOffsets[0:startLine+1], append(addedLineOffsets, lineOffsets[endLine+1:]...)...)
this.lineOffsets = lineOffsets
}
diff := len(change.Text) - int(endOffset-startOffset)
if diff != 0 {
length := len(lineOffsets)
for i := int(startLine) + 1 + len(addedLineOffsets); i < length; i++ {
lineOffsets[i] += uint32(diff)
}
}
} else if this.isFull(change) {
this.content = change.Text
this.lineOffsets = nil
} else {
//TODO: Fix panic.
panic("Unknown change event received")
}
}
this.version = version
}
func (this *FullTextDocument) getLineOffsets() []uint32 {
if this.lineOffsets == nil {
this.lineOffsets = computeLineOffsets(this.content, true, 0)
}
return this.lineOffsets
}
func (this *FullTextDocument) offsetAt(position lsp.Position) uint32 {
lineOffsets := this.getLineOffsets()
if position.Line >= uint32(len(lineOffsets)) {
return uint32(len(this.content))
} else if position.Line < 0 {
return 0
}
lineOffset := lineOffsets[position.Line]
nextLineOffset := uint32(len(this.content))
if position.Line+1 < uint32(len(lineOffsets)) {
nextLineOffset = lineOffsets[position.Line+1]
}
return max(min(lineOffset+position.Character, nextLineOffset), lineOffset)
}
func min[T int | uint32](a, b T) T {
if a < b {
return a
}
return b
}
func max[T int | uint32](a, b T) T {
if a > b {
return a
}
return b
}
func (this *FullTextDocument) isIncremental(event lsp.TextDocumentContentChangeEvent) bool {
return event.Range != nil
}
func (this *FullTextDocument) isFull(event lsp.TextDocumentContentChangeEvent) bool {
return event.Range == nil
}
func computeLineOffsets(text string, isAtLineStart bool, textOffset uint32) (result []uint32) {
if isAtLineStart {
result = append(result, textOffset)
}
var i int
var ch rune
for i, ch = range text {
if ch == '\r' || ch == '\n' {
if ch == '\r' && i+1 < len(text) && text[i+1] == '\n' {
i++
}
result = append(result, textOffset+uint32(i)+1)
}
}
if i != len(text) {
result = append(result, uint32(len(text)))
}
return result
}
func getWellformedRange(r lsp.Range) lsp.Range {
start, end := r.Start, r.End
if start.Line > end.Line || (start.Line == end.Line && start.Character > end.Character) {
return lsp.Range{Start: end, End: start}
}
return r
}
I'll stick with my version.
from templ.
Related Issues (20)
- Proposal: Introduce JsGenericVar for Event and Element Access in element event handlers HOT 5
- `templ generate` takes a long time for unformatted files HOT 6
- refactor: update `templ generate` to support pwd being a symlink HOT 3
- Templ formatting breaks imports at v0.2.747 HOT 7
- Composable templates do not render properly in tests HOT 1
- App freeze using air proxy HOT 1
- templ-vscode "Request textDocument/codeAction failed" HOT 2
- Streaming Not Working
- [Proposal] Improve embedding components HOT 2
- proposal: Go comments within HTML tags HOT 1
- Raw go errors if block ends with a comment
- LSP crashes after being started by kakoune-lsp HOT 4
- How to prevent cache HOT 1
- .templ file with only script elements leads to unused import HOT 1
- Anyway to instantiate a templ once per rendering context? HOT 4
- performance: templ parse takes a long time and uses high CPU when unclosed void elements are used HOT 8
- Little typo in streaming documentation
- bug: `templ generate --watch --proxy` triggers 2 browser reload events HOT 1
- bug: ambiguous child/string expression grammar HOT 4
- bug: go to definition causes error in Neovim v0.10.x HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from templ.