15 "github.com/zyedidia/micro/cmd/micro/config"
16 "github.com/zyedidia/micro/cmd/micro/highlight"
18 . "github.com/zyedidia/micro/cmd/micro/util"
21 // LargeFileThreshold is the number of bytes when fastdirty is forced
22 // because hashing is too slow
23 const LargeFileThreshold = 50000
25 // overwriteFile opens the given file for writing, truncating if one exists, and then calls
26 // the supplied function with the file as io.Writer object, also making sure the file is
28 func overwriteFile(name string, fn func(io.Writer) error) (err error) {
31 if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
36 if e := file.Close(); e != nil && err == nil {
41 w := bufio.NewWriter(file)
43 if err = fn(w); err != nil {
51 // The BufType defines what kind of buffer this is
54 Readonly bool // The file cannot be edited
55 Scratch bool // The file cannot be saved
59 BTDefault = BufType{0, false, false}
60 BTHelp = BufType{1, true, true}
61 BTLog = BufType{2, true, true}
62 BTScratch = BufType{3, false, true}
63 BTRaw = BufType{4, true, true}
73 // Path to the file on disk
75 // Absolute path to the file on disk
77 // Name of the buffer on the status line
80 // Whether or not the buffer has been modified since it was opened
83 // Stores the last modification time of the file the buffer is pointing to
86 SyntaxDef *highlight.Def
87 Highlighter *highlight.Highlighter
89 // Hash of the original buffer -- empty if fastdirty is on
90 origHash [md5.Size]byte
92 // Settings customized by the user
93 Settings map[string]interface{}
95 // Type of the buffer (e.g. help, raw, scratch etc..)
99 // NewBufferFromFile opens a new buffer using the given path
100 // It will also automatically handle `~`, and line/column with filename:l:c
101 // It will return an empty buffer if the path does not exist
102 // and an error if the file is a directory
103 func NewBufferFromFile(path string) (*Buffer, error) {
105 filename, cursorPosition := GetPathAndCursorPosition(path)
106 filename, err = ReplaceHome(filename)
111 file, err := os.Open(filename)
112 fileInfo, _ := os.Stat(filename)
114 if err == nil && fileInfo.IsDir() {
115 return nil, errors.New(filename + " is a directory")
122 // File does not exist -- create an empty buffer with that name
123 buf = NewBufferFromString("", filename)
125 buf = NewBuffer(file, FSize(file), filename, cursorPosition)
131 // NewBufferFromString creates a new buffer containing the given string
132 func NewBufferFromString(text, path string) *Buffer {
133 return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
136 // NewBuffer creates a new buffer from a given reader with a given path
137 // Ensure that ReadSettings and InitGlobalSettings have been called before creating
139 func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
142 b.Settings = config.DefaultLocalSettings()
143 for k, v := range config.GlobalSettings {
144 if _, ok := b.Settings[k]; ok {
148 config.InitLocalSettings(b.Settings, b.Path)
150 b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
152 absPath, _ := filepath.Abs(path)
157 // The last time this file was modified
158 b.ModTime, _ = GetModTime(b.Path)
160 b.EventHandler = NewEventHandler(b)
164 if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
165 os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
168 // cursorLocation, err := GetBufferCursorLocation(cursorPosition, b)
169 // b.startcursor = Cursor{
170 // Loc: cursorLocation,
174 if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
175 err := b.Unserialize()
181 if !b.Settings["fastdirty"].(bool) {
182 if size > LargeFileThreshold {
183 // If the file is larger than LargeFileThreshold fastdirty needs to be on
184 b.Settings["fastdirty"] = true
186 calcHash(b, &b.origHash)
193 // GetName returns the name that should be displayed in the statusline
195 func (b *Buffer) GetName() string {
205 // FileType returns the buffer's filetype
206 func (b *Buffer) FileType() string {
207 return b.Settings["filetype"].(string)
210 // ReOpen reloads the current buffer from disk
211 func (b *Buffer) ReOpen() error {
212 data, err := ioutil.ReadFile(b.Path)
218 b.EventHandler.ApplyDiff(txt)
220 b.ModTime, err = GetModTime(b.Path)
223 // TODO: buffer cursor
224 // b.Cursor.Relocate()
227 func (b *Buffer) SetCursors(c []*Cursor) {
231 func (b *Buffer) GetActiveCursor() *Cursor {
235 func (b *Buffer) GetCursor(n int) *Cursor {
239 func (b *Buffer) GetCursors() []*Cursor {
243 func (b *Buffer) NumCursors() int {
244 return len(b.cursors)
247 func (b *Buffer) LineBytes(n int) []byte {
248 if n >= len(b.lines) || n < 0 {
251 return b.lines[n].data
254 func (b *Buffer) LinesNum() int {
258 func (b *Buffer) Start() Loc {
262 // End returns the location of the last character in the buffer
263 func (b *Buffer) End() Loc {
264 numlines := len(b.lines)
265 return Loc{utf8.RuneCount(b.lines[numlines-1].data), numlines - 1}
268 // RuneAt returns the rune at a given location in the buffer
269 func (b *Buffer) RuneAt(loc Loc) rune {
270 line := b.LineBytes(loc.Y)
274 r, size := utf8.DecodeRune(line)
286 // Modified returns if this buffer has been modified since
288 func (b *Buffer) Modified() bool {
289 if b.Settings["fastdirty"].(bool) {
293 var buff [md5.Size]byte
296 return buff != b.origHash
299 // calcHash calculates md5 hash of all lines in the buffer
300 func calcHash(b *Buffer, out *[md5.Size]byte) {
303 if len(b.lines) > 0 {
304 h.Write(b.lines[0].data)
306 for _, l := range b.lines[1:] {
307 h.Write([]byte{'\n'})
315 func (b *Buffer) insert(pos Loc, value []byte) {
317 b.LineArray.insert(pos, value)
319 func (b *Buffer) remove(start, end Loc) []byte {
321 sub := b.LineArray.remove(start, end)
324 func (b *Buffer) deleteToEnd(start Loc) {
326 b.LineArray.deleteToEnd(start)
329 // UpdateRules updates the syntax rules and filetype for this buffer
330 // This is called when the colorscheme changes
331 func (b *Buffer) UpdateRules() {
333 var files []*highlight.File
334 for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
335 data, err := f.Data()
337 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
339 file, err := highlight.ParseFile(data)
341 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
344 ftdetect, err := highlight.ParseFtDetect(file)
346 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
350 ft := b.Settings["filetype"].(string)
351 if (ft == "Unknown" || ft == "") && !rehighlight {
352 if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
353 header := new(highlight.Header)
354 header.FileType = file.FileType
355 header.FtDetect = ftdetect
356 b.SyntaxDef, err = highlight.ParseDef(file, header)
358 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
364 if file.FileType == ft && !rehighlight {
365 header := new(highlight.Header)
366 header.FileType = file.FileType
367 header.FtDetect = ftdetect
368 b.SyntaxDef, err = highlight.ParseDef(file, header)
370 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
376 files = append(files, file)
380 if b.SyntaxDef != nil {
381 highlight.ResolveIncludes(b.SyntaxDef, files)
384 if b.Highlighter == nil || rehighlight {
385 if b.SyntaxDef != nil {
386 b.Settings["filetype"] = b.SyntaxDef.FileType
387 b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
388 if b.Settings["syntax"].(bool) {
389 b.Highlighter.HighlightStates(b)