Gim/internal/program/program_builder.go
Hayden Hargreaves d4980c5532
All checks were successful
Run Test Suite / test (push) Successful in 15s
feat: created (tested) program_builder.
Also adjusted some of the IO tests for writing and force writing.
2026-03-10 14:18:20 -07:00

121 lines
3.0 KiB
Go

package program
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
"git.gophernest.net/azpect/TextEditor/internal/editor"
tea "github.com/charmbracelet/bubbletea"
)
type ProgramBuilder struct {
model *editor.Model
opts []tea.ProgramOption
}
func NewProgramBuilder() *ProgramBuilder {
return &ProgramBuilder{
model: &editor.Model{},
opts: []tea.ProgramOption{},
}
}
// ProgramBuilder.FileProgram: Sets the internal state of the builder to the required
// state to start the program (editor) with a filename. This is what will happen when
// a user runs 'gim <filename>'.
func (p *ProgramBuilder) FileProgram(filename string) *ProgramBuilder {
// Only difference: open the file
ext := filepath.Ext(filename)
file, err := os.Open(filename)
notFound := errors.Is(err, os.ErrNotExist)
if err != nil && !notFound {
// TODO: Handle this
panic(fmt.Errorf("Failed to find file: %w", err))
}
if file != nil {
defer file.Close()
}
buf := core.NewBufferBuilder().
WithType(core.FileBuffer).
WithFilename(filename).
WithFiletype(ext).
Build()
win := core.NewWindowBuilder().
WithBuffer(&buf).
WithOptions(core.NewDefaultWinOptions()).
Build()
p.model = editor.NewModelBuilder().
AddBuffer(&buf).
AddWindow(&win).
WithActiveWindowId(win.Id).
Build()
// If we did not find the file, all we need to do is set the filename and type
if notFound {
return p
}
// Otherwise we have to create everything, then read the file (since we need settings)
// COPIED FROM `internal/command/handlers.go`
var lines []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSuffix(line, "\r")
// BUG: This is bad, we don't want to this, but we have to
cleaned := strings.ReplaceAll(line, "\t", strings.Repeat(" ", p.model.Settings().TabStop))
lines = append(lines, cleaned)
}
// Only setting lines if we found some
if len(lines) > 0 {
p.model.ActiveBuffer().SetLines(lines)
}
return p
}
// ProgramBuilder.EmptyProgram: Sets the internal state of the builder to the required
// state to start the program (editor) in the current directory. This is what will
// happen when a user runs 'gim'.
func (p *ProgramBuilder) EmptyProgram() *ProgramBuilder {
buf := core.NewBufferBuilder().
ReadOnly().
Build()
win := core.NewWindowBuilder().
WithBuffer(&buf).
WithOptions(core.NewDefaultWinOptions()).
Build()
p.model = editor.NewModelBuilder().
AddBuffer(&buf).
AddWindow(&win).
WithActiveWindowId(win.Id).
Build()
return p
}
// ProgramBuilder.WithOpt: Add an option to the list of options that will be used when
// the program is built.
func (p *ProgramBuilder) WithOpt(opt tea.ProgramOption) *ProgramBuilder {
p.opts = append(p.opts, opt)
return p
}
// ProgramBuilder.Build: Build and return the configured tea.Program instance.
func (p *ProgramBuilder) Build() *tea.Program {
return tea.NewProgram(p.model, p.opts...)
}