"bytes"
"crypto/md5"
"encoding/gob"
+ "errors"
"io"
"io/ioutil"
"os"
ModTime time.Time
}
+// NewBufferFromFile opens a new buffer using the given filepath
+// It will also automatically handle `~`, and line/column with filename:l:c
+// It will return an empty buffer if the filepath does not exist
+// and an error if the file is a directory
+func NewBufferFromFile(path string) (*Buffer, error) {
+ filename := GetPath(path)
+ filename = ReplaceHome(filename)
+ file, err := os.Open(filename)
+ fileInfo, _ := os.Stat(filename)
+
+ if err == nil && fileInfo.IsDir() {
+ return nil, errors.New(filename + " is a directory")
+ }
+
+ defer file.Close()
+
+ var buf *Buffer
+ if err != nil {
+ // File does not exist -- create an empty buffer with that name
+ buf = NewBufferFromString("", path)
+ } else {
+ buf = NewBuffer(file, FSize(file), path)
+ }
+
+ return buf, nil
+}
+
// NewBufferFromString creates a new buffer containing the given
// string
func NewBufferFromString(text, path string) *Buffer {
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer(reader io.Reader, size int64, path string) *Buffer {
+ startpos := Loc{0, 0}
+ startposErr := true
+ if strings.Contains(path, ":") {
+ var err error
+ split := strings.Split(path, ":")
+ path = split[0]
+ startpos.Y, err = strconv.Atoi(split[1])
+ if err != nil {
+ messenger.Error("Error opening file: ", err)
+ } else {
+ startposErr = false
+ if len(split) > 2 {
+ startpos.X, err = strconv.Atoi(split[2])
+ if err != nil {
+ messenger.Error("Error opening file: ", err)
+ }
+ }
+ }
+ }
+
if path != "" {
for _, tab := range tabs {
- for _, view := range tab.views {
+ for _, view := range tab.Views {
if view.Buf.Path == path {
return view.Buf
}
cursorStartX := 0
cursorStartY := 0
// If -startpos LINE,COL was passed, use start position LINE,COL
- if len(*flagStartPos) > 0 {
+ if len(*flagStartPos) > 0 || !startposErr {
positions := strings.Split(*flagStartPos, ",")
- if len(positions) == 2 {
- lineNum, errPos1 := strconv.Atoi(positions[0])
- colNum, errPos2 := strconv.Atoi(positions[1])
+ if len(positions) == 2 || !startposErr {
+ var lineNum, colNum int
+ var errPos1, errPos2 error
+ if !startposErr {
+ lineNum = startpos.Y
+ colNum = startpos.X
+ } else {
+ lineNum, errPos1 = strconv.Atoi(positions[0])
+ colNum, errPos2 = strconv.Atoi(positions[1])
+ }
if errPos1 == nil && errPos2 == nil {
cursorStartX = colNum
cursorStartY = lineNum - 1
InitLocalSettings(b)
- if len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
+ if startposErr && len(*flagStartPos) == 0 && (b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool)) {
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
b.ModTime, _ = GetModTime(filename)
}()
- f, err := os.OpenFile(ReplaceHome(filename), os.O_WRONLY|os.O_CREATE, 0644)
+ // Removes any tilde and replaces with the absolute path to home
+ var absFilename string = 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 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")
+ }
+ }
+ }
+
+ f, err := os.OpenFile(absFilename, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
// RuneAt returns the rune at a given location in the buffer
func (b *Buffer) RuneAt(loc Loc) rune {
- line := []rune(b.Line(loc.Y))
+ line := b.LineRunes(loc.Y)
if len(line) > 0 {
return line[loc.X]
}
return '\n'
}
+// Line returns a single line as an array of runes
+func (b *Buffer) LineBytes(n int) []byte {
+ if n >= len(b.lines) {
+ return []byte{}
+ }
+ return b.lines[n].data
+}
+
+// Line returns a single line as an array of runes
+func (b *Buffer) LineRunes(n int) []rune {
+ if n >= len(b.lines) {
+ return []rune{}
+ }
+ return toRunes(b.lines[n].data)
+}
+
// Line returns a single line
func (b *Buffer) Line(n int) string {
if n >= len(b.lines) {
b.UpdateCursors()
b.Cursor.ResetSelection()
}
+
+var bracePairs = [][2]rune{
+ {'(', ')'},
+ {'{', '}'},
+ {'[', ']'},
+}
+
+// FindMatchingBrace returns the location in the buffer of the matching bracket
+// It is given a brace type containing the open and closing character, (for example
+// '{' and '}') as well as the location to match from
+func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) Loc {
+ curLine := b.LineRunes(start.Y)
+ startChar := curLine[start.X]
+ var i int
+ if startChar == braceType[0] {
+ for y := start.Y; y < b.NumLines; y++ {
+ l := b.LineRunes(y)
+ xInit := 0
+ if y == start.Y {
+ xInit = start.X
+ }
+ for x := xInit; x < len(l); x++ {
+ r := l[x]
+ if r == braceType[0] {
+ i++
+ } else if r == braceType[1] {
+ i--
+ if i == 0 {
+ return Loc{x, y}
+ }
+ }
+ }
+ }
+ } else if startChar == braceType[1] {
+ for y := start.Y; y >= 0; y-- {
+ l := []rune(string(b.lines[y].data))
+ xInit := len(l) - 1
+ if y == start.Y {
+ xInit = start.X
+ }
+ for x := xInit; x >= 0; x-- {
+ r := l[x]
+ if r == braceType[0] {
+ i--
+ if i == 0 {
+ return Loc{x, y}
+ }
+ } else if r == braceType[1] {
+ i++
+ }
+ }
+ }
+ }
+ return start
+}