wip: implement windows, buffers and builders
This commit is contained in:
parent
3339dd4409
commit
ea4638d815
@ -22,26 +22,12 @@ type Buffer struct {
|
||||
// UndoTree TODO: This will be big
|
||||
}
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentBufferId int = 1
|
||||
// ==================================================
|
||||
// Helper methods
|
||||
// ==================================================
|
||||
|
||||
func NewEmptyBuffer(lines []string) *Buffer {
|
||||
buf := Buffer{
|
||||
Id: CurrentBufferId,
|
||||
Filename: "",
|
||||
Filetype: "",
|
||||
Lines: lines,
|
||||
Modified: false,
|
||||
Loaded: true,
|
||||
Listed: true,
|
||||
}
|
||||
|
||||
CurrentBufferId++
|
||||
|
||||
return &buf
|
||||
}
|
||||
|
||||
// Get the line at an index
|
||||
// Buffer.Line: Get the line at an index. Returns an empty string if the index
|
||||
// is out of bounds.
|
||||
func (b *Buffer) Line(idx int) string {
|
||||
if idx < 0 || idx >= len(b.Lines) {
|
||||
return ""
|
||||
@ -49,14 +35,17 @@ func (b *Buffer) Line(idx int) string {
|
||||
return b.Lines[idx]
|
||||
}
|
||||
|
||||
// Set the content at an index.
|
||||
// Buffer.SetLine: Set the content of the line at an index. Does nothing if the
|
||||
// index is out of bounds.
|
||||
func (b *Buffer) SetLine(idx int, content string) {
|
||||
if idx >= 0 && idx < len(b.Lines) {
|
||||
b.Lines[idx] = content
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a line with content at an index
|
||||
// Buffer.InsertLine: Insert a line with content at an index. The index is clamped
|
||||
// to valid bounds (0 to len(Lines)). The new line is inserted before the line at
|
||||
// the given index.
|
||||
func (b *Buffer) InsertLine(idx int, content string) {
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
@ -67,14 +56,56 @@ func (b *Buffer) InsertLine(idx int, content string) {
|
||||
b.Lines = append(b.Lines[:idx], append([]string{content}, b.Lines[idx:]...)...)
|
||||
}
|
||||
|
||||
// Delete a line at an index
|
||||
// Buffer.DeleteLine: Delete a line at an index. Does nothing if the index is out
|
||||
// of bounds.
|
||||
func (b *Buffer) DeleteLine(idx int) {
|
||||
if idx >= 0 && idx < len(b.Lines) {
|
||||
b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the number of lines in the buffer
|
||||
// Buffer.LineCount: Get the number of lines in the buffer.
|
||||
func (b *Buffer) LineCount() int {
|
||||
return len(b.Lines)
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Setters
|
||||
// ==================================================
|
||||
|
||||
// Buffer.SetFilename: Set the filename associated with this buffer. This is
|
||||
// typically the path to the file on disk that this buffer represents.
|
||||
func (b *Buffer) SetFilename(filename string) {
|
||||
b.Filename = filename
|
||||
}
|
||||
|
||||
// Buffer.SetFiletype: Set the filetype of this buffer. The filetype is used
|
||||
// for syntax highlighting and other language-specific features.
|
||||
func (b *Buffer) SetFiletype(filetype string) {
|
||||
b.Filetype = filetype
|
||||
}
|
||||
|
||||
// Buffer.SetLines: Replace all lines in the buffer with the provided lines.
|
||||
// This is useful when loading a file or resetting buffer content.
|
||||
func (b *Buffer) SetLines(lines []string) {
|
||||
b.Lines = lines
|
||||
}
|
||||
|
||||
// Buffer.SetModified: Set the modified flag for this buffer. A modified buffer
|
||||
// has unsaved changes that differ from the file on disk.
|
||||
func (b *Buffer) SetModified(modified bool) {
|
||||
b.Modified = modified
|
||||
}
|
||||
|
||||
// Buffer.SetLoaded: Set the loaded flag for this buffer. A loaded buffer has
|
||||
// its content in memory, while an unloaded buffer exists only as metadata.
|
||||
func (b *Buffer) SetLoaded(loaded bool) {
|
||||
b.Loaded = loaded
|
||||
}
|
||||
|
||||
// Buffer.SetListed: Set the listed flag for this buffer. A listed buffer appears
|
||||
// in buffer lists (like :ls), while an unlisted buffer is hidden from normal
|
||||
// buffer navigation.
|
||||
func (b *Buffer) SetListed(listed bool) {
|
||||
b.Listed = listed
|
||||
}
|
||||
|
||||
73
internal/action/buffer_builder.go
Normal file
73
internal/action/buffer_builder.go
Normal file
@ -0,0 +1,73 @@
|
||||
package action
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentBufferId int = 1
|
||||
|
||||
type BufferBuilder struct {
|
||||
buffer Buffer
|
||||
}
|
||||
|
||||
// NewBufferBuilder: Creates a new buffer builder. The buffer builder implements a
|
||||
// builder pattern to create a buffer with the defined properties and values.
|
||||
func NewBufferBuilder() *BufferBuilder {
|
||||
return &BufferBuilder{
|
||||
buffer: Buffer{
|
||||
Id: 0, // This is set when built
|
||||
Filename: "",
|
||||
Filetype: "",
|
||||
Lines: []string{""},
|
||||
Modified: false,
|
||||
Loaded: false,
|
||||
Listed: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BufferBuilder.WithFilename: Attaches a file name to the buffer that is being built.
|
||||
func (b *BufferBuilder) WithFilename(filename string) *BufferBuilder {
|
||||
b.buffer.Filename = filename
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.WithFiletype: Attaches a file type to the buffer that is being built.
|
||||
func (b *BufferBuilder) WithFiletype(filetype string) *BufferBuilder {
|
||||
b.buffer.Filetype = filetype
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.WithLines: Attaches a lines to the buffer that is being built.
|
||||
func (b *BufferBuilder) WithLines(lines []string) *BufferBuilder {
|
||||
b.buffer.Lines = lines
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.Modified: Sets the modified flag of the buffer being built. By default,
|
||||
// buffers are built with the modified flag being false.
|
||||
func (b *BufferBuilder) Modified() *BufferBuilder {
|
||||
b.buffer.Modified = true
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.Loaded: Sets the loaded flag of the buffer being built. By default,
|
||||
// buffers are built with the loaded flag being false.
|
||||
func (b *BufferBuilder) Loaded() *BufferBuilder {
|
||||
b.buffer.Loaded = true
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.Listed: Sets the listed flag of the buffer being built. By default,
|
||||
// buffers are built with the listed flag being false.
|
||||
func (b *BufferBuilder) Listed() *BufferBuilder {
|
||||
b.buffer.Listed = true
|
||||
return b
|
||||
}
|
||||
|
||||
// BufferBuilder.Build: Build the final buffer and return it to the caller. Final
|
||||
// step in the process. This is where the ID is set, so many buffers can be "in-progress"
|
||||
// but the ID will be set when they are built. Meaning, this is not thread safe.
|
||||
func (b *BufferBuilder) Build() Buffer {
|
||||
b.buffer.Id = CurrentBufferId
|
||||
CurrentBufferId++
|
||||
|
||||
return b.buffer
|
||||
}
|
||||
@ -16,36 +16,20 @@ type Window struct {
|
||||
Anchor Position
|
||||
|
||||
ScrollY int
|
||||
Width int
|
||||
Height int
|
||||
Width int
|
||||
|
||||
// Folds // TODO
|
||||
// Options WinOptions
|
||||
}
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentWindowId int = 1000
|
||||
|
||||
func NewEmptyWindow(lines []string, w, h int) *Window {
|
||||
win := &Window{
|
||||
Id: CurrentWindowId,
|
||||
Number: 1, // Ignored for now
|
||||
|
||||
Buffer: NewEmptyBuffer(lines),
|
||||
|
||||
Cursor: Position{Line: 0, Col: 0},
|
||||
Anchor: Position{Line: 0, Col: 0},
|
||||
|
||||
ScrollY: 0,
|
||||
Width: w,
|
||||
Height: h,
|
||||
}
|
||||
|
||||
// Increment
|
||||
CurrentWindowId++
|
||||
|
||||
return win
|
||||
}
|
||||
// ==================================================
|
||||
// Helper methods
|
||||
// ==================================================
|
||||
|
||||
// Window.ClampCursorX: Clamps the cursor in the X direction to ensure the cursor
|
||||
// does not go into an invalid position. Such as negative values or past the end of
|
||||
// the line.
|
||||
func (w *Window) ClampCursorX() {
|
||||
lineLen := len(w.Buffer.Lines[w.Cursor.Line])
|
||||
if lineLen == 0 {
|
||||
@ -54,3 +38,87 @@ func (w *Window) ClampCursorX() {
|
||||
w.Cursor.Col = lineLen
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Setters
|
||||
// ==================================================
|
||||
|
||||
// Window.SetNumber: Sets the position-based number of this window. Currently ignored
|
||||
// until splits are implemented.
|
||||
func (w *Window) SetNumber(number int) {
|
||||
w.Number = number
|
||||
}
|
||||
|
||||
// Window.SetBuffer: Sets the buffer that this window should display. This is used when
|
||||
// switching between buffers or opening a new file in the current window.
|
||||
func (w *Window) SetBuffer(buffer *Buffer) {
|
||||
w.Buffer = buffer
|
||||
}
|
||||
|
||||
// Window.SetCursor: Sets the cursor position in this window to the given position.
|
||||
func (w *Window) SetCursor(cursor Position) {
|
||||
w.Cursor = cursor
|
||||
}
|
||||
|
||||
// Window.SetCursorLine: Sets the line number of the cursor position.
|
||||
func (w *Window) SetCursorLine(line int) {
|
||||
w.Cursor.Line = line
|
||||
}
|
||||
|
||||
// Window.SetCursorCol: Sets the column number of the cursor position.
|
||||
func (w *Window) SetCursorCol(col int) {
|
||||
w.Cursor.Col = col
|
||||
}
|
||||
|
||||
// Window.SetCursorPos: Sets both the line and column of the cursor position. This is
|
||||
// a convenience method for setting both components at once.
|
||||
func (w *Window) SetCursorPos(line, col int) {
|
||||
w.Cursor.Line = line
|
||||
w.Cursor.Col = col
|
||||
}
|
||||
|
||||
// Window.SetAnchor: Sets the anchor position in this window. The anchor is used for
|
||||
// visual mode selections as the starting point of the selection.
|
||||
func (w *Window) SetAnchor(anchor Position) {
|
||||
w.Anchor = anchor
|
||||
}
|
||||
|
||||
// Window.SetAnchorLine: Sets the line number of the anchor position.
|
||||
func (w *Window) SetAnchorLine(line int) {
|
||||
w.Anchor.Line = line
|
||||
}
|
||||
|
||||
// Window.SetAnchorCol: Sets the column number of the anchor position.
|
||||
func (w *Window) SetAnchorCol(col int) {
|
||||
w.Anchor.Col = col
|
||||
}
|
||||
|
||||
// Window.SetAnchorPos: Sets both the line and column of the anchor position. This is
|
||||
// a convenience method for setting both components at once.
|
||||
func (w *Window) SetAnchorPos(line, col int) {
|
||||
w.Anchor.Line = line
|
||||
w.Anchor.Col = col
|
||||
}
|
||||
|
||||
// Window.SetScrollY: Sets the vertical scroll offset of this window. This determines
|
||||
// which line appears at the top of the visible viewport.
|
||||
func (w *Window) SetScrollY(scrollY int) {
|
||||
w.ScrollY = scrollY
|
||||
}
|
||||
|
||||
// Window.SetHeight: Sets the height of this window in lines.
|
||||
func (w *Window) SetHeight(height int) {
|
||||
w.Height = height
|
||||
}
|
||||
|
||||
// Window.SetWidth: Sets the width of this window in columns.
|
||||
func (w *Window) SetWidth(width int) {
|
||||
w.Width = width
|
||||
}
|
||||
|
||||
// Window.SetDimensions: Sets both the width and height of this window. This is a
|
||||
// convenience method for setting both dimensions at once.
|
||||
func (w *Window) SetDimensions(width, height int) {
|
||||
w.Width = width
|
||||
w.Height = height
|
||||
}
|
||||
|
||||
103
internal/action/window_builder.go
Normal file
103
internal/action/window_builder.go
Normal file
@ -0,0 +1,103 @@
|
||||
package action
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentWindowId int = 1000
|
||||
|
||||
type WindowBuilder struct {
|
||||
window Window
|
||||
}
|
||||
|
||||
// NewWindowBuilder: Creates a new window builder. The window builder implements a
|
||||
// builder pattern to create a window with the defined properties and values.
|
||||
func NewWindowBuilder() *WindowBuilder {
|
||||
return &WindowBuilder{
|
||||
window: Window{
|
||||
Id: 0, // This is set when built
|
||||
Number: 1, // Ignored for now, will be used for splits
|
||||
Buffer: nil,
|
||||
Cursor: Position{Line: 0, Col: 0},
|
||||
Anchor: Position{Line: 0, Col: 0},
|
||||
ScrollY: 0,
|
||||
Height: 0,
|
||||
Width: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// WindowBuilder.WithNumber: Attaches a window number to the window that is being built.
|
||||
// Window numbers are position-based and change when windows are rearranged. This is
|
||||
// ignored for now, but will be used when splits are implemented.
|
||||
func (w *WindowBuilder) WithNumber(number int) *WindowBuilder {
|
||||
w.window.Number = number
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithBuffer: Attaches a buffer to the window that is being built. The
|
||||
// window will display and edit the content of this buffer.
|
||||
func (w *WindowBuilder) WithBuffer(buffer *Buffer) *WindowBuilder {
|
||||
w.window.Buffer = buffer
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithCursor: Sets the cursor position in the window that is being built.
|
||||
func (w *WindowBuilder) WithCursor(cursor Position) *WindowBuilder {
|
||||
w.window.Cursor = cursor
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithCursorPos: Sets the cursor position in the window that is being built.
|
||||
// This is an alias for WithCursor that accepts line and column separately.
|
||||
func (w *WindowBuilder) WithCursorPos(line, col int) *WindowBuilder {
|
||||
w.window.Cursor = Position{Line: line, Col: col}
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithAnchor: Sets the anchor position in the window that is being built.
|
||||
// The anchor is used for visual mode selections.
|
||||
func (w *WindowBuilder) WithAnchor(anchor Position) *WindowBuilder {
|
||||
w.window.Anchor = anchor
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithAnchorPos: Sets the anchor position in the window that is being built.
|
||||
// This is an alias for WithAnchor that accepts line and column separately.
|
||||
func (w *WindowBuilder) WithAnchorPos(line, col int) *WindowBuilder {
|
||||
w.window.Anchor = Position{Line: line, Col: col}
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithScrollY: Sets the vertical scroll offset of the window that is being built.
|
||||
func (w *WindowBuilder) WithScrollY(scrollY int) *WindowBuilder {
|
||||
w.window.ScrollY = scrollY
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithHeight: Sets the height of the window that is being built.
|
||||
func (w *WindowBuilder) WithHeight(height int) *WindowBuilder {
|
||||
w.window.Height = height
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithWidth: Sets the width of the window that is being built.
|
||||
func (w *WindowBuilder) WithWidth(width int) *WindowBuilder {
|
||||
w.window.Width = width
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.WithDimensions: Sets both width and height of the window that is being built.
|
||||
// This is a convenience method for setting dimensions in one call.
|
||||
func (w *WindowBuilder) WithDimensions(width, height int) *WindowBuilder {
|
||||
w.window.Width = width
|
||||
w.window.Height = height
|
||||
return w
|
||||
}
|
||||
|
||||
// WindowBuilder.Build: Build the final window and return it to the caller. Final
|
||||
// step in the process. This is where the ID is set, so many windows can be "in-progress"
|
||||
// but the ID will be set when they are built. Meaning, this is not thread safe.
|
||||
func (w *WindowBuilder) Build() Window {
|
||||
w.window.Id = CurrentWindowId
|
||||
CurrentWindowId++
|
||||
|
||||
return w.window
|
||||
}
|
||||
@ -10,31 +10,36 @@ import (
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
// lines []string
|
||||
// cursor cursor
|
||||
// anchor cursor // starting point for visual modes
|
||||
// scrollY int
|
||||
mode action.Mode
|
||||
win_h int
|
||||
win_w int
|
||||
// Buffers
|
||||
buffers []*action.Buffer
|
||||
//next buffer id?
|
||||
|
||||
activeWindow int
|
||||
// Windows
|
||||
windows []*action.Window
|
||||
activeWindowId int
|
||||
|
||||
// Editor wide state
|
||||
mode action.Mode
|
||||
|
||||
// Terminal dimensions
|
||||
termWidth int
|
||||
termHeight int
|
||||
|
||||
// Input and key handling
|
||||
input *input.Handler
|
||||
|
||||
// Insert repetition
|
||||
// Insert mode state & repetition (applied to active window)
|
||||
insertCount int
|
||||
insertKeys []string
|
||||
insertAction action.Action
|
||||
|
||||
// Command mode
|
||||
// Command line state
|
||||
command string
|
||||
commandCursor int
|
||||
commandError error
|
||||
commandOutput string
|
||||
|
||||
// Settings
|
||||
// Global settings (TODO: This needs to be refactored)
|
||||
settings action.Settings
|
||||
|
||||
// Registers
|
||||
@ -43,12 +48,6 @@ type Model struct {
|
||||
|
||||
func NewModel(lines []string, pos action.Position) *Model {
|
||||
m := Model{
|
||||
// lines: lines,
|
||||
// cursor: cursor{
|
||||
// x: pos.Col,
|
||||
// y: pos.Line,
|
||||
// },
|
||||
// scrollY: 0,
|
||||
mode: action.NormalMode,
|
||||
command: "",
|
||||
input: input.NewHandler(),
|
||||
@ -58,11 +57,22 @@ func NewModel(lines []string, pos action.Position) *Model {
|
||||
windows: []*action.Window{},
|
||||
}
|
||||
|
||||
// Temporary
|
||||
win := action.NewEmptyWindow(lines, 0, 0)
|
||||
win.Cursor = pos // Set initial cursor position
|
||||
m.windows = append(m.windows, win)
|
||||
m.activeWindow = win.Id
|
||||
// TODO: Temporary: Build the single buffer and window
|
||||
buf := action.
|
||||
NewBufferBuilder().
|
||||
WithLines(lines).
|
||||
Build()
|
||||
|
||||
m.buffers = append(m.buffers, &buf)
|
||||
|
||||
win := action.
|
||||
NewWindowBuilder().
|
||||
WithBuffer(&buf).
|
||||
WithCursor(pos).
|
||||
Build()
|
||||
|
||||
m.windows = append(m.windows, &win)
|
||||
m.activeWindowId = win.Id
|
||||
|
||||
return &m
|
||||
}
|
||||
@ -297,7 +307,7 @@ func (m *Model) Windows() []*action.Window {
|
||||
}
|
||||
|
||||
func (m *Model) ActiveWindowId() int {
|
||||
return m.activeWindow
|
||||
return m.activeWindowId
|
||||
}
|
||||
|
||||
func (m *Model) ActiveWindow() *action.Window {
|
||||
|
||||
@ -10,10 +10,37 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.win_h = msg.Height
|
||||
m.win_w = msg.Width
|
||||
m.termHeight = msg.Height
|
||||
m.termWidth = msg.Width
|
||||
|
||||
// TODO: Temp, this is lame
|
||||
// TODO: Implement a layout method that handles this
|
||||
//
|
||||
// func (m *Model) layoutWindows() {
|
||||
// if len(m.windows) == 0 {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if len(m.windows) == 1 {
|
||||
// // Single window - full screen
|
||||
// m.windows[0].Width = m.termWidth
|
||||
// m.windows[0].Height = m.termHeight
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // Multiple windows - distribute space
|
||||
// // This is where you'd implement split layout logic
|
||||
// // For example, horizontal split:
|
||||
// halfHeight := m.termHeight / 2
|
||||
// for i, win := range m.windows {
|
||||
// win.Width = m.termWidth
|
||||
// if i < len(m.windows)-1 {
|
||||
// win.Height = halfHeight
|
||||
// } else {
|
||||
// // Last window gets remainder
|
||||
// win.Height = m.termHeight - (halfHeight * (len(m.windows) - 1))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for i := range m.windows {
|
||||
m.windows[i].Height = msg.Height
|
||||
m.windows[i].Width = msg.Width
|
||||
|
||||
@ -146,7 +146,7 @@ func drawStatusBar(m Model) string {
|
||||
left := leftBar(m)
|
||||
right := rightBar(m)
|
||||
|
||||
diff := m.win_w - (len(left) + len(right))
|
||||
diff := m.termWidth - (len(left) + len(right))
|
||||
|
||||
// This happens when the terminal spawns
|
||||
if diff <= 0 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user