]> git.lizzy.rs Git - micro.git/blob - cmd/micro/buffer.go
Add `savecursor` option
[micro.git] / cmd / micro / buffer.go
1 package main
2
3 import (
4         "encoding/gob"
5         "io/ioutil"
6         "os"
7         "path/filepath"
8         "strings"
9
10         "github.com/vinzmay/go-rope"
11 )
12
13 // Buffer stores the text for files that are loaded into the text editor
14 // It uses a rope to efficiently store the string and contains some
15 // simple functions for saving and wrapper functions for modifying the rope
16 type Buffer struct {
17         // The eventhandler for undo/redo
18         *EventHandler
19
20         // Stores the text of the buffer
21         r *rope.Rope
22
23         Cursor Cursor
24
25         // Path to the file on disk
26         Path string
27         // Name of the buffer on the status line
28         Name string
29
30         IsModified bool
31
32         // Provide efficient and easy access to text and lines so the rope String does not
33         // need to be constantly recalculated
34         // These variables are updated in the update() function
35         Lines    []string
36         NumLines int
37
38         // Syntax highlighting rules
39         rules []SyntaxRule
40         // The buffer's filetype
41         FileType string
42 }
43
44 // NewBuffer creates a new buffer from `txt` with path and name `path`
45 func NewBuffer(txt, path string) *Buffer {
46         b := new(Buffer)
47         if txt == "" {
48                 b.r = new(rope.Rope)
49         } else {
50                 b.r = rope.New(txt)
51         }
52         b.Path = path
53         b.Name = path
54
55         b.EventHandler = NewEventHandler(b)
56
57         b.Update()
58         b.UpdateRules()
59
60         if _, err := os.Stat(configDir + "/buffers/"); os.IsNotExist(err) {
61                 os.Mkdir(configDir+"/buffers/", os.ModePerm)
62         }
63
64         if settings["savecursor"].(bool) {
65                 absPath, _ := filepath.Abs(b.Path)
66                 file, err := os.Open(configDir + "/buffers/" + EscapePath(absPath))
67                 if err == nil {
68                         var cursor Cursor
69                         decoder := gob.NewDecoder(file)
70                         err = decoder.Decode(&cursor)
71                         if err != nil {
72                                 TermMessage(err.Error())
73                         }
74                         b.Cursor = cursor
75                         b.Cursor.buf = b
76                         b.Cursor.Clamp()
77                 } else {
78                         // Put the cursor at the first spot
79                         b.Cursor = Cursor{
80                                 X:   0,
81                                 Y:   0,
82                                 buf: b,
83                         }
84                 }
85                 file.Close()
86         } else {
87                 // Put the cursor at the first spot
88                 b.Cursor = Cursor{
89                         X:   0,
90                         Y:   0,
91                         buf: b,
92                 }
93         }
94
95         return b
96 }
97
98 // UpdateRules updates the syntax rules and filetype for this buffer
99 // This is called when the colorscheme changes
100 func (b *Buffer) UpdateRules() {
101         b.rules, b.FileType = GetRules(b)
102 }
103
104 func (b *Buffer) String() string {
105         if b.r.Len() != 0 {
106                 return b.r.String()
107         }
108         return ""
109 }
110
111 // Update fetches the string from the rope and updates the `text` and `lines` in the buffer
112 func (b *Buffer) Update() {
113         b.Lines = strings.Split(b.String(), "\n")
114         b.NumLines = len(b.Lines)
115 }
116
117 // Save saves the buffer to its default path
118 func (b *Buffer) Save() error {
119         return b.SaveAs(b.Path)
120 }
121
122 // Serialize serializes the buffer to configDir/buffers
123 func (b *Buffer) Serialize() error {
124         if settings["savecursor"].(bool) {
125                 absPath, _ := filepath.Abs(b.Path)
126                 file, err := os.Create(configDir + "/buffers/" + EscapePath(absPath))
127                 if err == nil {
128                         enc := gob.NewEncoder(file)
129                         err = enc.Encode(b.Cursor)
130                 }
131                 file.Close()
132                 return err
133         }
134         return nil
135 }
136
137 // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist
138 func (b *Buffer) SaveAs(filename string) error {
139         b.UpdateRules()
140         data := []byte(b.String())
141         err := ioutil.WriteFile(filename, data, 0644)
142         if err == nil {
143                 b.IsModified = false
144                 err = b.Serialize()
145         }
146         return err
147 }
148
149 // This directly inserts value at idx, bypassing all undo/redo
150 func (b *Buffer) insert(idx int, value string) {
151         b.IsModified = true
152         b.r = b.r.Insert(idx, value)
153         b.Update()
154 }
155
156 // Remove a slice of the rope from start to end (exclusive)
157 // Returns the string that was removed
158 // This directly removes from start to end from the buffer, bypassing all undo/redo
159 func (b *Buffer) remove(start, end int) string {
160         b.IsModified = true
161         if start < 0 {
162                 start = 0
163         }
164         if end > b.Len() {
165                 end = b.Len()
166         }
167         if start == end {
168                 return ""
169         }
170         removed := b.Substr(start, end)
171         // The rope implenentation I am using wants indicies starting at 1 instead of 0
172         start++
173         end++
174         b.r = b.r.Delete(start, end-start)
175         b.Update()
176         return removed
177 }
178
179 // Substr returns the substring of the rope from start to end
180 func (b *Buffer) Substr(start, end int) string {
181         return b.r.Substr(start+1, end-start).String()
182 }
183
184 // Len gives the length of the buffer
185 func (b *Buffer) Len() int {
186         return b.r.Len()
187 }