Quellcode durchsuchen

Add a bunch of layout operations

Johan Rong vor 1 Woche
Ursprung
Commit
36661fea69

+ 2 - 1
README.md

@@ -1 +1,2 @@
-# Moose
+# Moose
+- text editor

+ 1 - 0
cmd/moose/main.go

@@ -31,6 +31,7 @@ func main() {
 	s.SetStyle(style)
 
 	m := editor.NewModel(s, style)
+	m.AddBuffer(false)
 
 	s.EnableMouse()
 	s.EnablePaste()

+ 55 - 39
internal/buffer/buffer.go

@@ -1,12 +1,13 @@
 package buffer
 
 import (
+	"fmt"
+	"os"
 	"slices"
 	"unicode"
 	"unicode/utf8"
+
 	"github.com/zyedidia/rope"
-	"fmt"
-	"os"
 )
 
 type BufferManager struct {
@@ -16,7 +17,15 @@ type BufferManager struct {
 }
 
 func (bm *BufferManager) Current() *Buffer {
-	return &bm.Buffers[bm.CurrentIdx]
+    if len(bm.Buffers) == 0 {
+        return nil
+    }
+
+    if bm.CurrentIdx < 0 || bm.CurrentIdx >= len(bm.Buffers) {
+        bm.CurrentIdx = len(bm.Buffers) - 1
+    }
+
+    return &bm.Buffers[bm.CurrentIdx]
 }
 
 // TODO: implement BufferType (BufferNormal, BufferVisual, BufferInteractive).
@@ -64,6 +73,10 @@ func NewBufferFromPath(path string) Buffer {
 }
 
 func (buf *Buffer) ensureRope() {
+	if buf == nil {
+		return
+	}
+
 	if buf.Rope == nil {
 		buf.Rope = rope.New([]byte{})
 	}
@@ -73,6 +86,10 @@ func (buf *Buffer) ensureRope() {
 }
 
 func (buf *Buffer) Insert(content string) {
+	if buf == nil {
+		return
+	}
+
 	buf.ensureRope()
 	buf.CM.DeduplicateAndSort()
 
@@ -162,51 +179,51 @@ func (buf *Buffer) Delete() {
 }
 
 func (buf *Buffer) DeleteLine() {
-    buf.ensureRope()
-    buf.CM.DeduplicateAndSort()
+	buf.ensureRope()
+	buf.CM.DeduplicateAndSort()
 
-    if buf.Rope.Len() == 0 || len(buf.CM.Cursors) == 0 {
-        return
-    }
+	if buf.Rope.Len() == 0 || len(buf.CM.Cursors) == 0 {
+		return
+	}
 
-    cursorsBefore, primaryBefore := buf.begin()
+	cursorsBefore, primaryBefore := buf.begin()
 
-    primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
-    line, _ := LineCol(buf, primary.Offset)
+	primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
+	line, _ := LineCol(buf, primary.Offset)
 
-    start := OffsetForLine(buf, line)
-    end := buf.Rope.Len()
+	start := OffsetForLine(buf, line)
+	end := buf.Rope.Len()
 
 	if line+1 < LineCount(buf) {
-        end = OffsetForLine(buf, line+1)
-    } else if line > 0 && start > 0 && buf.Rope.At(start-1) == '\n' {
-        start--
-    }
+		end = OffsetForLine(buf, line+1)
+	} else if line > 0 && start > 0 && buf.Rope.At(start-1) == '\n' {
+		start--
+	}
 
-    if end <= start {
-        return
-    }
+	if end <= start {
+		return
+	}
 
-    deleted := append([]byte{}, buf.Rope.Slice(start, end)...)
-    edit := Edit{Offset: start, Deleted: deleted}
-    buf.apply(edit)
+	deleted := append([]byte{}, buf.Rope.Slice(start, end)...)
+	edit := Edit{Offset: start, Deleted: deleted}
+	buf.apply(edit)
 
-    newLine := line
-    if newLine >= LineCount(buf) {
-        newLine = LineCount(buf) - 1
-    }
-    if newLine < 0 {
-        newLine = 0
-    }
-    newOffset := OffsetForLine(buf, newLine)
+	newLine := line
+	if newLine >= LineCount(buf) {
+		newLine = LineCount(buf) - 1
+	}
+	if newLine < 0 {
+		newLine = 0
+	}
+	newOffset := OffsetForLine(buf, newLine)
 
-    for i := range buf.CM.Cursors {
-        buf.CM.Cursors[i].Offset = newOffset
-        buf.CM.Cursors[i].Goal = 0
-    }
+	for i := range buf.CM.Cursors {
+		buf.CM.Cursors[i].Offset = newOffset
+		buf.CM.Cursors[i].Goal = 0
+	}
 
-    buf.CM.DeduplicateAndSort()
-    buf.commit(cursorsBefore, primaryBefore, []Edit{edit})
+	buf.CM.DeduplicateAndSort()
+	buf.commit(cursorsBefore, primaryBefore, []Edit{edit})
 }
 
 func (buf *Buffer) Clear() {
@@ -357,7 +374,6 @@ func (buf *Buffer) AddCursorVert(dir int) {
 			continue
 		}
 
-
 		goal := buf.CM.Cursors[buf.CM.PrimaryIdx].Goal
 
 		newCursors = append(newCursors, Cursor{
@@ -557,4 +573,4 @@ func (buf Buffer) String() string {
 	}
 
 	return string(buf.Rope.Value())
-}
+}

+ 0 - 9
internal/buffer/draw.go

@@ -1,9 +0,0 @@
-package buffer
-
-import (
-	"moose/internal/layout"
-)
-
-func (b *Buffer) Draw(rect layout.Rect) {
-
-}	

+ 72 - 1
internal/editor/action.go

@@ -10,6 +10,7 @@ import(
 	"moose/internal/buffer"
 	"moose/internal/util"
 	"moose/internal/about"
+	"moose/internal/layout"
 )
 
 type ActionManager = struct {
@@ -379,7 +380,7 @@ func DefaultActionManager() ActionManager {
 				Bindings: []Binding{
 					Binding{
 						Type:  BindingChord,
-						Chord: []string{"l", "enter"}, // TODO: dette fungerer ikkje, kvifor, det veit eg ikkje...
+						Chord: []string{"l", "enter"},
 					},
 				},
 				Commands: []string{"lbuf"},
@@ -388,6 +389,76 @@ func DefaultActionManager() ActionManager {
 					m.AddBuffer(true)
 				},
 			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+left",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					m.CycleActiveBuffer(-1.0, 0.0)
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+v",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					if m.LM.CurrentSplit == layout.SplitHorizontal {
+						m.LM.CurrentSplit = layout.SplitVertical
+					} else {
+						m.LM.CurrentSplit = layout.SplitHorizontal
+					}
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+down",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					m.CycleActiveBuffer(0.0, 1.0)
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+up",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					m.CycleActiveBuffer(0.0, -1.0)
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+right",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					m.CycleActiveBuffer(1.0, 0.0)
+				},
+			},
+			Action{
+				Bindings: []Binding{
+					Binding{
+						Type:   BindingSingle,
+						Single: "ctrl+x",
+					},
+				},
+				Callback: func(m *Model, args []string) {
+					m.RemoveActiveBuffer()
+				},
+			},
 		},
 		Insert:  []Action{
 			Action{

+ 19 - 1
internal/editor/draw.go

@@ -45,8 +45,15 @@ func (m *Model) DrawPalette(rect layout.Rect) {
 			modeStr += " (replace)"
 		}
 	}
-
 	m.Screen.PutStrStyled(rect.Width - (len(modeStr) + 1), rect.Y, strings.ToUpper(modeStr), m.Style.Background(color.Black))
+
+	splitStr := ""
+	if m.LM.CurrentSplit == layout.SplitHorizontal {
+		splitStr = "H"
+	} else {
+		splitStr = "V"
+	}
+	m.Screen.PutStrStyled(rect.Width - (len(modeStr) + 1) - 5, rect.Y, splitStr, m.Style.Background(color.Black))
 	
 	logStr := m.DebugLog + ", " + strings.Join(m.AM.CM.Recorded, "+") + ", " + strconv.FormatBool(m.AM.CM.Recording)
 	m.Screen.PutStrStyled(1, rect.Y, logStr, m.Style.Background(color.Black))
@@ -120,6 +127,10 @@ func (m *Model) DrawContainer(c *layout.Container, rect layout.Rect) {
 func (m *Model) DrawContainerTabs(c *layout.ContainerBuffers, rect layout.Rect) {
 	tabs := []string{}
 	for _, bufIdx := range c.Buffers {
+		if bufIdx >= len(m.BM.Buffers) {
+			continue
+		}
+
 		buf := m.BM.Buffers[bufIdx]
 		if buf.Path == "" {
 			tabs = append(tabs, "Buffer " + strconv.Itoa(bufIdx))
@@ -141,6 +152,13 @@ func (m *Model) DrawContainerTabs(c *layout.ContainerBuffers, rect layout.Rect)
 }
 
 func (m *Model) DrawContainerBuffers(c *layout.ContainerBuffers, rect layout.Rect) {
+	if c.ActiveIdx >= len(c.Buffers) {
+		return
+	}
+	if c.Buffers[c.ActiveIdx] >= len(m.BM.Buffers) {
+		return
+	}
+
 	m.DrawBuffer(&m.BM.Buffers[c.Buffers[c.ActiveIdx]], c.Buffers[c.ActiveIdx] == m.BM.CurrentIdx, rect)
 }
 

+ 31 - 1
internal/editor/layout.go

@@ -2,6 +2,7 @@ package editor
 
 import (
 	"moose/internal/buffer"
+	"slices"
 )
 
 func (m *Model) AddBuffer(newNode bool) {
@@ -10,7 +11,6 @@ func (m *Model) AddBuffer(newNode bool) {
 	m.BM.Buffers = append(m.BM.Buffers, b)
 	m.BM.CurrentIdx = idx
 
-	//m.LM.Workspaces[m.LM.ActiveIdx].RootContainer.InsertBufferInContainer(idx)
 	m.LM.InsertBuffer(idx, newNode)
 }
 
@@ -21,4 +21,34 @@ func (m *Model) AddBufferFromPath(path string, newNode bool) {
 	m.BM.CurrentIdx = idx
 
 	m.LM.InsertBuffer(idx, newNode)
+}
+
+func (m *Model) CycleActiveBuffer(horisontal float32, vertical float32) {
+	m.LM.CycleActiveBuffer(horisontal, vertical)
+	bufIdx := m.LM.GetActiveBufferIdx()
+	m.BM.CurrentIdx = bufIdx
+}
+
+func (m *Model) RemoveActiveBuffer() {
+    if len(m.BM.Buffers) == 0 {
+        m.BM.Buffers = []buffer.Buffer{buffer.NewBuffer()}
+        m.BM.CurrentIdx = 0
+        return
+    }
+
+    removedIdx := m.BM.CurrentIdx
+    newActiveIdx := m.LM.RemoveActiveBufferAndReindex(removedIdx)
+    m.BM.Buffers = slices.Delete(m.BM.Buffers, removedIdx, removedIdx+1)
+
+    if len(m.BM.Buffers) == 0 {
+        m.BM.Buffers = []buffer.Buffer{buffer.NewBuffer()}
+        m.BM.CurrentIdx = 0
+        return
+    }
+
+    if newActiveIdx < 0 || newActiveIdx >= len(m.BM.Buffers) {
+        newActiveIdx = len(m.BM.Buffers) - 1
+    }
+
+    m.BM.CurrentIdx = newActiveIdx
 }

+ 16 - 15
internal/editor/model.go

@@ -18,23 +18,24 @@ type Model struct {
 }
 
 func NewModel(screen tcell.Screen, style tcell.Style) Model {
-	model := Model{
-		Screen:	       screen,
-		Style:		   style,
-		Mode:		   ModeNormal,
-		BM:            buffer.BufferManager{
-			Buffers:       []buffer.Buffer{buffer.NewBuffer()},
-			CurrentIdx:    0,
-			PaletteBuffer: buffer.NewBuffer(),
-		},
-		AM:            DefaultActionManager(),
-		LM:			   layout.NewLayoutManager(),
-		ShouldQuit:    false,
-	}
+    blank := buffer.NewBuffer()
 
-	return model
-}
+    model := Model{
+        Screen: screen,
+        Style:  style,
+        Mode:   ModeNormal,
+        BM: buffer.BufferManager{
+            Buffers:       []buffer.Buffer{blank},
+            CurrentIdx:    0,
+            PaletteBuffer: buffer.NewBuffer(),
+        },
+        AM:         DefaultActionManager(),
+        LM:         layout.NewLayoutManager(),
+        ShouldQuit: false,
+    }
 
+    return model
+}
 func (m *Model) CurrentActionSet() []Action {
 	switch m.Mode {
 	case ModeNormal:  return m.AM.Normal

+ 182 - 14
internal/layout/layout.go

@@ -58,20 +58,14 @@ func NewWorkspace() Workspace {
 }
 
 func NewContainerEmpty() Container {
-	return Container{
-		Children: 	    [2]ContainerNode{
-			ContainerBuffers{
-				Buffers: []int{0},
-				ActiveIdx: 0,
-			},
-			ContainerBuffers{
-				Buffers: []int{0},
-				ActiveIdx: 0,
-			},
-		},
-		Split:	  	    SplitVertical,
-		ActiveChildIdx: 0,
-	}
+    return Container{
+        Children: [2]ContainerNode{
+            nil,
+            nil,
+        },
+        Split:          SplitVertical,
+        ActiveChildIdx: 0,
+    }
 }
 
 func (c *Container) WalkAndMutateActive(fn func(cb *ContainerBuffers) ContainerNode) {
@@ -110,4 +104,178 @@ func (lm *LayoutManager) InsertBuffer(bufferIdx int, newNode bool) {
 	})
 
 	lm.Workspaces[lm.ActiveIdx] = workspace
+}
+
+func (lm *LayoutManager) CycleActiveBuffer(horisontal float32, vertical float32) {
+    if horisontal == 0 && vertical == 0 {
+        return
+    }
+
+    ws := lm.Workspaces[lm.ActiveIdx]
+
+    if newRoot, moved := ws.RootContainer.NavigateContainer(horisontal, vertical); moved {
+        ws.RootContainer = newRoot
+        lm.Workspaces[lm.ActiveIdx] = ws
+    }
+}
+
+func (c Container) NavigateContainer(horisontal float32, vertical float32) (Container, bool) {
+    activeChild := c.Children[c.ActiveChildIdx]
+
+    if childContainer, ok := activeChild.(Container); ok {
+        updatedChild, moved := childContainer.NavigateContainer(horisontal, vertical)
+        if moved {
+            c.Children[c.ActiveChildIdx] = updatedChild
+            return c, true
+        }
+    }
+
+    targetIdx := -1
+
+    if horisontal != 0 && c.Split == SplitHorizontal {
+        if horisontal < 0 && c.ActiveChildIdx == 1 {
+            targetIdx = 0 // Try moving Left
+        } else if horisontal > 0 && c.ActiveChildIdx == 0 {
+            targetIdx = 1 // Try moving Right
+        }
+    } else if vertical != 0 && c.Split == SplitVertical {
+        if vertical < 0 && c.ActiveChildIdx == 1 {
+            targetIdx = 0 // Try moving Up
+        } else if vertical > 0 && c.ActiveChildIdx == 0 {
+            targetIdx = 1 // Try moving Down
+        }
+    }
+
+    if targetIdx != -1 && isPopulated(c.Children[targetIdx]) {
+        c.ActiveChildIdx = targetIdx
+        return c, true
+    }
+
+    return c, false
+}
+
+func isPopulated(node ContainerNode) bool {
+    if node == nil {
+        return false
+    }
+
+    switch n := node.(type) {
+    case ContainerBuffers:
+        return len(n.Buffers) > 0
+    case Container:
+        return isPopulated(n.Children[0]) || isPopulated(n.Children[1])
+    default:
+        return false
+    }
+}
+
+func (c Container) GetActiveBufferIdx() int {
+    switch child := c.Children[c.ActiveChildIdx].(type) {
+    case ContainerBuffers:
+        if len(child.Buffers) > 0 && child.ActiveIdx < len(child.Buffers) {
+            return child.Buffers[child.ActiveIdx]
+        }
+    case Container:
+        return child.GetActiveBufferIdx()
+    }
+    return -1
+}
+
+func (lm LayoutManager) GetActiveBufferIdx() int {
+    ws, ok := lm.Workspaces[lm.ActiveIdx]
+    if !ok {
+        return -1
+    }
+    return ws.RootContainer.GetActiveBufferIdx()
+}
+
+func (c Container) RemoveActive() (ContainerNode, bool) {
+    activeChild := c.Children[c.ActiveChildIdx]
+
+    switch child := activeChild.(type) {
+    case ContainerBuffers:
+        if len(child.Buffers) == 0 {
+            return nil, true
+        }
+
+        child.Buffers = append(child.Buffers[:child.ActiveIdx], child.Buffers[child.ActiveIdx+1:]...)
+
+        if child.ActiveIdx >= len(child.Buffers) && len(child.Buffers) > 0 {
+            child.ActiveIdx = len(child.Buffers) - 1
+        }
+
+        if len(child.Buffers) == 0 {
+            siblingIdx := 1 - c.ActiveChildIdx
+            if siblingIdx < 0 || siblingIdx >= len(c.Children) || c.Children[siblingIdx] == nil {
+                return nil, true
+            }
+            return c.Children[siblingIdx], false
+        }
+
+        c.Children[c.ActiveChildIdx] = child
+        return c, false
+
+    case Container:
+        updatedChild, childIsEmpty := child.RemoveActive()
+        if childIsEmpty {
+            siblingIdx := 1 - c.ActiveChildIdx
+            if siblingIdx < 0 || siblingIdx >= len(c.Children) || c.Children[siblingIdx] == nil {
+                return nil, true
+            }
+            return c.Children[siblingIdx], false
+        }
+
+        c.Children[c.ActiveChildIdx] = updatedChild
+        return c, false
+
+    default:
+        return nil, true
+    }
+}
+
+func (lm *LayoutManager) RemoveActiveBufferAndReindex(removedIdx int) int {
+    ws := lm.Workspaces[lm.ActiveIdx]
+
+    newRoot, isEmpty := ws.RootContainer.RemoveActive()
+    if newRoot == nil || isEmpty {
+        ws.RootContainer = NewContainerEmpty()
+        lm.Workspaces[lm.ActiveIdx] = ws
+        return -1
+    }
+
+    switch root := newRoot.(type) {
+    case Container:
+        ws.RootContainer = root
+    case ContainerBuffers:
+        ws.RootContainer = Container{
+            Children:       [2]ContainerNode{root, nil},
+            Split:          ws.RootContainer.Split,
+            ActiveChildIdx: 0,
+        }
+    }
+
+    ws.RootContainer.DecrementIndicesAbove(removedIdx)
+    lm.Workspaces[lm.ActiveIdx] = ws
+
+    return ws.RootContainer.GetActiveBufferIdx()
+}
+
+func (c Container) DecrementIndicesAbove(threshold int) {
+    for i := range c.Children {
+        if c.Children[i] == nil {
+            continue
+        }
+        switch child := c.Children[i].(type) {
+        case ContainerBuffers:
+            for bIdx := range child.Buffers {
+                if child.Buffers[bIdx] > threshold {
+                    child.Buffers[bIdx]--
+                }
+            }
+            c.Children[i] = child
+        case Container:
+            child.DecrementIndicesAbove(threshold)
+            c.Children[i] = child
+        }
+    }
 }