]> git.lizzy.rs Git - micro.git/blobdiff - internal/buffer/buffer.go
Use abspath for local glob settings
[micro.git] / internal / buffer / buffer.go
index 43be4a81ef689292930b02721edf9d07e147b1b4..f33b24389b225425bdcf2b61503cae3e4b1e79db 100644 (file)
@@ -146,18 +146,20 @@ func (b *SharedBuffer) remove(start, end Loc) []byte {
 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)-1)
        end = util.Clamp(end, 0, len(b.lines)-1)
 
-       l := -1
+       if b.Settings["syntax"].(bool) && b.SyntaxDef != nil {
+               l := -1
+               for i := start; i <= end; i++ {
+                       l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
+               }
+               b.Highlighter.HighlightMatches(b, start, l)
+       }
+
        for i := start; i <= end; i++ {
-               l = util.Max(b.Highlighter.ReHighlightStates(b, i), l)
+               b.LineArray.invalidateSearchMatches(i)
        }
-       b.Highlighter.HighlightMatches(b, start, l)
 }
 
 // DisableReload disables future reloads of this sharedbuffer
@@ -181,6 +183,7 @@ type DiffStatus byte
 // The syntax highlighting info must be stored with the buffer because the syntax
 // highlighter attaches information to each line of the buffer for optimization
 // purposes so it doesn't have to rehighlight everything on every update.
+// Likewise for the search highlighting.
 type Buffer struct {
        *EventHandler
        *SharedBuffer
@@ -189,6 +192,25 @@ type Buffer struct {
        cursors     []*Cursor
        curCursor   int
        StartCursor Loc
+
+       // OptionCallback is called after a buffer option value is changed.
+       // The display module registers its OptionCallback to ensure the buffer window
+       // is properly updated when needed. This is a workaround for the fact that
+       // the buffer module cannot directly call the display's API (it would mean
+       // a circular dependency between packages).
+       OptionCallback func(option string, nativeValue interface{})
+
+       // The display module registers its own GetVisualX function for getting
+       // the correct visual x location of a cursor when softwrap is used.
+       // This is hacky. Maybe it would be better to move all the visual x logic
+       // from buffer to display, but it would require rewriting a lot of code.
+       GetVisualX func(loc Loc) int
+
+       // Last search stores the last successful search
+       LastSearch      string
+       LastSearchRegex bool
+       // HighlightSearch enables highlighting all instances of the last successful search
+       HighlightSearch bool
 }
 
 // NewBufferFromFileAtLoc opens a new buffer with a given cursor location
@@ -212,21 +234,39 @@ func NewBufferFromFileAtLoc(path string, btype BufType, cursorLoc Loc) (*Buffer,
                return nil, err
        }
 
-       file, err := os.Open(filename)
-       fileInfo, _ := os.Stat(filename)
+       f, err := os.OpenFile(filename, os.O_WRONLY, 0)
+       readonly := os.IsPermission(err)
+       f.Close()
 
-       if err == nil && fileInfo.IsDir() {
+       fileInfo, serr := os.Stat(filename)
+       if serr != nil && !os.IsNotExist(serr) {
+               return nil, serr
+       }
+       if serr == nil && fileInfo.IsDir() {
                return nil, errors.New("Error: " + filename + " is a directory and cannot be opened")
        }
 
-       defer file.Close()
+       file, err := os.Open(filename)
+       if err == nil {
+               defer file.Close()
+       }
 
        var buf *Buffer
-       if err != nil {
+       if os.IsNotExist(err) {
                // File does not exist -- create an empty buffer with that name
                buf = NewBufferFromString("", filename, btype)
+       } else if err != nil {
+               return nil, err
        } else {
                buf = NewBuffer(file, util.FSize(file), filename, cursorLoc, btype)
+               if buf == nil {
+                       return nil, errors.New("could not open file")
+               }
+       }
+
+       if readonly && prompt != nil {
+               prompt.Message(fmt.Sprintf("Warning: file is readonly - %s will be attempted when saving", config.GlobalSettings["sucmd"].(string)))
+               // buf.SetOptionNative("readonly", true)
        }
 
        return buf, nil
@@ -256,7 +296,10 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer {
 // Places the cursor at startcursor. If startcursor is -1, -1 places the
 // cursor at an autodetected location (based on savecursor or :LINE:COL)
 func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufType) *Buffer {
-       absPath, _ := filepath.Abs(path)
+       absPath, err := filepath.Abs(path)
+       if err != nil {
+               absPath = path
+       }
 
        b := new(Buffer)
 
@@ -279,23 +322,38 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
                b.AbsPath = absPath
                b.Path = path
 
+               // this is a little messy since we need to know some settings to read
+               // the file properly, but some settings depend on the filetype, which
+               // we don't know until reading the file. We first read the settings
+               // into a local variable and then use that to determine the encoding,
+               // readonly, and fileformat necessary for reading the file and
+               // assigning the filetype.
+               settings := config.DefaultCommonSettings()
                b.Settings = config.DefaultCommonSettings()
                for k, v := range config.GlobalSettings {
                        if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
                                // make sure setting is not global-only
+                               settings[k] = v
                                b.Settings[k] = v
                        }
                }
-               config.InitLocalSettings(b.Settings, path)
+               config.InitLocalSettings(settings, absPath)
+               b.Settings["readonly"] = settings["readonly"]
+               b.Settings["filetype"] = settings["filetype"]
+               b.Settings["syntax"] = settings["syntax"]
 
-               enc, err := htmlindex.Get(b.Settings["encoding"].(string))
+               enc, err := htmlindex.Get(settings["encoding"].(string))
                if err != nil {
                        enc = unicode.UTF8
                        b.Settings["encoding"] = "utf-8"
                }
 
-               hasBackup = b.ApplyBackup(size)
+               var ok bool
+               hasBackup, ok = b.ApplyBackup(size)
 
+               if !ok {
+                       return NewBufferFromString("", "", btype)
+               }
                if !hasBackup {
                        reader := bufio.NewReader(transform.NewReader(r, enc.NewDecoder()))
 
@@ -304,7 +362,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
                        if size == 0 {
                                // for empty files, use the fileformat setting instead of
                                // autodetection
-                               switch b.Settings["fileformat"] {
+                               switch settings["fileformat"] {
                                case "unix":
                                        ff = FFUnix
                                case "dos":
@@ -341,12 +399,10 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
 
        if startcursor.X != -1 && startcursor.Y != -1 {
                b.StartCursor = startcursor
-       } else {
-               if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
-                       err := b.Unserialize()
-                       if err != nil {
-                               screen.TermMessage(err)
-                       }
+       } else if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
+               err := b.Unserialize()
+               if err != nil {
+                       screen.TermMessage(err)
                }
        }
 
@@ -511,16 +567,38 @@ func (b *Buffer) RuneAt(loc Loc) rune {
                for len(line) > 0 {
                        r, _, size := util.DecodeCharacter(line)
                        line = line[size:]
-                       i++
 
                        if i == loc.X {
                                return r
                        }
+
+                       i++
                }
        }
        return '\n'
 }
 
+// WordAt returns the word around a given location in the buffer
+func (b *Buffer) WordAt(loc Loc) []byte {
+       if len(b.LineBytes(loc.Y)) == 0 || !util.IsWordChar(b.RuneAt(loc)) {
+               return []byte{}
+       }
+
+       start := loc
+       end := loc.Move(1, b)
+
+       for start.X > 0 && util.IsWordChar(b.RuneAt(start.Move(-1, b))) {
+               start.X--
+       }
+
+       lineLen := util.CharacterCount(b.LineBytes(loc.Y))
+       for end.X < lineLen && util.IsWordChar(b.RuneAt(end)) {
+               end.X++
+       }
+
+       return b.Substr(start, end)
+}
+
 // Modified returns if this buffer has been modified since
 // being opened
 func (b *Buffer) Modified() bool {
@@ -988,9 +1066,9 @@ func (b *Buffer) Retab() {
                ws := util.GetLeadingWhitespace(l)
                if len(ws) != 0 {
                        if toSpaces {
-                               ws = bytes.Replace(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize), -1)
+                               ws = bytes.ReplaceAll(ws, []byte{'\t'}, bytes.Repeat([]byte{' '}, tabsize))
                        } else {
-                               ws = bytes.Replace(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'}, -1)
+                               ws = bytes.ReplaceAll(ws, bytes.Repeat([]byte{' '}, tabsize), []byte{'\t'})
                        }
                }
 
@@ -1133,6 +1211,12 @@ func (b *Buffer) DiffStatus(lineN int) DiffStatus {
        return b.diff[lineN]
 }
 
+// SearchMatch returns true if the given location is within a match of the last search.
+// It is used for search highlighting
+func (b *Buffer) SearchMatch(pos Loc) bool {
+       return b.LineArray.SearchMatch(b, pos)
+}
+
 // WriteLog writes a string to the log buffer
 func WriteLog(s string) {
        LogBuf.EventHandler.Insert(LogBuf.End(), s)