From 9628b73525a34762b9ec185b6915ad665504e3d7 Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Thu, 24 Aug 2017 13:13:14 -0400 Subject: [PATCH] Add support for switching between crlf and lf Dos and Unix line endings are now both supported (previously on unix line endings were supported) and can be accessed via the `fileformat` option. The file format will be automatically detected and displayed in the statusline but can be overriden. Possible values for the `fileformat` option are `dos` and `unix`. Closes #443 Closes #755 --- cmd/micro/buffer.go | 19 ++++++++++++++++--- cmd/micro/lineArray.go | 23 +++++++++++++++++++++++ cmd/micro/settings.go | 21 +++++++++++++++++++++ cmd/micro/statusline.go | 2 ++ runtime/help/options.md | 7 +++++++ 5 files changed, 69 insertions(+), 3 deletions(-) diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index aeb50bc4..0d4c652b 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -19,6 +19,13 @@ import ( "github.com/zyedidia/micro/cmd/micro/highlight" ) +var ( + // 0 - no line type detected + // 1 - lf detected + // 2 - crlf detected + fileformat = 0 +) + // Buffer stores the text for files that are loaded into the text editor // It uses a rope to efficiently store the string and contains some // simple functions for saving and wrapper functions for modifying the rope @@ -88,6 +95,12 @@ func NewBuffer(reader io.Reader, size int64, path string) *Buffer { } } + if fileformat == 1 { + b.Settings["fileformat"] = "unix" + } else if fileformat == 2 { + b.Settings["fileformat"] = "dos" + } + absPath, _ := filepath.Abs(path) b.Path = path @@ -355,7 +368,7 @@ func (b *Buffer) Serialize() error { b.ModTime, }) } - file.Close() + err = file.Close() return err } return nil @@ -383,7 +396,7 @@ func (b *Buffer) SaveAs(filename string) error { b.Insert(end, "\n") } } - str := b.String() + str := b.SaveString(b.Settings["fileformat"] == "dos") data := []byte(str) err := ioutil.WriteFile(filename, data, 0644) if err == nil { @@ -408,7 +421,7 @@ func (b *Buffer) SaveAsWithSudo(filename string) error { // Set up everything for the command cmd := exec.Command("sudo", "tee", filename) - cmd.Stdin = bytes.NewBufferString(b.String()) + cmd.Stdin = bytes.NewBufferString(b.SaveString(b.Settings["fileformat"] == "dos")) // 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 diff --git a/cmd/micro/lineArray.go b/cmd/micro/lineArray.go index 2ad4ea31..734a51c3 100644 --- a/cmd/micro/lineArray.go +++ b/cmd/micro/lineArray.go @@ -71,6 +71,12 @@ func NewLineArray(size int64, reader io.Reader) *LineArray { n := 0 for { data, err := br.ReadBytes('\n') + if len(data) > 0 && data[len(data)-2] == '\r' { + data = append(data[:len(data)-2], '\n') + fileformat = 2 + } else if len(data) > 0 { + fileformat = 1 + } if n >= 1000 && loaded >= 0 { totalLinesNum := int(float64(size) * (float64(n) / float64(loaded))) @@ -114,6 +120,23 @@ func (la *LineArray) String() string { return str } +// SaveString returns the string that should be written to disk when +// the line array is saved +// It is the same as string but uses crlf or lf line endings depending +func (la *LineArray) SaveString(useCrlf bool) string { + str := "" + for i, l := range la.lines { + str += string(l.data) + if i != len(la.lines)-1 { + if useCrlf { + str += "\r" + } + str += "\n" + } + } + return str +} + // NewlineBelow adds a newline below the given line number func (la *LineArray) NewlineBelow(y int) { la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false}) diff --git a/cmd/micro/settings.go b/cmd/micro/settings.go index 03321b36..6851e356 100644 --- a/cmd/micro/settings.go +++ b/cmd/micro/settings.go @@ -27,6 +27,7 @@ var optionValidators = map[string]optionValidator{ "scrollspeed": validateNonNegativeValue, "colorscheme": validateColorscheme, "colorcolumn": validateNonNegativeValue, + "fileformat": validateLineEnding, } // InitGlobalSettings initializes the options map and sets all options to their default values @@ -220,6 +221,7 @@ func DefaultGlobalSettings() map[string]interface{} { }, "pluginrepos": []string{}, "useprimary": true, + "fileformat": "unix", } } @@ -251,6 +253,7 @@ func DefaultLocalSettings() map[string]interface{} { "tabsize": float64(4), "tabstospaces": false, "useprimary": true, + "fileformat": "unix", } } @@ -365,6 +368,10 @@ func SetLocalOption(option, value string, view *View) error { buf.UpdateRules() } + if option == "fileformat" { + buf.IsModified = true + } + if option == "syntax" { if !nativeValue.(bool) { buf.ClearMatches() @@ -445,3 +452,17 @@ func validateColorscheme(option string, value interface{}) error { return nil } + +func validateLineEnding(option string, value interface{}) error { + endingType, ok := value.(string) + + if !ok { + return errors.New("Expected string type for file format") + } + + if endingType != "unix" && endingType != "dos" { + return errors.New("File format must be either 'unix' or 'dos'") + } + + return nil +} diff --git a/cmd/micro/statusline.go b/cmd/micro/statusline.go index e339cf19..2e2886d1 100644 --- a/cmd/micro/statusline.go +++ b/cmd/micro/statusline.go @@ -36,6 +36,8 @@ func (sline *Statusline) Display() { // Add the filetype file += " " + sline.view.Buf.FileType() + file += " " + sline.view.Buf.Settings["fileformat"].(string) + rightText := "" if len(helpBinding) > 0 { rightText = helpBinding + " for help " diff --git a/runtime/help/options.md b/runtime/help/options.md index 88374e21..db78f95c 100644 --- a/runtime/help/options.md +++ b/runtime/help/options.md @@ -173,6 +173,13 @@ Default plugin options: default value: `on` +* `fileformat`: this determines what kind of line endings micro will use for the file. Unix line endings + are just `\n` (lf) whereas dos line endings are `\r\n` (crlf). The two possible values for this option + are `unix` and `dos`. The fileformat will be automatically detected and displayed on the statusline but + this option is useful if you would like to change the line endings or if you are starting a new file. + + default value: `unix` + Any option you set in the editor will be saved to the file ~/.config/micro/settings.json so, in effect, your configuration file will be created for you. If you'd like to take your configuration with you to another