diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 8fadd20b..11b1884f 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -90,6 +90,8 @@ type BufHandler struct { // Last search stores the last successful search for FindNext and FindPrev lastSearch string + + splitID uint64 } func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler { @@ -212,6 +214,19 @@ func (h *BufHandler) DoRuneInsert(r rune) { } } +func (h *BufHandler) vsplit(buf *buffer.Buffer) { + e := NewBufEditPane(0, 0, 0, 0, buf) + e.splitID = MainTab.GetNode(h.splitID).VSplit(h.Buf.Settings["splitright"].(bool)) + MainTab.Panes = append(MainTab.Panes, e) + MainTab.Resize() +} +func (h *BufHandler) hsplit(buf *buffer.Buffer) { + e := NewBufEditPane(0, 0, 0, 0, buf) + e.splitID = MainTab.GetNode(h.splitID).HSplit(h.Buf.Settings["splitbottom"].(bool)) + MainTab.Panes = append(MainTab.Panes, e) + MainTab.Resize() +} + // BufKeyActions contains the list of all possible key actions the bufhandler could execute var BufKeyActions = map[string]BufKeyAction{ "CursorUp": (*BufHandler).CursorUp, diff --git a/cmd/micro/action/command.go b/cmd/micro/action/command.go index 6ca60156..96ea957e 100644 --- a/cmd/micro/action/command.go +++ b/cmd/micro/action/command.go @@ -3,6 +3,7 @@ package action import ( "os" + "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/shellwords" "github.com/zyedidia/micro/cmd/micro/util" ) @@ -198,11 +199,25 @@ func Help(args []string) { // VSplit opens a vertical split with file given in the first argument // If no file is given, it opens an empty buffer in a new split func VSplit(args []string) { + buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault) + if err != nil { + InfoBar.Error(err) + return + } + + MainTab.CurPane().vsplit(buf) } // HSplit opens a horizontal split with file given in the first argument // If no file is given, it opens an empty buffer in a new split func HSplit(args []string) { + buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault) + if err != nil { + InfoBar.Error(err) + return + } + + MainTab.CurPane().hsplit(buf) } // Eval evaluates a lua expression diff --git a/cmd/micro/action/editpane.go b/cmd/micro/action/editpane.go index 92cc929c..a017ec2d 100644 --- a/cmd/micro/action/editpane.go +++ b/cmd/micro/action/editpane.go @@ -4,6 +4,7 @@ import ( "github.com/zyedidia/micro/cmd/micro/buffer" "github.com/zyedidia/micro/cmd/micro/display" "github.com/zyedidia/micro/cmd/micro/info" + "github.com/zyedidia/micro/cmd/micro/views" ) type EditPane struct { @@ -27,6 +28,17 @@ func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane { return e } +func NewTabPane(width, height int, b *buffer.Buffer) *TabPane { + t := new(TabPane) + t.Node = views.NewRoot(0, 0, width, height) + + e := NewBufEditPane(0, 0, width, height, b) + e.splitID = t.ID() + + t.Panes = append(t.Panes, e) + return t +} + func NewInfoBar() *InfoPane { e := new(InfoPane) ib := info.NewBuffer() diff --git a/cmd/micro/action/tab.go b/cmd/micro/action/tab.go new file mode 100644 index 00000000..4a8c4724 --- /dev/null +++ b/cmd/micro/action/tab.go @@ -0,0 +1,35 @@ +package action + +import ( + "log" + + "github.com/zyedidia/micro/cmd/micro/views" + "github.com/zyedidia/tcell" +) + +var MainTab *TabPane + +type TabPane struct { + *views.Node + Panes []*EditPane + active int +} + +func (t *TabPane) HandleEvent(event tcell.Event) { + t.Panes[t.active].HandleEvent(event) +} + +func (t *TabPane) Resize() { + for _, p := range t.Panes { + log.Println(p.splitID) + v := t.GetNode(p.splitID).GetView() + pv := p.GetView() + pv.X, pv.Y = v.X, v.Y + p.SetView(pv) + p.Resize(v.W, v.H) + } +} + +func (t *TabPane) CurPane() *EditPane { + return t.Panes[t.active] +} diff --git a/cmd/micro/display/infowindow.go b/cmd/micro/display/infowindow.go index 499b4401..73f334d2 100644 --- a/cmd/micro/display/infowindow.go +++ b/cmd/micro/display/infowindow.go @@ -48,6 +48,10 @@ func NewInfoWindow(b *info.InfoBuf) *InfoWindow { return iw } +func (i *InfoWindow) Resize(w, h int) { + i.width = w +} + // func (i *InfoWindow) YesNoPrompt() (bool, bool) { // for { // i.Clear() diff --git a/cmd/micro/display/window.go b/cmd/micro/display/window.go index 5aec83b9..4b0ed338 100644 --- a/cmd/micro/display/window.go +++ b/cmd/micro/display/window.go @@ -1,6 +1,7 @@ package display import ( + "log" "strconv" "unicode/utf8" @@ -29,6 +30,7 @@ type Window interface { GetView() *View SetView(v *View) GetMouseLoc(vloc buffer.Loc) buffer.Loc + Resize(w, h int) } // The BufWindow provides a way of displaying a certain section @@ -65,6 +67,11 @@ func (v *View) SetView(view *View) { v = view } +func (w *BufWindow) Resize(width, height int) { + w.Width, w.Height = width, height + w.lineHeight = make([]int, height) +} + func (w *BufWindow) getStartInfo(n, lineN int) ([]byte, int, int, *tcell.Style) { tabsize := util.IntOpt(w.Buf.Settings["tabsize"]) width := 0 @@ -431,6 +438,7 @@ func (w *BufWindow) displayBuffer() { nColsBeforeStart-- } + log.Println(len(w.lineHeight), vloc.Y) w.lineHeight[vloc.Y] = bloc.Y totalwidth := w.StartCol - nColsBeforeStart diff --git a/cmd/micro/micro.go b/cmd/micro/micro.go index 468b63d9..8fe6923d 100644 --- a/cmd/micro/micro.go +++ b/cmd/micro/micro.go @@ -187,7 +187,8 @@ func main() { b := LoadInput()[0] width, height := screen.Screen.Size() - ep := action.NewBufEditPane(0, 0, width, height-1, b) + + action.MainTab = action.NewTabPane(width, height-1, b) action.InitGlobals() @@ -205,7 +206,9 @@ func main() { // Display everything screen.Screen.Fill(' ', config.DefStyle) screen.Screen.HideCursor() - ep.Display() + for _, ep := range action.MainTab.Panes { + ep.Display() + } action.InfoBar.Display() screen.Screen.Show() @@ -220,7 +223,7 @@ func main() { if action.InfoBar.HasPrompt { action.InfoBar.HandleEvent(event) } else { - ep.HandleEvent(event) + action.MainTab.Panes[0].HandleEvent(event) } } } diff --git a/cmd/micro/views/splits.go b/cmd/micro/views/splits.go new file mode 100644 index 00000000..5ba50434 --- /dev/null +++ b/cmd/micro/views/splits.go @@ -0,0 +1,354 @@ +package views + +import ( + "fmt" + "strings" +) + +type SplitType uint8 + +const ( + STVert = 0 + STHoriz = 1 + STUndef = 2 +) + +var idcounter uint64 + +func NewID() uint64 { + idcounter++ + return idcounter +} + +type View struct { + X, Y int + W, H int +} + +type Node struct { + View + + kind SplitType + + parent *Node + children []*Node + + // Nodes can be marked as non resizable if they shouldn't be rescaled + // when the terminal window is resized or when a new split is added + // Only the splits on the edges of the screen can be marked as non resizable + canResize bool + // A node may also be marked with proportional scaling. This means that when + // the window is resized the split maintains its proportions + propScale bool + + id uint64 +} + +func (n *Node) ID() uint64 { + if n.IsLeaf() { + return n.id + } + return 0 +} +func (n *Node) CanResize() bool { + return n.canResize +} +func (n *Node) PropScale() bool { + return n.propScale +} +func (n *Node) SetResize(b bool) { + n.canResize = b +} +func (n *Node) SetPropScale(b bool) { + n.propScale = b +} +func (n *Node) GetView() View { + return n.View +} +func (n *Node) SetView(v View) { + n.X, n.Y, n.W, n.H = v.X, v.Y, v.W, v.H +} + +func (n *Node) GetNode(id uint64) *Node { + if n.id == id && n.IsLeaf() { + return n + } + for _, c := range n.children { + if c.id == id && c.IsLeaf() { + return c + } + gc := c.GetNode(id) + if gc != nil { + return gc + } + } + return nil +} + +func NewNode(kind SplitType, x, y, w, h int, parent *Node, id uint64) *Node { + n := new(Node) + n.kind = kind + n.canResize = true + n.propScale = true + n.X, n.Y, n.W, n.H = x, y, w, h + n.children = make([]*Node, 0) + n.parent = parent + n.id = id + + return n +} + +func NewRoot(x, y, w, h int) *Node { + n1 := NewNode(STUndef, x, y, w, h, nil, NewID()) + + return n1 +} + +func (n *Node) IsLeaf() bool { + return len(n.children) == 0 +} + +func (n *Node) vResizeSplit(i int, size int) bool { + if i < 0 || i >= len(n.children)-1 { + return false + } + v1, v2 := n.children[i].GetView(), n.children[i+1].GetView() + toth := v1.H + v2.H + if size >= toth { + return false + } + v1.H, v2.H = size, toth-size + v2.Y = size + n.children[i].SetView(v1) + n.children[i+1].SetView(v2) + return true +} +func (n *Node) hResizeSplit(i int, size int) bool { + if i < 0 || i >= len(n.children)-1 { + return false + } + v1, v2 := n.children[i].GetView(), n.children[i+1].GetView() + totw := v1.W + v2.W + if size >= totw { + return false + } + v1.W, v2.W = size, totw-size + v2.X = size + n.children[i].SetView(v1) + n.children[i+1].SetView(v2) + return true +} + +func (n *Node) ResizeSplit(size int) bool { + ind := 0 + for i, c := range n.parent.children { + if c.id == n.id { + ind = i + } + } + if n.parent.kind == STVert { + return n.parent.vResizeSplit(ind, size) + } + return n.parent.hResizeSplit(ind, size) +} + +func (n *Node) vVSplit(right bool) uint64 { + ind := 0 + for i, c := range n.parent.children { + if c.id == n.id { + ind = i + } + } + return n.parent.hVSplit(ind, right) +} +func (n *Node) hHSplit(bottom bool) uint64 { + ind := 0 + for i, c := range n.parent.children { + if c.id == n.id { + ind = i + } + } + return n.parent.vHSplit(ind, bottom) +} +func (n *Node) vHSplit(i int, right bool) uint64 { + if n.IsLeaf() { + newid := NewID() + hn1 := NewNode(STHoriz, n.X, n.Y, n.W, n.H/2, n, n.id) + hn2 := NewNode(STHoriz, n.X, n.Y+hn1.H, n.W, n.H/2, n, newid) + if !right { + hn1.id, hn2.id = hn2.id, hn1.id + } + + n.children = append(n.children, hn1, hn2) + return newid + } else { + numr := 0 + numnr := 0 + nonrh := 0 + for _, c := range n.children { + view := c.GetView() + if !c.CanResize() { + nonrh += view.H + numnr++ + } else { + numr++ + } + } + + // if there are no resizable splits make them all resizable + if numr == 0 { + numr = numnr + } + + height := (n.H - nonrh) / (numr + 1) + + newid := NewID() + hn := NewNode(STHoriz, n.X, 0, n.W, height, n, newid) + n.children = append(n.children, nil) + inspos := i + if right { + inspos++ + } + copy(n.children[inspos+1:], n.children[inspos:]) + n.children[inspos] = hn + + y := 0 + for _, c := range n.children { + view := c.GetView() + if c.CanResize() { + view.H = height + view.Y = y + } else { + view.Y = y + } + y += view.H + c.SetView(view) + } + return newid + } +} +func (n *Node) hVSplit(i int, right bool) uint64 { + if n.IsLeaf() { + newid := NewID() + vn1 := NewNode(STVert, n.X, n.Y, n.W/2, n.H, n, n.id) + vn2 := NewNode(STVert, n.X+vn1.W, n.Y, n.W/2, n.H, n, newid) + if !right { + vn1.id, vn2.id = vn2.id, vn1.id + } + + n.children = append(n.children, vn1, vn2) + return newid + } else { + numr := 0 + numnr := 0 + nonrw := 0 + for _, c := range n.children { + view := c.GetView() + if !c.CanResize() { + nonrw += view.W + numnr++ + } else { + numr++ + } + } + + // if there are no resizable splits make them all resizable + if numr == 0 { + numr = numnr + } + + width := (n.W - nonrw) / (numr + 1) + + newid := NewID() + vn := NewNode(STVert, 0, n.Y, width, n.H, n, newid) + n.children = append(n.children, nil) + inspos := i + if right { + inspos++ + } + copy(n.children[inspos+1:], n.children[inspos:]) + n.children[inspos] = vn + + x := 0 + for _, c := range n.children { + view := c.GetView() + if c.CanResize() { + view.W = width + view.X = x + } else { + view.X = x + } + x += view.W + c.SetView(view) + } + return newid + } +} + +func (n *Node) HSplit(bottom bool) uint64 { + if !n.IsLeaf() { + return 0 + } + if n.kind == STUndef { + n.kind = STVert + } + if n.kind == STVert { + return n.vHSplit(0, bottom) + } + return n.hHSplit(bottom) +} + +func (n *Node) VSplit(right bool) uint64 { + if !n.IsLeaf() { + return 0 + } + if n.kind == STUndef { + n.kind = STHoriz + } + if n.kind == STVert { + return n.vVSplit(right) + } + return n.hVSplit(0, right) +} + +func (n *Node) Resize(w, h int) { + propW, propH := float64(w)/float64(n.W), float64(h)/float64(n.H) + x, y := n.X, n.Y + for _, c := range n.children { + cW := int(float64(c.W) * propW) + cH := int(float64(c.H) * propH) + c.Resize(cW, cH) + c.X = x + c.Y = y + if n.kind == STHoriz { + x += cW + } else { + y += cH + } + } + n.W, n.H = w, h +} + +func (n *Node) Unsplit() { + +} + +func (n *Node) String() string { + var strf func(n *Node, ident int) string + strf = func(n *Node, ident int) string { + marker := "|" + if n.kind == STHoriz { + marker = "-" + } + str := fmt.Sprint(strings.Repeat("\t", ident), marker, n.View, n.id) + if n.IsLeaf() { + str += "🍁" + } + str += "\n" + for _, c := range n.children { + str += strf(c, ident+1) + } + return str + } + return strf(n, 0) +} diff --git a/cmd/micro/views/splits_test.go b/cmd/micro/views/splits_test.go new file mode 100644 index 00000000..94f8a2b9 --- /dev/null +++ b/cmd/micro/views/splits_test.go @@ -0,0 +1,16 @@ +package views + +import ( + "fmt" + "testing" +) + +func TestHSplit(t *testing.T) { + root := NewRoot(0, 0, 80, 80) + n1 := root.VSplit(true) + root.GetNode(n1).VSplit(true) + root.GetNode(root.id).ResizeSplit(7) + root.Resize(120, 120) + + fmt.Println(root.String()) +}