]> git.lizzy.rs Git - micro.git/blob - src/view.go
Add cross-compilation script
[micro.git] / src / view.go
1 package main
2
3 import (
4         "github.com/atotto/clipboard"
5         "github.com/zyedidia/tcell"
6         "strconv"
7 )
8
9 // The View struct stores information about a view into a buffer.
10 // It has a value for the cursor, and the window that the user sees
11 // the buffer from.
12 type View struct {
13         cursor  Cursor
14         topline int
15         // Leftmost column. Used for horizontal scrolling
16         leftCol int
17
18         // Percentage of the terminal window that this view takes up
19         heightPercent float32
20         widthPercent  float32
21         height        int
22         width         int
23
24         // How much to offset because of line numbers
25         lineNumOffset int
26
27         eh *EventHandler
28
29         buf *Buffer
30         sl  Statusline
31
32         mouseReleased bool
33
34         // Syntax highlighting matches
35         matches map[int]tcell.Style
36
37         s tcell.Screen
38 }
39
40 // NewView returns a new view with fullscreen width and height
41 func NewView(buf *Buffer, s tcell.Screen) *View {
42         return NewViewWidthHeight(buf, s, 1, 1)
43 }
44
45 // NewViewWidthHeight returns a new view with the specified width and height percentages
46 func NewViewWidthHeight(buf *Buffer, s tcell.Screen, w, h float32) *View {
47         v := new(View)
48
49         v.buf = buf
50         v.s = s
51
52         v.widthPercent = w
53         v.heightPercent = h
54         v.Resize(s.Size())
55
56         v.topline = 0
57         v.cursor = Cursor{
58                 x:   0,
59                 y:   0,
60                 loc: 0,
61                 v:   v,
62         }
63
64         v.eh = NewEventHandler(v)
65
66         v.sl = Statusline{
67                 v: v,
68         }
69
70         return v
71 }
72
73 // Resize recalculates the width and height of the view based on the width and height percentages
74 func (v *View) Resize(w, h int) {
75         h--
76         v.height = int(float32(h)*v.heightPercent) - 1
77         v.width = int(float32(w) * v.widthPercent)
78 }
79
80 // ScrollUp scrolls the view up n lines (if possible)
81 func (v *View) ScrollUp(n int) {
82         // Try to scroll by n but if it would overflow, scroll by 1
83         if v.topline-n >= 0 {
84                 v.topline -= n
85         } else if v.topline > 0 {
86                 v.topline--
87         }
88 }
89
90 // ScrollDown scrolls the view down n lines (if possible)
91 func (v *View) ScrollDown(n int) {
92         // Try to scroll by n but if it would overflow, scroll by 1
93         if v.topline+n <= len(v.buf.lines)-v.height {
94                 v.topline += n
95         } else if v.topline < len(v.buf.lines)-v.height {
96                 v.topline++
97         }
98 }
99
100 // PageUp scrolls the view up a page
101 func (v *View) PageUp() {
102         if v.topline > v.height {
103                 v.ScrollUp(v.height)
104         } else {
105                 v.topline = 0
106         }
107 }
108
109 // PageDown scrolls the view down a page
110 func (v *View) PageDown() {
111         if len(v.buf.lines)-(v.topline+v.height) > v.height {
112                 v.ScrollDown(v.height)
113         } else {
114                 v.topline = len(v.buf.lines) - v.height
115         }
116 }
117
118 // HalfPageUp scrolls the view up half a page
119 func (v *View) HalfPageUp() {
120         if v.topline > v.height/2 {
121                 v.ScrollUp(v.height / 2)
122         } else {
123                 v.topline = 0
124         }
125 }
126
127 // HalfPageDown scrolls the view down half a page
128 func (v *View) HalfPageDown() {
129         if len(v.buf.lines)-(v.topline+v.height) > v.height/2 {
130                 v.ScrollDown(v.height / 2)
131         } else {
132                 v.topline = len(v.buf.lines) - v.height
133         }
134 }
135
136 // HandleEvent handles an event passed by the main loop
137 // It returns an int describing how the screen needs to be redrawn
138 // 0: Screen does not need to be redrawn
139 // 1: Only the cursor/statusline needs to be redrawn
140 // 2: Everything needs to be redrawn
141 func (v *View) HandleEvent(event tcell.Event) int {
142         var ret int
143         switch e := event.(type) {
144         case *tcell.EventResize:
145                 v.Resize(e.Size())
146                 ret = 2
147         case *tcell.EventKey:
148                 switch e.Key() {
149                 case tcell.KeyUp:
150                         v.cursor.Up()
151                         ret = 1
152                 case tcell.KeyDown:
153                         v.cursor.Down()
154                         ret = 1
155                 case tcell.KeyLeft:
156                         v.cursor.Left()
157                         ret = 1
158                 case tcell.KeyRight:
159                         v.cursor.Right()
160                         ret = 1
161                 case tcell.KeyEnter:
162                         v.eh.Insert(v.cursor.loc, "\n")
163                         v.cursor.Right()
164                         ret = 2
165                 case tcell.KeySpace:
166                         v.eh.Insert(v.cursor.loc, " ")
167                         v.cursor.Right()
168                         ret = 2
169                 case tcell.KeyBackspace2:
170                         if v.cursor.HasSelection() {
171                                 v.cursor.DeleteSelection()
172                                 v.cursor.ResetSelection()
173                                 ret = 2
174                         } else if v.cursor.loc > 0 {
175                                 // We have to do something a bit hacky here because we want to
176                                 // delete the line by first moving left and then deleting backwards
177                                 // but the undo redo would place the cursor in the wrong place
178                                 // So instead we move left, save the position, move back, delete
179                                 // and restore the position
180                                 v.cursor.Left()
181                                 cx, cy, cloc := v.cursor.x, v.cursor.y, v.cursor.loc
182                                 v.cursor.Right()
183                                 v.eh.Remove(v.cursor.loc-1, v.cursor.loc)
184                                 v.cursor.x, v.cursor.y, v.cursor.loc = cx, cy, cloc
185                                 ret = 2
186                         }
187                 case tcell.KeyTab:
188                         v.eh.Insert(v.cursor.loc, "\t")
189                         v.cursor.Right()
190                         ret = 2
191                 case tcell.KeyCtrlS:
192                         err := v.buf.Save()
193                         if err != nil {
194                                 // Error!
195                         }
196                         // Need to redraw the status line
197                         ret = 1
198                 case tcell.KeyCtrlZ:
199                         v.eh.Undo()
200                         ret = 2
201                 case tcell.KeyCtrlY:
202                         v.eh.Redo()
203                         ret = 2
204                 case tcell.KeyCtrlC:
205                         if v.cursor.HasSelection() {
206                                 if !clipboard.Unsupported {
207                                         clipboard.WriteAll(v.cursor.GetSelection())
208                                         ret = 2
209                                 }
210                         }
211                 case tcell.KeyCtrlX:
212                         if v.cursor.HasSelection() {
213                                 if !clipboard.Unsupported {
214                                         clipboard.WriteAll(v.cursor.GetSelection())
215                                         v.cursor.DeleteSelection()
216                                         v.cursor.ResetSelection()
217                                         ret = 2
218                                 }
219                         }
220                 case tcell.KeyCtrlV:
221                         if !clipboard.Unsupported {
222                                 if v.cursor.HasSelection() {
223                                         v.cursor.DeleteSelection()
224                                         v.cursor.ResetSelection()
225                                 }
226                                 clip, _ := clipboard.ReadAll()
227                                 v.eh.Insert(v.cursor.loc, clip)
228                                 // This is a bit weird... Not sure if there's a better way
229                                 for i := 0; i < Count(clip); i++ {
230                                         v.cursor.Right()
231                                 }
232                                 ret = 2
233                         }
234                 case tcell.KeyPgUp:
235                         v.PageUp()
236                         return 2
237                 case tcell.KeyPgDn:
238                         v.PageDown()
239                         return 2
240                 case tcell.KeyCtrlU:
241                         v.HalfPageUp()
242                         return 2
243                 case tcell.KeyCtrlD:
244                         v.HalfPageDown()
245                         return 2
246                 case tcell.KeyRune:
247                         if v.cursor.HasSelection() {
248                                 v.cursor.DeleteSelection()
249                                 v.cursor.ResetSelection()
250                         }
251                         v.eh.Insert(v.cursor.loc, string(e.Rune()))
252                         v.cursor.Right()
253                         ret = 2
254                 }
255         case *tcell.EventMouse:
256                 x, y := e.Position()
257                 x -= v.lineNumOffset
258                 y += v.topline
259                 // Position always seems to be off by one
260                 x--
261                 y--
262
263                 button := e.Buttons()
264
265                 switch button {
266                 case tcell.Button1:
267                         if y-v.topline > v.height-1 {
268                                 v.ScrollDown(1)
269                                 y = v.height + v.topline - 1
270                         }
271                         if y >= len(v.buf.lines) {
272                                 y = len(v.buf.lines) - 1
273                         }
274                         if x < 0 {
275                                 x = 0
276                         }
277
278                         x = v.cursor.GetCharPosInLine(y, x)
279                         if x > Count(v.buf.lines[y]) {
280                                 x = Count(v.buf.lines[y])
281                         }
282                         d := v.cursor.Distance(x, y)
283                         v.cursor.loc += d
284                         v.cursor.x = x
285                         v.cursor.y = y
286
287                         if v.mouseReleased {
288                                 v.cursor.selectionStart = v.cursor.loc
289                                 v.cursor.selectionStartX = v.cursor.x
290                                 v.cursor.selectionStartY = v.cursor.y
291                         }
292                         v.cursor.selectionEnd = v.cursor.loc
293                         v.mouseReleased = false
294                         return 2
295                 case tcell.ButtonNone:
296                         v.mouseReleased = true
297                         return 0
298                 case tcell.WheelUp:
299                         v.ScrollUp(2)
300                         return 2
301                 case tcell.WheelDown:
302                         v.ScrollDown(2)
303                         return 2
304                 }
305         }
306
307         cy := v.cursor.y
308         if cy < v.topline {
309                 v.topline = cy
310                 ret = 2
311         }
312         if cy > v.topline+v.height-1 {
313                 v.topline = cy - v.height + 1
314                 ret = 2
315         }
316
317         return ret
318 }
319
320 // Display renders the view to the screen
321 func (v *View) Display() {
322         var x int
323
324         charNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
325
326         // Convert the length of buffer to a string, and get the length of the string
327         // We are going to have to offset by that amount
328         maxLineLength := len(strconv.Itoa(len(v.buf.lines)))
329         // + 1 for the little space after the line number
330         v.lineNumOffset = maxLineLength + 1
331
332         var highlightStyle tcell.Style
333
334         for lineN := 0; lineN < v.height; lineN++ {
335                 if lineN+v.topline >= len(v.buf.lines) {
336                         break
337                 }
338                 line := v.buf.lines[lineN+v.topline]
339
340                 // Write the line number
341                 lineNumStyle := tcell.StyleDefault
342                 // Write the spaces before the line number if necessary
343                 lineNum := strconv.Itoa(lineN + v.topline + 1)
344                 for i := 0; i < maxLineLength-len(lineNum); i++ {
345                         v.s.SetContent(x, lineN, ' ', nil, lineNumStyle)
346                         x++
347                 }
348                 // Write the actual line number
349                 for _, ch := range lineNum {
350                         v.s.SetContent(x, lineN, ch, nil, lineNumStyle)
351                         x++
352                 }
353                 // Write the extra space
354                 v.s.SetContent(x, lineN, ' ', nil, lineNumStyle)
355                 x++
356
357                 // Write the line
358                 tabchars := 0
359                 for _, ch := range line {
360                         var lineStyle tcell.Style
361                         st, ok := v.matches[charNum]
362                         if ok {
363                                 highlightStyle = st
364                         } else {
365                                 highlightStyle = tcell.StyleDefault
366                         }
367
368                         if v.cursor.HasSelection() &&
369                                 (charNum >= v.cursor.selectionStart && charNum <= v.cursor.selectionEnd ||
370                                         charNum <= v.cursor.selectionStart && charNum >= v.cursor.selectionEnd) {
371                                 lineStyle = tcell.StyleDefault
372                                 lineStyle = lineStyle.Reverse(true)
373                         } else {
374                                 lineStyle = highlightStyle
375                         }
376
377                         if ch == '\t' {
378                                 v.s.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
379                                 for i := 0; i < tabSize-1; i++ {
380                                         tabchars++
381                                         v.s.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
382                                 }
383                         } else {
384                                 v.s.SetContent(x+tabchars, lineN, ch, nil, lineStyle)
385                         }
386                         charNum++
387                         x++
388                 }
389                 x = 0
390                 st, ok := v.matches[charNum]
391                 if ok {
392                         highlightStyle = st
393                 }
394                 charNum++
395         }
396 }