From 5424a6b92738f4973cd480106b3df44bc9103c2a Mon Sep 17 00:00:00 2001 From: Leon Mika Date: Fri, 1 Jul 2022 16:15:54 +1000 Subject: [PATCH] Allowed open-right to work on all columns Previously it only worked on the right-most column. Also bounded this command to "O" (this might change). --- commandmap.go | 9 ++--- go.mod | 1 + go.sum | 15 ++++++++ stdmodel.go | 12 +++++- viewmodel.go | 37 +++++++++++++++++- viewmodel_test.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 viewmodel_test.go diff --git a/commandmap.go b/commandmap.go index 157bfb0..4ad8582 100644 --- a/commandmap.go +++ b/commandmap.go @@ -187,11 +187,7 @@ func (cm *CommandMapping) RegisterViewCommands() { grid := ctx.Frame().Grid() cellX, _ := grid.CellPosition() - height, width := ctx.ModelVC().Model().Dimensions() - if cellX == width-1 { - return ctx.ModelVC().Resize(height, width+1) - } - return nil + return ctx.ModelVC().OpenRight(cellX) }) cm.Define("open-down", "Inserts a row below the curser", "", func(ctx *CommandContext) error { @@ -277,7 +273,7 @@ func (cm *CommandMapping) RegisterViewCommands() { cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error { ctx.Frame().Prompt(PromptOptions{ - Prompt: ":", + Prompt: ":", CancelOnEmptyBackspace: true, }, func(res string) error { return cm.Eval(ctx, res) @@ -407,6 +403,7 @@ func (cm *CommandMapping) RegisterViewKeyBindings() { cm.MapKey('a', cm.Command("append")) + cm.MapKey('O', cm.Command("open-right")) cm.MapKey('D', cm.Command("delete-row")) cm.MapKey('/', cm.Command("search")) diff --git a/go.mod b/go.mod index 5c67045..6bb538b 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,5 @@ require ( github.com/lmika/shellwords v0.0.0-20140714114018-ce258dd729fe github.com/mattn/go-runewidth v0.0.10 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/stretchr/testify v1.7.5 // indirect ) diff --git a/go.sum b/go.sum index 101b134..b1e8d4a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU= @@ -9,11 +12,23 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +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/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/stdmodel.go b/stdmodel.go index 306e224..975279d 100644 --- a/stdmodel.go +++ b/stdmodel.go @@ -14,12 +14,20 @@ type StdModel struct { // func NewSingleCellStdModel() *StdModel { sm := new(StdModel) - sm.appendStr([]string { "" }) + sm.appendStr([]string{""}) sm.dirty = false return sm } +func NewStdModelFromSlice(str [][]string) *StdModel { + sm := new(StdModel) + for _, r := range str { + sm.appendStr(r) + } + return sm +} + /** * The dimensions of the model (height, width). */ @@ -73,7 +81,7 @@ func (sm *StdModel) SetCellValue(r, c int, value string) { func (sm *StdModel) appendStr(row []string) { if len(sm.Cells) == 0 { cells := sm.strSliceToCell(row, len(row)) - sm.Cells = [][]Cell{ cells } + sm.Cells = [][]Cell{cells} return } diff --git a/viewmodel.go b/viewmodel.go index 1d0b3cd..720552f 100644 --- a/viewmodel.go +++ b/viewmodel.go @@ -44,7 +44,9 @@ func (gvm *ModelViewCtrl) SetRowAttrs(row int, newAttrs SliceAttr) { } func (gvm *ModelViewCtrl) SetColAttrs(col int, newAttrs SliceAttr) { - gvm.colAttrs[col] = newAttrs + if col >= 0 && col < len(gvm.colAttrs) { + gvm.colAttrs[col] = newAttrs + } } func (gvm *ModelViewCtrl) SetCellValue(r, c int, newValue string) error { @@ -69,6 +71,39 @@ func (gvm *ModelViewCtrl) Resize(newRow, newCol int) error { return nil } +func (gvm *ModelViewCtrl) OpenRight(col int) error { + if col < 0 { + return errors.New("col out of bound") + } + return gvm.insertColumn(col + 1) +} + +func (gvm *ModelViewCtrl) insertColumn(col int) error { + rwModel, isRWModel := gvm.model.(RWModel) + if !isRWModel { + return ErrModelReadOnly + } + + dr, dc := rwModel.Dimensions() + if col < 0 || col > dc { + return errors.New("col out of bound") + } + + rwModel.Resize(dr, dc+1) + + for c := dc; c >= col; c-- { + for r := 0; r < dr; r++ { + if c == col { + rwModel.SetCellValue(r, c, "") + } else { + rwModel.SetCellValue(r, c, rwModel.CellValue(r, c-1)) + } + } + } + + return nil +} + // Deletes a row of a model func (gvm *ModelViewCtrl) DeleteRow(row int) error { rwModel, isRWModel := gvm.model.(RWModel) diff --git a/viewmodel_test.go b/viewmodel_test.go new file mode 100644 index 0000000..56ff8e6 --- /dev/null +++ b/viewmodel_test.go @@ -0,0 +1,98 @@ +package main + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestModelViewCtrl_OpenRight(t *testing.T) { + t.Run("should move cols to the right within the model", func(t *testing.T) { + rwModel := NewStdModelFromSlice([][]string{ + {"letters", "numbers", "greek"}, + {"a", "1", "alpha"}, + {"b", "2", "bravo"}, + {"c", "3", "charlie"}, + }) + + mvc := NewGridViewModel(rwModel) + err := mvc.OpenRight(1) + assert.NoError(t, err) + + assertModel(t, rwModel, [][]string{ + {"letters", "numbers", "", "greek"}, + {"a", "1", "", "alpha"}, + {"b", "2", "", "bravo"}, + {"c", "3", "", "charlie"}, + }) + }) + + t.Run("should move cols to the right at the left of the model", func(t *testing.T) { + rwModel := NewStdModelFromSlice([][]string{ + {"letters", "numbers", "greek"}, + {"a", "1", "alpha"}, + {"b", "2", "bravo"}, + {"c", "3", "charlie"}, + }) + + mvc := NewGridViewModel(rwModel) + err := mvc.OpenRight(0) + assert.NoError(t, err) + + assertModel(t, rwModel, [][]string{ + {"letters", "", "numbers", "greek"}, + {"a", "", "1", "alpha"}, + {"b", "", "2", "bravo"}, + {"c", "", "3", "charlie"}, + }) + }) + + t.Run("should move cols to the right at the right of the model", func(t *testing.T) { + rwModel := NewStdModelFromSlice([][]string{ + {"letters", "numbers", "greek"}, + {"a", "1", "alpha"}, + {"b", "2", "bravo"}, + {"c", "3", "charlie"}, + }) + + mvc := NewGridViewModel(rwModel) + err := mvc.OpenRight(2) + assert.NoError(t, err) + + assertModel(t, rwModel, [][]string{ + {"letters", "numbers", "greek", ""}, + {"a", "1", "alpha", ""}, + {"b", "2", "bravo", ""}, + {"c", "3", "charlie", ""}, + }) + }) + + t.Run("should return error if row out of bounds", func(t *testing.T) { + scenario := []int{-1, 3, 12} + for _, scenario := range scenario { + t.Run(fmt.Sprint(scenario), func(t *testing.T) { + rwModel := NewStdModelFromSlice([][]string{ + {"letters", "numbers", "greek"}, + {"a", "1", "alpha"}, + {"b", "2", "bravo"}, + {"c", "3", "charlie"}, + }) + mvc := NewGridViewModel(rwModel) + err := mvc.OpenRight(scenario) + assert.Error(t, err) + }) + } + }) +} + +func assertModel(t *testing.T, actual Model, expected [][]string) { + dr, dc := actual.Dimensions() + assert.Equalf(t, len(expected), dr, "number of rows in model") + + for r, row := range expected { + assert.Equalf(t, len(row), dc, "number of cols in row %v", r) + for c, cell := range row { + assert.Equalf(t, cell, actual.CellValue(r, c), "cell value at row %v, col %v", r, c) + } + } +}