]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/buffer.go
Start refactor
[micro.git] / cmd / micro / buffer.go
index fdcc37ad2d43cf3f8e71773c43d749b174f48165..747ace3d5051f599a361b30a7ff09459010f40ba 100644 (file)
@@ -1,33 +1,46 @@
 package main
 
 import (
+       "bufio"
        "bytes"
-       "encoding/gob"
+       "crypto/md5"
+       "errors"
        "io"
        "io/ioutil"
+       "log"
        "os"
        "os/exec"
        "os/signal"
        "path/filepath"
-       "regexp"
-       "strconv"
        "strings"
        "time"
        "unicode/utf8"
 
-       "github.com/mitchellh/go-homedir"
+       "github.com/zyedidia/micro/cmd/micro/highlight"
+)
+
+// LargeFileThreshold is the number of bytes when fastdirty is forced
+// because hashing is too slow
+const LargeFileThreshold = 50000
+
+// The BufType defines what kind of buffer this is
+type BufType struct {
+       Kind     int
+       Readonly bool // The file cannot be edited
+       Scratch  bool // The file cannot be saved
+}
+
+var (
+       btDefault = BufType{0, false, false}
+       btHelp    = BufType{1, true, true}
+       btLog     = BufType{2, true, true}
+       btScratch = BufType{3, false, true}
+       btRaw     = BufType{4, true, true}
 )
 
-// Buffer stores the text for files that are loaded into the text editor
-// It uses a rope to efficiently store the string and contains some
-// simple functions for saving and wrapper functions for modifying the rope
 type Buffer struct {
-       // The eventhandler for undo/redo
-       *EventHandler
-       // This stores all the text in the buffer as an array of lines
        *LineArray
-
-       Cursor Cursor
+       *EventHandler
 
        // Path to the file on disk
        Path string
@@ -37,46 +50,66 @@ type Buffer struct {
        name string
 
        // Whether or not the buffer has been modified since it was opened
-       IsModified bool
+       isModified bool
 
        // Stores the last modification time of the file the buffer is pointing to
        ModTime time.Time
 
-       NumLines int
+       syntaxDef   *highlight.Def
+       highlighter *highlight.Highlighter
 
-       // Syntax highlighting rules
-       rules []SyntaxRule
+       // Hash of the original buffer -- empty if fastdirty is on
+       origHash [md5.Size]byte
 
-       // Buffer local settings
+       // Settings customized by the user
        Settings map[string]interface{}
+
+       // Type of the buffer (e.g. help, raw, scratch etc..)
+       Type BufType
 }
 
-// The SerializedBuffer holds the types that get serialized when a buffer is saved
-// These are used for the savecursor and saveundo options
-type SerializedBuffer struct {
-       EventHandler *EventHandler
-       Cursor       Cursor
-       ModTime      time.Time
+// NewBufferFromFile opens a new buffer using the given path
+// It will also automatically handle `~`, and line/column with filename:l:c
+// It will return an empty buffer if the path does not exist
+// and an error if the file is a directory
+func NewBufferFromFile(path string) (*Buffer, error) {
+       var err error
+       filename, cursorPosition := GetPathAndCursorPosition(path)
+       filename, err = ReplaceHome(filename)
+       if err != nil {
+               return nil, err
+       }
+
+       file, err := os.Open(filename)
+       fileInfo, _ := os.Stat(filename)
+
+       if err == nil && fileInfo.IsDir() {
+               return nil, errors.New(filename + " is a directory")
+       }
+
+       defer file.Close()
+
+       var buf *Buffer
+       if err != nil {
+               // File does not exist -- create an empty buffer with that name
+               buf = NewBufferFromString("", filename)
+       } else {
+               buf = NewBuffer(file, FSize(file), filename, cursorPosition)
+       }
+
+       return buf, nil
 }
 
+// NewBufferFromString creates a new buffer containing the given string
 func NewBufferFromString(text, path string) *Buffer {
-       return NewBuffer(strings.NewReader(text), path)
+       return NewBuffer(strings.NewReader(text), int64(len(text)), path, nil)
 }
 
 // NewBuffer creates a new buffer from a given reader with a given path
-func NewBuffer(reader io.Reader, path string) *Buffer {
-       if path != "" {
-               for _, tab := range tabs {
-                       for _, view := range tab.views {
-                               if view.Buf.Path == path {
-                                       return view.Buf
-                               }
-                       }
-               }
-       }
-
+// Ensure that ReadSettings and InitGlobalSettings have been called before creating
+// a new buffer
+func NewBuffer(reader io.Reader, size int64, path string, cursorPosition []string) *Buffer {
        b := new(Buffer)
-       b.LineArray = NewLineArray(reader)
 
        b.Settings = DefaultLocalSettings()
        for k, v := range globalSettings {
@@ -84,6 +117,9 @@ func NewBuffer(reader io.Reader, path string) *Buffer {
                        b.Settings[k] = v
                }
        }
+       InitLocalSettings(b)
+
+       b.LineArray = NewLineArray(uint64(size), FFAuto, reader)
 
        absPath, _ := filepath.Abs(path)
 
@@ -95,83 +131,23 @@ func NewBuffer(reader io.Reader, path string) *Buffer {
 
        b.EventHandler = NewEventHandler(b)
 
-       b.Update()
-       b.FindFileType()
        b.UpdateRules()
-
-       if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
-               os.Mkdir(configDir+"/buffers/", os.ModePerm)
-       }
-
-       // Put the cursor at the first spot
-       cursorStartX := 0
-       cursorStartY := 0
-       // If -startpos LINE,COL was passed, use start position LINE,COL
-       if len(*flagStartPos) > 0 {
-               positions := strings.Split(*flagStartPos, ",")
-               if len(positions) == 2 {
-                       lineNum, errPos1 := strconv.Atoi(positions[0])
-                       colNum, errPos2 := strconv.Atoi(positions[1])
-                       if errPos1 == nil && errPos2 == nil {
-                               cursorStartX = colNum
-                               cursorStartY = lineNum - 1
-                               // Check to avoid line overflow
-                               if cursorStartY > b.NumLines {
-                                       cursorStartY = b.NumLines - 1
-                               } else if cursorStartY < 0 {
-                                       cursorStartY = 0
-                               }
-                               // Check to avoid column overflow
-                               if cursorStartX > len(b.Line(cursorStartY)) {
-                                       cursorStartX = len(b.Line(cursorStartY))
-                               } else if cursorStartX < 0 {
-                                       cursorStartX = 0
-                               }
-                       }
+       log.Println("Filetype detected: ", b.Settings["filetype"])
+
+       if !b.Settings["fastdirty"].(bool) {
+               if size > LargeFileThreshold {
+                       // If the file is larger than LargeFileThreshold fastdirty needs to be on
+                       b.Settings["fastdirty"] = true
+               } else {
+                       calcHash(b, &b.origHash)
                }
        }
-       b.Cursor = Cursor{
-               Loc: Loc{
-                       X: cursorStartX,
-                       Y: cursorStartY,
-               },
-               buf: b,
-       }
-
-       InitLocalSettings(b)
-
-       if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
-               // If either savecursor or saveundo is turned on, we need to load the serialized information
-               // from ~/.config/micro/buffers
-               file, err := os.Open(configDir + "/buffers/" + EscapePath(b.AbsPath))
-               if err == nil {
-                       var buffer SerializedBuffer
-                       decoder := gob.NewDecoder(file)
-                       gob.Register(TextEvent{})
-                       err = decoder.Decode(&buffer)
-                       if err != nil {
-                               TermMessage(err.Error(), "\n", "You may want to remove the files in ~/.config/micro/buffers (these files store the information for the 'saveundo' and 'savecursor' options) if this problem persists.")
-                       }
-                       if b.Settings["savecursor"].(bool) {
-                               b.Cursor = buffer.Cursor
-                               b.Cursor.buf = b
-                               b.Cursor.Relocate()
-                       }
-
-                       if b.Settings["saveundo"].(bool) {
-                               // We should only use last time's eventhandler if the file wasn't by someone else in the meantime
-                               if b.ModTime == buffer.ModTime {
-                                       b.EventHandler = buffer.EventHandler
-                                       b.EventHandler.buf = b
-                               }
-                       }
-               }
-               file.Close()
-       }
 
        return b
 }
 
+// GetName returns the name that should be displayed in the statusline
+// for this buffer
 func (b *Buffer) GetName() string {
        if b.name == "" {
                if b.Path == "" {
@@ -182,160 +158,181 @@ func (b *Buffer) GetName() string {
        return b.name
 }
 
-// UpdateRules updates the syntax rules and filetype for this buffer
-// This is called when the colorscheme changes
-func (b *Buffer) UpdateRules() {
-       b.rules = GetRules(b)
-}
-
-// FindFileType identifies this buffer's filetype based on the extension or header
-func (b *Buffer) FindFileType() {
-       b.Settings["filetype"] = FindFileType(b)
-}
-
 // FileType returns the buffer's filetype
 func (b *Buffer) FileType() string {
        return b.Settings["filetype"].(string)
 }
 
-// IndentString returns a string representing one level of indentation
-func (b *Buffer) IndentString() string {
-       if b.Settings["tabstospaces"].(bool) {
-               return Spaces(int(b.Settings["tabsize"].(float64)))
-       }
-       return "\t"
-}
-
-// CheckModTime makes sure that the file this buffer points to hasn't been updated
-// by an external program since it was last read
-// If it has, we ask the user if they would like to reload the file
-func (b *Buffer) CheckModTime() {
-       modTime, ok := GetModTime(b.Path)
-       if ok {
-               if modTime != b.ModTime {
-                       choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)")
-                       messenger.Reset()
-                       messenger.Clear()
-                       if !choice || canceled {
-                               // Don't load new changes -- do nothing
-                               b.ModTime, _ = GetModTime(b.Path)
-                       } else {
-                               // Load new changes
-                               b.ReOpen()
-                       }
-               }
-       }
-}
-
 // ReOpen reloads the current buffer from disk
-func (b *Buffer) ReOpen() {
+func (b *Buffer) ReOpen() error {
        data, err := ioutil.ReadFile(b.Path)
        txt := string(data)
 
        if err != nil {
-               messenger.Error(err.Error())
-               return
+               return err
        }
        b.EventHandler.ApplyDiff(txt)
 
-       b.ModTime, _ = GetModTime(b.Path)
-       b.IsModified = false
-       b.Update()
-       b.Cursor.Relocate()
+       b.ModTime, err = GetModTime(b.Path)
+       b.isModified = false
+       return err
+       // TODO: buffer cursor
+       // b.Cursor.Relocate()
 }
 
-// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
-func (b *Buffer) Update() {
-       b.NumLines = len(b.lines)
-}
+// Saving
 
 // Save saves the buffer to its default path
 func (b *Buffer) Save() error {
        return b.SaveAs(b.Path)
 }
 
-// SaveWithSudo saves the buffer to the default path with sudo
-func (b *Buffer) SaveWithSudo() error {
-       return b.SaveAsWithSudo(b.Path)
-}
-
-// Serialize serializes the buffer to configDir/buffers
-func (b *Buffer) Serialize() error {
-       if b.Settings["savecursor"].(bool) || b.Settings["saveundo"].(bool) {
-               file, err := os.Create(configDir + "/buffers/" + EscapePath(b.AbsPath))
-               if err == nil {
-                       enc := gob.NewEncoder(file)
-                       gob.Register(TextEvent{})
-                       err = enc.Encode(SerializedBuffer{
-                               b.EventHandler,
-                               b.Cursor,
-                               b.ModTime,
-                       })
-               }
-               file.Close()
-               return err
-       }
-       return nil
-}
-
 // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
 func (b *Buffer) SaveAs(filename string) error {
-       b.FindFileType()
+       // TODO: rmtrailingws and updaterules
        b.UpdateRules()
-       dir, _ := homedir.Dir()
-       b.Path = strings.Replace(filename, "~", dir, 1)
-       if b.Settings["rmtrailingws"].(bool) {
-               r, _ := regexp.Compile(`[ \t]+$`)
-               for lineNum, line := range b.Lines(0, b.NumLines) {
-                       indices := r.FindStringIndex(line)
-                       if indices == nil {
-                               continue
-                       }
-                       startLoc := Loc{indices[0], lineNum}
-                       b.deleteToEnd(startLoc)
-               }
-               b.Cursor.Relocate()
-       }
+       // if b.Settings["rmtrailingws"].(bool) {
+       //      for i, l := range b.lines {
+       //              pos := len(bytes.TrimRightFunc(l.data, unicode.IsSpace))
+       //
+       //              if pos < len(l.data) {
+       //                      b.deleteToEnd(Loc{pos, i})
+       //              }
+       //      }
+       //
+       //      b.Cursor.Relocate()
+       // }
+
        if b.Settings["eofnewline"].(bool) {
                end := b.End()
                if b.RuneAt(Loc{end.X - 1, end.Y}) != '\n' {
                        b.Insert(end, "\n")
                }
        }
-       str := b.String()
-       data := []byte(str)
-       err := ioutil.WriteFile(filename, data, 0644)
-       if err == nil {
-               b.IsModified = false
+
+       // Update the last time this file was updated after saving
+       defer func() {
                b.ModTime, _ = GetModTime(filename)
-               return b.Serialize()
+       }()
+
+       // Removes any tilde and replaces with the absolute path to home
+       absFilename, _ := ReplaceHome(filename)
+
+       // TODO: save creates parent dirs
+       // // Get the leading path to the file | "." is returned if there's no leading path provided
+       // if dirname := filepath.Dir(absFilename); dirname != "." {
+       //      // Check if the parent dirs don't exist
+       //      if _, statErr := os.Stat(dirname); os.IsNotExist(statErr) {
+       //              // Prompt to make sure they want to create the dirs that are missing
+       //              if yes, canceled := messenger.YesNoPrompt("Parent folders \"" + dirname + "\" do not exist. Create them? (y,n)"); yes && !canceled {
+       //                      // Create all leading dir(s) since they don't exist
+       //                      if mkdirallErr := os.MkdirAll(dirname, os.ModePerm); mkdirallErr != nil {
+       //                              // If there was an error creating the dirs
+       //                              return mkdirallErr
+       //                      }
+       //              } else {
+       //                      // If they canceled the creation of leading dirs
+       //                      return errors.New("Save aborted")
+       //              }
+       //      }
+       // }
+
+       var fileSize int
+
+       err := overwriteFile(absFilename, func(file io.Writer) (e error) {
+               if len(b.lines) == 0 {
+                       return
+               }
+
+               // end of line
+               var eol []byte
+               if b.Settings["fileformat"] == "dos" {
+                       eol = []byte{'\r', '\n'}
+               } else {
+                       eol = []byte{'\n'}
+               }
+
+               // write lines
+               if fileSize, e = file.Write(b.lines[0].data); e != nil {
+                       return
+               }
+
+               for _, l := range b.lines[1:] {
+                       if _, e = file.Write(eol); e != nil {
+                               return
+                       }
+                       if _, e = file.Write(l.data); e != nil {
+                               return
+                       }
+                       fileSize += len(eol) + len(l.data)
+               }
+               return
+       })
+
+       if err != nil {
+               return err
        }
-       b.ModTime, _ = GetModTime(filename)
-       return err
+
+       if !b.Settings["fastdirty"].(bool) {
+               if fileSize > LargeFileThreshold {
+                       // For large files 'fastdirty' needs to be on
+                       b.Settings["fastdirty"] = true
+               } else {
+                       calcHash(b, &b.origHash)
+               }
+       }
+
+       b.Path = filename
+       absPath, _ := filepath.Abs(filename)
+       b.AbsPath = absPath
+       b.isModified = false
+       // TODO: serialize
+       // return b.Serialize()
+       return nil
+}
+
+// overwriteFile opens the given file for writing, truncating if one exists, and then calls
+// the supplied function with the file as io.Writer object, also making sure the file is
+// closed afterwards.
+func overwriteFile(name string, fn func(io.Writer) error) (err error) {
+       var file *os.File
+
+       if file, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
+               return
+       }
+
+       defer func() {
+               if e := file.Close(); e != nil && err == nil {
+                       err = e
+               }
+       }()
+
+       w := bufio.NewWriter(file)
+
+       if err = fn(w); err != nil {
+               return
+       }
+
+       err = w.Flush()
+       return
+}
+
+// SaveWithSudo saves the buffer to the default path with sudo
+func (b *Buffer) SaveWithSudo() error {
+       return b.SaveAsWithSudo(b.Path)
 }
 
 // SaveAsWithSudo is the same as SaveAs except it uses a neat trick
 // with tee to use sudo so the user doesn't have to reopen micro with sudo
 func (b *Buffer) SaveAsWithSudo(filename string) error {
-       b.FindFileType()
        b.UpdateRules()
        b.Path = filename
-
-       // The user may have already used sudo in which case we won't need the password
-       // It's a bit nicer for them if they don't have to enter the password every time
-       _, err := RunShellCommand("sudo -v")
-       needPassword := err != nil
-
-       // If we need the password, we have to close the screen and ask using the shell
-       if needPassword {
-               // Shut down the screen because we're going to interact directly with the shell
-               screen.Fini()
-               screen = nil
-       }
+       absPath, _ := filepath.Abs(filename)
+       b.AbsPath = absPath
 
        // Set up everything for the command
-       cmd := exec.Command("sudo", "tee", filename)
-       cmd.Stdin = bytes.NewBufferString(b.String())
+       cmd := exec.Command(globalSettings["sucmd"].(string), "tee", filename)
+       cmd.Stdin = bytes.NewBuffer(b.Bytes())
 
        // This is a trap for Ctrl-C so that it doesn't kill micro
        // Instead we trap Ctrl-C to kill the program we're running
@@ -349,121 +346,162 @@ func (b *Buffer) SaveAsWithSudo(filename string) error {
 
        // Start the command
        cmd.Start()
-       err = cmd.Wait()
+       err := cmd.Wait()
 
-       // If we needed the password, we closed the screen, so we have to initialize it again
-       if needPassword {
-               // Start the screen back up
-               InitScreen()
-       }
        if err == nil {
-               b.IsModified = false
+               b.isModified = false
                b.ModTime, _ = GetModTime(filename)
-               b.Serialize()
+               // TODO: serialize
        }
        return err
 }
 
-func (b *Buffer) insert(pos Loc, value []byte) {
-       b.IsModified = true
-       b.LineArray.insert(pos, value)
-       b.Update()
+func (b *Buffer) GetActiveCursor() *Cursor {
+       return nil
 }
-func (b *Buffer) remove(start, end Loc) string {
-       b.IsModified = true
-       sub := b.LineArray.remove(start, end)
-       b.Update()
-       return sub
+
+func (b *Buffer) GetCursor(n int) *Cursor {
+       return nil
 }
-func (b *Buffer) deleteToEnd(start Loc) {
-       b.IsModified = true
-       b.LineArray.DeleteToEnd(start)
-       b.Update()
+
+func (b *Buffer) GetCursors() []*Cursor {
+       return nil
+}
+
+func (b *Buffer) NumCursors() int {
+       return 0
+}
+
+func (b *Buffer) LineBytes(n int) []byte {
+       if n >= len(b.lines) || n < 0 {
+               return []byte{}
+       }
+       return b.lines[n].data
+}
+
+func (b *Buffer) LinesNum() int {
+       return len(b.lines)
 }
 
-// Start returns the location of the first character in the buffer
 func (b *Buffer) Start() Loc {
        return Loc{0, 0}
 }
 
 // End returns the location of the last character in the buffer
 func (b *Buffer) End() Loc {
-       return Loc{utf8.RuneCount(b.lines[b.NumLines-1]), b.NumLines - 1}
+       numlines := len(b.lines)
+       return Loc{utf8.RuneCount(b.lines[numlines-1].data), numlines - 1}
 }
 
 // RuneAt returns the rune at a given location in the buffer
 func (b *Buffer) RuneAt(loc Loc) rune {
-       line := []rune(b.Line(loc.Y))
+       line := b.LineBytes(loc.Y)
        if len(line) > 0 {
-               return line[loc.X]
+               i := 0
+               for len(line) > 0 {
+                       r, size := utf8.DecodeRune(line)
+                       line = line[size:]
+                       i++
+
+                       if i == loc.X {
+                               return r
+                       }
+               }
        }
        return '\n'
 }
 
-// Line returns a single line
-func (b *Buffer) Line(n int) string {
-       if n >= len(b.lines) {
-               return ""
+// Modified returns if this buffer has been modified since
+// being opened
+func (b *Buffer) Modified() bool {
+       if b.Settings["fastdirty"].(bool) {
+               return b.isModified
        }
-       return string(b.lines[n])
+
+       var buff [md5.Size]byte
+
+       calcHash(b, &buff)
+       return buff != b.origHash
 }
 
-// Lines returns an array of strings containing the lines from start to end
-func (b *Buffer) Lines(start, end int) []string {
-       lines := b.lines[start:end]
-       var slice []string
-       for _, line := range lines {
-               slice = append(slice, string(line))
+// calcHash calculates md5 hash of all lines in the buffer
+func calcHash(b *Buffer, out *[md5.Size]byte) {
+       h := md5.New()
+
+       if len(b.lines) > 0 {
+               h.Write(b.lines[0].data)
+
+               for _, l := range b.lines[1:] {
+                       h.Write([]byte{'\n'})
+                       h.Write(l.data)
+               }
        }
-       return slice
-}
 
-// Len gives the length of the buffer
-func (b *Buffer) Len() int {
-       return Count(b.String())
+       h.Sum((*out)[:0])
 }
 
-// MoveLinesUp moves the range of lines up one row
-func (b *Buffer) MoveLinesUp(start int, end int) {
-       // 0 < start < end <= len(b.lines)
-       if start < 1 || start >= end || end > len(b.lines) {
-               return // what to do? FIXME
+// UpdateRules updates the syntax rules and filetype for this buffer
+// This is called when the colorscheme changes
+func (b *Buffer) UpdateRules() {
+       rehighlight := false
+       var files []*highlight.File
+       for _, f := range ListRuntimeFiles(RTSyntax) {
+               data, err := f.Data()
+               if err != nil {
+                       TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+               } else {
+                       file, err := highlight.ParseFile(data)
+                       if err != nil {
+                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                               continue
+                       }
+                       ftdetect, err := highlight.ParseFtDetect(file)
+                       if err != nil {
+                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                               continue
+                       }
+
+                       ft := b.Settings["filetype"].(string)
+                       if (ft == "Unknown" || ft == "") && !rehighlight {
+                               if highlight.MatchFiletype(ftdetect, b.Path, b.lines[0].data) {
+                                       header := new(highlight.Header)
+                                       header.FileType = file.FileType
+                                       header.FtDetect = ftdetect
+                                       b.syntaxDef, err = highlight.ParseDef(file, header)
+                                       if err != nil {
+                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                                               continue
+                                       }
+                                       rehighlight = true
+                               }
+                       } else {
+                               if file.FileType == ft && !rehighlight {
+                                       header := new(highlight.Header)
+                                       header.FileType = file.FileType
+                                       header.FtDetect = ftdetect
+                                       b.syntaxDef, err = highlight.ParseDef(file, header)
+                                       if err != nil {
+                                               TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
+                                               continue
+                                       }
+                                       rehighlight = true
+                               }
+                       }
+                       files = append(files, file)
+               }
        }
-       if end == len(b.lines) {
-               b.Insert(
-                       Loc{
-                               utf8.RuneCount(b.lines[end-1]),
-                               end - 1,
-                       },
-                       "\n"+b.Line(start-1),
-               )
-       } else {
-               b.Insert(
-                       Loc{0, end},
-                       b.Line(start-1)+"\n",
-               )
+
+       if b.syntaxDef != nil {
+               highlight.ResolveIncludes(b.syntaxDef, files)
        }
-       b.Remove(
-               Loc{0, start - 1},
-               Loc{0, start},
-       )
-}
 
-// MoveLinesDown moves the range of lines down one row
-func (b *Buffer) MoveLinesDown(start int, end int) {
-       // 0 <= start < end < len(b.lines)
-       // if end == len(b.lines), we can't do anything here because the
-       // last line is unaccessible, FIXME
-       if start < 0 || start >= end || end >= len(b.lines)-1 {
-               return // what to do? FIXME
+       if b.highlighter == nil || rehighlight {
+               if b.syntaxDef != nil {
+                       b.Settings["filetype"] = b.syntaxDef.FileType
+                       b.highlighter = highlight.NewHighlighter(b.syntaxDef)
+                       if b.Settings["syntax"].(bool) {
+                               b.highlighter.HighlightStates(b)
+                       }
+               }
        }
-       b.Insert(
-               Loc{0, start},
-               b.Line(end)+"\n",
-       )
-       end++
-       b.Remove(
-               Loc{0, end},
-               Loc{0, end + 1},
-       )
 }