2016-03-17 21:27:57 +00:00
package main
import (
2016-06-02 17:01:13 +00:00
"bytes"
2016-05-28 21:29:49 +00:00
"encoding/gob"
2016-11-29 18:44:30 +00:00
"io"
2016-03-17 21:27:57 +00:00
"io/ioutil"
2016-05-28 21:29:49 +00:00
"os"
2016-06-02 17:01:13 +00:00
"os/exec"
"os/signal"
2016-05-28 21:29:49 +00:00
"path/filepath"
2016-12-15 07:54:30 +00:00
"regexp"
2016-08-30 15:28:28 +00:00
"strconv"
"strings"
2016-05-29 21:58:06 +00:00
"time"
2016-06-07 15:43:28 +00:00
"unicode/utf8"
2016-12-13 13:58:08 +00:00
"github.com/mitchellh/go-homedir"
2017-02-25 22:02:39 +00:00
"github.com/zyedidia/micro/cmd/micro/highlight"
2016-03-17 21:27:57 +00:00
)
2016-03-19 00:40:00 +00: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-17 21:27:57 +00:00
type Buffer struct {
2016-05-22 19:01:02 +00:00
// The eventhandler for undo/redo
* EventHandler
2016-07-10 17:26:05 +00:00
// This stores all the text in the buffer as an array of lines
2016-06-07 15:43:28 +00:00
* LineArray
2016-03-17 21:27:57 +00:00
2016-05-22 19:01:02 +00:00
Cursor Cursor
2016-03-17 21:27:57 +00:00
// Path to the file on disk
2016-04-25 16:48:43 +00:00
Path string
2016-11-18 16:53:48 +00:00
// Absolute path to the file on disk
AbsPath string
2016-03-17 21:27:57 +00:00
// Name of the buffer on the status line
2016-11-20 00:07:51 +00:00
name string
2016-03-17 21:27:57 +00:00
2016-07-10 17:26:05 +00:00
// Whether or not the buffer has been modified since it was opened
2016-05-14 16:04:13 +00:00
IsModified bool
2016-03-17 21:27:57 +00:00
2016-05-29 21:58:06 +00:00
// Stores the last modification time of the file the buffer is pointing to
ModTime time . Time
2016-04-25 16:48:43 +00:00
NumLines int
2016-03-20 20:27:02 +00:00
2017-02-18 20:17:07 +00:00
syntaxDef * highlight . Def
highlighter * highlight . Highlighter
2016-08-24 23:55:44 +00:00
// Buffer local settings
Settings map [ string ] interface { }
2016-03-17 21:27:57 +00:00
}
2016-05-29 15:02:56 +00:00
// The SerializedBuffer holds the types that get serialized when a buffer is saved
2016-07-10 17:26:05 +00:00
// These are used for the savecursor and saveundo options
2016-05-29 15:02:56 +00:00
type SerializedBuffer struct {
EventHandler * EventHandler
Cursor Cursor
2016-05-29 21:58:06 +00:00
ModTime time . Time
2016-05-29 15:02:56 +00:00
}
2016-11-29 18:44:30 +00:00
func NewBufferFromString ( text , path string ) * Buffer {
return NewBuffer ( strings . NewReader ( text ) , path )
}
// NewBuffer creates a new buffer from a given reader with a given path
func NewBuffer ( reader io . Reader , path string ) * Buffer {
2016-09-14 07:37:12 +00:00
if path != "" {
for _ , tab := range tabs {
for _ , view := range tab . views {
if view . Buf . Path == path {
return view . Buf
}
2016-09-10 14:30:15 +00:00
}
}
}
2016-03-17 21:27:57 +00:00
b := new ( Buffer )
2016-11-29 18:44:30 +00:00
b . LineArray = NewLineArray ( reader )
2016-06-08 17:26:50 +00:00
2016-08-25 19:03:37 +00:00
b . Settings = DefaultLocalSettings ( )
2016-08-24 23:55:44 +00:00
for k , v := range globalSettings {
2016-08-25 19:03:37 +00:00
if _ , ok := b . Settings [ k ] ; ok {
b . Settings [ k ] = v
}
2016-08-24 23:55:44 +00:00
}
2016-11-18 16:53:48 +00:00
absPath , _ := filepath . Abs ( path )
2016-04-25 16:48:43 +00:00
b . Path = path
2016-11-18 16:53:48 +00:00
b . AbsPath = absPath
2016-06-08 17:26:50 +00:00
2016-07-10 17:26:05 +00:00
// The last time this file was modified
2016-05-29 21:58:06 +00:00
b . ModTime , _ = GetModTime ( b . Path )
2016-05-22 19:01:02 +00:00
b . EventHandler = NewEventHandler ( b )
2016-03-19 00:40:00 +00:00
b . Update ( )
2016-03-26 14:54:18 +00:00
b . UpdateRules ( )
2016-03-20 21:08:57 +00:00
2016-05-28 21:29:49 +00:00
if _ , err := os . Stat ( configDir + "/buffers/" ) ; os . IsNotExist ( err ) {
os . Mkdir ( configDir + "/buffers/" , os . ModePerm )
}
2016-05-29 15:02:56 +00:00
// Put the cursor at the first spot
2016-08-30 15:28:28 +00:00
cursorStartX := 0
cursorStartY := 0
2016-09-03 15:26:01 +00:00
// If -startpos LINE,COL was passed, use start position LINE,COL
if len ( * flagStartPos ) > 0 {
positions := strings . Split ( * flagStartPos , "," )
2016-08-30 15:28:28 +00:00
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
}
}
}
}
2016-05-29 15:02:56 +00:00
b . Cursor = Cursor {
2016-06-07 15:43:28 +00:00
Loc : Loc {
2016-08-30 15:28:28 +00:00
X : cursorStartX ,
Y : cursorStartY ,
2016-06-07 15:43:28 +00:00
} ,
2016-05-29 15:02:56 +00:00
buf : b ,
}
2016-08-26 00:15:58 +00:00
InitLocalSettings ( b )
2016-08-24 23:55:44 +00:00
if b . Settings [ "savecursor" ] . ( bool ) || b . Settings [ "saveundo" ] . ( bool ) {
2016-07-10 17:26:05 +00:00
// If either savecursor or saveundo is turned on, we need to load the serialized information
// from ~/.config/micro/buffers
2016-11-18 16:53:48 +00:00
file , err := os . Open ( configDir + "/buffers/" + EscapePath ( b . AbsPath ) )
2016-05-28 21:29:49 +00:00
if err == nil {
2016-05-29 15:02:56 +00:00
var buffer SerializedBuffer
2016-05-28 21:29:49 +00:00
decoder := gob . NewDecoder ( file )
2016-05-29 15:02:56 +00:00
gob . Register ( TextEvent { } )
err = decoder . Decode ( & buffer )
2016-05-28 21:29:49 +00:00
if err != nil {
2016-06-07 21:03:05 +00: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-28 21:29:49 +00:00
}
2016-08-24 23:55:44 +00:00
if b . Settings [ "savecursor" ] . ( bool ) {
2016-05-29 15:02:56 +00:00
b . Cursor = buffer . Cursor
b . Cursor . buf = b
2016-05-29 21:58:06 +00:00
b . Cursor . Relocate ( )
2016-05-29 15:02:56 +00:00
}
2016-08-24 23:55:44 +00:00
if b . Settings [ "saveundo" ] . ( bool ) {
2016-05-29 21:58:06 +00: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-28 21:29:49 +00:00
}
}
file . Close ( )
}
2016-03-17 21:27:57 +00:00
return b
}
2016-11-20 00:07:51 +00:00
func ( b * Buffer ) GetName ( ) string {
if b . name == "" {
2016-11-29 18:44:30 +00:00
if b . Path == "" {
return "No name"
}
2016-11-20 00:07:51 +00:00
return b . Path
}
return b . name
}
2016-03-26 14:54:18 +00:00
// UpdateRules updates the syntax rules and filetype for this buffer
// This is called when the colorscheme changes
func ( b * Buffer ) UpdateRules ( ) {
2017-02-18 20:17:07 +00:00
b . syntaxDef = highlight . DetectFiletype ( syntaxDefs , b . Path , [ ] byte ( b . Line ( 0 ) ) )
if b . highlighter == nil || b . Settings [ "filetype" ] . ( string ) != b . syntaxDef . FileType {
b . Settings [ "filetype" ] = b . syntaxDef . FileType
b . highlighter = highlight . NewHighlighter ( b . syntaxDef )
2017-02-19 21:59:46 +00:00
if b . Settings [ "syntax" ] . ( bool ) {
2017-03-21 16:45:27 +00:00
b . highlighter . HighlightStates ( b )
2017-02-19 21:59:46 +00:00
}
2017-02-18 20:17:07 +00:00
}
2016-08-25 01:29:23 +00:00
}
// FileType returns the buffer's filetype
func ( b * Buffer ) FileType ( ) string {
return b . Settings [ "filetype" ] . ( string )
2016-03-26 14:54:18 +00:00
}
2016-10-15 14:09:20 +00:00
// 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 ) ) )
}
2016-10-18 15:12:28 +00:00
return "\t"
2016-10-15 14:09:20 +00:00
}
2016-05-29 21:58:06 +00: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-30 21:48:33 +00:00
b . ReOpen ( )
2016-05-29 21:58:06 +00:00
}
}
}
}
2016-05-30 21:48:33 +00: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 01:01:40 +00:00
b . EventHandler . ApplyDiff ( txt )
2016-05-30 21:48:33 +00:00
b . ModTime , _ = GetModTime ( b . Path )
b . IsModified = false
b . Update ( )
2016-05-30 22:22:10 +00:00
b . Cursor . Relocate ( )
2016-05-30 21:48:33 +00:00
}
2016-03-19 00:40:00 +00:00
// Update fetches the string from the rope and updates the `text` and `lines` in the buffer
func ( b * Buffer ) Update ( ) {
2016-06-07 15:43:28 +00:00
b . NumLines = len ( b . lines )
2016-03-17 21:27:57 +00:00
}
2016-03-19 00:40:00 +00:00
// Save saves the buffer to its default path
func ( b * Buffer ) Save ( ) error {
2016-04-25 16:48:43 +00:00
return b . SaveAs ( b . Path )
2016-03-17 21:27:57 +00:00
}
2016-06-02 17:01:13 +00:00
// SaveWithSudo saves the buffer to the default path with sudo
func ( b * Buffer ) SaveWithSudo ( ) error {
return b . SaveAsWithSudo ( b . Path )
}
2016-05-28 21:29:49 +00:00
// Serialize serializes the buffer to configDir/buffers
func ( b * Buffer ) Serialize ( ) error {
2016-08-24 23:55:44 +00:00
if b . Settings [ "savecursor" ] . ( bool ) || b . Settings [ "saveundo" ] . ( bool ) {
2016-11-18 16:53:48 +00:00
file , err := os . Create ( configDir + "/buffers/" + EscapePath ( b . AbsPath ) )
2016-05-28 21:29:49 +00:00
if err == nil {
enc := gob . NewEncoder ( file )
2016-05-29 15:02:56 +00:00
gob . Register ( TextEvent { } )
err = enc . Encode ( SerializedBuffer {
b . EventHandler ,
b . Cursor ,
2016-05-29 21:58:06 +00:00
b . ModTime ,
2016-05-29 15:02:56 +00:00
} )
2016-05-28 21:29:49 +00:00
}
file . Close ( )
return err
}
return nil
}
2016-03-19 00:40:00 +00: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-04 22:05:34 +00:00
b . UpdateRules ( )
2016-12-13 13:58:08 +00:00
dir , _ := homedir . Dir ( )
b . Path = strings . Replace ( filename , "~" , dir , 1 )
2016-12-15 07:54:30 +00:00
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 ( )
}
2016-10-23 22:37:29 +00:00
if b . Settings [ "eofnewline" ] . ( bool ) {
end := b . End ( )
if b . RuneAt ( Loc { end . X - 1 , end . Y } ) != '\n' {
b . Insert ( end , "\n" )
}
}
2016-12-15 07:27:10 +00:00
str := b . String ( )
2016-10-23 22:37:29 +00:00
data := [ ] byte ( str )
2016-05-01 23:07:54 +00:00
err := ioutil . WriteFile ( filename , data , 0644 )
2016-03-23 14:28:12 +00:00
if err == nil {
2016-05-15 06:26:36 +00:00
b . IsModified = false
2016-05-29 21:58:06 +00:00
b . ModTime , _ = GetModTime ( filename )
2016-05-29 15:02:56 +00:00
return b . Serialize ( )
2016-03-23 14:28:12 +00:00
}
2016-11-03 14:55:44 +00:00
b . ModTime , _ = GetModTime ( filename )
2016-03-17 21:27:57 +00:00
return err
}
2016-06-02 17:01:13 +00: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 . 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 15:43:28 +00:00
func ( b * Buffer ) insert ( pos Loc , value [ ] byte ) {
2016-05-14 16:04:13 +00:00
b . IsModified = true
2016-06-07 15:43:28 +00:00
b . LineArray . insert ( pos , value )
2016-03-19 00:40:00 +00:00
b . Update ( )
2016-03-17 21:27:57 +00:00
}
2016-06-07 15:43:28 +00:00
func ( b * Buffer ) remove ( start , end Loc ) string {
2016-05-14 16:04:13 +00:00
b . IsModified = true
2016-06-07 15:43:28 +00:00
sub := b . LineArray . remove ( start , end )
2016-03-19 00:40:00 +00:00
b . Update ( )
2016-06-07 15:43:28 +00:00
return sub
}
2016-12-15 07:54:30 +00:00
func ( b * Buffer ) deleteToEnd ( start Loc ) {
b . IsModified = true
b . LineArray . DeleteToEnd ( start )
b . Update ( )
}
2016-06-07 15:43:28 +00:00
// 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 {
2017-02-18 20:45:49 +00:00
return Loc { utf8 . RuneCount ( b . lines [ b . NumLines - 1 ] . data ) , b . NumLines - 1 }
2016-06-07 15:43:28 +00:00
}
2016-10-23 22:37:29 +00:00
// RuneAt returns the rune at a given location in the buffer
func ( b * Buffer ) RuneAt ( loc Loc ) rune {
line := [ ] rune ( b . Line ( loc . Y ) )
if len ( line ) > 0 {
return line [ loc . X ]
}
return '\n'
}
2016-06-07 15:43:28 +00:00
// Line returns a single line
func ( b * Buffer ) Line ( n int ) string {
2016-10-12 15:38:44 +00:00
if n >= len ( b . lines ) {
return ""
}
2017-02-18 20:45:49 +00:00
return string ( b . lines [ n ] . data )
2016-03-17 21:27:57 +00:00
}
2016-06-07 15:43:28 +00: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 {
2017-02-18 20:45:49 +00:00
slice = append ( slice , string ( line . data ) )
2016-06-07 15:43:28 +00:00
}
return slice
2016-05-07 14:57:40 +00:00
}
2016-03-19 00:40:00 +00:00
// Len gives the length of the buffer
func ( b * Buffer ) Len ( ) int {
2016-06-07 15:43:28 +00:00
return Count ( b . String ( ) )
2016-03-17 21:27:57 +00:00
}
2016-10-12 04:44:49 +00:00
2016-10-18 15:12:28 +00:00
// MoveLinesUp moves the range of lines up one row
2016-10-12 04:44:49 +00:00
func ( b * Buffer ) MoveLinesUp ( start int , end int ) {
2016-10-12 15:38:44 +00:00
// 0 < start < end <= len(b.lines)
if start < 1 || start >= end || end > len ( b . lines ) {
return // what to do? FIXME
}
if end == len ( b . lines ) {
b . Insert (
Loc {
2017-02-18 20:45:49 +00:00
utf8 . RuneCount ( b . lines [ end - 1 ] . data ) ,
2016-10-12 15:38:44 +00:00
end - 1 ,
} ,
2016-10-13 16:12:55 +00:00
"\n" + b . Line ( start - 1 ) ,
2016-10-12 15:38:44 +00:00
)
} else {
b . Insert (
Loc { 0 , end } ,
2016-10-13 16:12:55 +00:00
b . Line ( start - 1 ) + "\n" ,
2016-10-12 15:38:44 +00:00
)
}
b . Remove (
Loc { 0 , start - 1 } ,
Loc { 0 , start } ,
)
2016-10-12 04:44:49 +00:00
}
2016-10-18 15:12:28 +00:00
// MoveLinesDown moves the range of lines down one row
2016-10-12 04:44:49 +00:00
func ( b * Buffer ) MoveLinesDown ( start int , end int ) {
2016-10-12 15:38:44 +00:00
// 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
}
b . Insert (
Loc { 0 , start } ,
2016-10-13 16:12:55 +00:00
b . Line ( end ) + "\n" ,
2016-10-12 15:38:44 +00:00
)
2016-10-18 15:12:28 +00:00
end ++
2016-10-12 15:38:44 +00:00
b . Remove (
Loc { 0 , end } ,
Loc { 0 , end + 1 } ,
)
2016-10-12 04:44:49 +00:00
}
2017-03-20 21:40:33 +00:00
// ClearMatches clears all of the syntax highlighting for this buffer
func ( b * Buffer ) ClearMatches ( ) {
for i := range b . lines {
b . SetMatch ( i , nil )
b . SetState ( i , nil )
}
}