15 // Buffer stores the text for files that are loaded into the text editor
16 // It uses a rope to efficiently store the string and contains some
17 // simple functions for saving and wrapper functions for modifying the rope
19 // The eventhandler for undo/redo
21 // This stores all the text in the buffer as an array of lines
26 // Path to the file on disk
28 // Name of the buffer on the status line
31 // Whether or not the buffer has been modified since it was opened
34 // Stores the last modification time of the file the buffer is pointing to
39 // Syntax highlighting rules
41 // The buffer's filetype
45 // The SerializedBuffer holds the types that get serialized when a buffer is saved
46 // These are used for the savecursor and saveundo options
47 type SerializedBuffer struct {
48 EventHandler *EventHandler
53 // NewBuffer creates a new buffer from `txt` with path and name `path`
54 func NewBuffer(txt []byte, path string) *Buffer {
56 b.LineArray = NewLineArray(txt)
61 // If the file doesn't have a path to disk then we give it no name
66 // The last time this file was modified
67 b.ModTime, _ = GetModTime(b.Path)
69 b.EventHandler = NewEventHandler(b)
74 if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
75 os.Mkdir(configDir+"/buffers/", os.ModePerm)
78 // Put the cursor at the first spot
87 if settings["savecursor"].(bool) || settings["saveundo"].(bool) {
88 // If either savecursor or saveundo is turned on, we need to load the serialized information
89 // from ~/.config/micro/buffers
90 absPath, _ := filepath.Abs(b.Path)
91 file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
93 var buffer SerializedBuffer
94 decoder := gob.NewDecoder(file)
95 gob.Register(TextEvent{})
96 err = decoder.Decode(&buffer)
98 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.")
100 if settings["savecursor"].(bool) {
101 b.Cursor = buffer.Cursor
106 if settings["saveundo"].(bool) {
107 // We should only use last time's eventhandler if the file wasn't by someone else in the meantime
108 if b.ModTime == buffer.ModTime {
109 b.EventHandler = buffer.EventHandler
110 b.EventHandler.buf = b
120 // UpdateRules updates the syntax rules and filetype for this buffer
121 // This is called when the colorscheme changes
122 func (b *Buffer) UpdateRules() {
123 b.rules, b.FileType = GetRules(b)
126 // CheckModTime makes sure that the file this buffer points to hasn't been updated
127 // by an external program since it was last read
128 // If it has, we ask the user if they would like to reload the file
129 func (b *Buffer) CheckModTime() {
130 modTime, ok := GetModTime(b.Path)
132 if modTime != b.ModTime {
133 choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)")
136 if !choice || canceled {
137 // Don't load new changes -- do nothing
138 b.ModTime, _ = GetModTime(b.Path)
147 // ReOpen reloads the current buffer from disk
148 func (b *Buffer) ReOpen() {
149 data, err := ioutil.ReadFile(b.Path)
153 messenger.Error(err.Error())
156 b.EventHandler.ApplyDiff(txt)
158 b.ModTime, _ = GetModTime(b.Path)
164 // Update fetches the string from the rope and updates the `text` and `lines` in the buffer
165 func (b *Buffer) Update() {
166 b.NumLines = len(b.lines)
169 // Save saves the buffer to its default path
170 func (b *Buffer) Save() error {
171 return b.SaveAs(b.Path)
174 // SaveWithSudo saves the buffer to the default path with sudo
175 func (b *Buffer) SaveWithSudo() error {
176 return b.SaveAsWithSudo(b.Path)
179 // Serialize serializes the buffer to configDir/buffers
180 func (b *Buffer) Serialize() error {
181 if settings["savecursor"].(bool) || settings["saveundo"].(bool) {
182 absPath, _ := filepath.Abs(b.Path)
183 file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
185 enc := gob.NewEncoder(file)
186 gob.Register(TextEvent{})
187 err = enc.Encode(SerializedBuffer{
192 // err = enc.Encode(b.Cursor)
200 // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
201 func (b *Buffer) SaveAs(filename string) error {
205 data := []byte(b.String())
206 err := ioutil.WriteFile(filename, data, 0644)
209 b.ModTime, _ = GetModTime(filename)
215 // SaveAsWithSudo is the same as SaveAs except it uses a neat trick
216 // with tee to use sudo so the user doesn't have to reopen micro with sudo
217 func (b *Buffer) SaveAsWithSudo(filename string) error {
222 // The user may have already used sudo in which case we won't need the password
223 // It's a bit nicer for them if they don't have to enter the password every time
224 _, err := RunShellCommand("sudo -v")
225 needPassword := err != nil
227 // If we need the password, we have to close the screen and ask using the shell
229 // Shut down the screen because we're going to interact directly with the shell
234 // Set up everything for the command
235 cmd := exec.Command("sudo", "tee", filename)
236 cmd.Stdin = bytes.NewBufferString(b.String())
238 // This is a trap for Ctrl-C so that it doesn't kill micro
239 // Instead we trap Ctrl-C to kill the program we're running
240 c := make(chan os.Signal, 1)
241 signal.Notify(c, os.Interrupt)
252 // If we needed the password, we closed the screen, so we have to initialize it again
254 // Start the screen back up
259 b.ModTime, _ = GetModTime(filename)
265 func (b *Buffer) insert(pos Loc, value []byte) {
267 b.LineArray.insert(pos, value)
270 func (b *Buffer) remove(start, end Loc) string {
272 sub := b.LineArray.remove(start, end)
277 // Start returns the location of the first character in the buffer
278 func (b *Buffer) Start() Loc {
282 // End returns the location of the last character in the buffer
283 func (b *Buffer) End() Loc {
284 return Loc{utf8.RuneCount(b.lines[b.NumLines-1]), b.NumLines - 1}
287 // Line returns a single line
288 func (b *Buffer) Line(n int) string {
289 return string(b.lines[n])
292 // Lines returns an array of strings containing the lines from start to end
293 func (b *Buffer) Lines(start, end int) []string {
294 lines := b.lines[start:end]
296 for _, line := range lines {
297 slice = append(slice, string(line))
302 // Len gives the length of the buffer
303 func (b *Buffer) Len() int {
304 return Count(b.String())