Added the notion of a grid view model.
This commit is contained in:
parent
598d9bd962
commit
a49613f7e9
122
commandmap.go
122
commandmap.go
|
|
@ -110,23 +110,13 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
grid := ctx.Frame().Grid()
|
||||
_, cellY := grid.CellPosition()
|
||||
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
DeleteRow(rwModel, cellY)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("model is read-only")
|
||||
return ctx.ModelVC().DeleteRow(cellY)
|
||||
})
|
||||
cm.Define("delete-col", "Removes the currently selected column", "", func(ctx *CommandContext) error {
|
||||
grid := ctx.Frame().Grid()
|
||||
cellX, _ := grid.CellPosition()
|
||||
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
DeleteCol(rwModel, cellX)
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("model is read-only")
|
||||
return ctx.ModelVC().DeleteCol(cellX)
|
||||
})
|
||||
cm.Define("search", "Search for a cell", "", func(ctx *CommandContext) error {
|
||||
ctx.Frame().Prompt(PromptOptions{ Prompt: "/" }, func(res string) error {
|
||||
|
|
@ -145,7 +135,7 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
ctx.Session().Commands.Eval(ctx, "search")
|
||||
}
|
||||
|
||||
height, width := ctx.session.Model.Dimensions()
|
||||
height, width := ctx.ModelVC().Model().Dimensions()
|
||||
startX, startY := ctx.Frame().Grid().CellPosition()
|
||||
cellX, cellY := startX, startY
|
||||
|
||||
|
|
@ -155,7 +145,7 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
cellX = 0
|
||||
cellY = (cellY + 1) % height
|
||||
}
|
||||
if ctx.session.LastSearch.MatchString(ctx.session.Model.CellValue(cellY, cellX)) {
|
||||
if ctx.session.LastSearch.MatchString(ctx.ModelVC().Model().CellValue(cellY, cellX)) {
|
||||
ctx.Frame().Grid().MoveTo(cellX, cellY)
|
||||
return nil
|
||||
} else if (cellX == startX) && (cellY == startY) {
|
||||
|
|
@ -168,30 +158,22 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
grid := ctx.Frame().Grid()
|
||||
cellX, _ := grid.CellPosition()
|
||||
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
height, width := rwModel.Dimensions()
|
||||
if cellX == width-1 {
|
||||
rwModel.Resize(height, width+1)
|
||||
}
|
||||
return nil
|
||||
height, width := ctx.ModelVC().Model().Dimensions()
|
||||
if cellX == width-1 {
|
||||
return ctx.ModelVC().Resize(height, width+1)
|
||||
}
|
||||
|
||||
return errors.New("model is read-only")
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("open-down", "Inserts a row below the curser", "", func(ctx *CommandContext) error {
|
||||
grid := ctx.Frame().Grid()
|
||||
_, cellY := grid.CellPosition()
|
||||
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
height, width := rwModel.Dimensions()
|
||||
if cellY == height-1 {
|
||||
rwModel.Resize(height+1, width)
|
||||
}
|
||||
return nil
|
||||
height, width := ctx.ModelVC().Model().Dimensions()
|
||||
if cellY == height-1 {
|
||||
return ctx.ModelVC().Resize(height+1, width)
|
||||
}
|
||||
|
||||
return errors.New("model is read-only")
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("append", "Inserts a row below the curser", "", func(ctx *CommandContext) error {
|
||||
|
|
@ -207,6 +189,64 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
return ctx.Session().Commands.Eval(ctx, "set-cell")
|
||||
})
|
||||
|
||||
cm.Define("inc-col-width", "Increase the width of the current column", "", func(ctx *CommandContext) error {
|
||||
cellX, _ := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().ColAttrs(cellX)
|
||||
attrs.Size += 2
|
||||
ctx.ModelVC().SetColAttrs(cellX, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("dec-col-width", "Decrease the width of the current column", "", func(ctx *CommandContext) error {
|
||||
cellX, _ := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().ColAttrs(cellX)
|
||||
attrs.Size -= 2
|
||||
if attrs.Size < 4 {
|
||||
attrs.Size = 4
|
||||
}
|
||||
ctx.ModelVC().SetColAttrs(cellX, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("clear-row-marker", "Clears any row markers", "", func(ctx *CommandContext) error {
|
||||
_, cellY := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().RowAttrs(cellY)
|
||||
attrs.Marker = MarkerNone
|
||||
ctx.ModelVC().SetRowAttrs(cellY, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("mark-row-red", "Set row marker to red", "", func(ctx *CommandContext) error {
|
||||
_, cellY := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().RowAttrs(cellY)
|
||||
attrs.Marker = MarkerRed
|
||||
ctx.ModelVC().SetRowAttrs(cellY, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("mark-row-green", "Set row marker to green", "", func(ctx *CommandContext) error {
|
||||
_, cellY := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().RowAttrs(cellY)
|
||||
attrs.Marker = MarkerGreen
|
||||
ctx.ModelVC().SetRowAttrs(cellY, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
cm.Define("mark-row-blue", "Set row marker to blue", "", func(ctx *CommandContext) error {
|
||||
_, cellY := ctx.Frame().Grid().CellPosition()
|
||||
|
||||
attrs := ctx.ModelVC().RowAttrs(cellY)
|
||||
attrs.Marker = MarkerBlue
|
||||
ctx.ModelVC().SetRowAttrs(cellY, attrs)
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
cm.Define("enter-command", "Enter command", "", func(ctx *CommandContext) error {
|
||||
ctx.Frame().Prompt(PromptOptions{ Prompt: ":" }, func(res string) error {
|
||||
return cm.Eval(ctx, res)
|
||||
|
|
@ -217,10 +257,9 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
cm.Define("replace-cell", "Replace the value of the selected cell", "", func(ctx *CommandContext) error {
|
||||
grid := ctx.Frame().Grid()
|
||||
cellX, cellY := grid.CellPosition()
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
if _, isRwModel := ctx.ModelVC().Model().(RWModel); isRwModel {
|
||||
ctx.Frame().Prompt(PromptOptions{ Prompt: "> " }, func(res string) error {
|
||||
rwModel.SetCellValue(cellY, cellX, res)
|
||||
return nil
|
||||
return ctx.ModelVC().SetCellValue(cellY, cellX, res)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
|
|
@ -229,13 +268,12 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
grid := ctx.Frame().Grid()
|
||||
cellX, cellY := grid.CellPosition()
|
||||
|
||||
if rwModel, isRwModel := ctx.Session().Model.(RWModel); isRwModel {
|
||||
if _, isRwModel := ctx.ModelVC().Model().(RWModel); isRwModel {
|
||||
ctx.Frame().Prompt(PromptOptions{
|
||||
Prompt: "> ",
|
||||
InitialValue: grid.Model().CellValue(cellY, cellX),
|
||||
InitialValue: grid.Model().CellValue(cellX, cellY),
|
||||
}, func(res string) error {
|
||||
rwModel.SetCellValue(cellY, cellX, res)
|
||||
return nil
|
||||
return ctx.ModelVC().SetCellValue(cellY, cellX, res)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
|
|
@ -247,7 +285,7 @@ func (cm *CommandMapping) RegisterViewCommands() {
|
|||
return fmt.Errorf("model is not writable")
|
||||
}
|
||||
|
||||
if err := wSource.Write(ctx.Session().Model); err != nil {
|
||||
if err := wSource.Write(ctx.ModelVC().Model()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -304,6 +342,14 @@ func (cm *CommandMapping) RegisterViewKeyBindings() {
|
|||
cm.MapKey('/', cm.Command("search"))
|
||||
cm.MapKey('n', cm.Command("search-next"))
|
||||
|
||||
cm.MapKey('0', cm.Command("clear-row-marker"))
|
||||
cm.MapKey('1', cm.Command("mark-row-red"))
|
||||
cm.MapKey('2', cm.Command("mark-row-green"))
|
||||
cm.MapKey('3', cm.Command("mark-row-blue"))
|
||||
|
||||
cm.MapKey('{', cm.Command("dec-col-width"))
|
||||
cm.MapKey('}', cm.Command("inc-col-width"))
|
||||
|
||||
cm.MapKey(':', cm.Command("enter-command"))
|
||||
}
|
||||
|
||||
|
|
|
|||
21
model.go
21
model.go
|
|
@ -27,25 +27,4 @@ type RWModel interface {
|
|||
IsDirty() bool
|
||||
}
|
||||
|
||||
// Deletes a row of a model
|
||||
func DeleteRow(model RWModel, row int) {
|
||||
h, w := model.Dimensions()
|
||||
for r := row; r < h-1; r++ {
|
||||
for c := 0; c < w; c++ {
|
||||
model.SetCellValue(r, c, model.CellValue(r+1, c))
|
||||
}
|
||||
}
|
||||
|
||||
model.Resize(h-1, w)
|
||||
}
|
||||
|
||||
// Deletes a column of a model
|
||||
func DeleteCol(model RWModel, col int) {
|
||||
h, w := model.Dimensions()
|
||||
for c := col; c < w-1; c++ {
|
||||
for r := 0; r < h; r++ {
|
||||
model.SetCellValue(r, c, model.CellValue(r, c+1))
|
||||
}
|
||||
}
|
||||
model.Resize(h, w-1)
|
||||
}
|
||||
|
|
|
|||
64
session.go
64
session.go
|
|
@ -8,25 +8,28 @@ import (
|
|||
// The session is responsible for managing the UI and the model and handling
|
||||
// the interaction between the two and the user.
|
||||
type Session struct {
|
||||
Model Model
|
||||
Source ModelSource
|
||||
Frame *Frame
|
||||
Commands *CommandMapping
|
||||
UIManager *ui.Ui
|
||||
model Model
|
||||
Source ModelSource
|
||||
Frame *Frame
|
||||
Commands *CommandMapping
|
||||
UIManager *ui.Ui
|
||||
modelController *ModelViewCtrl
|
||||
|
||||
LastSearch *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewSession(uiManager *ui.Ui, frame *Frame, source ModelSource) *Session {
|
||||
model := NewSingleCellStdModel()
|
||||
session := &Session{
|
||||
Model: NewSingleCellStdModel(),
|
||||
Source: source,
|
||||
Frame: frame,
|
||||
Commands: NewCommandMapping(),
|
||||
UIManager: uiManager,
|
||||
model: model,
|
||||
Source: source,
|
||||
Frame: frame,
|
||||
Commands: NewCommandMapping(),
|
||||
UIManager: uiManager,
|
||||
modelController: NewGridViewModel(model),
|
||||
}
|
||||
|
||||
frame.SetModel(&SessionGridModel{session})
|
||||
frame.SetModel(&SessionGridModel{session.modelController})
|
||||
|
||||
session.Commands.RegisterViewCommands()
|
||||
session.Commands.RegisterViewKeyBindings()
|
||||
|
|
@ -45,7 +48,8 @@ func (session *Session) LoadFromSource() {
|
|||
return
|
||||
}
|
||||
|
||||
session.Model = newModel
|
||||
session.model = newModel
|
||||
session.modelController.SetModel(newModel)
|
||||
}
|
||||
|
||||
// Input from the frame
|
||||
|
|
@ -69,6 +73,10 @@ type CommandContext struct {
|
|||
session *Session
|
||||
}
|
||||
|
||||
func (scc *CommandContext) ModelVC() *ModelViewCtrl {
|
||||
return scc.session.modelController
|
||||
}
|
||||
|
||||
func (scc *CommandContext) Session() *Session {
|
||||
return scc.session
|
||||
}
|
||||
|
|
@ -84,26 +92,44 @@ func (scc *CommandContext) Error(err error) {
|
|||
|
||||
// Session grid model
|
||||
type SessionGridModel struct {
|
||||
Session *Session
|
||||
GridViewModel *ModelViewCtrl
|
||||
}
|
||||
|
||||
// Returns the size of the grid model (width x height)
|
||||
func (sgm *SessionGridModel) Dimensions() (int, int) {
|
||||
rs, cs := sgm.Session.Model.Dimensions()
|
||||
rs, cs := sgm.GridViewModel.Model().Dimensions()
|
||||
return cs, rs
|
||||
}
|
||||
|
||||
// Returns the size of the particular column. If the size is 0, this indicates that the column is hidden.
|
||||
func (sgm *SessionGridModel) ColWidth(int) int {
|
||||
return 24
|
||||
func (sgm *SessionGridModel) ColWidth(col int) int {
|
||||
return sgm.GridViewModel.ColAttrs(col).Size
|
||||
}
|
||||
|
||||
// Returns the size of the particular row. If the size is 0, this indicates that the row is hidden.
|
||||
func (sgm *SessionGridModel) RowHeight(int) int {
|
||||
return 1
|
||||
func (sgm *SessionGridModel) RowHeight(row int) int {
|
||||
return sgm.GridViewModel.RowAttrs(row).Size
|
||||
}
|
||||
|
||||
// Returns the value of the cell a position X, Y
|
||||
func (sgm *SessionGridModel) CellValue(x int, y int) string {
|
||||
return sgm.Session.Model.CellValue(y, x)
|
||||
return sgm.GridViewModel.Model().CellValue(y, x)
|
||||
}
|
||||
|
||||
func (sgm *SessionGridModel) CellAttributes(x int, y int) (fg, bg ui.Attribute) {
|
||||
rowAttrs := sgm.GridViewModel.RowAttrs(y)
|
||||
colAttrs := sgm.GridViewModel.ColAttrs(y)
|
||||
|
||||
if rowAttrs.Marker != MarkerNone {
|
||||
return markerAttributes[rowAttrs.Marker], 0
|
||||
} else if colAttrs.Marker != MarkerNone {
|
||||
return markerAttributes[colAttrs.Marker], 0
|
||||
}
|
||||
return 0,0
|
||||
}
|
||||
|
||||
var markerAttributes = map[Marker]ui.Attribute {
|
||||
MarkerRed: ui.ColorRed,
|
||||
MarkerGreen: ui.ColorGreen,
|
||||
MarkerBlue: ui.ColorBlue,
|
||||
}
|
||||
11
ui/grid.go
11
ui/grid.go
|
|
@ -28,6 +28,8 @@ type GridModel interface {
|
|||
* Returns the value of the cell a position X, Y
|
||||
*/
|
||||
CellValue(int, int) string
|
||||
|
||||
CellAttributes(int, int) (fg, bg Attribute)
|
||||
}
|
||||
|
||||
type gridPoint int
|
||||
|
|
@ -173,10 +175,13 @@ func (grid *Grid) getCellData(cellX, cellY int) (text string, fg, bg Attribute)
|
|||
} else {
|
||||
// The data from the model
|
||||
if (modelCellX >= 0) && (modelCellY >= 0) && (modelCellX < modelMaxX) && (modelCellY < modelMaxY) {
|
||||
value := grid.model.CellValue(modelCellX, modelCellY)
|
||||
fg, bg := grid.model.CellAttributes(modelCellX, modelCellY)
|
||||
|
||||
if (modelCellX == grid.selCellX) && (modelCellY == grid.selCellY) {
|
||||
return grid.model.CellValue(modelCellX, modelCellY), AttrReverse, AttrReverse
|
||||
return value, fg | AttrReverse, bg | AttrReverse
|
||||
} else {
|
||||
return grid.model.CellValue(modelCellX, modelCellY), 0, 0
|
||||
return value, fg, bg
|
||||
}
|
||||
} else {
|
||||
return "~", ColorBlue, 0
|
||||
|
|
@ -365,7 +370,7 @@ func (grid *Grid) KeyPressed(key rune, mod int) {
|
|||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Test Model
|
||||
// Test ModelVC
|
||||
|
||||
type TestModel struct {
|
||||
thing int
|
||||
|
|
|
|||
154
viewmodel.go
Normal file
154
viewmodel.go
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ModelViewCtrl struct {
|
||||
model Model
|
||||
rowAttrs []SliceAttr
|
||||
colAttrs []SliceAttr
|
||||
}
|
||||
|
||||
func NewGridViewModel(model Model) *ModelViewCtrl {
|
||||
gvm := &ModelViewCtrl{}
|
||||
gvm.SetModel(model)
|
||||
return gvm
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) Model() Model {
|
||||
return gvm.model
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) SetModel(m Model) {
|
||||
gvm.model = m
|
||||
gvm.modelWasResized()
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) RowAttrs(row int) SliceAttr {
|
||||
if row < len(gvm.rowAttrs) {
|
||||
return gvm.rowAttrs[row]
|
||||
}
|
||||
return DefaultRowAttrs
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) ColAttrs(col int) SliceAttr {
|
||||
if col < len(gvm.colAttrs) {
|
||||
return gvm.colAttrs[col]
|
||||
}
|
||||
return DefaultColAttrs
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) SetRowAttrs(row int, newAttrs SliceAttr) {
|
||||
gvm.rowAttrs[row] = newAttrs
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) SetColAttrs(col int, newAttrs SliceAttr) {
|
||||
gvm.colAttrs[col] = newAttrs
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) SetCellValue(r, c int, newValue string) error {
|
||||
rwModel, isRWModel := gvm.model.(RWModel)
|
||||
if !isRWModel {
|
||||
return ErrModelReadOnly
|
||||
}
|
||||
|
||||
rwModel.SetCellValue(r, c, newValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) Resize(newRow, newCol int) error {
|
||||
rwModel, isRWModel := gvm.model.(RWModel)
|
||||
if !isRWModel {
|
||||
return ErrModelReadOnly
|
||||
}
|
||||
|
||||
rwModel.Resize(newRow, newCol)
|
||||
gvm.modelWasResized()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deletes a row of a model
|
||||
func (gvm *ModelViewCtrl) DeleteRow(row int) error {
|
||||
rwModel, isRWModel := gvm.model.(RWModel)
|
||||
if !isRWModel {
|
||||
return ErrModelReadOnly
|
||||
}
|
||||
|
||||
h, w := rwModel.Dimensions()
|
||||
for r := row; r < h-1; r++ {
|
||||
for c := 0; c < w; c++ {
|
||||
rwModel.SetCellValue(r, c, rwModel.CellValue(r+1, c))
|
||||
gvm.rowAttrs[r] = gvm.rowAttrs[r+1]
|
||||
}
|
||||
}
|
||||
|
||||
rwModel.Resize(h-1, w)
|
||||
gvm.modelWasResized()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deletes a column of a model
|
||||
func (gvm *ModelViewCtrl) DeleteCol(col int) error {
|
||||
rwModel, isRWModel := gvm.model.(RWModel)
|
||||
if !isRWModel {
|
||||
return ErrModelReadOnly
|
||||
}
|
||||
|
||||
h, w := rwModel.Dimensions()
|
||||
for c := col; c < w-1; c++ {
|
||||
for r := 0; r < h; r++ {
|
||||
rwModel.SetCellValue(r, c, rwModel.CellValue(r, c+1))
|
||||
gvm.colAttrs[c] = gvm.colAttrs[c+1]
|
||||
}
|
||||
}
|
||||
|
||||
rwModel.Resize(h, w-1)
|
||||
gvm.modelWasResized()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) modelWasResized() {
|
||||
rows, cols := gvm.model.Dimensions()
|
||||
gvm.rowAttrs = gvm.resizeAttrSlice(gvm.rowAttrs, rows, DefaultRowAttrs)
|
||||
gvm.colAttrs = gvm.resizeAttrSlice(gvm.colAttrs, cols, DefaultColAttrs)
|
||||
}
|
||||
|
||||
func (gvm *ModelViewCtrl) resizeAttrSlice(oldSlice []SliceAttr, newSize int, defaultAttrs SliceAttr) []SliceAttr {
|
||||
oldLen := len(oldSlice)
|
||||
newSlice := oldSlice
|
||||
|
||||
if newSize > oldLen {
|
||||
newSlice = make([]SliceAttr, newSize)
|
||||
for i := 0; i < newSize; i++ {
|
||||
if i < oldLen {
|
||||
newSlice[i] = oldSlice[i]
|
||||
} else {
|
||||
newSlice[i] = defaultAttrs
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newSlice = newSlice[:newSize]
|
||||
}
|
||||
return newSlice
|
||||
}
|
||||
|
||||
type SliceAttr struct {
|
||||
Size int
|
||||
Marker Marker
|
||||
}
|
||||
|
||||
type Marker int
|
||||
|
||||
const (
|
||||
MarkerNone Marker = iota
|
||||
MarkerRed
|
||||
MarkerGreen
|
||||
MarkerBlue
|
||||
)
|
||||
|
||||
var DefaultRowAttrs = SliceAttr{Size: 1}
|
||||
var DefaultColAttrs = SliceAttr{Size: 24}
|
||||
|
||||
var ErrModelReadOnly = errors.New("ModelVC is read-only")
|
||||
Loading…
Reference in a new issue