package command import ( "bufio" "fmt" "os" "git.gophernest.net/azpect/TextEditor/internal/action" "git.gophernest.net/azpect/TextEditor/internal/core" tea "github.com/charmbracelet/bubbletea" ) func writeBuffer(m action.Model, buf *core.Buffer, args []string, force bool) (tea.Cmd, error) { // Key Steps: // - Backup Creation (if backup is set): Copy original to .bak file // - Line Ending Application: Apply fileformat setting (unix=\n, dos=\r\n, mac=\r) // - Character Encoding: Convert from internal representation to fileencoding // - Atomic Write: Write to temporary file first (.swp or similar) // - Atomic Rename: Rename temp file to target filename (atomic operation) // - Metadata Update: Clear modified flag, update timestamp // Safety Mechanisms: // " Vim's write safety // 1. Check file permissions // 2. If file exists and 'writebackup' set: // - Create backup (.bak) // 3. Write to temporary file (.swp~) // 4. Verify write succeeded // 5. Rename temp to target (atomic) // 6. Remove backup if 'backup' not set // 7. Update buffer metadata // TODO: Implement atomic and safe writes // Check readonly flag ONLY if not forced with ! if !force && buf.ReadOnly { return nil, fmt.Errorf("cannot write to 'readonly' buffer") } if !force && buf.Type == core.ScatchBuffer { return nil, fmt.Errorf("cannot write to buffer of type 'ScratchBuffer'") } // Get the filename; differs by the type var filename string if len(args) > 0 { filename = args[0] } else { if buf.Filename == "" { return nil, fmt.Errorf("cannot write: no file name provided") } filename = buf.Filename } file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0664) if err != nil { return nil, err } defer file.Close() // Same write operation regardless of where the file came from var bytes int // Using a bufio.Writer because its more efficient writer := bufio.NewWriter(file) for _, line := range buf.Lines { n, err := writer.WriteString(line.String() + "\n") if err != nil { return nil, err } bytes += n } if err := writer.Flush(); err != nil { return nil, err } m.SetCommandOutput(&core.CommandOutput{ Lines: []string{fmt.Sprintf("'%s', %dL %db written", filename, buf.LineCount(), bytes)}, Inline: true, }) buf.SetModified(false) return nil, nil }