diff --git a/cmd/micro/action/actions.go b/cmd/micro/action/actions.go index 14dded21..7bbf8ca3 100644 --- a/cmd/micro/action/actions.go +++ b/cmd/micro/action/actions.go @@ -818,7 +818,7 @@ func (h *BufHandler) SelectAll() bool { func (h *BufHandler) OpenFile() bool { InfoBar.Prompt("> ", "open ", "Open", nil, func(resp string, canceled bool) { if !canceled { - HandleCommand(resp) + h.HandleCommand(resp) } }) return false @@ -989,7 +989,7 @@ func (h *BufHandler) ShellMode() bool { func (h *BufHandler) CommandMode() bool { InfoBar.Prompt("> ", "", "Command", nil, func(resp string, canceled bool) { if !canceled { - HandleCommand(resp) + h.HandleCommand(resp) } }) return false @@ -1023,6 +1023,9 @@ func (h *BufHandler) Quit() bool { InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) { if !canceled && !yes { quit() + } else if !canceled && yes { + h.Save() + quit() } }) } else { @@ -1040,7 +1043,7 @@ func (h *BufHandler) QuitAll() bool { func (h *BufHandler) AddTab() bool { width, height := screen.Screen.Size() b := buffer.NewBufferFromString("", "", buffer.BTDefault) - tp := NewTabPane(0, 0, width, height-1, b) + tp := NewTabFromBuffer(0, 0, width, height-1, b) Tabs.AddTab(tp) Tabs.SetActive(len(Tabs.List) - 1) diff --git a/cmd/micro/action/bufhandler.go b/cmd/micro/action/bufhandler.go index 153cb166..566bcb96 100644 --- a/cmd/micro/action/bufhandler.go +++ b/cmd/micro/action/bufhandler.go @@ -111,6 +111,14 @@ func NewBufHandler(buf *buffer.Buffer, win display.Window) *BufHandler { return h } +func (h *BufHandler) ID() uint64 { + return h.splitID +} + +func (h *BufHandler) Name() string { + return h.Buf.GetName() +} + // HandleEvent executes the tcell event properly // TODO: multiple actions bound to one key func (h *BufHandler) HandleEvent(event tcell.Event) { diff --git a/cmd/micro/action/command.go b/cmd/micro/action/command.go index 27b16729..ca4f275b 100644 --- a/cmd/micro/action/command.go +++ b/cmd/micro/action/command.go @@ -12,7 +12,7 @@ import ( // A Command contains an action (a function to call) as well as information about how to autocomplete the command type Command struct { - action func([]string) + action func(*BufHandler, []string) completions []Completion } @@ -24,37 +24,33 @@ type StrCommand struct { var commands map[string]Command -var commandActions map[string]func([]string) - -func init() { - commandActions = map[string]func([]string){ - "Set": Set, - "SetLocal": SetLocal, - "Show": Show, - "ShowKey": ShowKey, - "Run": Run, - "Bind": Bind, - "Quit": Quit, - "Save": Save, - "Replace": Replace, - "ReplaceAll": ReplaceAll, - "VSplit": VSplit, - "HSplit": HSplit, - "Tab": NewTab, - "Help": Help, - "Eval": Eval, - "ToggleLog": ToggleLog, - "Plugin": PluginCmd, - "Reload": Reload, - "Cd": Cd, - "Pwd": Pwd, - "Open": Open, - "TabSwitch": TabSwitch, - "Term": Term, - "MemUsage": MemUsage, - "Retab": Retab, - "Raw": Raw, - } +var commandActions = map[string]func(*BufHandler, []string){ + "Set": (*BufHandler).SetCmd, + "SetLocal": (*BufHandler).SetLocalCmd, + "Show": (*BufHandler).ShowCmd, + "ShowKey": (*BufHandler).ShowKeyCmd, + "Run": (*BufHandler).RunCmd, + "Bind": (*BufHandler).BindCmd, + "Quit": (*BufHandler).QuitCmd, + "Save": (*BufHandler).SaveCmd, + "Replace": (*BufHandler).ReplaceCmd, + "ReplaceAll": (*BufHandler).ReplaceAllCmd, + "VSplit": (*BufHandler).VSplitCmd, + "HSplit": (*BufHandler).HSplitCmd, + "Tab": (*BufHandler).NewTabCmd, + "Help": (*BufHandler).HelpCmd, + "Eval": (*BufHandler).EvalCmd, + "ToggleLog": (*BufHandler).ToggleLogCmd, + "Plugin": (*BufHandler).PluginCmd, + "Reload": (*BufHandler).ReloadCmd, + "Cd": (*BufHandler).CdCmd, + "Pwd": (*BufHandler).PwdCmd, + "Open": (*BufHandler).OpenCmd, + "TabSwitch": (*BufHandler).TabSwitchCmd, + "Term": (*BufHandler).TermCmd, + "MemUsage": (*BufHandler).MemUsageCmd, + "Retab": (*BufHandler).RetabCmd, + "Raw": (*BufHandler).RawCmd, } // InitCommands initializes the default commands @@ -123,7 +119,7 @@ func CommandEditAction(prompt string) BufKeyAction { return func(h *BufHandler) bool { InfoBar.Prompt("> ", prompt, "Command", nil, func(resp string, canceled bool) { if !canceled { - HandleCommand(resp) + MainTab().CurPane().HandleCommand(resp) } }) return false @@ -134,46 +130,46 @@ func CommandEditAction(prompt string) BufKeyAction { // given command func CommandAction(cmd string) BufKeyAction { return func(h *BufHandler) bool { - HandleCommand(cmd) + MainTab().CurPane().HandleCommand(cmd) return false } } // PluginCmd installs, removes, updates, lists, or searches for given plugins -func PluginCmd(args []string) { +func (h *BufHandler) PluginCmd(args []string) { } -// Retab changes all spaces to tabs or all tabs to spaces +// RetabCmd changes all spaces to tabs or all tabs to spaces // depending on the user's settings -func Retab(args []string) { +func (h *BufHandler) RetabCmd(args []string) { } -// Raw opens a new raw view which displays the escape sequences micro +// RawCmd opens a new raw view which displays the escape sequences micro // is receiving in real-time -func Raw(args []string) { +func (h *BufHandler) RawCmd(args []string) { } -// TabSwitch switches to a given tab either by name or by number -func TabSwitch(args []string) { +// TabSwitchCmd switches to a given tab either by name or by number +func (h *BufHandler) TabSwitchCmd(args []string) { } -// Cd changes the current working directory -func Cd(args []string) { +// CdCmd changes the current working directory +func (h *BufHandler) CdCmd(args []string) { } -// MemUsage prints micro's memory usage +// MemUsageCmd prints micro's memory usage // Alloc shows how many bytes are currently in use // Sys shows how many bytes have been requested from the operating system // NumGC shows how many times the GC has been run // Note that Go commonly reserves more memory from the OS than is currently in-use/required // Additionally, even if Go returns memory to the OS, the OS does not always claim it because // there may be plenty of memory to spare -func MemUsage(args []string) { +func (h *BufHandler) MemUsageCmd(args []string) { InfoBar.Message(util.GetMemStats()) } -// Pwd prints the current working directory -func Pwd(args []string) { +// PwdCmd prints the current working directory +func (h *BufHandler) PwdCmd(args []string) { wd, err := os.Getwd() if err != nil { InfoBar.Message(err.Error()) @@ -182,68 +178,86 @@ func Pwd(args []string) { } } -// Open opens a new buffer with a given filename -func Open(args []string) { +// OpenCmd opens a new buffer with a given filename +func (h *BufHandler) OpenCmd(args []string) { } -// ToggleLog toggles the log view -func ToggleLog(args []string) { +// ToggleLogCmd toggles the log view +func (h *BufHandler) ToggleLogCmd(args []string) { } -// Reload reloads all files (syntax files, colorschemes...) -func Reload(args []string) { +// ReloadCmd reloads all files (syntax files, colorschemes...) +func (h *BufHandler) ReloadCmd(args []string) { } -// Help tries to open the given help page in a horizontal split -func Help(args []string) { +// HelpCmd tries to open the given help page in a horizontal split +func (h *BufHandler) HelpCmd(args []string) { } -// VSplit opens a vertical split with file given in the first argument +// VSplitCmd 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) { +func (h *BufHandler) VSplitCmd(args []string) { buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault) if err != nil { InfoBar.Error(err) return } - MainTab().CurPane().vsplit(buf) + h.vsplit(buf) } -// HSplit opens a horizontal split with file given in the first argument +// HSplitCmd 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) { +func (h *BufHandler) HSplitCmd(args []string) { buf, err := buffer.NewBufferFromFile(args[0], buffer.BTDefault) if err != nil { InfoBar.Error(err) return } - MainTab().CurPane().hsplit(buf) + h.hsplit(buf) } -// Eval evaluates a lua expression -func Eval(args []string) { +// EvalCmd evaluates a lua expression +func (h *BufHandler) EvalCmd(args []string) { } -// NewTab opens the given file in a new tab -func NewTab(args []string) { +// NewTabCmd opens the given file in a new tab +func (h *BufHandler) NewTabCmd(args []string) { + width, height := screen.Screen.Size() + if len(args) > 0 { + for _, a := range args { + b, err := buffer.NewBufferFromFile(a, buffer.BTDefault) + if err != nil { + InfoBar.Error(err) + return + } + tp := NewTabFromBuffer(0, 0, width, height-1, b) + Tabs.AddTab(tp) + Tabs.SetActive(len(Tabs.List) - 1) + } + } else { + b := buffer.NewBufferFromString("", "", buffer.BTDefault) + tp := NewTabFromBuffer(0, 0, width, height-1, b) + Tabs.AddTab(tp) + Tabs.SetActive(len(Tabs.List) - 1) + } } -// Set sets an option -func Set(args []string) { +// SetCmd sets an option +func (h *BufHandler) SetCmd(args []string) { } -// SetLocal sets an option local to the buffer -func SetLocal(args []string) { +// SetLocalCmd sets an option local to the buffer +func (h *BufHandler) SetLocalCmd(args []string) { } -// Show shows the value of the given option -func Show(args []string) { +// ShowCmd shows the value of the given option +func (h *BufHandler) ShowCmd(args []string) { } -// ShowKey displays the action that a key is bound to -func ShowKey(args []string) { +// ShowKeyCmd displays the action that a key is bound to +func (h *BufHandler) ShowKeyCmd(args []string) { if len(args) < 1 { InfoBar.Error("Please provide a key to show") return @@ -256,12 +270,12 @@ func ShowKey(args []string) { } } -// Bind creates a new keybinding -func Bind(args []string) { +// BindCmd creates a new keybinding +func (h *BufHandler) BindCmd(args []string) { } -// Run runs a shell command in the background -func Run(args []string) { +// RunCmd runs a shell command in the background +func (h *BufHandler) RunCmd(args []string) { runf, err := shell.RunBackgroundShell(shellwords.Join(args...)) if err != nil { InfoBar.Error(err) @@ -273,28 +287,54 @@ func Run(args []string) { } } -// Quit closes the main view -func Quit(args []string) { +// QuitCmd closes the main view +func (h *BufHandler) QuitCmd(args []string) { } -// Save saves the buffer in the main view -func Save(args []string) { +// SaveCmd saves the buffer in the main view +func (h *BufHandler) SaveCmd(args []string) { } -// Replace runs search and replace -func Replace(args []string) { +// ReplaceCmd runs search and replace +func (h *BufHandler) ReplaceCmd(args []string) { } -// ReplaceAll replaces search term all at once -func ReplaceAll(args []string) { +// ReplaceAllCmd replaces search term all at once +func (h *BufHandler) ReplaceAllCmd(args []string) { } -// Term opens a terminal in the current view -func Term(args []string) { +// TermCmd opens a terminal in the current view +func (h *BufHandler) TermCmd(args []string) { + ps := MainTab().Panes + + term := func(i int) { + v := h.GetView() + t := new(shell.Terminal) + t.Start(args, false, true) + MainTab().Panes[i] = NewTermHandler(v.X, v.Y, v.Width, v.Height, t, h.ID()) + MainTab().SetActive(i) + } + + for i, p := range ps { + if p.ID() == h.ID() { + if h.Buf.Modified() { + InfoBar.YNPrompt("Save changes to "+h.Buf.GetName()+" before closing? (y,n,esc)", func(yes, canceled bool) { + if !canceled && !yes { + term(i) + } else if !canceled && yes { + h.Save() + term(i) + } + }) + } else { + term(i) + } + } + } } // HandleCommand handles input from the user -func HandleCommand(input string) { +func (h *BufHandler) HandleCommand(input string) { args, err := shellwords.Split(input) if err != nil { InfoBar.Error("Error parsing args ", err) @@ -306,6 +346,6 @@ func HandleCommand(input string) { if _, ok := commands[inputCmd]; !ok { InfoBar.Error("Unknown command ", inputCmd) } else { - commands[inputCmd].action(args[1:]) + commands[inputCmd].action(h, args[1:]) } } diff --git a/cmd/micro/action/editpane.go b/cmd/micro/action/editpane.go index b0bab86c..dfc2d78f 100644 --- a/cmd/micro/action/editpane.go +++ b/cmd/micro/action/editpane.go @@ -4,13 +4,13 @@ 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 Pane interface { Handler display.Window ID() uint64 + Name() string } type EditPane struct { @@ -34,18 +34,6 @@ func NewBufEditPane(x, y, width, height int, b *buffer.Buffer) *EditPane { return e } -func NewTabPane(x, y, width, height int, b *buffer.Buffer) *TabPane { - t := new(TabPane) - t.Node = views.NewRoot(x, y, width, height) - t.UIWindow = display.NewUIWindow(t.Node) - - e := NewBufEditPane(x, y, 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/events.go b/cmd/micro/action/events.go index 437a6789..5bf6c58e 100644 --- a/cmd/micro/action/events.go +++ b/cmd/micro/action/events.go @@ -38,4 +38,5 @@ type MouseAction func(Handler, tcell.EventMouse) bool // appropriately type Handler interface { HandleEvent(tcell.Event) + HandleCommand(string) } diff --git a/cmd/micro/action/tab.go b/cmd/micro/action/tab.go index 90f58a35..4b271fe9 100644 --- a/cmd/micro/action/tab.go +++ b/cmd/micro/action/tab.go @@ -10,19 +10,19 @@ import ( type TabList struct { *display.TabWindow - List []*TabPane + List []*Tab } func NewTabList(bufs []*buffer.Buffer) *TabList { w, h := screen.Screen.Size() tl := new(TabList) - tl.List = make([]*TabPane, len(bufs)) + tl.List = make([]*Tab, len(bufs)) if len(bufs) > 1 { for i, b := range bufs { - tl.List[i] = NewTabPane(0, 1, w, h-2, b) + tl.List[i] = NewTabFromBuffer(0, 1, w, h-2, b) } } else { - tl.List[0] = NewTabPane(0, 0, w, h-1, bufs[0]) + tl.List[0] = NewTabFromBuffer(0, 0, w, h-1, bufs[0]) } tl.TabWindow = display.NewTabWindow(w, 0) tl.Names = make([]string, len(bufs)) @@ -33,11 +33,11 @@ func NewTabList(bufs []*buffer.Buffer) *TabList { func (t *TabList) UpdateNames() { t.Names = t.Names[:0] for _, p := range t.List { - t.Names = append(t.Names, p.Panes[p.active].Buf.GetName()) + t.Names = append(t.Names, p.Panes[p.active].Name()) } } -func (t *TabList) AddTab(p *TabPane) { +func (t *TabList) AddTab(p *Tab) { t.List = append(t.List, p) t.Resize() t.UpdateNames() @@ -48,7 +48,7 @@ func (t *TabList) RemoveTab(id uint64) { if len(p.Panes) == 0 { continue } - if p.Panes[0].splitID == id { + if p.Panes[0].ID() == id { copy(t.List[i:], t.List[i+1:]) t.List[len(t.List)-1] = nil t.List = t.List[:len(t.List)-1] @@ -118,29 +118,41 @@ func InitTabs(bufs []*buffer.Buffer) { Tabs = NewTabList(bufs) } -func MainTab() *TabPane { +func MainTab() *Tab { return Tabs.List[Tabs.Active()] } -// A TabPane represents a single tab +// A Tab represents a single tab // It consists of a list of edit panes (the open buffers), // a split tree (stored as just the root node), and a uiwindow // to display the UI elements like the borders between splits -type TabPane struct { +type Tab struct { *views.Node *display.UIWindow - Panes []*EditPane + Panes []Pane active int resizing *views.Node // node currently being resized } +func NewTabFromBuffer(x, y, width, height int, b *buffer.Buffer) *Tab { + t := new(Tab) + t.Node = views.NewRoot(x, y, width, height) + t.UIWindow = display.NewUIWindow(t.Node) + + e := NewBufEditPane(x, y, width, height, b) + e.splitID = t.ID() + + t.Panes = append(t.Panes, e) + return t +} + // HandleEvent takes a tcell event and usually dispatches it to the current // active pane. However if the event is a resize or a mouse event where the user // is interacting with the UI (resizing splits) then the event is consumed here // If the event is a mouse event in a pane, that pane will become active and get // the event -func (t *TabPane) HandleEvent(event tcell.Event) { +func (t *Tab) HandleEvent(event tcell.Event) { switch e := event.(type) { case *tcell.EventMouse: mx, my := e.Position() @@ -192,7 +204,7 @@ func (t *TabPane) HandleEvent(event tcell.Event) { } // SetActive changes the currently active pane to the specified index -func (t *TabPane) SetActive(i int) { +func (t *Tab) SetActive(i int) { t.active = i for j, p := range t.Panes { if j == i { @@ -204,9 +216,9 @@ func (t *TabPane) SetActive(i int) { } // GetPane returns the pane with the given split index -func (t *TabPane) GetPane(splitid uint64) int { +func (t *Tab) GetPane(splitid uint64) int { for i, p := range t.Panes { - if p.splitID == splitid { + if p.ID() == splitid { return i } } @@ -214,16 +226,16 @@ func (t *TabPane) GetPane(splitid uint64) int { } // Remove pane removes the pane with the given index -func (t *TabPane) RemovePane(i int) { +func (t *Tab) RemovePane(i int) { copy(t.Panes[i:], t.Panes[i+1:]) t.Panes[len(t.Panes)-1] = nil t.Panes = t.Panes[:len(t.Panes)-1] } // Resize resizes all panes according to their corresponding split nodes -func (t *TabPane) Resize() { +func (t *Tab) Resize() { for _, p := range t.Panes { - n := t.GetNode(p.splitID) + n := t.GetNode(p.ID()) pv := p.GetView() offset := 0 if n.X != 0 { @@ -236,6 +248,6 @@ func (t *TabPane) Resize() { } // CurPane returns the currently active pane -func (t *TabPane) CurPane() *EditPane { +func (t *Tab) CurPane() Pane { return t.Panes[t.active] } diff --git a/cmd/micro/action/termhandler.go b/cmd/micro/action/termhandler.go index 34338a81..184daf76 100644 --- a/cmd/micro/action/termhandler.go +++ b/cmd/micro/action/termhandler.go @@ -1,8 +1,11 @@ package action import ( + "os" + "github.com/zyedidia/clipboard" "github.com/zyedidia/micro/cmd/micro/display" + "github.com/zyedidia/micro/cmd/micro/screen" "github.com/zyedidia/micro/cmd/micro/shell" "github.com/zyedidia/tcell" "github.com/zyedidia/terminal" @@ -13,6 +16,40 @@ type TermHandler struct { display.Window mouseReleased bool + id uint64 +} + +func NewTermHandler(x, y, w, h int, t *shell.Terminal, id uint64) *TermHandler { + th := new(TermHandler) + th.Terminal = t + th.id = id + th.Window = display.NewTermWindow(x, y, w, h, t) + return th +} + +func (t *TermHandler) ID() uint64 { + return t.id +} + +func (t *TermHandler) Quit() { + if len(MainTab().Panes) > 1 { + t.Unsplit() + } else if len(Tabs.List) > 1 { + Tabs.RemoveTab(t.id) + } else { + screen.Screen.Fini() + InfoBar.Close() + os.Exit(0) + } +} + +func (t *TermHandler) Unsplit() { + n := MainTab().GetNode(t.id) + n.Unsplit() + + MainTab().RemovePane(MainTab().GetPane(t.id)) + MainTab().Resize() + MainTab().SetActive(len(MainTab().Panes) - 1) } // HandleEvent handles a tcell event by forwarding it to the terminal emulator @@ -25,6 +62,7 @@ func (t *TermHandler) HandleEvent(event tcell.Event) { switch e.Key() { case tcell.KeyEscape, tcell.KeyCtrlQ, tcell.KeyEnter: t.Close() + t.Quit() default: } } @@ -64,3 +102,7 @@ func (t *TermHandler) HandleEvent(event tcell.Event) { } } } + +func (t *TermHandler) HandleCommand(input string) { + InfoBar.Error("Commands are unsupported in term for now") +} diff --git a/cmd/micro/display/termwindow.go b/cmd/micro/display/termwindow.go index e456c45b..1741ad6b 100644 --- a/cmd/micro/display/termwindow.go +++ b/cmd/micro/display/termwindow.go @@ -18,9 +18,11 @@ type TermWindow struct { func NewTermWindow(x, y, w, h int, term *shell.Terminal) *TermWindow { tw := new(TermWindow) + tw.View = new(View) tw.Terminal = term tw.X, tw.Y = x, y tw.Width, tw.Height = w, h + tw.Resize(w, h) return tw } diff --git a/cmd/micro/shell/terminal.go b/cmd/micro/shell/terminal.go index b4e5da31..ea098221 100644 --- a/cmd/micro/shell/terminal.go +++ b/cmd/micro/shell/terminal.go @@ -15,7 +15,7 @@ import ( type TermType int const ( - TTIdle = iota // Waiting for a new command + TTClose = iota // Should be closed TTRunning // Currently running a command TTDone // Finished running a command ) @@ -38,6 +38,10 @@ func (t *Terminal) HasSelection() bool { return t.Selection[0] != t.Selection[1] } +func (t *Terminal) Name() string { + return t.title +} + // GetSelection returns the selected text func (t *Terminal) GetSelection(width int) string { start := t.Selection[0] @@ -60,7 +64,7 @@ func (t *Terminal) GetSelection(width int) string { } // Start begins a new command in this terminal with a given view -func (t *Terminal) Start(execCmd []string, getOutput bool) error { +func (t *Terminal) Start(execCmd []string, getOutput bool, wait bool) error { if len(execCmd) <= 0 { return nil } @@ -78,6 +82,7 @@ func (t *Terminal) Start(execCmd []string, getOutput bool) error { t.getOutput = getOutput t.Status = TTRunning t.title = execCmd[0] + ":" + strconv.Itoa(cmd.Process.Pid) + t.wait = wait go func() { for { @@ -88,6 +93,7 @@ func (t *Terminal) Start(execCmd []string, getOutput bool) error { } screen.Redraw() } + t.Stop() // TODO: close Term // closeterm <- view.Num }() @@ -107,10 +113,10 @@ func (t *Terminal) Stop() { } } -// Close sets the Status to TTIdle indicating that the terminal -// is ready for a new command to execute +// Close sets the Status to TTClose indicating that the terminal +// is done and should be closed func (t *Terminal) Close() { - t.Status = TTIdle + t.Status = TTClose // call the lua function that the user has given as a callback if t.getOutput { // TODO: plugin callback on Term emulator diff --git a/cmd/micro/views/splits.go b/cmd/micro/views/splits.go index 6c1a6c01..55d9d751 100644 --- a/cmd/micro/views/splits.go +++ b/cmd/micro/views/splits.go @@ -2,7 +2,6 @@ package views import ( "fmt" - "log" "strings" ) @@ -440,7 +439,6 @@ func (n *Node) unsplit(i int, h bool) { copy(n.children[i:], n.children[i+1:]) n.children[len(n.children)-1] = nil n.children = n.children[:len(n.children)-1] - log.Println(len(n.children)) nonrs, numr := n.getResizeInfo(h) if numr == 0 { @@ -475,7 +473,6 @@ func (n *Node) Unsplit() { } if n.parent.IsLeaf() { - log.Println("destroy parent") n.parent.Unsplit() } }