4 "github.com/atotto/clipboard"
5 "github.com/zyedidia/tcell"
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
15 // Leftmost column. Used for horizontal scrolling
18 // Percentage of the terminal window that this view takes up
24 // How much to offset because of line numbers
34 // Syntax highlighting matches
35 matches map[int]tcell.Style
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)
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 {
64 v.eh = NewEventHandler(v)
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) {
76 v.height = int(float32(h)*v.heightPercent) - 1
77 v.width = int(float32(w) * v.widthPercent)
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
85 } else if v.topline > 0 {
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 {
95 } else if v.topline < len(v.buf.lines)-v.height {
100 // PageUp scrolls the view up a page
101 func (v *View) PageUp() {
102 if v.topline > v.height {
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)
114 v.topline = len(v.buf.lines) - v.height
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)
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)
132 v.topline = len(v.buf.lines) - v.height
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 {
143 switch e := event.(type) {
144 case *tcell.EventResize:
147 case *tcell.EventKey:
162 v.eh.Insert(v.cursor.loc, "\n")
166 v.eh.Insert(v.cursor.loc, " ")
169 case tcell.KeyBackspace2:
170 if v.cursor.HasSelection() {
171 v.cursor.DeleteSelection()
172 v.cursor.ResetSelection()
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
181 cx, cy, cloc := v.cursor.x, v.cursor.y, v.cursor.loc
183 v.eh.Remove(v.cursor.loc-1, v.cursor.loc)
184 v.cursor.x, v.cursor.y, v.cursor.loc = cx, cy, cloc
188 v.eh.Insert(v.cursor.loc, "\t")
196 // Need to redraw the status line
205 if v.cursor.HasSelection() {
206 if !clipboard.Unsupported {
207 clipboard.WriteAll(v.cursor.GetSelection())
212 if v.cursor.HasSelection() {
213 if !clipboard.Unsupported {
214 clipboard.WriteAll(v.cursor.GetSelection())
215 v.cursor.DeleteSelection()
216 v.cursor.ResetSelection()
221 if !clipboard.Unsupported {
222 if v.cursor.HasSelection() {
223 v.cursor.DeleteSelection()
224 v.cursor.ResetSelection()
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++ {
247 if v.cursor.HasSelection() {
248 v.cursor.DeleteSelection()
249 v.cursor.ResetSelection()
251 v.eh.Insert(v.cursor.loc, string(e.Rune()))
255 case *tcell.EventMouse:
259 // Position always seems to be off by one
263 button := e.Buttons()
267 if y-v.topline > v.height-1 {
269 y = v.height + v.topline - 1
271 if y >= len(v.buf.lines) {
272 y = len(v.buf.lines) - 1
278 x = v.cursor.GetCharPosInLine(y, x)
279 if x > Count(v.buf.lines[y]) {
280 x = Count(v.buf.lines[y])
282 d := v.cursor.Distance(x, y)
288 v.cursor.selectionStart = v.cursor.loc
289 v.cursor.selectionStartX = v.cursor.x
290 v.cursor.selectionStartY = v.cursor.y
292 v.cursor.selectionEnd = v.cursor.loc
293 v.mouseReleased = false
295 case tcell.ButtonNone:
296 v.mouseReleased = true
301 case tcell.WheelDown:
312 if cy > v.topline+v.height-1 {
313 v.topline = cy - v.height + 1
320 // Display renders the view to the screen
321 func (v *View) Display() {
324 charNum := v.cursor.loc + v.cursor.Distance(0, v.topline)
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
332 var highlightStyle tcell.Style
334 for lineN := 0; lineN < v.height; lineN++ {
335 if lineN+v.topline >= len(v.buf.lines) {
338 line := v.buf.lines[lineN+v.topline]
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)
348 // Write the actual line number
349 for _, ch := range lineNum {
350 v.s.SetContent(x, lineN, ch, nil, lineNumStyle)
353 // Write the extra space
354 v.s.SetContent(x, lineN, ' ', nil, lineNumStyle)
359 for _, ch := range line {
360 var lineStyle tcell.Style
361 st, ok := v.matches[charNum]
365 highlightStyle = tcell.StyleDefault
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)
374 lineStyle = highlightStyle
378 v.s.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
379 for i := 0; i < tabSize-1; i++ {
381 v.s.SetContent(x+tabchars, lineN, ' ', nil, lineStyle)
384 v.s.SetContent(x+tabchars, lineN, ch, nil, lineStyle)
390 st, ok := v.matches[charNum]