]> git.lizzy.rs Git - micro.git/blob - cmd/micro/view.go
Use new syntax highlighting engine from zyedidia/highlight
[micro.git] / cmd / micro / view.go
1 package main
2
3 import (
4         "os"
5         "strings"
6         "time"
7
8         "github.com/mitchellh/go-homedir"
9         "github.com/zyedidia/highlight"
10         "github.com/zyedidia/tcell"
11 )
12
13 type ViewType struct {
14         readonly bool // The file cannot be edited
15         scratch  bool // The file cannot be saved
16 }
17
18 var (
19         vtDefault = ViewType{false, false}
20         vtHelp    = ViewType{true, true}
21         vtLog     = ViewType{true, true}
22         vtScratch = ViewType{false, true}
23 )
24
25 // The View struct stores information about a view into a buffer.
26 // It stores information about the cursor, and the viewport
27 // that the user sees the buffer from.
28 type View struct {
29         // A pointer to the buffer's cursor for ease of access
30         Cursor *Cursor
31
32         // The topmost line, used for vertical scrolling
33         Topline int
34         // The leftmost column, used for horizontal scrolling
35         leftCol int
36
37         // Specifies whether or not this view holds a help buffer
38         Type ViewType
39
40         // Actual width and height
41         Width  int
42         Height int
43
44         LockWidth  bool
45         LockHeight bool
46
47         // Where this view is located
48         x, y int
49
50         // How much to offset because of line numbers
51         lineNumOffset int
52
53         // Holds the list of gutter messages
54         messages map[string][]GutterMessage
55
56         // This is the index of this view in the views array
57         Num int
58         // What tab is this view stored in
59         TabNum int
60
61         // The buffer
62         Buf *Buffer
63         // The statusline
64         sline Statusline
65
66         // Since tcell doesn't differentiate between a mouse release event
67         // and a mouse move event with no keys pressed, we need to keep
68         // track of whether or not the mouse was pressed (or not released) last event to determine
69         // mouse release events
70         mouseReleased bool
71
72         // This stores when the last click was
73         // This is useful for detecting double and triple clicks
74         lastClickTime time.Time
75
76         // lastCutTime stores when the last ctrl+k was issued.
77         // It is used for clearing the clipboard to replace it with fresh cut lines.
78         lastCutTime time.Time
79
80         // freshClip returns true if the clipboard has never been pasted.
81         freshClip bool
82
83         // Was the last mouse event actually a double click?
84         // Useful for detecting triple clicks -- if a double click is detected
85         // but the last mouse event was actually a double click, it's a triple click
86         doubleClick bool
87         // Same here, just to keep track for mouse move events
88         tripleClick bool
89
90         // Syntax highlighting matches
91         matches []highlight.LineMatch
92
93         cellview *CellView
94
95         splitNode *LeafNode
96 }
97
98 // NewView returns a new fullscreen view
99 func NewView(buf *Buffer) *View {
100         screenW, screenH := screen.Size()
101         return NewViewWidthHeight(buf, screenW, screenH)
102 }
103
104 // NewViewWidthHeight returns a new view with the specified width and height
105 // Note that w and h are raw column and row values
106 func NewViewWidthHeight(buf *Buffer, w, h int) *View {
107         v := new(View)
108
109         v.x, v.y = 0, 0
110
111         v.Width = w
112         v.Height = h
113         v.cellview = new(CellView)
114
115         v.ToggleTabbar()
116
117         v.OpenBuffer(buf)
118
119         v.messages = make(map[string][]GutterMessage)
120
121         v.sline = Statusline{
122                 view: v,
123         }
124
125         if v.Buf.Settings["statusline"].(bool) {
126                 v.Height--
127         }
128
129         for pl := range loadedPlugins {
130                 _, err := Call(pl+".onViewOpen", v)
131                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
132                         TermMessage(err)
133                         continue
134                 }
135         }
136
137         return v
138 }
139
140 // ToggleStatusLine creates an extra row for the statusline if necessary
141 func (v *View) ToggleStatusLine() {
142         if v.Buf.Settings["statusline"].(bool) {
143                 v.Height--
144         } else {
145                 v.Height++
146         }
147 }
148
149 // ToggleTabbar creates an extra row for the tabbar if necessary
150 func (v *View) ToggleTabbar() {
151         if len(tabs) > 1 {
152                 if v.y == 0 {
153                         // Include one line for the tab bar at the top
154                         v.Height--
155                         v.y = 1
156                 }
157         } else {
158                 if v.y == 1 {
159                         v.y = 0
160                         v.Height++
161                 }
162         }
163 }
164
165 func (v *View) paste(clip string) {
166         leadingWS := GetLeadingWhitespace(v.Buf.Line(v.Cursor.Y))
167
168         if v.Cursor.HasSelection() {
169                 v.Cursor.DeleteSelection()
170                 v.Cursor.ResetSelection()
171         }
172         clip = strings.Replace(clip, "\n", "\n"+leadingWS, -1)
173         v.Buf.Insert(v.Cursor.Loc, clip)
174         v.Cursor.Loc = v.Cursor.Loc.Move(Count(clip), v.Buf)
175         v.freshClip = false
176         messenger.Message("Pasted clipboard")
177 }
178
179 // ScrollUp scrolls the view up n lines (if possible)
180 func (v *View) ScrollUp(n int) {
181         // Try to scroll by n but if it would overflow, scroll by 1
182         if v.Topline-n >= 0 {
183                 v.Topline -= n
184         } else if v.Topline > 0 {
185                 v.Topline--
186         }
187 }
188
189 // ScrollDown scrolls the view down n lines (if possible)
190 func (v *View) ScrollDown(n int) {
191         // Try to scroll by n but if it would overflow, scroll by 1
192         if v.Topline+n <= v.Buf.NumLines-v.Height {
193                 v.Topline += n
194         } else if v.Topline < v.Buf.NumLines-v.Height {
195                 v.Topline++
196         }
197 }
198
199 // CanClose returns whether or not the view can be closed
200 // If there are unsaved changes, the user will be asked if the view can be closed
201 // causing them to lose the unsaved changes
202 func (v *View) CanClose() bool {
203         if v.Type == vtDefault && v.Buf.IsModified {
204                 var char rune
205                 var canceled bool
206                 if v.Buf.Settings["autosave"].(bool) {
207                         char = 'y'
208                 } else {
209                         char, canceled = messenger.LetterPrompt("Save changes to "+v.Buf.GetName()+" before closing? (y,n,esc) ", 'y', 'n')
210                 }
211                 if !canceled {
212                         if char == 'y' {
213                                 v.Save(true)
214                                 return true
215                         } else if char == 'n' {
216                                 return true
217                         }
218                 }
219         } else {
220                 return true
221         }
222         return false
223 }
224
225 // OpenBuffer opens a new buffer in this view.
226 // This resets the topline, event handler and cursor.
227 func (v *View) OpenBuffer(buf *Buffer) {
228         screen.Clear()
229         v.CloseBuffer()
230         v.Buf = buf
231         v.Cursor = &buf.Cursor
232         v.Topline = 0
233         v.leftCol = 0
234         v.Cursor.ResetSelection()
235         v.Relocate()
236         v.Center(false)
237         v.messages = make(map[string][]GutterMessage)
238
239         // Set mouseReleased to true because we assume the mouse is not being pressed when
240         // the editor is opened
241         v.mouseReleased = true
242         v.lastClickTime = time.Time{}
243 }
244
245 // Open opens the given file in the view
246 func (v *View) Open(filename string) {
247         home, _ := homedir.Dir()
248         filename = strings.Replace(filename, "~", home, 1)
249         file, err := os.Open(filename)
250         defer file.Close()
251
252         var buf *Buffer
253         if err != nil {
254                 messenger.Message(err.Error())
255                 // File does not exist -- create an empty buffer with that name
256                 buf = NewBuffer(strings.NewReader(""), filename)
257         } else {
258                 buf = NewBuffer(file, filename)
259         }
260         v.OpenBuffer(buf)
261 }
262
263 // CloseBuffer performs any closing functions on the buffer
264 func (v *View) CloseBuffer() {
265         if v.Buf != nil {
266                 v.Buf.Serialize()
267         }
268 }
269
270 // ReOpen reloads the current buffer
271 func (v *View) ReOpen() {
272         if v.CanClose() {
273                 screen.Clear()
274                 v.Buf.ReOpen()
275                 v.Relocate()
276         }
277 }
278
279 // HSplit opens a horizontal split with the given buffer
280 func (v *View) HSplit(buf *Buffer) {
281         i := 0
282         if v.Buf.Settings["splitBottom"].(bool) {
283                 i = 1
284         }
285         v.splitNode.HSplit(buf, v.Num+i)
286 }
287
288 // VSplit opens a vertical split with the given buffer
289 func (v *View) VSplit(buf *Buffer) {
290         i := 0
291         if v.Buf.Settings["splitRight"].(bool) {
292                 i = 1
293         }
294         v.splitNode.VSplit(buf, v.Num+i)
295 }
296
297 // HSplitIndex opens a horizontal split with the given buffer at the given index
298 func (v *View) HSplitIndex(buf *Buffer, splitIndex int) {
299         v.splitNode.HSplit(buf, splitIndex)
300 }
301
302 // VSplitIndex opens a vertical split with the given buffer at the given index
303 func (v *View) VSplitIndex(buf *Buffer, splitIndex int) {
304         v.splitNode.VSplit(buf, splitIndex)
305 }
306
307 // GetSoftWrapLocation gets the location of a visual click on the screen and converts it to col,line
308 func (v *View) GetSoftWrapLocation(vx, vy int) (int, int) {
309         if !v.Buf.Settings["softwrap"].(bool) {
310                 if vy >= v.Buf.NumLines {
311                         vy = v.Buf.NumLines - 1
312                 }
313                 vx = v.Cursor.GetCharPosInLine(vy, vx)
314                 return vx, vy
315         }
316
317         screenX, screenY := 0, v.Topline
318         for lineN := v.Topline; lineN < v.Bottomline(); lineN++ {
319                 line := v.Buf.Line(lineN)
320                 if lineN >= v.Buf.NumLines {
321                         return 0, v.Buf.NumLines - 1
322                 }
323
324                 colN := 0
325                 for _, ch := range line {
326                         if screenX >= v.Width-v.lineNumOffset {
327                                 screenX = 0
328                                 screenY++
329                         }
330
331                         if screenX == vx && screenY == vy {
332                                 return colN, lineN
333                         }
334
335                         if ch == '\t' {
336                                 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
337                         }
338
339                         screenX++
340                         colN++
341                 }
342                 if screenY == vy {
343                         return colN, lineN
344                 }
345                 screenX = 0
346                 screenY++
347         }
348
349         return 0, 0
350 }
351
352 func (v *View) Bottomline() int {
353         if !v.Buf.Settings["softwrap"].(bool) {
354                 return v.Topline + v.Height
355         }
356
357         screenX, screenY := 0, 0
358         numLines := 0
359         for lineN := v.Topline; lineN < v.Topline+v.Height; lineN++ {
360                 line := v.Buf.Line(lineN)
361
362                 colN := 0
363                 for _, ch := range line {
364                         if screenX >= v.Width-v.lineNumOffset {
365                                 screenX = 0
366                                 screenY++
367                         }
368
369                         if ch == '\t' {
370                                 screenX += int(v.Buf.Settings["tabsize"].(float64)) - 1
371                         }
372
373                         screenX++
374                         colN++
375                 }
376                 screenX = 0
377                 screenY++
378                 numLines++
379
380                 if screenY >= v.Height {
381                         break
382                 }
383         }
384         return numLines + v.Topline
385 }
386
387 // Relocate moves the view window so that the cursor is in view
388 // This is useful if the user has scrolled far away, and then starts typing
389 func (v *View) Relocate() bool {
390         height := v.Bottomline() - v.Topline
391         ret := false
392         cy := v.Cursor.Y
393         scrollmargin := int(v.Buf.Settings["scrollmargin"].(float64))
394         if cy < v.Topline+scrollmargin && cy > scrollmargin-1 {
395                 v.Topline = cy - scrollmargin
396                 ret = true
397         } else if cy < v.Topline {
398                 v.Topline = cy
399                 ret = true
400         }
401         if cy > v.Topline+height-1-scrollmargin && cy < v.Buf.NumLines-scrollmargin {
402                 v.Topline = cy - height + 1 + scrollmargin
403                 ret = true
404         } else if cy >= v.Buf.NumLines-scrollmargin && cy > height {
405                 v.Topline = v.Buf.NumLines - height
406                 ret = true
407         }
408
409         if !v.Buf.Settings["softwrap"].(bool) {
410                 cx := v.Cursor.GetVisualX()
411                 if cx < v.leftCol {
412                         v.leftCol = cx
413                         ret = true
414                 }
415                 if cx+v.lineNumOffset+1 > v.leftCol+v.Width {
416                         v.leftCol = cx - v.Width + v.lineNumOffset + 1
417                         ret = true
418                 }
419         }
420         return ret
421 }
422
423 // MoveToMouseClick moves the cursor to location x, y assuming x, y were given
424 // by a mouse click
425 func (v *View) MoveToMouseClick(x, y int) {
426         if y-v.Topline > v.Height-1 {
427                 v.ScrollDown(1)
428                 y = v.Height + v.Topline - 1
429         }
430         if y < 0 {
431                 y = 0
432         }
433         if x < 0 {
434                 x = 0
435         }
436
437         x, y = v.GetSoftWrapLocation(x, y)
438         // x = v.Cursor.GetCharPosInLine(y, x)
439         if x > Count(v.Buf.Line(y)) {
440                 x = Count(v.Buf.Line(y))
441         }
442         v.Cursor.X = x
443         v.Cursor.Y = y
444         v.Cursor.LastVisualX = v.Cursor.GetVisualX()
445 }
446
447 // HandleEvent handles an event passed by the main loop
448 func (v *View) HandleEvent(event tcell.Event) {
449         // This bool determines whether the view is relocated at the end of the function
450         // By default it's true because most events should cause a relocate
451         relocate := true
452
453         v.Buf.CheckModTime()
454
455         switch e := event.(type) {
456         case *tcell.EventKey:
457                 // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune
458                 isBinding := false
459                 if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
460                         for key, actions := range bindings {
461                                 if e.Key() == key.keyCode {
462                                         if e.Key() == tcell.KeyRune {
463                                                 if e.Rune() != key.r {
464                                                         continue
465                                                 }
466                                         }
467                                         if e.Modifiers() == key.modifiers {
468                                                 relocate = false
469                                                 isBinding = true
470                                                 for _, action := range actions {
471                                                         relocate = action(v, true) || relocate
472                                                         funcName := FuncName(action)
473                                                         if funcName != "main.(*View).ToggleMacro" && funcName != "main.(*View).PlayMacro" {
474                                                                 if recordingMacro {
475                                                                         curMacro = append(curMacro, action)
476                                                                 }
477                                                         }
478                                                 }
479                                                 break
480                                         }
481                                 }
482                         }
483                 }
484                 if !isBinding && e.Key() == tcell.KeyRune {
485                         // Insert a character
486                         if v.Cursor.HasSelection() {
487                                 v.Cursor.DeleteSelection()
488                                 v.Cursor.ResetSelection()
489                         }
490                         v.Buf.Insert(v.Cursor.Loc, string(e.Rune()))
491                         v.Cursor.Right()
492
493                         for pl := range loadedPlugins {
494                                 _, err := Call(pl+".onRune", string(e.Rune()), v)
495                                 if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") {
496                                         TermMessage(err)
497                                 }
498                         }
499
500                         if recordingMacro {
501                                 curMacro = append(curMacro, e.Rune())
502                         }
503                 }
504         case *tcell.EventPaste:
505                 if !PreActionCall("Paste", v) {
506                         break
507                 }
508
509                 v.paste(e.Text())
510
511                 PostActionCall("Paste", v)
512         case *tcell.EventMouse:
513                 x, y := e.Position()
514                 x -= v.lineNumOffset - v.leftCol + v.x
515                 y += v.Topline - v.y
516                 // Don't relocate for mouse events
517                 relocate = false
518
519                 button := e.Buttons()
520
521                 switch button {
522                 case tcell.Button1:
523                         // Left click
524                         if v.mouseReleased {
525                                 v.MoveToMouseClick(x, y)
526                                 if time.Since(v.lastClickTime)/time.Millisecond < doubleClickThreshold {
527                                         if v.doubleClick {
528                                                 // Triple click
529                                                 v.lastClickTime = time.Now()
530
531                                                 v.tripleClick = true
532                                                 v.doubleClick = false
533
534                                                 v.Cursor.SelectLine()
535                                                 v.Cursor.CopySelection("primary")
536                                         } else {
537                                                 // Double click
538                                                 v.lastClickTime = time.Now()
539
540                                                 v.doubleClick = true
541                                                 v.tripleClick = false
542
543                                                 v.Cursor.SelectWord()
544                                                 v.Cursor.CopySelection("primary")
545                                         }
546                                 } else {
547                                         v.doubleClick = false
548                                         v.tripleClick = false
549                                         v.lastClickTime = time.Now()
550
551                                         v.Cursor.OrigSelection[0] = v.Cursor.Loc
552                                         v.Cursor.CurSelection[0] = v.Cursor.Loc
553                                         v.Cursor.CurSelection[1] = v.Cursor.Loc
554                                 }
555                                 v.mouseReleased = false
556                         } else if !v.mouseReleased {
557                                 v.MoveToMouseClick(x, y)
558                                 if v.tripleClick {
559                                         v.Cursor.AddLineToSelection()
560                                 } else if v.doubleClick {
561                                         v.Cursor.AddWordToSelection()
562                                 } else {
563                                         v.Cursor.SetSelectionEnd(v.Cursor.Loc)
564                                         v.Cursor.CopySelection("primary")
565                                 }
566                         }
567                 case tcell.Button2:
568                         // Middle mouse button was clicked,
569                         // We should paste primary
570                         v.PastePrimary(true)
571                 case tcell.ButtonNone:
572                         // Mouse event with no click
573                         if !v.mouseReleased {
574                                 // Mouse was just released
575
576                                 // Relocating here isn't really necessary because the cursor will
577                                 // be in the right place from the last mouse event
578                                 // However, if we are running in a terminal that doesn't support mouse motion
579                                 // events, this still allows the user to make selections, except only after they
580                                 // release the mouse
581
582                                 if !v.doubleClick && !v.tripleClick {
583                                         v.MoveToMouseClick(x, y)
584                                         v.Cursor.SetSelectionEnd(v.Cursor.Loc)
585                                         v.Cursor.CopySelection("primary")
586                                 }
587                                 v.mouseReleased = true
588                         }
589                 case tcell.WheelUp:
590                         // Scroll up
591                         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
592                         v.ScrollUp(scrollspeed)
593                 case tcell.WheelDown:
594                         // Scroll down
595                         scrollspeed := int(v.Buf.Settings["scrollspeed"].(float64))
596                         v.ScrollDown(scrollspeed)
597                 }
598         }
599
600         if relocate {
601                 v.Relocate()
602         }
603 }
604
605 // GutterMessage creates a message in this view's gutter
606 func (v *View) GutterMessage(section string, lineN int, msg string, kind int) {
607         lineN--
608         gutterMsg := GutterMessage{
609                 lineNum: lineN,
610                 msg:     msg,
611                 kind:    kind,
612         }
613         for _, v := range v.messages {
614                 for _, gmsg := range v {
615                         if gmsg.lineNum == lineN {
616                                 return
617                         }
618                 }
619         }
620         messages := v.messages[section]
621         v.messages[section] = append(messages, gutterMsg)
622 }
623
624 // ClearGutterMessages clears all gutter messages from a given section
625 func (v *View) ClearGutterMessages(section string) {
626         v.messages[section] = []GutterMessage{}
627 }
628
629 // ClearAllGutterMessages clears all the gutter messages
630 func (v *View) ClearAllGutterMessages() {
631         for k := range v.messages {
632                 v.messages[k] = []GutterMessage{}
633         }
634 }
635
636 // Opens the given help page in a new horizontal split
637 func (v *View) openHelp(helpPage string) {
638         if data, err := FindRuntimeFile(RTHelp, helpPage).Data(); err != nil {
639                 TermMessage("Unable to load help text", helpPage, "\n", err)
640         } else {
641                 helpBuffer := NewBuffer(strings.NewReader(string(data)), helpPage+".md")
642                 helpBuffer.name = "Help"
643
644                 if v.Type == vtHelp {
645                         v.OpenBuffer(helpBuffer)
646                 } else {
647                         v.HSplit(helpBuffer)
648                         CurView().Type = vtHelp
649                 }
650         }
651 }
652
653 func (v *View) drawCell(x, y int, ch rune, combc []rune, style tcell.Style) {
654         if x >= v.x && x < v.x+v.Width && y >= v.y && y < v.y+v.Height {
655                 screen.SetContent(x, y, ch, combc, style)
656         }
657 }
658
659 // DisplayView renders the view to the screen
660 // func (v *View) DisplayView() {
661 //      if v.Type == vtLog {
662 //              // Log views should always follow the cursor...
663 //              v.Relocate()
664 //      }
665 //
666 //      if v.Buf.Settings["syntax"].(bool) {
667 //              v.matches = Match(v)
668 //      }
669 //
670 //      // The charNum we are currently displaying
671 //      // starts at the start of the viewport
672 //      charNum := Loc{0, v.Topline}
673 //
674 //      // Convert the length of buffer to a string, and get the length of the string
675 //      // We are going to have to offset by that amount
676 //      maxLineLength := len(strconv.Itoa(v.Buf.NumLines))
677 //
678 //      if v.Buf.Settings["ruler"] == true {
679 //              // + 1 for the little space after the line number
680 //              v.lineNumOffset = maxLineLength + 1
681 //      } else {
682 //              v.lineNumOffset = 0
683 //      }
684 //
685 //      // We need to add to the line offset if there are gutter messages
686 //      var hasGutterMessages bool
687 //      for _, v := range v.messages {
688 //              if len(v) > 0 {
689 //                      hasGutterMessages = true
690 //              }
691 //      }
692 //      if hasGutterMessages {
693 //              v.lineNumOffset += 2
694 //      }
695 //
696 //      if v.x != 0 {
697 //              // One space for the extra split divider
698 //              v.lineNumOffset++
699 //      }
700 //
701 //      // These represent the current screen coordinates
702 //      screenX, screenY := v.x, v.y-1
703 //
704 //      highlightStyle := defStyle
705 //      curLineN := 0
706 //
707 //      // ViewLine is the current line from the top of the viewport
708 //      for viewLine := 0; viewLine < v.Height; viewLine++ {
709 //              screenY++
710 //              screenX = v.x
711 //
712 //              // This is the current line number of the buffer that we are drawing
713 //              curLineN = viewLine + v.Topline
714 //
715 //              if screenY-v.y >= v.Height {
716 //                      break
717 //              }
718 //
719 //              if v.x != 0 {
720 //                      // Draw the split divider
721 //                      v.drawCell(screenX, screenY, '|', nil, defStyle.Reverse(true))
722 //                      screenX++
723 //              }
724 //
725 //              // If the buffer is smaller than the view height we have to clear all this space
726 //              if curLineN >= v.Buf.NumLines {
727 //                      for i := screenX; i < v.x+v.Width; i++ {
728 //                              v.drawCell(i, screenY, ' ', nil, defStyle)
729 //                      }
730 //
731 //                      continue
732 //              }
733 //              line := v.Buf.Line(curLineN)
734 //
735 //              // If there are gutter messages we need to display the '>>' symbol here
736 //              if hasGutterMessages {
737 //                      // msgOnLine stores whether or not there is a gutter message on this line in particular
738 //                      msgOnLine := false
739 //                      for k := range v.messages {
740 //                              for _, msg := range v.messages[k] {
741 //                                      if msg.lineNum == curLineN {
742 //                                              msgOnLine = true
743 //                                              gutterStyle := defStyle
744 //                                              switch msg.kind {
745 //                                              case GutterInfo:
746 //                                                      if style, ok := colorscheme["gutter-info"]; ok {
747 //                                                              gutterStyle = style
748 //                                                      }
749 //                                              case GutterWarning:
750 //                                                      if style, ok := colorscheme["gutter-warning"]; ok {
751 //                                                              gutterStyle = style
752 //                                                      }
753 //                                              case GutterError:
754 //                                                      if style, ok := colorscheme["gutter-error"]; ok {
755 //                                                              gutterStyle = style
756 //                                                      }
757 //                                              }
758 //                                              v.drawCell(screenX, screenY, '>', nil, gutterStyle)
759 //                                              screenX++
760 //                                              v.drawCell(screenX, screenY, '>', nil, gutterStyle)
761 //                                              screenX++
762 //                                              if v.Cursor.Y == curLineN && !messenger.hasPrompt {
763 //                                                      messenger.Message(msg.msg)
764 //                                                      messenger.gutterMessage = true
765 //                                              }
766 //                                      }
767 //                              }
768 //                      }
769 //                      // If there is no message on this line we just display an empty offset
770 //                      if !msgOnLine {
771 //                              v.drawCell(screenX, screenY, ' ', nil, defStyle)
772 //                              screenX++
773 //                              v.drawCell(screenX, screenY, ' ', nil, defStyle)
774 //                              screenX++
775 //                              if v.Cursor.Y == curLineN && messenger.gutterMessage {
776 //                                      messenger.Reset()
777 //                                      messenger.gutterMessage = false
778 //                              }
779 //                      }
780 //              }
781 //
782 //              lineNumStyle := defStyle
783 //              if v.Buf.Settings["ruler"] == true {
784 //                      // Write the line number
785 //                      if style, ok := colorscheme["line-number"]; ok {
786 //                              lineNumStyle = style
787 //                      }
788 //                      if style, ok := colorscheme["current-line-number"]; ok {
789 //                              if curLineN == v.Cursor.Y && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() {
790 //                                      lineNumStyle = style
791 //                              }
792 //                      }
793 //
794 //                      lineNum := strconv.Itoa(curLineN + 1)
795 //
796 //                      // Write the spaces before the line number if necessary
797 //                      for i := 0; i < maxLineLength-len(lineNum); i++ {
798 //                              v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
799 //                              screenX++
800 //                      }
801 //                      // Write the actual line number
802 //                      for _, ch := range lineNum {
803 //                              v.drawCell(screenX, screenY, ch, nil, lineNumStyle)
804 //                              screenX++
805 //                      }
806 //
807 //                      // Write the extra space
808 //                      v.drawCell(screenX, screenY, ' ', nil, lineNumStyle)
809 //                      screenX++
810 //              }
811 //
812 //              // Now we actually draw the line
813 //              colN := 0
814 //              strWidth := 0
815 //              tabSize := int(v.Buf.Settings["tabsize"].(float64))
816 //              for _, ch := range line {
817 //                      if v.Buf.Settings["softwrap"].(bool) {
818 //                              if screenX-v.x >= v.Width {
819 //                                      screenY++
820 //
821 //                                      x := 0
822 //                                      if hasGutterMessages {
823 //                                              v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
824 //                                              x++
825 //                                              v.drawCell(v.x+x, screenY, ' ', nil, defStyle)
826 //                                              x++
827 //                                      }
828 //                                      for i := 0; i < v.lineNumOffset; i++ {
829 //                                              screen.SetContent(v.x+i+x, screenY, ' ', nil, lineNumStyle)
830 //                                      }
831 //                                      screenX = v.x + v.lineNumOffset
832 //                              }
833 //                      }
834 //
835 //                      if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
836 //                              v.DisplayCursor(screenX-v.leftCol, screenY)
837 //                      }
838 //
839 //                      lineStyle := defStyle
840 //
841 //                      if v.Buf.Settings["syntax"].(bool) {
842 //                              // Syntax highlighting is enabled
843 //                              highlightStyle = v.matches[viewLine][colN]
844 //                      }
845 //
846 //                      if v.Cursor.HasSelection() &&
847 //                              (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
848 //                                      charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
849 //                              // The current character is selected
850 //                              lineStyle = defStyle.Reverse(true)
851 //
852 //                              if style, ok := colorscheme["selection"]; ok {
853 //                                      lineStyle = style
854 //                              }
855 //                      } else {
856 //                              lineStyle = highlightStyle
857 //                      }
858 //
859 //                      // We need to display the background of the linestyle with the correct color if cursorline is enabled
860 //                      // and this is the current view and there is no selection on this line and the cursor is on this line
861 //                      if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
862 //                              if style, ok := colorscheme["cursor-line"]; ok {
863 //                                      fg, _, _ := style.Decompose()
864 //                                      lineStyle = lineStyle.Background(fg)
865 //                              }
866 //                      }
867 //
868 //                      if ch == '\t' {
869 //                              // If the character we are displaying is a tab, we need to do a bunch of special things
870 //
871 //                              // First the user may have configured an `indent-char` to be displayed to show that this
872 //                              // is a tab character
873 //                              lineIndentStyle := defStyle
874 //                              if style, ok := colorscheme["indent-char"]; ok && v.Buf.Settings["indentchar"].(string) != " " {
875 //                                      lineIndentStyle = style
876 //                              }
877 //                              if v.Cursor.HasSelection() &&
878 //                                      (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
879 //                                              charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
880 //
881 //                                      lineIndentStyle = defStyle.Reverse(true)
882 //
883 //                                      if style, ok := colorscheme["selection"]; ok {
884 //                                              lineIndentStyle = style
885 //                                      }
886 //                              }
887 //                              if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
888 //                                      if style, ok := colorscheme["cursor-line"]; ok {
889 //                                              fg, _, _ := style.Decompose()
890 //                                              lineIndentStyle = lineIndentStyle.Background(fg)
891 //                                      }
892 //                              }
893 //                              // Here we get the indent char
894 //                              indentChar := []rune(v.Buf.Settings["indentchar"].(string))
895 //                              if screenX-v.x-v.leftCol >= v.lineNumOffset {
896 //                                      v.drawCell(screenX-v.leftCol, screenY, indentChar[0], nil, lineIndentStyle)
897 //                              }
898 //                              // Now the tab has to be displayed as a bunch of spaces
899 //                              visLoc := strWidth
900 //                              remainder := tabSize - (visLoc % tabSize)
901 //                              for i := 0; i < remainder-1; i++ {
902 //                                      screenX++
903 //                                      if screenX-v.x-v.leftCol >= v.lineNumOffset {
904 //                                              v.drawCell(screenX-v.leftCol, screenY, ' ', nil, lineStyle)
905 //                                      }
906 //                              }
907 //                              strWidth += remainder
908 //                      } else if runewidth.RuneWidth(ch) > 1 {
909 //                              if screenX-v.x-v.leftCol >= v.lineNumOffset {
910 //                                      v.drawCell(screenX, screenY, ch, nil, lineStyle)
911 //                              }
912 //                              for i := 0; i < runewidth.RuneWidth(ch)-1; i++ {
913 //                                      screenX++
914 //                                      if screenX-v.x-v.leftCol >= v.lineNumOffset {
915 //                                              v.drawCell(screenX-v.leftCol, screenY, '<', nil, lineStyle)
916 //                                      }
917 //                              }
918 //                              strWidth += StringWidth(string(ch), tabSize)
919 //                      } else {
920 //                              if screenX-v.x-v.leftCol >= v.lineNumOffset {
921 //                                      v.drawCell(screenX-v.leftCol, screenY, ch, nil, lineStyle)
922 //                              }
923 //                              strWidth += StringWidth(string(ch), tabSize)
924 //                      }
925 //                      charNum = charNum.Move(1, v.Buf)
926 //                      screenX++
927 //                      colN++
928 //              }
929 //              // Here we are at a newline
930 //
931 //              if tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN && colN == v.Cursor.X {
932 //                      v.DisplayCursor(screenX-v.leftCol, screenY)
933 //              }
934 //
935 //              // The newline may be selected, in which case we should draw the selection style
936 //              // with a space to represent it
937 //              if v.Cursor.HasSelection() &&
938 //                      (charNum.GreaterEqual(v.Cursor.CurSelection[0]) && charNum.LessThan(v.Cursor.CurSelection[1]) ||
939 //                              charNum.LessThan(v.Cursor.CurSelection[0]) && charNum.GreaterEqual(v.Cursor.CurSelection[1])) {
940 //
941 //                      selectStyle := defStyle.Reverse(true)
942 //
943 //                      if style, ok := colorscheme["selection"]; ok {
944 //                              selectStyle = style
945 //                      }
946 //                      v.drawCell(screenX, screenY, ' ', nil, selectStyle)
947 //                      screenX++
948 //              }
949 //
950 //              charNum = charNum.Move(1, v.Buf)
951 //
952 //              for i := 0; i < v.Width; i++ {
953 //                      lineStyle := defStyle
954 //                      if v.Buf.Settings["cursorline"].(bool) && tabs[curTab].CurView == v.Num && !v.Cursor.HasSelection() && v.Cursor.Y == curLineN {
955 //                              if style, ok := colorscheme["cursor-line"]; ok {
956 //                                      fg, _, _ := style.Decompose()
957 //                                      lineStyle = lineStyle.Background(fg)
958 //                              }
959 //                      }
960 //                      if screenX-v.x-v.leftCol+i >= v.lineNumOffset {
961 //                              colorcolumn := int(v.Buf.Settings["colorcolumn"].(float64))
962 //                              if colorcolumn != 0 && screenX-v.lineNumOffset+i == colorcolumn-1 {
963 //                                      if style, ok := colorscheme["color-column"]; ok {
964 //                                              fg, _, _ := style.Decompose()
965 //                                              lineStyle = lineStyle.Background(fg)
966 //                                      }
967 //                              }
968 //                              v.drawCell(screenX-v.leftCol+i, screenY, ' ', nil, lineStyle)
969 //                      }
970 //              }
971 //      }
972 // }
973
974 // DisplayCursor draws the current buffer's cursor to the screen
975 func (v *View) DisplayCursor(x, y int) {
976         // screen.ShowCursor(v.x+v.Cursor.GetVisualX()+v.lineNumOffset-v.leftCol, y)
977         screen.ShowCursor(x, y)
978 }
979
980 // Display renders the view, the cursor, and statusline
981 func (v *View) Display() {
982         v.DisplayView()
983         // Don't draw the cursor if it is out of the viewport or if it has a selection
984         if (v.Cursor.Y-v.Topline < 0 || v.Cursor.Y-v.Topline > v.Height-1) || v.Cursor.HasSelection() {
985                 screen.HideCursor()
986         }
987         _, screenH := screen.Size()
988         if v.Buf.Settings["statusline"].(bool) {
989                 v.sline.Display()
990         } else if (v.y + v.Height) != screenH-1 {
991                 for x := 0; x < v.Width; x++ {
992                         screen.SetContent(v.x+x, v.y+v.Height, '-', nil, defStyle.Reverse(true))
993                 }
994         }
995 }