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

fix: Calculate byte offset after applying each document change #304

Merged
merged 2 commits into from
Nov 18, 2020
Merged
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
194 changes: 137 additions & 57 deletions internal/filesystem/document_test.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/spf13/afero"
)

@@ -52,16 +51,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello world",
FileChange: &testChange{
text: "terraform",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
rng: &Range{
Start: Pos{
Line: 0,
Column: 6,
},
End: hcl.Pos{
Line: 1,
Column: 12,
Byte: 11,
End: Pos{
Line: 0,
Column: 11,
},
},
},
@@ -72,16 +69,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello world",
FileChange: &testChange{
text: "earth",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
rng: &Range{
Start: Pos{
Line: 0,
Column: 6,
},
End: hcl.Pos{
Line: 1,
Column: 12,
Byte: 11,
End: Pos{
Line: 0,
Column: 11,
},
},
},
@@ -92,16 +87,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello world",
FileChange: &testChange{
text: "HCL",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
rng: &Range{
Start: Pos{
Line: 0,
Column: 6,
},
End: hcl.Pos{
Line: 1,
Column: 12,
Byte: 11,
End: Pos{
Line: 0,
Column: 11,
},
},
},
@@ -112,16 +105,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello world",
FileChange: &testChange{
text: "abc ",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
rng: &Range{
Start: Pos{
Line: 0,
Column: 6,
},
End: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
End: Pos{
Line: 0,
Column: 6,
},
},
},
@@ -132,16 +123,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello world",
FileChange: &testChange{
text: "𐐀𐐀 ",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
rng: &Range{
Start: Pos{
Line: 0,
Column: 6,
},
End: hcl.Pos{
Line: 1,
Column: 7,
Byte: 6,
End: Pos{
Line: 0,
Column: 6,
},
},
},
@@ -152,16 +141,14 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
Content: "hello 𐐀𐐀 world",
FileChange: &testChange{
text: "aa𐐀",
rng: hcl.Range{
Start: hcl.Pos{
Line: 1,
Column: 9,
Byte: 10,
rng: &Range{
Start: Pos{
Line: 0,
Column: 8,
},
End: hcl.Pos{
Line: 1,
Column: 11,
Byte: 14,
End: Pos{
Line: 0,
Column: 10,
},
},
},
@@ -200,6 +187,99 @@ func TestFile_ApplyChange_partialUpdate(t *testing.T) {
}
}

func TestFile_ApplyChange_partialUpdateMultipleChanges(t *testing.T) {
testData := []struct {
Content string
FileChanges DocumentChanges
Expect string
}{
{
Content: `variable "service_host" {
default = "blah"
}
module "app" {
source = "./sub"
service_listeners = [
{
hosts = [var.service_host]
listener = ""
}
]
}
`,
FileChanges: DocumentChanges{
&testChange{
text: "\n",
rng: &Range{
Start: Pos{Line: 8, Column: 18},
End: Pos{Line: 8, Column: 18},
},
},
&testChange{
text: " ",
rng: &Range{
Start: Pos{Line: 9, Column: 0},
End: Pos{Line: 9, Column: 0},
},
},
&testChange{
text: " ",
rng: &Range{
Start: Pos{Line: 9, Column: 6},
End: Pos{Line: 9, Column: 6},
},
},
},
Expect: `variable "service_host" {
default = "blah"
}
module "app" {
source = "./sub"
service_listeners = [
{
hosts = [
var.service_host]
listener = ""
}
]
}
`,
},
}

for _, v := range testData {
fs := testDocumentStorage()
dh := &testHandler{uri: "file:///test.tf"}

err := fs.CreateAndOpenDocument(dh, []byte(v.Content))
if err != nil {
t.Fatal(err)
}

changes := v.FileChanges
err = fs.ChangeDocument(dh, changes)
if err != nil {
t.Fatal(err)
}

doc, err := fs.GetDocument(dh)
if err != nil {
t.Fatal(err)
}

text, err := doc.Text()
if err != nil {
t.Fatal(err)
}

if diff := cmp.Diff(v.Expect, string(text)); diff != "" {
t.Fatalf("content mismatch: %s", diff)
}
}
}

func testDocument(t *testing.T, dh DocumentHandler, meta *documentMetadata, b []byte) Document {
fs := afero.NewMemMapFs()
f, err := fs.Create(dh.FullPath())
@@ -220,13 +300,13 @@ func testDocument(t *testing.T, dh DocumentHandler, meta *documentMetadata, b []

type testChange struct {
text string
rng hcl.Range
rng *Range
}

func (fc *testChange) Text() string {
return fc.text
}

func (fc *testChange) Range() hcl.Range {
func (fc *testChange) Range() *Range {
return fc.rng
}
8 changes: 8 additions & 0 deletions internal/filesystem/errors.go
Original file line number Diff line number Diff line change
@@ -27,3 +27,11 @@ type UnknownDocumentErr struct {
func (e *UnknownDocumentErr) Error() string {
return fmt.Sprintf("unknown document: %s", e.DocumentHandler.URI())
}

type InvalidPosErr struct {
Pos Pos
}

func (e *InvalidPosErr) Error() string {
return fmt.Sprintf("invalid position: %s", e.Pos)
}
25 changes: 15 additions & 10 deletions internal/filesystem/filesystem.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"os"
"sync"

"github.com/hashicorp/terraform-ls/internal/source"
"github.com/spf13/afero"
)

@@ -107,19 +108,28 @@ func (fs *fsystem) ChangeDocument(dh VersionedDocumentHandler, changes DocumentC

func (fs *fsystem) applyDocumentChange(buf *bytes.Buffer, change DocumentChange) error {
// if the range is nil, we assume it is full content change
if rangeIsNil(change.Range()) {
if change.Range() == nil {
buf.Reset()
_, err := buf.WriteString(change.Text())
return err
}

// apply partial change
diff := diffLen(change)
lines := source.MakeSourceLines("", buf.Bytes())

startByte, err := ByteOffsetForPos(lines, change.Range().Start)
if err != nil {
return err
}
endByte, err := ByteOffsetForPos(lines, change.Range().End)
if err != nil {
return err
}

diff := endByte - startByte
if diff > 0 {
buf.Grow(diff)
}

startByte, endByte := change.Range().Start.Byte, change.Range().End.Byte
beforeChange := make([]byte, startByte, startByte)
copy(beforeChange, buf.Bytes())
afterBytes := buf.Bytes()[endByte:]
@@ -128,7 +138,7 @@ func (fs *fsystem) applyDocumentChange(buf *bytes.Buffer, change DocumentChange)

buf.Reset()

_, err := buf.Write(beforeChange)
_, err = buf.Write(beforeChange)
if err != nil {
return err
}
@@ -144,11 +154,6 @@ func (fs *fsystem) applyDocumentChange(buf *bytes.Buffer, change DocumentChange)
return nil
}

func diffLen(change DocumentChange) int {
rangeLen := change.Range().End.Byte - change.Range().Start.Byte
return len(change.Text()) - rangeLen
}

func (fs *fsystem) CloseAndRemoveDocument(dh DocumentHandler) error {
isOpen, err := fs.isDocumentOpen(dh)
if err != nil {
Loading