]> git.lizzy.rs Git - micro.git/blob - internal/buffer/buffer.go
Add diff gutter
[micro.git] / internal / buffer / buffer.go
1 package buffer
2
3 import (
4         "bytes"
5         "crypto/md5"
6         "errors"
7         "io"
8         "io/ioutil"
9         "os"
10         "path/filepath"
11         "strconv"
12         "strings"
13         "sync"
14         "time"
15         "unicode/utf8"
16
17         luar "layeh.com/gopher-luar"
18
19         "github.com/zyedidia/micro/internal/config"
20         ulua "github.com/zyedidia/micro/internal/lua"
21         "github.com/zyedidia/micro/internal/screen"
22         "github.com/zyedidia/micro/internal/util"
23         "github.com/zyedidia/micro/pkg/highlight"
24         "golang.org/x/text/encoding/htmlindex"
25         "golang.org/x/text/encoding/unicode"
26         "golang.org/x/text/transform"
27         dmp "github.com/sergi/go-diff/diffmatchpatch"
28 )
29
30 const backupTime = 8000
31
32 var (
33         // OpenBuffers is a list of the currently open buffers
34         OpenBuffers []*Buffer
35         // LogBuf is a reference to the log buffer which can be opened with the
36         // `> log` command
37         LogBuf *Buffer
38 )
39
40 // The BufType defines what kind of buffer this is
41 type BufType struct {
42         Kind     int
43         Readonly bool // The buffer cannot be edited
44         Scratch  bool // The buffer cannot be saved
45         Syntax   bool // Syntax highlighting is enabled
46 }
47
48 var (
49         // BTDefault is a default buffer
50         BTDefault = BufType{0, false, false, true}
51         // BTHelp is a help buffer
52         BTHelp = BufType{1, true, true, true}
53         // BTLog is a log buffer
54         BTLog = BufType{2, true, true, false}
55         // BTScratch is a buffer that cannot be saved (for scratch work)
56         BTScratch = BufType{3, false, true, false}
57         // BTRaw is is a buffer that shows raw terminal events
58         BTRaw = BufType{4, false, true, false}
59         // BTInfo is a buffer for inputting information
60         BTInfo = BufType{5, false, true, false}
61
62         // ErrFileTooLarge is returned when the file is too large to hash
63         // (fastdirty is automatically enabled)
64         ErrFileTooLarge = errors.New("File is too large to hash")
65 )
66
67 // SharedBuffer is a struct containing info that is shared among buffers
68 // that have the same file open
69 type SharedBuffer struct {
70         *LineArray
71         // Stores the last modification time of the file the buffer is pointing to
72         ModTime time.Time
73         // Type of the buffer (e.g. help, raw, scratch etc..)
74         Type BufType
75
76         isModified bool
77         // Whether or not suggestions can be autocompleted must be shared because
78         // it changes based on how the buffer has changed
79         HasSuggestions bool
80
81         // Modifications is the list of modified regions for syntax highlighting
82         Modifications []Loc
83 }
84
85 func (b *SharedBuffer) insert(pos Loc, value []byte) {
86         b.isModified = true
87         b.HasSuggestions = false
88         b.LineArray.insert(pos, value)
89
90         // b.Modifications is cleared every screen redraw so it's
91         // ok to append duplicates
92         b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
93 }
94 func (b *SharedBuffer) remove(start, end Loc) []byte {
95         b.isModified = true
96         b.HasSuggestions = false
97         b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
98         return b.LineArray.remove(start, end)
99 }
100
101 const (
102         DSUnchanged    = 0
103         DSAdded        = 1
104         DSModified     = 2
105         DSDeletedAbove = 3
106 )
107
108 type DiffStatus byte
109
110 // Buffer stores the main information about a currently open file including
111 // the actual text (in a LineArray), the undo/redo stack (in an EventHandler)
112 // all the cursors, the syntax highlighting info, the settings for the buffer
113 // and some misc info about modification time and path location.
114 // The syntax highlighting info must be stored with the buffer because the syntax
115 // highlighter attaches information to each line of the buffer for optimization
116 // purposes so it doesn't have to rehighlight everything on every update.
117 type Buffer struct {
118         *EventHandler
119         *SharedBuffer
120
121         cursors     []*Cursor
122         curCursor   int
123         StartCursor Loc
124
125         // Path to the file on disk
126         Path string
127         // Absolute path to the file on disk
128         AbsPath string
129         // Name of the buffer on the status line
130         name string
131
132         // SyntaxDef represents the syntax highlighting definition being used
133         // This stores the highlighting rules and filetype detection info
134         SyntaxDef *highlight.Def
135         // The Highlighter struct actually performs the highlighting
136         Highlighter   *highlight.Highlighter
137         HighlightLock sync.Mutex
138
139         // Hash of the original buffer -- empty if fastdirty is on
140         origHash [md5.Size]byte
141
142         // Settings customized by the user
143         Settings map[string]interface{}
144
145         Suggestions   []string
146         Completions   []string
147         CurSuggestion int
148
149         Messages []*Message
150
151         updateDiffTimer   *time.Timer
152         diffBase          []byte
153         diffBaseLineCount int
154         diffLock          sync.RWMutex
155         diff              map[int]DiffStatus
156
157         // counts the number of edits
158         // resets every backupTime edits
159         lastbackup time.Time
160 }
161
162 // NewBufferFromFile opens a new buffer using the given path
163 // It will also automatically handle `~`, and line/column with filename:l:c
164 // It will return an empty buffer if the path does not exist
165 // and an error if the file is a directory
166 func NewBufferFromFile(path string, btype BufType) (*Buffer, error) {
167         var err error
168         filename, cursorPos := util.GetPathAndCursorPosition(path)
169         filename, err = util.ReplaceHome(filename)
170         if err != nil {
171                 return nil, err
172         }
173
174         file, err := os.Open(filename)
175         fileInfo, _ := os.Stat(filename)
176
177         if err == nil && fileInfo.IsDir() {
178                 return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
179         }
180
181         defer file.Close()
182
183         cursorLoc, cursorerr := ParseCursorLocation(cursorPos)
184         if cursorerr != nil {
185                 cursorLoc = Loc{-1, -1}
186         }
187
188         var buf *Buffer
189         if err != nil {
190                 // File does not exist -- create an empty buffer with that name
191                 buf = NewBufferFromString("", filename, btype)
192         } else {
193                 buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
194         }
195
196         return buf, nil
197 }
198
199 // NewBufferFromString creates a new buffer containing the given string
200 func NewBufferFromString(text, path string, btype BufType) *Buffer {
201         return NewBuffer(strings.NewReader(text), int64(len(text)), path, Loc{-1, -1}, btype)
202 }
203
204 // NewBuffer creates a new buffer from a given reader with a given path
205 // Ensure that ReadSettings and InitGlobalSettings have been called before creating
206 // a new buffer
207 // Places the cursor at startcursor. If startcursor is -1, -1 places the
208 // cursor at an autodetected location (based on savecursor or :LINE:COL)
209 func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
210         absPath, _ := filepath.Abs(path)
211
212         b := new(Buffer)
213
214         b.Settings = config.DefaultCommonSettings()
215         for k, v := range config.GlobalSettings {
216                 if _, ok := b.Settings[k]; ok {
217                         b.Settings[k] = v
218                 }
219         }
220         config.InitLocalSettings(b.Settings, path)
221
222         enc, err := htmlindex.Get(b.Settings["encoding"].(string))
223         if err != nil {
224                 enc = unicode.UTF8
225                 b.Settings["encoding"] = "utf-8"
226         }
227
228         reader := transform.NewReader(r, enc.NewDecoder())
229
230         found := false
231         if len(path) > 0 {
232                 for _, buf := range OpenBuffers {
233                         if buf.AbsPath == absPath && buf.Type != BTInfo {
234                                 found = true
235                                 b.SharedBuffer = buf.SharedBuffer
236                                 b.EventHandler = buf.EventHandler
237                         }
238                 }
239         }
240
241         b.Path = path
242         b.AbsPath = absPath
243
244         if !found {
245                 b.SharedBuffer = new(SharedBuffer)
246                 b.Type = btype
247
248                 hasBackup := b.ApplyBackup(size)
249
250                 if !hasBackup {
251                         b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
252                 }
253                 b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
254         }
255
256         if b.Settings["readonly"].(bool) && b.Type == BTDefault {
257                 b.Type.Readonly = true
258         }
259
260         // The last time this file was modified
261         b.UpdateModTime()
262
263         switch b.Endings {
264         case FFUnix:
265                 b.Settings["fileformat"] = "unix"
266         case FFDos:
267                 b.Settings["fileformat"] = "dos"
268         }
269
270         b.UpdateRules()
271         config.InitLocalSettings(b.Settings, b.Path)
272
273         if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
274                 os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
275         }
276
277         if startcursor.X != -1 && startcursor.Y != -1 {
278                 b.StartCursor = startcursor
279         } else {
280                 if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
281                         err := b.Unserialize()
282                         if err != nil {
283                                 screen.TermMessage(err)
284                         }
285                 }
286         }
287
288         b.AddCursor(NewCursor(b, b.StartCursor))
289         b.GetActiveCursor().Relocate()
290
291         if !b.Settings["fastdirty"].(bool) {
292                 if size > LargeFileThreshold {
293                         // If the file is larger than LargeFileThreshold fastdirty needs to be on
294                         b.Settings["fastdirty"] = true
295                 } else {
296                         calcHash(b, &b.origHash)
297                 }
298         }
299
300         err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
301         if err != nil {
302                 screen.TermMessage(err)
303         }
304
305         b.Modifications = make([]Loc, 0, 10)
306
307         OpenBuffers = append(OpenBuffers, b)
308
309         return b
310 }
311
312 // Close removes this buffer from the list of open buffers
313 func (b *Buffer) Close() {
314         for i, buf := range OpenBuffers {
315                 if b == buf {
316                         b.Fini()
317                         copy(OpenBuffers[i:], OpenBuffers[i+1:])
318                         OpenBuffers[len(OpenBuffers)-1] = nil
319                         OpenBuffers = OpenBuffers[:len(OpenBuffers)-1]
320                         return
321                 }
322         }
323 }
324
325 // Fini should be called when a buffer is closed and performs
326 // some cleanup
327 func (b *Buffer) Fini() {
328         if !b.Modified() {
329                 b.Serialize()
330         }
331         b.RemoveBackup()
332 }
333
334 // GetName returns the name that should be displayed in the statusline
335 // for this buffer
336 func (b *Buffer) GetName() string {
337         if b.name == "" {
338                 if b.Path == "" {
339                         return "No name"
340                 }
341                 return b.Path
342         }
343         return b.name
344 }
345
346 //SetName changes the name for this buffer
347 func (b *Buffer) SetName(s string) {
348         b.name = s
349 }
350
351 // Insert inserts the given string of text at the start location
352 func (b *Buffer) Insert(start Loc, text string) {
353         if !b.Type.Readonly {
354                 b.EventHandler.cursors = b.cursors
355                 b.EventHandler.active = b.curCursor
356                 b.EventHandler.Insert(start, text)
357
358                 go b.Backup(true)
359         }
360 }
361
362 // Remove removes the characters between the start and end locations
363 func (b *Buffer) Remove(start, end Loc) {
364         if !b.Type.Readonly {
365                 b.EventHandler.cursors = b.cursors
366                 b.EventHandler.active = b.curCursor
367                 b.EventHandler.Remove(start, end)
368
369                 go b.Backup(true)
370         }
371 }
372
373 // ClearModifications clears the list of modified lines in this buffer
374 // The list of modified lines is used for syntax highlighting so that
375 // we can selectively highlight only the necessary lines
376 // This function should be called every time this buffer is drawn to
377 // the screen
378 func (b *Buffer) ClearModifications() {
379         // clear slice without resetting the cap
380         b.Modifications = b.Modifications[:0]
381 }
382
383 // FileType returns the buffer's filetype
384 func (b *Buffer) FileType() string {
385         return b.Settings["filetype"].(string)
386 }
387
388 // ExternallyModified returns whether the file being edited has
389 // been modified by some external process
390 func (b *Buffer) ExternallyModified() bool {
391         modTime, err := util.GetModTime(b.Path)
392         if err == nil {
393                 return modTime != b.ModTime
394         }
395         return false
396 }
397
398 // UpdateModTime updates the modtime of this file
399 func (b *Buffer) UpdateModTime() (err error) {
400         b.ModTime, err = util.GetModTime(b.Path)
401         return
402 }
403
404 // ReOpen reloads the current buffer from disk
405 func (b *Buffer) ReOpen() error {
406         file, err := os.Open(b.Path)
407         if err != nil {
408                 return err
409         }
410
411         enc, err := htmlindex.Get(b.Settings["encoding"].(string))
412         if err != nil {
413                 return err
414         }
415
416         reader := transform.NewReader(file, enc.NewDecoder())
417         data, err := ioutil.ReadAll(reader)
418         txt := string(data)
419
420         if err != nil {
421                 return err
422         }
423         b.EventHandler.ApplyDiff(txt)
424
425         err = b.UpdateModTime()
426         b.isModified = false
427         b.RelocateCursors()
428         return err
429 }
430
431 // RelocateCursors relocates all cursors (makes sure they are in the buffer)
432 func (b *Buffer) RelocateCursors() {
433         for _, c := range b.cursors {
434                 c.Relocate()
435         }
436 }
437
438 // RuneAt returns the rune at a given location in the buffer
439 func (b *Buffer) RuneAt(loc Loc) rune {
440         line := b.LineBytes(loc.Y)
441         if len(line) > 0 {
442                 i := 0
443                 for len(line) > 0 {
444                         r, size := utf8.DecodeRune(line)
445                         line = line[size:]
446                         i++
447
448                         if i == loc.X {
449                                 return r
450                         }
451                 }
452         }
453         return '\n'
454 }
455
456 // Modified returns if this buffer has been modified since
457 // being opened
458 func (b *Buffer) Modified() bool {
459         if b.Type.Scratch {
460                 return false
461         }
462
463         if b.Settings["fastdirty"].(bool) {
464                 return b.isModified
465         }
466
467         var buff [md5.Size]byte
468
469         calcHash(b, &buff)
470         return buff != b.origHash
471 }
472
473 // calcHash calculates md5 hash of all lines in the buffer
474 func calcHash(b *Buffer, out *[md5.Size]byte) error {
475         h := md5.New()
476
477         size := 0
478         if len(b.lines) > 0 {
479                 n, e := h.Write(b.lines[0].data)
480                 if e != nil {
481                         return e
482                 }
483                 size += n
484
485                 for _, l := range b.lines[1:] {
486                         n, e = h.Write([]byte{'\n'})
487                         if e != nil {
488                                 return e
489                         }
490                         size += n
491                         n, e = h.Write(l.data)
492                         if e != nil {
493                                 return e
494                         }
495                         size += n
496                 }
497         }
498
499         if size > LargeFileThreshold {
500                 return ErrFileTooLarge
501         }
502
503         h.Sum((*out)[:0])
504         return nil
505 }
506
507 // UpdateRules updates the syntax rules and filetype for this buffer
508 // This is called when the colorscheme changes
509 func (b *Buffer) UpdateRules() {
510         if !b.Type.Syntax {
511                 return
512         }
513         ft := b.Settings["filetype"].(string)
514         if ft == "off" {
515                 return
516         }
517         syntaxFile := ""
518         var header *highlight.Header
519         for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
520                 data, err := f.Data()
521                 if err != nil {
522                         screen.TermMessage("Error loading syntax header file " + f.Name() + ": " + err.Error())
523                         continue
524                 }
525
526                 header, err = highlight.MakeHeader(data)
527                 if err != nil {
528                         screen.TermMessage("Error reading syntax header file", f.Name(), err)
529                         continue
530                 }
531
532                 if ft == "unknown" || ft == "" {
533                         if highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data) {
534                                 syntaxFile = f.Name()
535                                 break
536                         }
537                 } else if header.FileType == ft {
538                         syntaxFile = f.Name()
539                         break
540                 }
541         }
542
543         if syntaxFile == "" {
544                 // search for the syntax file in the user's custom syntax files
545                 for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
546                         data, err := f.Data()
547                         if err != nil {
548                                 screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
549                                 continue
550                         }
551
552                         header, err = highlight.MakeHeaderYaml(data)
553                         file, err := highlight.ParseFile(data)
554                         if err != nil {
555                                 screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
556                                 continue
557                         }
558
559                         if (ft == "unknown" || ft == "" && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
560                                 syndef, err := highlight.ParseDef(file, header)
561                                 if err != nil {
562                                         screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
563                                         continue
564                                 }
565                                 b.SyntaxDef = syndef
566                                 break
567                         }
568                 }
569         } else {
570                 for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
571                         if f.Name() == syntaxFile {
572                                 data, err := f.Data()
573                                 if err != nil {
574                                         screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
575                                         continue
576                                 }
577
578                                 file, err := highlight.ParseFile(data)
579                                 if err != nil {
580                                         screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
581                                         continue
582                                 }
583
584                                 syndef, err := highlight.ParseDef(file, header)
585                                 if err != nil {
586                                         screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
587                                         continue
588                                 }
589                                 b.SyntaxDef = syndef
590                                 break
591                         }
592                 }
593         }
594
595         if b.SyntaxDef != nil && highlight.HasIncludes(b.SyntaxDef) {
596                 includes := highlight.GetIncludes(b.SyntaxDef)
597
598                 var files []*highlight.File
599                 for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
600                         data, err := f.Data()
601                         if err != nil {
602                                 screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
603                                 continue
604                         }
605                         header, err := highlight.MakeHeaderYaml(data)
606                         if err != nil {
607                                 screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
608                                 continue
609                         }
610
611                         for _, i := range includes {
612                                 if header.FileType == i {
613                                         file, err := highlight.ParseFile(data)
614                                         if err != nil {
615                                                 screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
616                                                 continue
617                                         }
618                                         files = append(files, file)
619                                         break
620                                 }
621                         }
622                         if len(files) >= len(includes) {
623                                 break
624                         }
625                 }
626
627                 highlight.ResolveIncludes(b.SyntaxDef, files)
628         }
629
630         if b.Highlighter == nil || syntaxFile != "" {
631                 if b.SyntaxDef != nil {
632                         b.Settings["filetype"] = b.SyntaxDef.FileType
633                 }
634         } else {
635                 b.SyntaxDef = &highlight.EmptyDef
636         }
637
638         if b.SyntaxDef != nil {
639                 b.Highlighter = highlight.NewHighlighter(b.SyntaxDef)
640                 if b.Settings["syntax"].(bool) {
641                         go func() {
642                                 b.Highlighter.HighlightStates(b)
643                                 b.Highlighter.HighlightMatches(b, 0, b.End().Y)
644                                 screen.DrawChan <- true
645                         }()
646                 }
647         }
648 }
649
650 // ClearMatches clears all of the syntax highlighting for the buffer
651 func (b *Buffer) ClearMatches() {
652         for i := range b.lines {
653                 b.SetMatch(i, nil)
654                 b.SetState(i, nil)
655         }
656 }
657
658 // IndentString returns this buffer's indent method (a tabstop or n spaces
659 // depending on the settings)
660 func (b *Buffer) IndentString(tabsize int) string {
661         if b.Settings["tabstospaces"].(bool) {
662                 return util.Spaces(tabsize)
663         }
664         return "\t"
665 }
666
667 // SetCursors resets this buffer's cursors to a new list
668 func (b *Buffer) SetCursors(c []*Cursor) {
669         b.cursors = c
670         b.EventHandler.cursors = b.cursors
671         b.EventHandler.active = b.curCursor
672 }
673
674 // AddCursor adds a new cursor to the list
675 func (b *Buffer) AddCursor(c *Cursor) {
676         b.cursors = append(b.cursors, c)
677         b.EventHandler.cursors = b.cursors
678         b.EventHandler.active = b.curCursor
679         b.UpdateCursors()
680 }
681
682 // SetCurCursor sets the current cursor
683 func (b *Buffer) SetCurCursor(n int) {
684         b.curCursor = n
685 }
686
687 // GetActiveCursor returns the main cursor in this buffer
688 func (b *Buffer) GetActiveCursor() *Cursor {
689         return b.cursors[b.curCursor]
690 }
691
692 // GetCursor returns the nth cursor
693 func (b *Buffer) GetCursor(n int) *Cursor {
694         return b.cursors[n]
695 }
696
697 // GetCursors returns the list of cursors in this buffer
698 func (b *Buffer) GetCursors() []*Cursor {
699         return b.cursors
700 }
701
702 // NumCursors returns the number of cursors
703 func (b *Buffer) NumCursors() int {
704         return len(b.cursors)
705 }
706
707 // MergeCursors merges any cursors that are at the same position
708 // into one cursor
709 func (b *Buffer) MergeCursors() {
710         var cursors []*Cursor
711         for i := 0; i < len(b.cursors); i++ {
712                 c1 := b.cursors[i]
713                 if c1 != nil {
714                         for j := 0; j < len(b.cursors); j++ {
715                                 c2 := b.cursors[j]
716                                 if c2 != nil && i != j && c1.Loc == c2.Loc {
717                                         b.cursors[j] = nil
718                                 }
719                         }
720                         cursors = append(cursors, c1)
721                 }
722         }
723
724         b.cursors = cursors
725
726         for i := range b.cursors {
727                 b.cursors[i].Num = i
728         }
729
730         if b.curCursor >= len(b.cursors) {
731                 b.curCursor = len(b.cursors) - 1
732         }
733         b.EventHandler.cursors = b.cursors
734         b.EventHandler.active = b.curCursor
735 }
736
737 // UpdateCursors updates all the cursors indicies
738 func (b *Buffer) UpdateCursors() {
739         b.EventHandler.cursors = b.cursors
740         b.EventHandler.active = b.curCursor
741         for i, c := range b.cursors {
742                 c.Num = i
743         }
744 }
745
746 func (b *Buffer) RemoveCursor(i int) {
747         copy(b.cursors[i:], b.cursors[i+1:])
748         b.cursors[len(b.cursors)-1] = nil
749         b.cursors = b.cursors[:len(b.cursors)-1]
750         b.curCursor = util.Clamp(b.curCursor, 0, len(b.cursors)-1)
751         b.UpdateCursors()
752 }
753
754 // ClearCursors removes all extra cursors
755 func (b *Buffer) ClearCursors() {
756         for i := 1; i < len(b.cursors); i++ {
757                 b.cursors[i] = nil
758         }
759         b.cursors = b.cursors[:1]
760         b.UpdateCursors()
761         b.curCursor = 0
762         b.GetActiveCursor().ResetSelection()
763 }
764
765 // MoveLinesUp moves the range of lines up one row
766 func (b *Buffer) MoveLinesUp(start int, end int) {
767         if start < 1 || start >= end || end > len(b.lines) {
768                 return
769         }
770         l := string(b.LineBytes(start - 1))
771         if end == len(b.lines) {
772                 b.Insert(
773                         Loc{
774                                 utf8.RuneCount(b.lines[end-1].data),
775                                 end - 1,
776                         },
777                         "\n"+l,
778                 )
779         } else {
780                 b.Insert(
781                         Loc{0, end},
782                         l+"\n",
783                 )
784         }
785         b.Remove(
786                 Loc{0, start - 1},
787                 Loc{0, start},
788         )
789 }
790
791 // MoveLinesDown moves the range of lines down one row
792 func (b *Buffer) MoveLinesDown(start int, end int) {
793         if start < 0 || start >= end || end >= len(b.lines)-1 {
794                 return
795         }
796         l := string(b.LineBytes(end))
797         b.Insert(
798                 Loc{0, start},
799                 l+"\n",
800         )
801         end++
802         b.Remove(
803                 Loc{0, end},
804                 Loc{0, end + 1},
805         )
806 }
807
808 var BracePairs = [][2]rune{
809         {'(', ')'},
810         {'{', '}'},
811         {'[', ']'},
812 }
813
814 // FindMatchingBrace returns the location in the buffer of the matching bracket
815 // It is given a brace type containing the open and closing character, (for example
816 // '{' and '}') as well as the location to match from
817 // TODO: maybe can be more efficient with utf8 package
818 // returns the location of the matching brace
819 // if the boolean returned is true then the original matching brace is one character left
820 // of the starting location
821 func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
822         curLine := []rune(string(b.LineBytes(start.Y)))
823         startChar := ' '
824         if start.X >= 0 && start.X < len(curLine) {
825                 startChar = curLine[start.X]
826         }
827         leftChar := ' '
828         if start.X-1 >= 0 && start.X-1 < len(curLine) {
829                 leftChar = curLine[start.X-1]
830         }
831         var i int
832         if startChar == braceType[0] || leftChar == braceType[0] {
833                 for y := start.Y; y < b.LinesNum(); y++ {
834                         l := []rune(string(b.LineBytes(y)))
835                         xInit := 0
836                         if y == start.Y {
837                                 if startChar == braceType[0] {
838                                         xInit = start.X
839                                 } else {
840                                         xInit = start.X - 1
841                                 }
842                         }
843                         for x := xInit; x < len(l); x++ {
844                                 r := l[x]
845                                 if r == braceType[0] {
846                                         i++
847                                 } else if r == braceType[1] {
848                                         i--
849                                         if i == 0 {
850                                                 if startChar == braceType[0] {
851                                                         return Loc{x, y}, false
852                                                 }
853                                                 return Loc{x, y}, true
854                                         }
855                                 }
856                         }
857                 }
858         } else if startChar == braceType[1] || leftChar == braceType[1] {
859                 for y := start.Y; y >= 0; y-- {
860                         l := []rune(string(b.lines[y].data))
861                         xInit := len(l) - 1
862                         if y == start.Y {
863                                 if leftChar == braceType[1] {
864                                         xInit = start.X - 1
865                                 } else {
866                                         xInit = start.X
867                                 }
868                         }
869                         for x := xInit; x >= 0; x-- {
870                                 r := l[x]
871                                 if r == braceType[0] {
872                                         i--
873                                         if i == 0 {
874                                                 if leftChar == braceType[1] {
875                                                         return Loc{x, y}, true
876                                                 }
877                                                 return Loc{x, y}, false
878                                         }
879                                 } else if r == braceType[1] {
880                                         i++
881                                 }
882                         }
883                 }
884         }
885         return start, true
886 }
887
888 // Retab changes all tabs to spaces or vice versa
889 func (b *Buffer) Retab() {
890         toSpaces := b.Settings["tabstospaces"].(bool)
891         tabsize := util.IntOpt(b.Settings["tabsize"])
892         dirty := false
893
894         for i := 0; i < b.LinesNum(); i++ {
895                 l := b.LineBytes(i)
896
897                 ws := util.GetLeadingWhitespace(l)
898                 if len(ws) != 0 {
899                         if toSpaces {
900                                 ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
901                         } else {
902                                 ws = bytes.Replace(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'}, -1)
903                         }
904                 }
905
906                 l = bytes.TrimLeft(l, " \t")
907                 b.lines[i].data = append(ws, l...)
908                 dirty = true
909         }
910
911         b.isModified = dirty
912 }
913
914 // ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)
915 // into a loc
916 func ParseCursorLocation(cursorPositions []string) (Loc, error) {
917         startpos := Loc{0, 0}
918         var err error
919
920         // if no positions are available exit early
921         if cursorPositions == nil {
922                 return startpos, errors.New("No cursor positions were provided.")
923         }
924
925         startpos.Y, err = strconv.Atoi(cursorPositions[0])
926         startpos.Y--
927         if err == nil {
928                 if len(cursorPositions) > 1 {
929                         startpos.X, err = strconv.Atoi(cursorPositions[1])
930                         if startpos.X > 0 {
931                                 startpos.X--
932                         }
933                 }
934         }
935
936         return startpos, err
937 }
938
939 // Line returns the string representation of the given line number
940 func (b *Buffer) Line(i int) string {
941         return string(b.LineBytes(i))
942 }
943
944 func (b *Buffer) Write(bytes []byte) (n int, err error) {
945         b.EventHandler.InsertBytes(b.End(), bytes)
946         return len(bytes), nil
947 }
948
949 func (b *Buffer) updateDiffSync() {
950         b.diffLock.Lock()
951         defer b.diffLock.Unlock()
952
953         b.diff = make(map[int]DiffStatus)
954
955         if b.diffBase == nil {
956                 return
957         }
958
959         differ := dmp.New()
960         baseRunes, bufferRunes, _ := differ.DiffLinesToRunes(string(b.diffBase), string(b.Bytes()))
961         diffs := differ.DiffMainRunes(baseRunes, bufferRunes, false)
962         lineN := 0
963
964         for _, diff := range diffs {
965                 lineCount := len([]rune(diff.Text))
966
967                 switch diff.Type {
968                 case dmp.DiffEqual:
969                         lineN += lineCount
970                 case dmp.DiffInsert:
971                         var status DiffStatus
972                         if b.diff[lineN] == DSDeletedAbove {
973                                 status = DSModified
974                         } else {
975                                 status = DSAdded
976                         }
977                         for i := 0; i < lineCount; i++ {
978                                 b.diff[lineN] = status
979                                 lineN++
980                         }
981                 case dmp.DiffDelete:
982                         b.diff[lineN] = DSDeletedAbove
983                 }
984         }
985 }
986
987 // UpdateDiff computes the diff between the diff base and the buffer content.
988 // The update may be performed synchronously or asynchronously.
989 // UpdateDiff calls the supplied callback when the update is complete.
990 // The argument passed to the callback is set to true if and only if
991 // the update was performed synchronously.
992 // If an asynchronous update is already pending when UpdateDiff is called,
993 // UpdateDiff does not schedule another update, in which case the callback
994 // is not called.
995 func (b *Buffer) UpdateDiff(callback func(bool)) {
996         if b.updateDiffTimer != nil {
997                 return
998         }
999
1000         lineCount := b.LinesNum()
1001         if b.diffBaseLineCount > lineCount {
1002                 lineCount = b.diffBaseLineCount
1003         }
1004
1005         if lineCount < 1000 {
1006                 b.updateDiffSync()
1007                 callback(true)
1008         } else if lineCount < 30000 {
1009                 b.updateDiffTimer = time.AfterFunc(500*time.Millisecond, func() {
1010                         b.updateDiffTimer = nil
1011                         b.updateDiffSync()
1012                         callback(false)
1013                 })
1014         } else {
1015                 // Don't compute diffs for very large files
1016                 b.diffLock.Lock()
1017                 b.diff = make(map[int]DiffStatus)
1018                 b.diffLock.Unlock()
1019                 callback(true)
1020         }
1021 }
1022
1023 // SetDiffBase sets the text that is used as the base for diffing the buffer content
1024 func (b *Buffer) SetDiffBase(diffBase []byte) {
1025         b.diffBase = diffBase
1026         if diffBase == nil {
1027                 b.diffBaseLineCount = 0
1028         } else {
1029                 b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
1030         }
1031         b.UpdateDiff(func(synchronous bool) {
1032                 screen.DrawChan <- true
1033         })
1034 }
1035
1036 // DiffStatus returns the diff status for a line in the buffer
1037 func (b *Buffer) DiffStatus(lineN int) DiffStatus {
1038         b.diffLock.RLock()
1039         defer b.diffLock.RUnlock()
1040         // Note that the zero value for DiffStatus is equal to DSUnchanged
1041         return b.diff[lineN]
1042 }
1043
1044 // WriteLog writes a string to the log buffer
1045 func WriteLog(s string) {
1046         LogBuf.EventHandler.Insert(LogBuf.End(), s)
1047 }
1048
1049 // GetLogBuf returns the log buffer
1050 func GetLogBuf() *Buffer {
1051         return LogBuf
1052 }