]> git.lizzy.rs Git - micro.git/commitdiff
Add buffer test and benchmark suite (and tool to generate it)
authorPhilipp Emanuel Weidmann <pew@worldwidemann.com>
Sat, 22 Feb 2020 03:21:38 +0000 (08:51 +0530)
committerPhilipp Emanuel Weidmann <pew@worldwidemann.com>
Sat, 22 Feb 2020 03:21:38 +0000 (08:51 +0530)
.gitignore
Makefile
go.mod
go.sum
internal/buffer/buffer_generated_test.go [new file with mode: 0644]
internal/buffer/buffer_test.go [new file with mode: 0644]
tools/testgen.go [new file with mode: 0644]

index 32302828fc3de4e73a71fdfb9653935a780a7a38..51f9a223801016704e26d64074e8a16ead53b219 100644 (file)
@@ -15,4 +15,5 @@ tools/build-version
 tools/build-date
 tools/info-plist
 tools/bindata
+tools/vscode-tests/
 *.hdr
index 4d536210d3c145f11929355ce74ac9945a0317f3..217963c23d715d596b8636fa78c593e9ac11c87c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \
        GOARCH=$(shell go env GOHOSTARCH))
 GOBIN ?= $(shell go env GOPATH)/bin
 GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF
+VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/'
 
 # Builds micro after checking dependencies but without updating the runtime
 build:
@@ -50,8 +51,20 @@ runtime:
        mv runtime.go internal/config
        gofmt -w internal/config/runtime.go
 
+testgen:
+       mkdir -p tools/vscode-tests
+       cd tools/vscode-tests && \
+       curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts
+       tsc tools/vscode-tests/*.ts > /dev/null; true
+       go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go
+       mv buffer_generated_test.go internal/buffer
+       gofmt -w internal/buffer/buffer_generated_test.go
+
 test:
        go test ./internal/...
 
+bench:
+       go test -bench=. ./internal/...
+
 clean:
        rm -f micro
diff --git a/go.mod b/go.mod
index 92c7c1967d43ae9b101e08110a45bad8922cf419..40d6b0754ff338811663f325270ddf7fc55531d9 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
        github.com/mattn/go-isatty v0.0.11
        github.com/mattn/go-runewidth v0.0.7
        github.com/mitchellh/go-homedir v1.1.0
+       github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
        github.com/sergi/go-diff v1.1.0
        github.com/stretchr/testify v1.4.0
        github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb
@@ -19,6 +20,7 @@ require (
        github.com/zyedidia/tcell v1.4.4
        github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
        golang.org/x/text v0.3.2
+       gopkg.in/sourcemap.v1 v1.0.5 // indirect
        gopkg.in/yaml.v2 v2.2.7
        layeh.com/gopher-luar v1.0.7
 )
diff --git a/go.sum b/go.sum
index 570a129993ed3517d1edcb65d33eff5cc39bbec4..3a0bea3936ccbe5fee15be32c26189dea8f64151 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -29,6 +29,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
+github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
@@ -67,6 +69,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
+gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
diff --git a/internal/buffer/buffer_generated_test.go b/internal/buffer/buffer_generated_test.go
new file mode 100644 (file)
index 0000000..3302fd3
--- /dev/null
@@ -0,0 +1,1889 @@
+// This file is generated from VSCode model tests by the testgen tool.
+// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!
+
+package buffer
+
+import "testing"
+
+func TestAuto1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "ioe",
+                       "",
+                       "yjct",
+                       "",
+                       "",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "b",
+                                       "r",
+                                       "fq",
+                               },
+                       },
+                       operation{
+                               start: Loc{3, 0},
+                               end:   Loc{0, 1},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "ib",
+                       "r",
+                       "fqoe",
+                       "",
+                       "yjct",
+                       "",
+                       "",
+               },
+       )
+}
+
+func TestAuto2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "f",
+                       "littnhskrq",
+                       "utxvsizqnk",
+                       "lslqz",
+                       "jxn",
+                       "gmm",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                                       "o",
+                               },
+                       },
+                       operation{
+                               start: Loc{3, 1},
+                               end:   Loc{3, 1},
+                               text: []string{
+                                       "zaq",
+                                       "avb",
+                               },
+                       },
+                       operation{
+                               start: Loc{4, 1},
+                               end:   Loc{1, 5},
+                               text: []string{
+                                       "jlr",
+                                       "zl",
+                                       "j",
+                               },
+                       },
+               },
+               []string{
+                       "f",
+                       "o",
+                       "litzaq",
+                       "avbtjlr",
+                       "zl",
+                       "jmm",
+               },
+       )
+}
+
+func TestAuto3(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "ofw",
+                       "qsxmziuvzw",
+                       "rp",
+                       "qsnymek",
+                       "elth",
+                       "wmgzbwudxz",
+                       "iwsdkndh",
+                       "bujlbwb",
+                       "asuouxfv",
+                       "xuccnb",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 3},
+                               end:   Loc{2, 3},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "ofw",
+                       "qsxmziuvzw",
+                       "rp",
+                       "qsnymek",
+                       "elth",
+                       "wmgzbwudxz",
+                       "iwsdkndh",
+                       "bujlbwb",
+                       "asuouxfv",
+                       "xuccnb",
+               },
+       )
+}
+
+func TestAuto4(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "fefymj",
+                       "qum",
+                       "vmiwxxaiqq",
+                       "dz",
+                       "lnqdgorosf",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{4, 0},
+                               text: []string{
+                                       "hp",
+                               },
+                       },
+                       operation{
+                               start: Loc{6, 0},
+                               end:   Loc{0, 1},
+                               text: []string{
+                                       "kcg",
+                                       "",
+                                       "mpx",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 1},
+                               end:   Loc{1, 1},
+                               text: []string{
+                                       "",
+                                       "aw",
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 1},
+                               end:   Loc{1, 1},
+                               text: []string{
+                                       "vqr",
+                                       "mo",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 3},
+                               end:   Loc{2, 4},
+                               text: []string{
+                                       "xyc",
+                               },
+                       },
+               },
+               []string{
+                       "fehpmjkcg",
+                       "",
+                       "mpxq",
+                       "aw",
+                       "vqr",
+                       "moum",
+                       "vmiwxxaiqq",
+                       "dxycqdgorosf",
+               },
+       )
+}
+
+func TestBug19872UndoIsFunky(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "something",
+                       " A",
+                       "",
+                       " B",
+                       "something else",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 1},
+                               end:   Loc{1, 1},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 2},
+                               end:   Loc{1, 3},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "something",
+                       "A",
+                       "B",
+                       "something else",
+               },
+       )
+}
+
+func TestBug19872UndoIsFunky_2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "something",
+                       "A",
+                       "B",
+                       "something else",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 1},
+                               end:   Loc{0, 1},
+                               text: []string{
+                                       " ",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 2},
+                               end:   Loc{0, 2},
+                               text: []string{
+                                       "",
+                                       " ",
+                               },
+                       },
+               },
+               []string{
+                       "something",
+                       " A",
+                       "",
+                       " B",
+                       "something else",
+               },
+       )
+}
+
+func TestInsertEmptyText(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestLastOpIsNoOp(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 3},
+                               end:   Loc{0, 3},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "y First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertTextWithoutNewline1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "foo ",
+                               },
+                       },
+               },
+               []string{
+                       "foo My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertTextWithoutNewline2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       " foo",
+                               },
+                       },
+               },
+               []string{
+                       "My foo First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertOneNewline(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{3, 0},
+                               end:   Loc{3, 0},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My ",
+                       "First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertTextWithOneNewline(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       " new line",
+                                       "No longer",
+                               },
+                       },
+               },
+               []string{
+                       "My new line",
+                       "No longer First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertTextWithTwoNewlines(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       " new line",
+                                       "One more line in the middle",
+                                       "No longer",
+                               },
+                       },
+               },
+               []string{
+                       "My new line",
+                       "One more line in the middle",
+                       "No longer First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertTextWithManyNewlines(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "",
+                                       "",
+                                       "",
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My",
+                       "",
+                       "",
+                       "",
+                       " First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestInsertMultipleNewlines(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "",
+                                       "",
+                                       "",
+                                       "",
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{14, 2},
+                               end:   Loc{14, 2},
+                               text: []string{
+                                       "a",
+                                       "b",
+                               },
+                       },
+               },
+               []string{
+                       "My",
+                       "",
+                       "",
+                       "",
+                       " First Line",
+                       "\t\tMy Second Line",
+                       "    Third Linea",
+                       "b",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteEmptyText(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteTextFromOneLine(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "y First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteTextFromOneLine2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "a",
+                               },
+                       },
+               },
+               []string{
+                       "a First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteAllTextFromALine(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{13, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteTextFromTwoLines(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{3, 0},
+                               end:   Loc{5, 1},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteTextFromManyLines(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{3, 0},
+                               end:   Loc{4, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My Third Line",
+                       "",
+                       "1",
+               },
+       )
+}
+
+func TestDeleteEverything(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "1",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 4},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "",
+               },
+       )
+}
+
+func TestTwoUnrelatedEdits(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "My First Line",
+                       "\t\tMy Second Line",
+                       "    Third Line",
+                       "",
+                       "123",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 1},
+                               end:   Loc{2, 1},
+                               text: []string{
+                                       "\t",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 2},
+                               end:   Loc{4, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "My First Line",
+                       "\tMy Second Line",
+                       "Third Line",
+                       "",
+                       "123",
+               },
+       )
+}
+
+func TestTwoEditsOnOneLine(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "\t\tfirst\t    ",
+                       "\t\tsecond line",
+                       "\tthird line",
+                       "fourth line",
+                       "\t\t<!@#fifth#@!>\t\t",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 4},
+                               end:   Loc{6, 4},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{11, 4},
+                               end:   Loc{15, 4},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "\t\tfirst\t    ",
+                       "\t\tsecond line",
+                       "\tthird line",
+                       "fourth line",
+                       "\t\tfifth\t\t",
+               },
+       )
+}
+
+func TestManyEdits(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "{\"x\" : 1}",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "\n  ",
+                               },
+                       },
+                       operation{
+                               start: Loc{4, 0},
+                               end:   Loc{5, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{8, 0},
+                               end:   Loc{8, 0},
+                               text: []string{
+                                       "\n",
+                               },
+                       },
+               },
+               []string{
+                       "{",
+                       "  \"x\": 1",
+                       "}",
+               },
+       )
+}
+
+func TestManyEditsReversed(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "{",
+                       "  \"x\": 1",
+                       "}",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{2, 1},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{5, 1},
+                               end:   Loc{5, 1},
+                               text: []string{
+                                       " ",
+                               },
+                       },
+                       operation{
+                               start: Loc{8, 1},
+                               end:   Loc{0, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "{\"x\" : 1}",
+               },
+       )
+}
+
+func TestReplacingNewlines1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "{",
+                       "\"a\": true,",
+                       "",
+                       "\"b\": true",
+                       "}",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{0, 1},
+                               text: []string{
+                                       "",
+                                       "\t",
+                               },
+                       },
+                       operation{
+                               start: Loc{10, 1},
+                               end:   Loc{0, 3},
+                               text: []string{
+                                       "",
+                                       "\t",
+                               },
+                       },
+               },
+               []string{
+                       "{",
+                       "\t\"a\": true,",
+                       "\t\"b\": true",
+                       "}",
+               },
+       )
+}
+
+func TestReplacingNewlines2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "some text",
+                       "some more text",
+                       "now comes an empty line",
+                       "",
+                       "after empty line",
+                       "and the last line",
+               },
+               []operation{
+                       operation{
+                               start: Loc{4, 0},
+                               end:   Loc{0, 2},
+                               text: []string{
+                                       " text",
+                                       "some more text",
+                                       "some more text",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 2},
+                               end:   Loc{0, 3},
+                               text: []string{
+                                       "o more lines",
+                                       "asd",
+                                       "asd",
+                                       "asd",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 4},
+                               end:   Loc{5, 4},
+                               text: []string{
+                                       "zzzzzzzz",
+                               },
+                       },
+                       operation{
+                               start: Loc{10, 4},
+                               end:   Loc{15, 5},
+                               text: []string{
+                                       "1",
+                                       "2",
+                                       "3",
+                                       "4",
+                               },
+                       },
+               },
+               []string{
+                       "some text",
+                       "some more text",
+                       "some more textno more lines",
+                       "asd",
+                       "asd",
+                       "asd",
+                       "zzzzzzzz empt1",
+                       "2",
+                       "3",
+                       "4ne",
+               },
+       )
+}
+
+func TestAdvanced1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       " {       \"d\": [",
+                       "             null",
+                       "        ] /*comment*/",
+                       "        ,\"e\": /*comment*/ [null] }",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{9, 0},
+                               text: []string{
+                                       "",
+                                       "  ",
+                               },
+                       },
+                       operation{
+                               start: Loc{15, 0},
+                               end:   Loc{13, 1},
+                               text: []string{
+                                       "",
+                                       "    ",
+                               },
+                       },
+                       operation{
+                               start: Loc{17, 1},
+                               end:   Loc{8, 2},
+                               text: []string{
+                                       "",
+                                       "  ",
+                               },
+                       },
+                       operation{
+                               start: Loc{21, 2},
+                               end:   Loc{8, 3},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{9, 3},
+                               end:   Loc{9, 3},
+                               text: []string{
+                                       "",
+                                       "  ",
+                               },
+                       },
+                       operation{
+                               start: Loc{27, 3},
+                               end:   Loc{27, 3},
+                               text: []string{
+                                       "",
+                                       "    ",
+                               },
+                       },
+                       operation{
+                               start: Loc{31, 3},
+                               end:   Loc{31, 3},
+                               text: []string{
+                                       "",
+                                       "  ",
+                               },
+                       },
+                       operation{
+                               start: Loc{32, 3},
+                               end:   Loc{33, 3},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "{",
+                       "  \"d\": [",
+                       "    null",
+                       "  ] /*comment*/,",
+                       "  \"e\": /*comment*/ [",
+                       "    null",
+                       "  ]",
+                       "}",
+               },
+       )
+}
+
+func TestAdvancedSimplified(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "   abc",
+                       " ,def",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{3, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{6, 0},
+                               end:   Loc{1, 1},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{2, 1},
+                               end:   Loc{2, 1},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "abc,",
+                       "def",
+               },
+       )
+}
+
+func TestIssue144(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "package caddy",
+                       "",
+                       "func main() {",
+                       "\tfmt.Println(\"Hello World! :)\")",
+                       "}",
+                       "",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 5},
+                               text: []string{
+                                       "package caddy",
+                                       "",
+                                       "import \"fmt\"",
+                                       "",
+                                       "func main() {",
+                                       "\tfmt.Println(\"Hello World! :)\")",
+                                       "}",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "package caddy",
+                       "",
+                       "import \"fmt\"",
+                       "",
+                       "func main() {",
+                       "\tfmt.Println(\"Hello World! :)\")",
+                       "}",
+                       "",
+               },
+       )
+}
+
+func TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "something",
+                       "interesting",
+               },
+               []operation{
+                       operation{
+                               start: Loc{9, 0},
+                               end:   Loc{0, 1},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "something",
+                       "interesting",
+               },
+       )
+}
+
+func TestIssue3980(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "class A {",
+                       "    someProperty = false;",
+                       "    someMethod() {",
+                       "    this.someMethod();",
+                       "    }",
+                       "}",
+               },
+               []operation{
+                       operation{
+                               start: Loc{7, 0},
+                               end:   Loc{8, 0},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{16, 2},
+                               end:   Loc{17, 2},
+                               text: []string{
+                                       "",
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{17, 2},
+                               end:   Loc{17, 2},
+                               text: []string{
+                                       "    ",
+                               },
+                       },
+                       operation{
+                               start: Loc{4, 3},
+                               end:   Loc{4, 3},
+                               text: []string{
+                                       "    ",
+                               },
+                       },
+               },
+               []string{
+                       "class A",
+                       "{",
+                       "    someProperty = false;",
+                       "    someMethod()",
+                       "    {",
+                       "        this.someMethod();",
+                       "    }",
+                       "}",
+               },
+       )
+}
+
+func TestTouchingEditsTwoInsertsAtTheSamePosition(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "a",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "b",
+                               },
+                       },
+               },
+               []string{
+                       "abhello world",
+               },
+       )
+}
+
+func TestTouchingEditsInsertAndReplaceTouching(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "b",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "ab",
+                               },
+                       },
+               },
+               []string{
+                       "babllo world",
+               },
+       )
+}
+
+func TestTouchingEditsTwoTouchingReplaces(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "H",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "E",
+                               },
+                       },
+               },
+               []string{
+                       "HEllo world",
+               },
+       )
+}
+
+func TestTouchingEditsTwoTouchingDeletes(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "llo world",
+               },
+       )
+}
+
+func TestTouchingEditsInsertAndReplace(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "H",
+                               },
+                       },
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "e",
+                               },
+                       },
+               },
+               []string{
+                       "Hello world",
+               },
+       )
+}
+
+func TestTouchingEditsReplaceAndInsert(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "H",
+                               },
+                       },
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{2, 0},
+                               text: []string{
+                                       "e",
+                               },
+                       },
+               },
+               []string{
+                       "Hello world",
+               },
+       )
+}
+
+func TestSingleDelete1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "ello world",
+               },
+       )
+}
+
+func TestSingleDelete2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "helloworld",
+               },
+               []operation{
+                       operation{
+                               start: Loc{2, 0},
+                               end:   Loc{7, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "herld",
+               },
+       )
+}
+
+func TestSingleDelete3(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{5, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       " world",
+               },
+       )
+}
+
+func TestSingleDelete4(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{6, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "hworld",
+               },
+       )
+}
+
+func TestSingleDelete5(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{11, 0},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "",
+               },
+       )
+}
+
+func TestMultiDelete6(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+                       "hello world",
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{5, 0},
+                               end:   Loc{5, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "hello world",
+               },
+       )
+}
+
+func TestMultiDelete7(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+                       "hello world",
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{11, 0},
+                               end:   Loc{11, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "hello world",
+               },
+       )
+}
+
+func TestMultiDelete8(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+                       "hello world",
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "hello world",
+               },
+       )
+}
+
+func TestMultiDelete9(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+                       "hello world",
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{11, 0},
+                               end:   Loc{0, 2},
+                               text: []string{
+                                       "",
+                               },
+                       },
+               },
+               []string{
+                       "hello worldhello world",
+               },
+       )
+}
+
+func TestSingleInsert1(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "xx",
+                               },
+                       },
+               },
+               []string{
+                       "xxhello world",
+               },
+       )
+}
+
+func TestSingleInsert2(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{1, 0},
+                               end:   Loc{1, 0},
+                               text: []string{
+                                       "xx",
+                               },
+                       },
+               },
+               []string{
+                       "hxxello world",
+               },
+       )
+}
+
+func TestSingleInsert3(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{5, 0},
+                               end:   Loc{5, 0},
+                               text: []string{
+                                       "xx",
+                               },
+                       },
+               },
+               []string{
+                       "helloxx world",
+               },
+       )
+}
+
+func TestSingleInsert4(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{6, 0},
+                               end:   Loc{6, 0},
+                               text: []string{
+                                       "xx",
+                               },
+                       },
+               },
+               []string{
+                       "hello xxworld",
+               },
+       )
+}
+
+func TestSingleInsert5(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{11, 0},
+                               end:   Loc{11, 0},
+                               text: []string{
+                                       "xx",
+                               },
+                       },
+               },
+               []string{
+                       "hello worldxx",
+               },
+       )
+}
+
+func TestMultiInsert6(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{0, 0},
+                               end:   Loc{0, 0},
+                               text: []string{
+                                       "\n",
+                               },
+                       },
+               },
+               []string{
+                       "",
+                       "hello world",
+               },
+       )
+}
+
+func TestMultiInsert7(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{11, 0},
+                               end:   Loc{11, 0},
+                               text: []string{
+                                       "\n",
+                               },
+                       },
+               },
+               []string{
+                       "hello world",
+                       "",
+               },
+       )
+}
+
+func TestMultiInsert8(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{6, 0},
+                               end:   Loc{6, 0},
+                               text: []string{
+                                       "\n",
+                               },
+                       },
+               },
+               []string{
+                       "hello ",
+                       "world",
+               },
+       )
+}
+
+func TestMultiInsert9(t *testing.T) {
+       check(
+               t,
+               []string{
+                       "hello world",
+                       "hello world",
+               },
+               []operation{
+                       operation{
+                               start: Loc{6, 0},
+                               end:   Loc{6, 0},
+                               text: []string{
+                                       "xx\nyy",
+                               },
+                       },
+               },
+               []string{
+                       "hello xx",
+                       "yyworld",
+                       "hello world",
+               },
+       )
+}
+
+func BenchmarkBuffer(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               TestAuto1(nil)
+               TestAuto2(nil)
+               TestAuto3(nil)
+               TestAuto4(nil)
+               TestBug19872UndoIsFunky(nil)
+               TestBug19872UndoIsFunky_2(nil)
+               TestInsertEmptyText(nil)
+               TestLastOpIsNoOp(nil)
+               TestInsertTextWithoutNewline1(nil)
+               TestInsertTextWithoutNewline2(nil)
+               TestInsertOneNewline(nil)
+               TestInsertTextWithOneNewline(nil)
+               TestInsertTextWithTwoNewlines(nil)
+               TestInsertTextWithManyNewlines(nil)
+               TestInsertMultipleNewlines(nil)
+               TestDeleteEmptyText(nil)
+               TestDeleteTextFromOneLine(nil)
+               TestDeleteTextFromOneLine2(nil)
+               TestDeleteAllTextFromALine(nil)
+               TestDeleteTextFromTwoLines(nil)
+               TestDeleteTextFromManyLines(nil)
+               TestDeleteEverything(nil)
+               TestTwoUnrelatedEdits(nil)
+               TestTwoEditsOnOneLine(nil)
+               TestManyEdits(nil)
+               TestManyEditsReversed(nil)
+               TestReplacingNewlines1(nil)
+               TestReplacingNewlines2(nil)
+               TestAdvanced1(nil)
+               TestAdvancedSimplified(nil)
+               TestIssue144(nil)
+               TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(nil)
+               TestIssue3980(nil)
+               TestTouchingEditsTwoInsertsAtTheSamePosition(nil)
+               TestTouchingEditsInsertAndReplaceTouching(nil)
+               TestTouchingEditsTwoTouchingReplaces(nil)
+               TestTouchingEditsTwoTouchingDeletes(nil)
+               TestTouchingEditsInsertAndReplace(nil)
+               TestTouchingEditsReplaceAndInsert(nil)
+               TestSingleDelete1(nil)
+               TestSingleDelete2(nil)
+               TestSingleDelete3(nil)
+               TestSingleDelete4(nil)
+               TestSingleDelete5(nil)
+               TestMultiDelete6(nil)
+               TestMultiDelete7(nil)
+               TestMultiDelete8(nil)
+               TestMultiDelete9(nil)
+               TestSingleInsert1(nil)
+               TestSingleInsert2(nil)
+               TestSingleInsert3(nil)
+               TestSingleInsert4(nil)
+               TestSingleInsert5(nil)
+               TestMultiInsert6(nil)
+               TestMultiInsert7(nil)
+               TestMultiInsert8(nil)
+               TestMultiInsert9(nil)
+       }
+}
diff --git a/internal/buffer/buffer_test.go b/internal/buffer/buffer_test.go
new file mode 100644 (file)
index 0000000..1e941d1
--- /dev/null
@@ -0,0 +1,108 @@
+package buffer
+
+import (
+       "strings"
+       "testing"
+
+       testifyAssert "github.com/stretchr/testify/assert"
+       lua "github.com/yuin/gopher-lua"
+
+       ulua "github.com/zyedidia/micro/internal/lua"
+)
+
+type operation struct {
+       start Loc
+       end   Loc
+       text  []string
+}
+
+type asserter interface {
+       Equal(interface{}, interface{}, ...interface{}) bool
+       NotEqual(interface{}, interface{}, ...interface{}) bool
+}
+
+type noOpAsserter struct {
+}
+
+func (a *noOpAsserter) Equal(interface{}, interface{}, ...interface{}) bool {
+       return true
+}
+
+func (a *noOpAsserter) NotEqual(interface{}, interface{}, ...interface{}) bool {
+       return true
+}
+
+func init() {
+       ulua.L = lua.NewState()
+}
+
+func check(t *testing.T, before []string, operations []operation, after []string) {
+       var assert asserter
+       if t == nil {
+               // Benchmark mode; don't perform assertions
+               assert = &noOpAsserter{}
+       } else {
+               assert = testifyAssert.New(t)
+       }
+
+       b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault)
+
+       assert.NotEqual(b.GetName(), "")
+       assert.Equal(b.ExternallyModified(), false)
+       assert.Equal(b.Modified(), false)
+       assert.Equal(b.NumCursors(), 1)
+
+       checkText := func(lines []string) {
+               assert.Equal(b.Bytes(), []byte(strings.Join(lines, "\n")))
+               assert.Equal(b.LinesNum(), len(lines))
+               for i, s := range lines {
+                       assert.Equal(b.Line(i), s)
+                       assert.Equal(b.LineBytes(i), []byte(s))
+               }
+       }
+
+       checkText(before)
+
+       var cursors []*Cursor
+
+       for _, op := range operations {
+               cursor := NewCursor(b, op.start)
+               cursor.SetSelectionStart(op.start)
+               cursor.SetSelectionEnd(op.end)
+               b.AddCursor(cursor)
+               cursors = append(cursors, cursor)
+       }
+
+       assert.Equal(b.NumCursors(), 1+len(operations))
+
+       for i, op := range operations {
+               cursor := cursors[i]
+               cursor.DeleteSelection()
+               b.Insert(cursor.Loc, strings.Join(op.text, "\n"))
+       }
+
+       checkText(after)
+
+       for _ = range operations {
+               b.UndoOneEvent()
+               b.UndoOneEvent()
+       }
+
+       checkText(before)
+
+       for i, op := range operations {
+               cursor := cursors[i]
+               assert.Equal(cursor.Loc, op.start)
+               assert.Equal(cursor.CurSelection[0], op.start)
+               assert.Equal(cursor.CurSelection[1], op.end)
+       }
+
+       for _ = range operations {
+               b.RedoOneEvent()
+               b.RedoOneEvent()
+       }
+
+       checkText(after)
+
+       b.Close()
+}
diff --git a/tools/testgen.go b/tools/testgen.go
new file mode 100644 (file)
index 0000000..6d53fa5
--- /dev/null
@@ -0,0 +1,267 @@
+package main
+
+import (
+       "fmt"
+       "io/ioutil"
+       "log"
+       "os"
+       "regexp"
+       "strings"
+
+       "github.com/robertkrimen/otto/ast"
+       "github.com/robertkrimen/otto/parser"
+)
+
+type walker struct {
+       nodes []ast.Node
+}
+
+func (w *walker) Enter(node ast.Node) ast.Visitor {
+       w.nodes = append(w.nodes, node)
+       return w
+}
+
+func (w *walker) Exit(node ast.Node) {
+}
+
+func getAllNodes(node ast.Node) []ast.Node {
+       w := &walker{}
+       ast.Walk(w, node)
+       return w.nodes
+}
+
+func getCalls(node ast.Node, name string) []*ast.CallExpression {
+       nodes := []*ast.CallExpression{}
+       for _, n := range getAllNodes(node) {
+               if ce, ok := n.(*ast.CallExpression); ok {
+                       var calleeName string
+                       switch callee := ce.Callee.(type) {
+                       case *ast.Identifier:
+                               calleeName = callee.Name
+                       case *ast.DotExpression:
+                               calleeName = callee.Identifier.Name
+                       default:
+                               continue
+                       }
+                       if calleeName == name {
+                               nodes = append(nodes, ce)
+                       }
+               }
+       }
+       return nodes
+}
+
+func getPropertyValue(node ast.Node, key string) ast.Expression {
+       for _, p := range node.(*ast.ObjectLiteral).Value {
+               if p.Key == key {
+                       return p.Value
+               }
+       }
+       return nil
+}
+
+type operation struct {
+       startLine   int
+       startColumn int
+       endLine     int
+       endColumn   int
+       text        []string
+}
+
+type check struct {
+       before     []string
+       operations []operation
+       after      []string
+}
+
+type test struct {
+       description string
+       checks      []check
+}
+
+func stringSliceToGoSource(slice []string) string {
+       var b strings.Builder
+       b.WriteString("[]string{\n")
+       for _, s := range slice {
+               b.WriteString(fmt.Sprintf("%#v,\n", s))
+       }
+       b.WriteString("}")
+       return b.String()
+}
+
+func testToGoTest(test test, name string) string {
+       var b strings.Builder
+
+       b.WriteString("func Test")
+       b.WriteString(name)
+       b.WriteString("(t *testing.T) {\n")
+
+       for _, c := range test.checks {
+               b.WriteString("check(\n")
+               b.WriteString("t,\n")
+               b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.before)))
+               b.WriteString("[]operation{\n")
+               for _, op := range c.operations {
+                       b.WriteString("operation{\n")
+                       b.WriteString(fmt.Sprintf("start: Loc{%v, %v},\n", op.startColumn, op.startLine))
+                       b.WriteString(fmt.Sprintf("end: Loc{%v, %v},\n", op.endColumn, op.endLine))
+                       b.WriteString(fmt.Sprintf("text: %v,\n", stringSliceToGoSource(op.text)))
+                       b.WriteString("},\n")
+               }
+               b.WriteString("},\n")
+               b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.after)))
+               b.WriteString(")\n")
+       }
+
+       b.WriteString("}\n")
+
+       return b.String()
+}
+
+func nodeToStringSlice(node ast.Node) []string {
+       var result []string
+       for _, s := range node.(*ast.ArrayLiteral).Value {
+               result = append(result, s.(*ast.StringLiteral).Value)
+       }
+       return result
+}
+
+func nodeToStringSlice2(node ast.Node) []string {
+       var result []string
+       for _, o := range node.(*ast.ArrayLiteral).Value {
+               result = append(result, getPropertyValue(o, "text").(*ast.StringLiteral).Value)
+       }
+       return result
+}
+
+func nodeToInt(node ast.Node) int {
+       return int(node.(*ast.NumberLiteral).Value.(int64))
+}
+
+func getChecks(node ast.Node) []check {
+       checks := []check{}
+
+       for _, ce := range getCalls(node, "testApplyEdits") {
+               if len(ce.ArgumentList) != 3 {
+                       // Wrong function
+                       continue
+               }
+
+               before := nodeToStringSlice2(ce.ArgumentList[0])
+               after := nodeToStringSlice2(ce.ArgumentList[2])
+
+               var operations []operation
+               for _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value {
+                       args := getPropertyValue(op, "range").(*ast.NewExpression).ArgumentList
+                       operations = append(operations, operation{
+                               startLine:   nodeToInt(args[0]) - 1,
+                               startColumn: nodeToInt(args[1]) - 1,
+                               endLine:     nodeToInt(args[2]) - 1,
+                               endColumn:   nodeToInt(args[3]) - 1,
+                               text:        []string{getPropertyValue(op, "text").(*ast.StringLiteral).Value},
+                       })
+               }
+
+               checks = append(checks, check{before, operations, after})
+       }
+
+       for _, ce := range getCalls(node, "testApplyEditsWithSyncedModels") {
+               if len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value {
+                       // inputEditsAreInvalid == true
+                       continue
+               }
+
+               before := nodeToStringSlice(ce.ArgumentList[0])
+               after := nodeToStringSlice(ce.ArgumentList[2])
+
+               var operations []operation
+               for _, op := range getCalls(ce.ArgumentList[1], "editOp") {
+                       operations = append(operations, operation{
+                               startLine:   nodeToInt(op.ArgumentList[0]) - 1,
+                               startColumn: nodeToInt(op.ArgumentList[1]) - 1,
+                               endLine:     nodeToInt(op.ArgumentList[2]) - 1,
+                               endColumn:   nodeToInt(op.ArgumentList[3]) - 1,
+                               text:        nodeToStringSlice(op.ArgumentList[4]),
+                       })
+               }
+
+               checks = append(checks, check{before, operations, after})
+       }
+
+       return checks
+}
+
+func getTests(node ast.Node) []test {
+       tests := []test{}
+       for _, ce := range getCalls(node, "test") {
+               description := ce.ArgumentList[0].(*ast.StringLiteral).Value
+               body := ce.ArgumentList[1].(*ast.FunctionLiteral).Body
+               checks := getChecks(body)
+               if len(checks) > 0 {
+                       tests = append(tests, test{description, checks})
+               }
+       }
+       return tests
+}
+
+func main() {
+       var tests []test
+
+       for _, filename := range os.Args[1:] {
+               source, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       log.Fatalln(err)
+               }
+
+               program, err := parser.ParseFile(nil, "", source, parser.IgnoreRegExpErrors)
+               if err != nil {
+                       log.Fatalln(err)
+               }
+
+               tests = append(tests, getTests(program)...)
+       }
+
+       if len(tests) == 0 {
+               log.Fatalln("no tests found!")
+       }
+
+       fmt.Println("// This file is generated from VSCode model tests by the testgen tool.")
+       fmt.Println("// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n")
+       fmt.Println("package buffer")
+       fmt.Println(`import "testing"`)
+
+       re := regexp.MustCompile(`[^\w]`)
+       usedNames := map[string]bool{}
+
+       var b strings.Builder
+
+       for _, test := range tests {
+               name := strings.Title(strings.ToLower(test.description))
+               name = re.ReplaceAllLiteralString(name, "")
+               if name == "" {
+                       name = "Unnamed"
+               }
+               if usedNames[name] {
+                       for i := 2; ; i++ {
+                               newName := fmt.Sprintf("%v_%v", name, i)
+                               if !usedNames[newName] {
+                                       name = newName
+                                       break
+                               }
+                       }
+               }
+               usedNames[name] = true
+
+               fmt.Println(testToGoTest(test, name))
+
+               b.WriteString("Test")
+               b.WriteString(name)
+               b.WriteString("(nil)\n")
+       }
+
+       fmt.Println("func BenchmarkBuffer(b *testing.B) {")
+       fmt.Println("for i := 0; i < b.N; i++ {")
+       fmt.Print(b.String())
+       fmt.Println("}")
+       fmt.Println("}")
+}