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 '. 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...) }