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 // The SerializedBuffer holds the types that get serialized when a buffer is saved
100 // These are used for the savecursor and saveundo options
101 type SerializedBuffer struct {
102 EventHandler *EventHandler
107 // NewBufferFromFile opens a new buffer using the given path
108 // It will also automatically handle `~`, and line/column with filename:l:c
109 // It will return an empty buffer if the path does not exist
110 // and an error if the file is a directory
111 func NewBufferFromFile(path string) (*Buffer, error) {
113 filename, cursorPosition := GetPathAndCursorPosition(path)
114 filename, err = ReplaceHome(filename)
119 file, err := os.Open(filename)
120 fileInfo, _ := os.Stat(filename)
122 if err == nil && fileInfo.IsDir() {
123 return nil, errors.New(filename + " is a directory")
130 // File does not exist -- create an empty buffer with that name
131 buf = NewBufferFromString("", filename)
133 buf = NewBuffer(file, FSize(file), filename, cursorPosition)
139 // NewBufferFromString creates a new buffer containing the given string
140 func NewBufferFromString(text, path string) *Buffer {
141 return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
144 // NewBuffer creates a new buffer from a given reader with a given path
145 // Ensure that ReadSettings and InitGlobalSettings have been called before creating
147 func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
150 b.Settings = config.DefaultLocalSettings()
151 for k, v := range config.GlobalSettings {
152 if _, ok := b.Settings[k]; ok {
156 config.InitLocalSettings(b.Settings, b.Path)
158 b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
160 absPath, _ := filepath.Abs(path)
165 // The last time this file was modified
166 b.ModTime, _ = GetModTime(b.Path)
168 b.EventHandler = NewEventHandler(b)
172 if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
173 os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
176 // cursorLocation, err := GetBufferCursorLocation(cursorPosition, b)
177 // b.startcursor = Cursor{
178 // Loc: cursorLocation,
182 if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
183 err := b.Unserialize()
189 if !b.Settings["fastdirty"].(bool) {
190 if size > LargeFileThreshold {
191 // If the file is larger than LargeFileThreshold fastdirty needs to be on
192 b.Settings["fastdirty"] = true
194 calcHash(b, &b.origHash)
201 // GetName returns the name that should be displayed in the statusline
203 func (b *Buffer) GetName() string {
213 // FileType returns the buffer's filetype
214 func (b *Buffer) FileType() string {
215 return b.Settings["filetype"].(string)
218 // ReOpen reloads the current buffer from disk
219 func (b *Buffer) ReOpen() error {
220 data, err := ioutil.ReadFile(b.Path)
226 b.EventHandler.ApplyDiff(txt)
228 b.ModTime, err = GetModTime(b.Path)
231 // TODO: buffer cursor
232 // b.Cursor.Relocate()
235 func (b *Buffer) SetCursors(c []*Cursor) {
239 func (b *Buffer) GetActiveCursor() *Cursor {
243 func (b *Buffer) GetCursor(n int) *Cursor {
247 func (b *Buffer) GetCursors() []*Cursor {
251 func (b *Buffer) NumCursors() int {
252 return len(b.cursors)
255 func (b *Buffer) LineBytes(n int) []byte {
256 if n >= len(b.lines) || n < 0 {
259 return b.lines[n].data
262 func (b *Buffer) LinesNum() int {
266 func (b *Buffer) Start() Loc {
270 // End returns the location of the last character in the buffer
271 func (b *Buffer) End() Loc {
272 numlines := len(b.lines)
273 return Loc{utf8.RuneCount(b.lines[numlines-1].data), numlines - 1}
276 // RuneAt returns the rune at a given location in the buffer
277 func (b *Buffer) RuneAt(loc Loc) rune {
278 line := b.LineBytes(loc.Y)
282 r, size := utf8.DecodeRune(line)
294 // Modified returns if this buffer has been modified since
296 func (b *Buffer) Modified() bool {
297 if b.Settings["fastdirty"].(bool) {
301 var buff [md5.Size]byte
304 return buff != b.origHash
307 // calcHash calculates md5 hash of all lines in the buffer
308 func calcHash(b *Buffer, out *[md5.Size]byte) {
311 if len(b.lines) > 0 {
312 h.Write(b.lines[0].data)
314 for _, l := range b.lines[1:] {
315 h.Write([]byte{'\n'})
323 // UpdateRules updates the syntax rules and filetype for this buffer
324 // This is called when the colorscheme changes
325 func (b *Buffer) UpdateRules() {
327 var files []*highlight.File
328 for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
329 data, err := f.Data()
331 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
333 file, err := highlight.ParseFile(data)
335 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
338 ftdetect, err := highlight.ParseFtDetect(file)
340 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
344 ft := b.Settings["filetype"].(string)
345 if (ft == "Unknown" || ft == "") && !rehighlight {
346 if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
347 header := new(highlight.Header)
348 header.FileType = file.FileType
349 header.FtDetect = ftdetect
350 b.SyntaxDef, err = highlight.ParseDef(file, header)
352 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
358 if file.FileType == ft && !rehighlight {
359 header := new(highlight.Header)
360 header.FileType = file.FileType
361 header.FtDetect = ftdetect
362 b.SyntaxDef, err = highlight.ParseDef(file, header)
364 TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
370 files = append(files, file)
374 if b.SyntaxDef != nil {
375 highlight.ResolveIncludes(b.SyntaxDef, files)
378 if b.Highlighter == nil || rehighlight {
379 if b.SyntaxDef != nil {
380 b.Settings["filetype"] = b.SyntaxDef.FileType
381 b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
382 if b.Settings["syntax"].(bool) {
383 b.Highlighter.HighlightStates(b)