2016-03-18 00:27:57 +03:00
package main
import (
2016-06-02 20:01:13 +03:00
"bytes"
2016-05-29 00:29:49 +03:00
"encoding/gob"
2016-03-18 00:27:57 +03:00
"io/ioutil"
2016-05-29 00:29:49 +03:00
"os"
2016-06-02 20:01:13 +03:00
"os/exec"
"os/signal"
2016-05-29 00:29:49 +03:00
"path/filepath"
2016-05-30 00:58:06 +03:00
"time"
2016-06-07 18:43:28 +03:00
"unicode/utf8"
2016-03-18 00:27:57 +03:00
)
2016-03-19 03:40:00 +03:00
// 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
2016-03-18 00:27:57 +03:00
type Buffer struct {
2016-05-22 22:01:02 +03:00
// The eventhandler for undo/redo
* EventHandler
2016-06-07 18:43:28 +03:00
* LineArray
2016-03-18 00:27:57 +03:00
2016-05-22 22:01:02 +03:00
Cursor Cursor
2016-03-18 00:27:57 +03:00
// Path to the file on disk
2016-04-25 19:48:43 +03:00
Path string
2016-03-18 00:27:57 +03:00
// Name of the buffer on the status line
2016-04-25 19:48:43 +03:00
Name string
2016-03-18 00:27:57 +03:00
2016-05-14 19:04:13 +03:00
IsModified bool
2016-03-18 00:27:57 +03:00
2016-05-30 00:58:06 +03:00
// Stores the last modification time of the file the buffer is pointing to
ModTime time . Time
2016-04-25 19:48:43 +03:00
NumLines int
2016-03-20 23:27:02 +03:00
// Syntax highlighting rules
2016-03-23 23:36:17 +03:00
rules [ ] SyntaxRule
2016-04-17 00:33:13 +03:00
// The buffer's filetype
2016-05-14 19:04:13 +03:00
FileType string
2016-03-18 00:27:57 +03:00
}
2016-05-29 18:02:56 +03:00
// The SerializedBuffer holds the types that get serialized when a buffer is saved
type SerializedBuffer struct {
EventHandler * EventHandler
Cursor Cursor
2016-05-30 00:58:06 +03:00
ModTime time . Time
2016-05-29 18:02:56 +03:00
}
2016-03-19 03:40:00 +03:00
// NewBuffer creates a new buffer from `txt` with path and name `path`
2016-06-07 18:43:28 +03:00
func NewBuffer ( txt [ ] byte , path string ) * Buffer {
2016-03-18 00:27:57 +03:00
b := new ( Buffer )
2016-06-07 18:43:28 +03:00
b . LineArray = NewLineArray ( txt )
2016-06-08 20:26:50 +03:00
2016-04-25 19:48:43 +03:00
b . Path = path
b . Name = path
2016-03-18 00:27:57 +03:00
2016-06-08 20:26:50 +03:00
if path == "" {
b . Name = "No name"
}
2016-05-30 00:58:06 +03:00
b . ModTime , _ = GetModTime ( b . Path )
2016-05-22 22:01:02 +03:00
b . EventHandler = NewEventHandler ( b )
2016-03-19 03:40:00 +03:00
b . Update ( )
2016-03-26 17:54:18 +03:00
b . UpdateRules ( )
2016-03-21 00:08:57 +03:00
2016-05-29 00:29:49 +03:00
if _ , err := os . Stat ( configDir + "/buffers/" ) ; os . IsNotExist ( err ) {
os . Mkdir ( configDir + "/buffers/" , os . ModePerm )
}
2016-05-29 18:02:56 +03:00
// Put the cursor at the first spot
b . Cursor = Cursor {
2016-06-07 18:43:28 +03:00
Loc : Loc {
X : 0 ,
Y : 0 ,
} ,
2016-05-29 18:02:56 +03:00
buf : b ,
}
if settings [ "savecursor" ] . ( bool ) || settings [ "saveundo" ] . ( bool ) {
2016-05-29 00:29:49 +03:00
absPath , _ := filepath . Abs ( b . Path )
file , err := os . Open ( configDir + "/buffers/" + EscapePath ( absPath ) )
if err == nil {
2016-05-29 18:02:56 +03:00
var buffer SerializedBuffer
2016-05-29 00:29:49 +03:00
decoder := gob . NewDecoder ( file )
2016-05-29 18:02:56 +03:00
gob . Register ( TextEvent { } )
err = decoder . Decode ( & buffer )
2016-05-29 00:29:49 +03:00
if err != nil {
2016-06-08 00:03:05 +03:00
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." )
2016-05-29 00:29:49 +03:00
}
2016-05-29 18:02:56 +03:00
if settings [ "savecursor" ] . ( bool ) {
b . Cursor = buffer . Cursor
b . Cursor . buf = b
2016-05-30 00:58:06 +03:00
b . Cursor . Relocate ( )
2016-05-29 18:02:56 +03:00
}
if settings [ "saveundo" ] . ( bool ) {
2016-05-30 00:58:06 +03:00
// 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
}
2016-05-29 00:29:49 +03:00
}
}
file . Close ( )
}
2016-03-18 00:27:57 +03:00
return b
}
2016-03-26 17:54:18 +03:00
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func ( b * Buffer ) UpdateRules ( ) {
2016-05-14 19:04:13 +03:00
b . rules , b . FileType = GetRules ( b )
2016-03-26 17:54:18 +03:00
}
2016-05-30 00:58:06 +03:00
// 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
2016-05-31 00:48:33 +03:00
b . ReOpen ( )
2016-05-30 00:58:06 +03:00
}
}
}
}
2016-05-31 00:48:33 +03:00
// ReOpen reloads the current buffer from disk
func ( b * Buffer ) ReOpen ( ) {
data , err := ioutil . ReadFile ( b . Path )
txt := string ( data )
if err != nil {
messenger . Error ( err . Error ( ) )
return
}
2016-05-31 04:01:40 +03:00
b . EventHandler . ApplyDiff ( txt )
2016-05-31 00:48:33 +03:00
b . ModTime , _ = GetModTime ( b . Path )
b . IsModified = false
b . Update ( )
2016-05-31 01:22:10 +03:00
b . Cursor . Relocate ( )
2016-05-31 00:48:33 +03:00
}
2016-03-19 03:40:00 +03:00
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
func ( b * Buffer ) Update ( ) {
2016-06-07 18:43:28 +03:00
b . NumLines = len ( b . lines )
2016-03-18 00:27:57 +03:00
}
2016-03-19 03:40:00 +03:00
// Save saves the buffer to its default path
func ( b * Buffer ) Save ( ) error {
2016-04-25 19:48:43 +03:00
return b . SaveAs ( b . Path )
2016-03-18 00:27:57 +03:00
}
2016-06-02 20:01:13 +03:00
// SaveWithSudo saves the buffer to the default path with sudo
func ( b * Buffer ) SaveWithSudo ( ) error {
return b . SaveAsWithSudo ( b . Path )
}
2016-05-29 00:29:49 +03:00
// Serialize serializes the buffer to configDir/buffers
func ( b * Buffer ) Serialize ( ) error {
2016-05-29 18:02:56 +03:00
if settings [ "savecursor" ] . ( bool ) || settings [ "saveundo" ] . ( bool ) {
2016-05-29 00:29:49 +03:00
absPath , _ := filepath . Abs ( b . Path )
file , err := os . Create ( configDir + "/buffers/" + EscapePath ( absPath ) )
if err == nil {
enc := gob . NewEncoder ( file )
2016-05-29 18:02:56 +03:00
gob . Register ( TextEvent { } )
err = enc . Encode ( SerializedBuffer {
b . EventHandler ,
b . Cursor ,
2016-05-30 00:58:06 +03:00
b . ModTime ,
2016-05-29 18:02:56 +03:00
} )
// err = enc.Encode(b.Cursor)
2016-05-29 00:29:49 +03:00
}
file . Close ( )
return err
}
return nil
}
2016-03-19 03:40:00 +03:00
// SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
func ( b * Buffer ) SaveAs ( filename string ) error {
2016-04-05 01:05:34 +03:00
b . UpdateRules ( )
2016-05-30 00:58:06 +03:00
b . Name = filename
b . Path = filename
2016-05-04 19:43:17 +03:00
data := [ ] byte ( b . String ( ) )
2016-05-02 02:07:54 +03:00
err := ioutil . WriteFile ( filename , data , 0644 )
2016-03-23 17:28:12 +03:00
if err == nil {
2016-05-15 09:26:36 +03:00
b . IsModified = false
2016-05-30 00:58:06 +03:00
b . ModTime , _ = GetModTime ( filename )
2016-05-29 18:02:56 +03:00
return b . Serialize ( )
2016-03-23 17:28:12 +03:00
}
2016-03-18 00:27:57 +03:00
return err
}
2016-06-02 20:01:13 +03:00
// 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 . UpdateRules ( )
b . Name = filename
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
}
// Set up everything for the command
cmd := exec . Command ( "sudo" , "tee" , filename )
cmd . Stdin = bytes . NewBufferString ( b . String ( ) )
// 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
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
for range c {
cmd . Process . Kill ( )
}
} ( )
// Start the command
cmd . Start ( )
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 . ModTime , _ = GetModTime ( filename )
b . Serialize ( )
}
return err
}
2016-06-07 18:43:28 +03:00
func ( b * Buffer ) insert ( pos Loc , value [ ] byte ) {
2016-05-14 19:04:13 +03:00
b . IsModified = true
2016-06-07 18:43:28 +03:00
b . LineArray . insert ( pos , value )
2016-03-19 03:40:00 +03:00
b . Update ( )
2016-03-18 00:27:57 +03:00
}
2016-06-07 18:43:28 +03:00
func ( b * Buffer ) remove ( start , end Loc ) string {
2016-05-14 19:04:13 +03:00
b . IsModified = true
2016-06-07 18:43:28 +03:00
sub := b . LineArray . remove ( start , end )
2016-03-19 03:40:00 +03:00
b . Update ( )
2016-06-07 18:43:28 +03:00
return sub
}
// 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 {
2016-06-07 23:10:39 +03:00
return Loc { utf8 . RuneCount ( b . lines [ b . NumLines - 1 ] ) , b . NumLines - 1 }
2016-06-07 18:43:28 +03:00
}
// Line returns a single line
func ( b * Buffer ) Line ( n int ) string {
return string ( b . lines [ n ] )
2016-03-18 00:27:57 +03:00
}
2016-06-07 18:43:28 +03:00
// 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 ) )
}
return slice
2016-05-07 17:57:40 +03:00
}
2016-03-19 03:40:00 +03:00
// Len gives the length of the buffer
func ( b * Buffer ) Len ( ) int {
2016-06-07 18:43:28 +03:00
return Count ( b . String ( ) )
2016-03-18 00:27:57 +03:00
}