]> git.lizzy.rs Git - micro.git/blobdiff - internal/buffer/buffer.go
Support csharp-script syntax. (#1425)
[micro.git] / internal / buffer / buffer.go
index 402aeffa782996e97aa4e517f94ae292bb88ba11..fc74efe5b9b1cede591db6507b4fb94c6961c0a3 100644 (file)
@@ -5,10 +5,11 @@ import (
        "bytes"
        "crypto/md5"
        "errors"
+       "fmt"
        "io"
        "io/ioutil"
-       "log"
        "os"
+       "path"
        "path/filepath"
        "strconv"
        "strings"
@@ -60,6 +61,9 @@ var (
        BTRaw = BufType{4, false, true, false}
        // BTInfo is a buffer for inputting information
        BTInfo = BufType{5, false, true, false}
+       // BTStdout is a buffer that only writes to stdout
+       // when closed
+       BTStdout = BufType{6, false, true, true}
 
        // ErrFileTooLarge is returned when the file is too large to hash
        // (fastdirty is automatically enabled)
@@ -75,13 +79,53 @@ type SharedBuffer struct {
        // Type of the buffer (e.g. help, raw, scratch etc..)
        Type BufType
 
+       // Path to the file on disk
+       Path string
+       // Absolute path to the file on disk
+       AbsPath string
+       // Name of the buffer on the status line
+       name string
+
+       toStdout bool
+
+       // Settings customized by the user
+       Settings map[string]interface{}
+
+       Suggestions   []string
+       Completions   []string
+       CurSuggestion int
+
+       Messages []*Message
+
+       updateDiffTimer   *time.Timer
+       diffBase          []byte
+       diffBaseLineCount int
+       diffLock          sync.RWMutex
+       diff              map[int]DiffStatus
+
+       // counts the number of edits
+       // resets every backupTime edits
+       lastbackup time.Time
+
+       // ReloadDisabled allows the user to disable reloads if they
+       // are viewing a file that is constantly changing
+       ReloadDisabled bool
+
        isModified bool
        // Whether or not suggestions can be autocompleted must be shared because
        // it changes based on how the buffer has changed
        HasSuggestions bool
 
-       // Modifications is the list of modified regions for syntax highlighting
-       Modifications []Loc
+       // The Highlighter struct actually performs the highlighting
+       Highlighter *highlight.Highlighter
+       // SyntaxDef represents the syntax highlighting definition being used
+       // This stores the highlighting rules and filetype detection info
+       SyntaxDef *highlight.Def
+
+       ModifiedThisFrame bool
+
+       // Hash of the original buffer -- empty if fastdirty is on
+       origHash [md5.Size]byte
 }
 
 func (b *SharedBuffer) insert(pos Loc, value []byte) {
@@ -89,17 +133,40 @@ func (b *SharedBuffer) insert(pos Loc, value []byte) {
        b.HasSuggestions = false
        b.LineArray.insert(pos, value)
 
-       // b.Modifications is cleared every screen redraw so it's
-       // ok to append duplicates
-       b.Modifications = append(b.Modifications, Loc{pos.Y, pos.Y + bytes.Count(value, []byte{'\n'})})
+       inslines := bytes.Count(value, []byte{'\n'})
+       b.MarkModified(pos.Y, pos.Y+inslines)
 }
 func (b *SharedBuffer) remove(start, end Loc) []byte {
        b.isModified = true
        b.HasSuggestions = false
-       b.Modifications = append(b.Modifications, Loc{start.Y, start.Y})
+       defer b.MarkModified(start.Y, end.Y)
        return b.LineArray.remove(start, end)
 }
 
+// MarkModified marks the buffer as modified for this frame
+// and performs rehighlighting if syntax highlighting is enabled
+func (b *SharedBuffer) MarkModified(start, end int) {
+       b.ModifiedThisFrame = true
+
+       if !b.Settings["syntax"].(bool) || b.SyntaxDef == nil {
+               return
+       }
+
+       start = util.Clamp(start, 0, len(b.lines))
+       end = util.Clamp(end, 0, len(b.lines))
+
+       l := -1
+       for i := start; i <= end; i++ {
+               l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
+       }
+       b.Highlighter.HighlightMatches(b, start, l+1)
+}
+
+// DisableReload disables future reloads of this sharedbuffer
+func (b *SharedBuffer) DisableReload() {
+       b.ReloadDisabled = true
+}
+
 const (
        DSUnchanged    = 0
        DSAdded        = 1
@@ -123,42 +190,6 @@ type Buffer struct {
        cursors     []*Cursor
        curCursor   int
        StartCursor Loc
-
-       // Path to the file on disk
-       Path string
-       // Absolute path to the file on disk
-       AbsPath string
-       // Name of the buffer on the status line
-       name string
-
-       // SyntaxDef represents the syntax highlighting definition being used
-       // This stores the highlighting rules and filetype detection info
-       SyntaxDef *highlight.Def
-       // The Highlighter struct actually performs the highlighting
-       Highlighter   *highlight.Highlighter
-       HighlightLock sync.Mutex
-
-       // Hash of the original buffer -- empty if fastdirty is on
-       origHash [md5.Size]byte
-
-       // Settings customized by the user
-       Settings map[string]interface{}
-
-       Suggestions   []string
-       Completions   []string
-       CurSuggestion int
-
-       Messages []*Message
-
-       updateDiffTimer   *time.Timer
-       diffBase          []byte
-       diffBaseLineCount int
-       diffLock          sync.RWMutex
-       diff              map[int]DiffStatus
-
-       // counts the number of edits
-       // resets every backupTime edits
-       lastbackup time.Time
 }
 
 // NewBufferFromFile opens a new buffer using the given path
@@ -213,22 +244,6 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
 
        b := new(Buffer)
 
-       b.Settings = config.DefaultCommonSettings()
-       for k, v := range config.GlobalSettings {
-               if _, ok := b.Settings[k]; ok {
-                       b.Settings[k] = v
-               }
-       }
-       config.InitLocalSettings(b.Settings, path)
-
-       enc, err := htmlindex.Get(b.Settings["encoding"].(string))
-       if err != nil {
-               enc = unicode.UTF8
-               b.Settings["encoding"] = "utf-8"
-       }
-
-       reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
-
        found := false
        if len(path) > 0 {
                for _, buf := range OpenBuffers {
@@ -240,28 +255,44 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
                }
        }
 
-       b.Path = path
-       b.AbsPath = absPath
-
        if !found {
                b.SharedBuffer = new(SharedBuffer)
                b.Type = btype
 
+               b.AbsPath = absPath
+               b.Path = path
+
+               b.Settings = config.DefaultCommonSettings()
+               for k, v := range config.GlobalSettings {
+                       if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
+                               // make sure setting is not global-only
+                               b.Settings[k] = v
+                       }
+               }
+               config.InitLocalSettings(b.Settings, path)
+
+               enc, err := htmlindex.Get(b.Settings["encoding"].(string))
+               if err != nil {
+                       enc = unicode.UTF8
+                       b.Settings["encoding"] = "utf-8"
+               }
+
                hasBackup := b.ApplyBackup(size)
 
                if !hasBackup {
+                       reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
                        b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
                }
                b.EventHandler = NewEventHandler(b.SharedBuffer, b.cursors)
+
+               // The last time this file was modified
+               b.UpdateModTime()
        }
 
        if b.Settings["readonly"].(bool) && b.Type == BTDefault {
                b.Type.Readonly = true
        }
 
-       // The last time this file was modified
-       b.UpdateModTime()
-
        switch b.Endings {
        case FFUnix:
                b.Settings["fileformat"] = "unix"
@@ -270,10 +301,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
        }
 
        b.UpdateRules()
+       // init local settings again now that we know the filetype
        config.InitLocalSettings(b.Settings, b.Path)
 
-       if _, err := os.Stat(config.ConfigDir + "/buffers/"); os.IsNotExist(err) {
-               os.Mkdir(config.ConfigDir+"/buffers/", os.ModePerm)
+       if _, err := os.Stat(filepath.Join(config.ConfigDir, "buffers")); os.IsNotExist(err) {
+               os.Mkdir(filepath.Join(config.ConfigDir, "buffers"), os.ModePerm)
        }
 
        if startcursor.X != -1 && startcursor.Y != -1 {
@@ -290,7 +322,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
        b.AddCursor(NewCursor(b, b.StartCursor))
        b.GetActiveCursor().Relocate()
 
-       if !b.Settings["fastdirty"].(bool) {
+       if !b.Settings["fastdirty"].(bool) && !found {
                if size > LargeFileThreshold {
                        // If the file is larger than LargeFileThreshold fastdirty needs to be on
                        b.Settings["fastdirty"] = true
@@ -299,13 +331,11 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
                }
        }
 
-       err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
+       err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
        if err != nil {
                screen.TermMessage(err)
        }
 
-       b.Modifications = make([]Loc, 0, 10)
-
        OpenBuffers = append(OpenBuffers, b)
 
        return b
@@ -331,18 +361,26 @@ func (b *Buffer) Fini() {
                b.Serialize()
        }
        b.RemoveBackup()
+
+       if b.Type == BTStdout {
+               fmt.Fprint(util.Stdout, string(b.Bytes()))
+       }
 }
 
 // GetName returns the name that should be displayed in the statusline
 // for this buffer
 func (b *Buffer) GetName() string {
-       if b.name == "" {
+       name := b.name
+       if name == "" {
                if b.Path == "" {
                        return "No name"
                }
-               return b.Path
+               name = b.Path
+       }
+       if b.Settings["basename"].(bool) {
+               return path.Base(name)
        }
-       return b.name
+       return name
 }
 
 //SetName changes the name for this buffer
@@ -372,16 +410,6 @@ func (b *Buffer) Remove(start, end Loc) {
        }
 }
 
-// ClearModifications clears the list of modified lines in this buffer
-// The list of modified lines is used for syntax highlighting so that
-// we can selectively highlight only the necessary lines
-// This function should be called every time this buffer is drawn to
-// the screen
-func (b *Buffer) ClearModifications() {
-       // clear slice without resetting the cap
-       b.Modifications = b.Modifications[:0]
-}
-
 // FileType returns the buffer's filetype
 func (b *Buffer) FileType() string {
        return b.Settings["filetype"].(string)
@@ -425,6 +453,9 @@ func (b *Buffer) ReOpen() error {
        b.EventHandler.ApplyDiff(txt)
 
        err = b.UpdateModTime()
+       if !b.Settings["fastdirty"].(bool) {
+               calcHash(b, &b.origHash)
+       }
        b.isModified = false
        b.RelocateCursors()
        return err
@@ -517,7 +548,37 @@ func (b *Buffer) UpdateRules() {
                return
        }
        syntaxFile := ""
+       foundDef := false
        var header *highlight.Header
+       // search for the syntax file in the user's custom syntax files
+       for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
+               data, err := f.Data()
+               if err != nil {
+                       screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                       continue
+               }
+
+               header, err = highlight.MakeHeaderYaml(data)
+               file, err := highlight.ParseFile(data)
+               if err != nil {
+                       screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
+                       continue
+               }
+
+               if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
+                       syndef, err := highlight.ParseDef(file, header)
+                       if err != nil {
+                               screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
+                               continue
+                       }
+                       b.SyntaxDef = syndef
+                       syntaxFile = f.Name()
+                       foundDef = true
+                       break
+               }
+       }
+
+       // search in the default syntax files
        for _, f := range config.ListRuntimeFiles(config.RTSyntaxHeader) {
                data, err := f.Data()
                if err != nil {
@@ -542,34 +603,8 @@ func (b *Buffer) UpdateRules() {
                }
        }
 
-       if syntaxFile == "" {
-               // search for the syntax file in the user's custom syntax files
-               for _, f := range config.ListRealRuntimeFiles(config.RTSyntax) {
-                       log.Println("real runtime file", f.Name())
-                       data, err := f.Data()
-                       if err != nil {
-                               screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
-                               continue
-                       }
-
-                       header, err = highlight.MakeHeaderYaml(data)
-                       file, err := highlight.ParseFile(data)
-                       if err != nil {
-                               screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
-                               continue
-                       }
-
-                       if ((ft == "unknown" || ft == "") && highlight.MatchFiletype(header.FtDetect, b.Path, b.lines[0].data)) || header.FileType == ft {
-                               syndef, err := highlight.ParseDef(file, header)
-                               if err != nil {
-                                       screen.TermMessage("Error parsing syntax file " + f.Name() + ": " + err.Error())
-                                       continue
-                               }
-                               b.SyntaxDef = syndef
-                               break
-                       }
-               }
-       } else {
+       if syntaxFile != "" && !foundDef {
+               // we found a syntax file using a syntax header file
                for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
                        if f.Name() == syntaxFile {
                                data, err := f.Data()
@@ -644,7 +679,7 @@ func (b *Buffer) UpdateRules() {
                        go func() {
                                b.Highlighter.HighlightStates(b)
                                b.Highlighter.HighlightMatches(b, 0, b.End().Y)
-                               screen.DrawChan <- true
+                               screen.Redraw()
                        }()
                }
        }
@@ -821,7 +856,7 @@ var BracePairs = [][2]rune{
 // returns the location of the matching brace
 // if the boolean returned is true then the original matching brace is one character left
 // of the starting location
-func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
+func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool, bool) {
        curLine := []rune(string(b.LineBytes(start.Y)))
        startChar := ' '
        if start.X >= 0 && start.X < len(curLine) {
@@ -851,9 +886,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
                                        i--
                                        if i == 0 {
                                                if startChar == braceType[0] {
-                                                       return Loc{x, y}, false
+                                                       return Loc{x, y}, false, true
                                                }
-                                               return Loc{x, y}, true
+                                               return Loc{x, y}, true, true
                                        }
                                }
                        }
@@ -875,9 +910,9 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
                                        i--
                                        if i == 0 {
                                                if leftChar == braceType[1] {
-                                                       return Loc{x, y}, true
+                                                       return Loc{x, y}, true, true
                                                }
-                                               return Loc{x, y}, false
+                                               return Loc{x, y}, false, true
                                        }
                                } else if r == braceType[1] {
                                        i++
@@ -885,7 +920,7 @@ func (b *Buffer) FindMatchingBrace(braceType [2]rune, start Loc) (Loc, bool) {
                        }
                }
        }
-       return start, true
+       return start, true, false
 }
 
 // Retab changes all tabs to spaces or vice versa
@@ -908,7 +943,7 @@ func (b *Buffer) Retab() {
 
                l = bytes.TrimLeft(l, " \t")
                b.lines[i].data = append(ws, l...)
-               b.Modifications = append(b.Modifications, Loc{i, i})
+               b.MarkModified(i, i)
                dirty = true
        }
 
@@ -1033,7 +1068,7 @@ func (b *Buffer) SetDiffBase(diffBase []byte) {
                b.diffBaseLineCount = strings.Count(string(diffBase), "\n")
        }
        b.UpdateDiff(func(synchronous bool) {
-               screen.DrawChan <- true
+               screen.Redraw()
        })
 }