Gim/internal/program/program_builder.go
Hayden Hargreaves 76fa55440e feat: syntax highlighting powered by chroma.
This is sick! It is not perfect, highlight groups would be cool, but for
now this works!
2026-03-16 22:48:57 -07:00

122 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).
Listed().
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...)
}