package buffer
import (
+ "bufio"
"bytes"
"errors"
"io"
"os/exec"
"os/signal"
"path/filepath"
+ "runtime"
"unicode"
"unicode/utf8"
"github.com/zyedidia/micro/internal/config"
- . "github.com/zyedidia/micro/internal/util"
+ "github.com/zyedidia/micro/internal/screen"
+ "github.com/zyedidia/micro/internal/util"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/htmlindex"
"golang.org/x/text/transform"
// overwriteFile opens the given file for writing, truncating if one exists, and then calls
// the supplied function with the file as io.Writer object, also making sure the file is
// closed afterwards.
-func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error) (err error) {
- var file *os.File
+func overwriteFile(name string, enc encoding.Encoding, fn func(io.Writer) error, withSudo bool) (err error) {
+ var writeCloser io.WriteCloser
- if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
- return
- }
+ if withSudo {
+ cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "dd", "bs=4k", "of="+name)
- defer func() {
- if e := file.Close(); e != nil && err == nil {
- err = e
+ if writeCloser, err = cmd.StdinPipe(); err != nil {
+ return
}
- }()
- w := transform.NewWriter(file, enc.NewEncoder())
- // w := bufio.NewWriter(file)
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ go func() {
+ <-c
+ cmd.Process.Kill()
+ }()
- if err = fn(w); err != nil {
+ defer func() {
+ screenb := screen.TempFini()
+ if e := cmd.Run(); e != nil && err == nil {
+ err = e
+ }
+ screen.TempStart(screenb)
+ }()
+ } else if writeCloser, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
return
}
- // err = w.Flush()
+ w := bufio.NewWriter(transform.NewWriter(writeCloser, enc.NewEncoder()))
+ err = fn(w)
+ w.Flush()
+
+ if e := writeCloser.Close(); e != nil && err == nil {
+ err = e
+ }
+
return
}
}
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
-func (b *Buffer) SaveAs(filename string) (err error) {
+func (b *Buffer) SaveAs(filename string) error {
+ return b.saveToFile(filename, false)
+}
+
+func (b *Buffer) SaveWithSudo() error {
+ return b.SaveAsWithSudo(b.Path)
+}
+
+func (b *Buffer) SaveAsWithSudo(filename string) error {
+ return b.saveToFile(filename, true)
+}
+
+func (b *Buffer) saveToFile(filename string, withSudo bool) error {
+ var err error
+ if b.Type.Readonly {
+ return errors.New("Cannot save readonly buffer")
+ }
if b.Type.Scratch {
return errors.New("Cannot save scratch buffer")
}
+ if withSudo && runtime.GOOS == "windows" {
+ return errors.New("Save with sudo not supported on Windows")
+ }
b.UpdateRules()
if b.Settings["rmtrailingws"].(bool) {
if b.Settings["eofnewline"].(bool) {
end := b.End()
- if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
- b.Insert(end, "\n")
+ if b.RuneAt(Loc{end.X, end.Y}) != '\n' {
+ b.insert(end, []byte{'\n'})
}
}
// Update the last time this file was updated after saving
defer func() {
- b.ModTime, _ = GetModTime(filename)
+ b.ModTime, _ = util.GetModTime(filename)
err = b.Serialize()
}()
// Removes any tilde and replaces with the absolute path to home
- absFilename, _ := ReplaceHome(filename)
-
- // TODO: save creates parent dirs
- // // Get the leading path to the file | "." is returned if there's no leading path provided
- // if dirname := filepath.Dir(absFilename); dirname != "." {
- // // Check if the parent dirs don't exist
- // if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
- // // Prompt to make sure they want to create the dirs that are missing
- // if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
- // // Create all leading dir(s) since they don't exist
- // if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
- // // If there was an error creating the dirs
- // return mkdirallErr
- // }
- // } else {
- // // If they canceled the creation of leading dirs
- // return errors.New("Save aborted")
- // }
- // }
- // }
+ absFilename, _ := util.ReplaceHome(filename)
+
+ // Get the leading path to the file | "." is returned if there's no leading path provided
+ if dirname := filepath.Dir(absFilename); dirname != "." {
+ // Check if the parent dirs don't exist
+ if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
+ // Prompt to make sure they want to create the dirs that are missing
+ if b.Settings["mkparents"].(bool) {
+ // Create all leading dir(s) since they don't exist
+ if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
+ // If there was an error creating the dirs
+ return mkdirallErr
+ }
+ } else {
+ return errors.New("Parent dirs don't exist, enable 'mkparents' for auto creation")
+ }
+ }
+ }
var fileSize int
return err
}
- err = overwriteFile(absFilename, enc, func(file io.Writer) (e error) {
+ fwriter := func(file io.Writer) (e error) {
if len(b.lines) == 0 {
return
}
fileSize += len(eol) + len(l.data)
}
return
- })
+ }
- if err != nil {
+ if err = overwriteFile(absFilename, enc, fwriter, withSudo); err != nil {
return err
}
absPath, _ := filepath.Abs(filename)
b.AbsPath = absPath
b.isModified = false
- return
-}
-
-// SaveWithSudo saves the buffer to the default path with sudo
-func (b *Buffer) SaveWithSudo() error {
- return b.SaveAsWithSudo(b.Path)
-}
-
-// SaveAsWithSudo is the same as SaveAs except it uses a neat trick
-// with tee to use sudo so the user doesn't have to reopen micro with sudo
-func (b *Buffer) SaveAsWithSudo(filename string) error {
- if b.Type.Scratch {
- return errors.New("Cannot save scratch buffer")
- }
-
- b.UpdateRules()
- b.Path = filename
- absPath, _ := filepath.Abs(filename)
- b.AbsPath = absPath
-
- // Set up everything for the command
- cmd := exec.Command(config.GlobalSettings["sucmd"].(string), "tee", filename)
- cmd.Stdin = bytes.NewBuffer(b.Bytes())
-
- // This is a trap for Ctrl-C so that it doesn't kill micro
- // Instead we trap Ctrl-C to kill the program we're running
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt)
- go func() {
- for range c {
- cmd.Process.Kill()
- }
- }()
-
- // Start the command
- cmd.Start()
- err := cmd.Wait()
-
- if err == nil {
- b.isModified = false
- b.ModTime, _ = GetModTime(filename)
- return b.Serialize()
- }
return err
}