1// Copyright 2014 The gocui Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gocui
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"strings"
12	"sync"
13	"unicode"
14	"unicode/utf8"
15
16	"github.com/go-errors/errors"
17	"github.com/mattn/go-runewidth"
18)
19
20// Constants for overlapping edges
21const (
22	TOP    = 1 // view is overlapping at top edge
23	BOTTOM = 2 // view is overlapping at bottom edge
24	LEFT   = 4 // view is overlapping at left edge
25	RIGHT  = 8 // view is overlapping at right edge
26)
27
28var (
29	// ErrInvalidPoint is returned when client passed invalid coordinates of a cell.
30	// Most likely client has passed negative coordinates of a cell.
31	ErrInvalidPoint = errors.New("invalid point")
32)
33
34// A View is a window. It maintains its own internal buffer and cursor
35// position.
36type View struct {
37	name           string
38	x0, y0, x1, y1 int      // left top right bottom
39	ox, oy         int      // view offsets
40	cx, cy         int      // cursor position
41	rx, ry         int      // Read() offsets
42	wx, wy         int      // Write() offsets
43	lines          [][]cell // All the data
44	outMode        OutputMode
45
46	// readBuffer is used for storing unread bytes
47	readBuffer []byte
48
49	// tained is true if the viewLines must be updated
50	tainted bool
51
52	// internal representation of the view's buffer. We will keep viewLines around
53	// from a previous render until we explicitly set them to nil, allowing us to
54	// render the same content twice without flicker. Wherever we want to render
55	// something without any chance of old content appearing (e.g. when actually
56	// rendering new content or if the view is resized) we should set tainted to
57	// true and viewLines to nil
58	viewLines []viewLine
59
60	// writeMutex protects locks the write process
61	writeMutex sync.Mutex
62
63	// ei is used to decode ESC sequences on Write
64	ei *escapeInterpreter
65
66	// Visible specifies whether the view is visible.
67	Visible bool
68
69	// BgColor and FgColor allow to configure the background and foreground
70	// colors of the View.
71	BgColor, FgColor Attribute
72
73	// SelBgColor and SelFgColor are used to configure the background and
74	// foreground colors of the selected line, when it is highlighted.
75	SelBgColor, SelFgColor Attribute
76
77	// If Editable is true, keystrokes will be added to the view's internal
78	// buffer at the cursor position.
79	Editable bool
80
81	// Editor allows to define the editor that manages the editing mode,
82	// including keybindings or cursor behaviour. DefaultEditor is used by
83	// default.
84	Editor Editor
85
86	// Overwrite enables or disables the overwrite mode of the view.
87	Overwrite bool
88
89	// If Highlight is true, Sel{Bg,Fg}Colors will be used
90	// for the line under the cursor position.
91	Highlight bool
92
93	// If Frame is true, a border will be drawn around the view.
94	Frame bool
95
96	// FrameColor allow to configure the color of the Frame when it is not highlighted.
97	FrameColor Attribute
98
99	// FrameRunes allows to define custom runes for the frame edges.
100	// The rune slice can be defined with 3 different lengths.
101	// If slice doesn't match these lengths, default runes will be used instead of missing one.
102	//
103	// 2 runes with only horizontal and vertical edges.
104	//  []rune{'─', '│'}
105	//  []rune{'═','║'}
106	// 6 runes with horizontal, vertical edges and top-left, top-right, bottom-left, bottom-right cornes.
107	//  []rune{'─', '│', '┌', '┐', '└', '┘'}
108	//  []rune{'═','║','╔','╗','╚','╝'}
109	// 11 runes which can be used with `gocui.Gui.SupportOverlaps` property.
110	//  []rune{'─', '│', '┌', '┐', '└', '┘', '├', '┤', '┬', '┴', '┼'}
111	//  []rune{'═','║','╔','╗','╚','╝','╠','╣','╦','╩','╬'}
112	FrameRunes []rune
113
114	// If Wrap is true, the content that is written to this View is
115	// automatically wrapped when it is longer than its width. If true the
116	// view's x-origin will be ignored.
117	Wrap bool
118
119	// If Autoscroll is true, the View will automatically scroll down when the
120	// text overflows. If true the view's y-origin will be ignored.
121	Autoscroll bool
122
123	// If Frame is true, Title allows to configure a title for the view.
124	Title string
125
126	Tabs     []string
127	TabIndex int
128	// HighlightTabWithoutFocus allows you to show which tab is selected without the view being focused
129	HighlightSelectedTabWithoutFocus bool
130	// TitleColor allow to configure the color of title and subtitle for the view.
131	TitleColor Attribute
132
133	// If Frame is true, Subtitle allows to configure a subtitle for the view.
134	Subtitle string
135
136	// If Mask is true, the View will display the mask instead of the real
137	// content
138	Mask rune
139
140	// Overlaps describes which edges are overlapping with another view's edges
141	Overlaps byte
142
143	// If HasLoader is true, the message will be appended with a spinning loader animation
144	HasLoader bool
145
146	// IgnoreCarriageReturns tells us whether to ignore '\r' characters
147	IgnoreCarriageReturns bool
148
149	// ParentView is the view which catches events bubbled up from the given view if there's no matching handler
150	ParentView *View
151
152	Context string // this is for assigning keybindings to a view only in certain contexts
153
154	searcher *searcher
155
156	// KeybindOnEdit should be set to true when you want to execute keybindings even when the view is editable
157	// (this is usually not the case)
158	KeybindOnEdit bool
159
160	TextArea *TextArea
161
162	// something like '1 of 20' for a list view
163	Footer string
164}
165
166// call this in the event of a view resize, or if you want to render new content
167// without the chance of old content still appearing, or if you want to remove
168// a line from the existing content
169func (v *View) clearViewLines() {
170	v.tainted = true
171	v.viewLines = nil
172}
173
174type searcher struct {
175	searchString       string
176	searchPositions    []cellPos
177	currentSearchIndex int
178	onSelectItem       func(int, int, int) error
179}
180
181func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) {
182	v.searcher.onSelectItem = onSelectItem
183}
184
185func (v *View) gotoNextMatch() error {
186	if len(v.searcher.searchPositions) == 0 {
187		return nil
188	}
189	if v.searcher.currentSearchIndex >= len(v.searcher.searchPositions)-1 {
190		v.searcher.currentSearchIndex = 0
191	} else {
192		v.searcher.currentSearchIndex++
193	}
194	return v.SelectSearchResult(v.searcher.currentSearchIndex)
195}
196
197func (v *View) gotoPreviousMatch() error {
198	if len(v.searcher.searchPositions) == 0 {
199		return nil
200	}
201	if v.searcher.currentSearchIndex == 0 {
202		if len(v.searcher.searchPositions) > 0 {
203			v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1
204		}
205	} else {
206		v.searcher.currentSearchIndex--
207	}
208	return v.SelectSearchResult(v.searcher.currentSearchIndex)
209}
210
211func (v *View) SelectSearchResult(index int) error {
212	itemCount := len(v.searcher.searchPositions)
213	if itemCount == 0 {
214		return nil
215	}
216	if index > itemCount-1 {
217		index = itemCount - 1
218	}
219
220	y := v.searcher.searchPositions[index].y
221	v.FocusPoint(v.ox, y)
222	if v.searcher.onSelectItem != nil {
223		return v.searcher.onSelectItem(y, index, itemCount)
224	}
225	return nil
226}
227
228func (v *View) Search(str string) error {
229	v.writeMutex.Lock()
230	v.searcher.search(str)
231	v.updateSearchPositions()
232
233	if len(v.searcher.searchPositions) > 0 {
234		// get the first result past the current cursor
235		currentIndex := 0
236		adjustedY := v.oy + v.cy
237		adjustedX := v.ox + v.cx
238		for i, pos := range v.searcher.searchPositions {
239			if pos.y > adjustedY || (pos.y == adjustedY && pos.x > adjustedX) {
240				currentIndex = i
241				break
242			}
243		}
244		v.searcher.currentSearchIndex = currentIndex
245		v.writeMutex.Unlock()
246		return v.SelectSearchResult(currentIndex)
247	} else {
248		v.writeMutex.Unlock()
249		return v.searcher.onSelectItem(-1, -1, 0)
250	}
251}
252
253func (v *View) ClearSearch() {
254	v.searcher.clearSearch()
255}
256
257func (v *View) IsSearching() bool {
258	return v.searcher.searchString != ""
259}
260
261func (v *View) FocusPoint(cx int, cy int) {
262	lineCount := len(v.lines)
263	if cy < 0 || cy > lineCount {
264		return
265	}
266	_, height := v.Size()
267
268	ly := height - 1
269	if ly == -1 {
270		ly = 0
271	}
272
273	// if line is above origin, move origin and set cursor to zero
274	// if line is below origin + height, move origin and set cursor to max
275	// otherwise set cursor to value - origin
276	if ly > lineCount {
277		v.cx = cx
278		v.cy = cy
279		v.oy = 0
280	} else if cy < v.oy {
281		v.cx = cx
282		v.cy = 0
283		v.oy = cy
284	} else if cy > v.oy+ly {
285		v.cx = cx
286		v.cy = ly
287		v.oy = cy - ly
288	} else {
289		v.cx = cx
290		v.cy = cy - v.oy
291	}
292}
293
294func (s *searcher) search(str string) {
295	s.searchString = str
296	s.searchPositions = []cellPos{}
297	s.currentSearchIndex = 0
298}
299
300func (s *searcher) clearSearch() {
301	s.searchString = ""
302	s.searchPositions = []cellPos{}
303	s.currentSearchIndex = 0
304}
305
306type cellPos struct {
307	x int
308	y int
309}
310
311type viewLine struct {
312	linesX, linesY int // coordinates relative to v.lines
313	line           []cell
314}
315
316type cell struct {
317	chr              rune
318	bgColor, fgColor Attribute
319}
320
321type lineType []cell
322
323// String returns a string from a given cell slice.
324func (l lineType) String() string {
325	str := ""
326	for _, c := range l {
327		str += string(c.chr)
328	}
329	return str
330}
331
332// newView returns a new View object.
333func newView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
334	v := &View{
335		name:     name,
336		x0:       x0,
337		y0:       y0,
338		x1:       x1,
339		y1:       y1,
340		Visible:  true,
341		Frame:    true,
342		Editor:   DefaultEditor,
343		tainted:  true,
344		outMode:  mode,
345		ei:       newEscapeInterpreter(mode),
346		searcher: &searcher{},
347		TextArea: &TextArea{},
348	}
349
350	v.FgColor, v.BgColor = ColorDefault, ColorDefault
351	v.SelFgColor, v.SelBgColor = ColorDefault, ColorDefault
352	v.TitleColor, v.FrameColor = ColorDefault, ColorDefault
353	return v
354}
355
356// Dimensions returns the dimensions of the View
357func (v *View) Dimensions() (int, int, int, int) {
358	return v.x0, v.y0, v.x1, v.y1
359}
360
361// Size returns the number of visible columns and rows in the View.
362func (v *View) Size() (x, y int) {
363	return v.Width(), v.Height()
364}
365
366func (v *View) Width() int {
367	return v.x1 - v.x0 - 1
368}
369
370func (v *View) Height() int {
371	return v.y1 - v.y0 - 1
372}
373
374// if a view has a frame, that leaves less space for its writeable area
375func (v *View) InnerWidth() int {
376	innerWidth := v.Width() - v.frameOffset()
377	if innerWidth < 0 {
378		return 0
379	}
380
381	return innerWidth
382}
383
384func (v *View) InnerHeight() int {
385	innerHeight := v.Height() - v.frameOffset()
386	if innerHeight < 0 {
387		return 0
388	}
389
390	return innerHeight
391}
392
393func (v *View) frameOffset() int {
394	if v.Frame {
395		return 1
396	} else {
397		return 0
398	}
399}
400
401// Name returns the name of the view.
402func (v *View) Name() string {
403	return v.name
404}
405
406// setRune sets a rune at the given point relative to the view. It applies the
407// specified colors, taking into account if the cell must be highlighted. Also,
408// it checks if the position is valid.
409func (v *View) setRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
410	maxX, maxY := v.Size()
411	if x < 0 || x >= maxX || y < 0 || y >= maxY {
412		return ErrInvalidPoint
413	}
414	var (
415		ry, rcy int
416		err     error
417	)
418	if v.Highlight {
419		_, ry, err = v.realPosition(x, y)
420		if err != nil {
421			return err
422		}
423		_, rcy, err = v.realPosition(v.cx, v.cy)
424		if err != nil {
425			return err
426		}
427	}
428
429	if v.Mask != 0 {
430		fgColor = v.FgColor
431		bgColor = v.BgColor
432		ch = v.Mask
433	} else if v.Highlight && ry == rcy {
434		fgColor = fgColor | AttrBold
435		bgColor = bgColor | v.SelBgColor
436	}
437
438	// Don't display NUL characters
439	if ch == 0 {
440		ch = ' '
441	}
442
443	tcellSetCell(v.x0+x+1, v.y0+y+1, ch, fgColor, bgColor, v.outMode)
444
445	return nil
446}
447
448// SetCursor sets the cursor position of the view at the given point,
449// relative to the view. It checks if the position is valid.
450func (v *View) SetCursor(x, y int) error {
451	maxX, maxY := v.Size()
452	if x < 0 || x >= maxX || y < 0 || y >= maxY {
453		return nil
454	}
455	v.cx = x
456	v.cy = y
457	return nil
458}
459
460// Cursor returns the cursor position of the view.
461func (v *View) Cursor() (x, y int) {
462	return v.cx, v.cy
463}
464
465// SetOrigin sets the origin position of the view's internal buffer,
466// so the buffer starts to be printed from this point, which means that
467// it is linked with the origin point of view. It can be used to
468// implement Horizontal and Vertical scrolling with just incrementing
469// or decrementing ox and oy.
470func (v *View) SetOrigin(x, y int) error {
471	if x < 0 || y < 0 {
472		return ErrInvalidPoint
473	}
474	v.ox = x
475	v.oy = y
476	return nil
477}
478
479func (v *View) SetOriginX(x int) error {
480	if x < 0 {
481		return ErrInvalidPoint
482	}
483	v.ox = x
484	return nil
485}
486
487func (v *View) SetOriginY(y int) error {
488	if y < 0 {
489		return ErrInvalidPoint
490	}
491	v.oy = y
492	return nil
493}
494
495// Origin returns the origin position of the view.
496func (v *View) Origin() (x, y int) {
497	return v.OriginX(), v.OriginY()
498}
499
500func (v *View) OriginX() int {
501	return v.ox
502}
503
504func (v *View) OriginY() int {
505	return v.oy
506}
507
508// SetWritePos sets the write position of the view's internal buffer.
509// So the next Write call would write directly to the specified position.
510func (v *View) SetWritePos(x, y int) error {
511	if x < 0 || y < 0 {
512		return ErrInvalidPoint
513	}
514	v.wx = x
515	v.wy = y
516	return nil
517}
518
519// WritePos returns the current write position of the view's internal buffer.
520func (v *View) WritePos() (x, y int) {
521	return v.wx, v.wy
522}
523
524// SetReadPos sets the read position of the view's internal buffer.
525// So the next Read call would read from the specified position.
526func (v *View) SetReadPos(x, y int) error {
527	if x < 0 || y < 0 {
528		return ErrInvalidPoint
529	}
530	v.readBuffer = nil
531	v.rx = x
532	v.ry = y
533	return nil
534}
535
536// ReadPos returns the current read position of the view's internal buffer.
537func (v *View) ReadPos() (x, y int) {
538	return v.rx, v.ry
539}
540
541// makeWriteable creates empty cells if required to make position (x, y) writeable.
542func (v *View) makeWriteable(x, y int) {
543	// TODO: make this more efficient
544
545	// line `y` must be index-able (that's why `<=`)
546	for len(v.lines) <= y {
547		if cap(v.lines) > len(v.lines) {
548			newLen := cap(v.lines)
549			if newLen > y {
550				newLen = y + 1
551			}
552			v.lines = v.lines[:newLen]
553		} else {
554			v.lines = append(v.lines, nil)
555		}
556	}
557	// cell `x` must not be index-able (that's why `<`)
558	// append should be used by `lines[y]` user if he wants to write beyond `x`
559	for len(v.lines[y]) < x {
560		if cap(v.lines[y]) > len(v.lines[y]) {
561			newLen := cap(v.lines[y])
562			if newLen > x {
563				newLen = x
564			}
565			v.lines[y] = v.lines[y][:newLen]
566		} else {
567			v.lines[y] = append(v.lines[y], cell{})
568		}
569	}
570}
571
572// writeCells copies []cell to specified location (x, y)
573// !!! caller MUST ensure that specified location (x, y) is writeable by calling makeWriteable
574func (v *View) writeCells(x, y int, cells []cell) {
575	var newLen int
576	// use maximum len available
577	line := v.lines[y][:cap(v.lines[y])]
578	maxCopy := len(line) - x
579	if maxCopy < len(cells) {
580		copy(line[x:], cells[:maxCopy])
581		line = append(line, cells[maxCopy:]...)
582		newLen = len(line)
583	} else { // maxCopy >= len(cells)
584		copy(line[x:], cells)
585		newLen = x + len(cells)
586		if newLen < len(v.lines[y]) {
587			newLen = len(v.lines[y])
588		}
589	}
590	v.lines[y] = line[:newLen]
591}
592
593// Write appends a byte slice into the view's internal buffer. Because
594// View implements the io.Writer interface, it can be passed as parameter
595// of functions like fmt.Fprintf, fmt.Fprintln, io.Copy, etc. Clear must
596// be called to clear the view's buffer.
597func (v *View) Write(p []byte) (n int, err error) {
598	v.writeMutex.Lock()
599	defer v.writeMutex.Unlock()
600
601	v.writeRunes(bytes.Runes(p))
602
603	return len(p), nil
604}
605
606func (v *View) WriteRunes(p []rune) {
607	v.writeMutex.Lock()
608	defer v.writeMutex.Unlock()
609
610	v.writeRunes(p)
611}
612
613// writeRunes copies slice of runes into internal lines buffer.
614func (v *View) writeRunes(p []rune) {
615	v.tainted = true
616
617	// Fill with empty cells, if writing outside current view buffer
618	v.makeWriteable(v.wx, v.wy)
619
620	for _, r := range p {
621		switch r {
622		case '\n':
623			v.wy++
624			if v.wy >= len(v.lines) {
625				v.lines = append(v.lines, nil)
626			}
627
628			fallthrough
629			// not valid in every OS, but making runtime OS checks in cycle is bad.
630		case '\r':
631			v.wx = 0
632		default:
633			moveCursor, cells := v.parseInput(r)
634			if cells == nil {
635				continue
636			}
637			v.writeCells(v.wx, v.wy, cells)
638			if moveCursor {
639				v.wx += len(cells)
640			}
641		}
642	}
643}
644
645// exported functions use the mutex. Non-exported functions are for internal use
646// and a calling function should use a mutex
647func (v *View) WriteString(s string) {
648	v.WriteRunes([]rune(s))
649}
650
651func (v *View) writeString(s string) {
652	v.writeRunes([]rune(s))
653}
654
655// parseInput parses char by char the input written to the View. It returns nil
656// while processing ESC sequences. Otherwise, it returns a cell slice that
657// contains the processed data.
658func (v *View) parseInput(ch rune) (bool, []cell) {
659	cells := []cell{}
660	moveCursor := true
661
662	isEscape, err := v.ei.parseOne(ch)
663	if err != nil {
664		for _, r := range v.ei.runes() {
665			c := cell{
666				fgColor: v.FgColor,
667				bgColor: v.BgColor,
668				chr:     r,
669			}
670			cells = append(cells, c)
671		}
672		v.ei.reset()
673	} else {
674		repeatCount := 1
675		if _, ok := v.ei.instruction.(eraseInLineFromCursor); ok {
676			// fill rest of line
677			v.ei.instructionRead()
678			repeatCount = v.InnerWidth() - v.wx
679			ch = ' '
680			moveCursor = false
681		} else if isEscape {
682			// do not output anything
683			return moveCursor, nil
684		} else if ch == '\t' {
685			// fill tab-sized space
686			ch = ' '
687			repeatCount = 4
688		}
689		c := cell{
690			fgColor: v.ei.curFgColor,
691			bgColor: v.ei.curBgColor,
692			chr:     ch,
693		}
694		for i := 0; i < repeatCount; i++ {
695			cells = append(cells, c)
696		}
697	}
698
699	return moveCursor, cells
700}
701
702// Read reads data into p from the current reading position set by SetReadPos.
703// It returns the number of bytes read into p.
704// At EOF, err will be io.EOF.
705func (v *View) Read(p []byte) (n int, err error) {
706	buffer := make([]byte, utf8.UTFMax)
707	offset := 0
708	if v.readBuffer != nil {
709		copy(p, v.readBuffer)
710		if len(v.readBuffer) >= len(p) {
711			if len(v.readBuffer) > len(p) {
712				v.readBuffer = v.readBuffer[len(p):]
713			}
714			return len(p), nil
715		}
716		v.readBuffer = nil
717	}
718	for v.ry < len(v.lines) {
719		for v.rx < len(v.lines[v.ry]) {
720			count := utf8.EncodeRune(buffer, v.lines[v.ry][v.rx].chr)
721			copy(p[offset:], buffer[:count])
722			v.rx++
723			newOffset := offset + count
724			if newOffset >= len(p) {
725				if newOffset > len(p) {
726					v.readBuffer = buffer[newOffset-len(p):]
727				}
728				return len(p), nil
729			}
730			offset += count
731		}
732		v.rx = 0
733		v.ry++
734	}
735	return offset, io.EOF
736}
737
738// only use this if the calling function has a lock on writeMutex
739func (v *View) clear() {
740	v.rewind()
741	v.lines = nil
742	v.clearViewLines()
743}
744
745// Clear empties the view's internal buffer.
746// And resets reading and writing offsets.
747func (v *View) Clear() {
748	v.writeMutex.Lock()
749	defer v.writeMutex.Unlock()
750
751	v.clear()
752}
753
754func (v *View) SetContent(str string) {
755	v.writeMutex.Lock()
756	defer v.writeMutex.Unlock()
757
758	v.clear()
759	v.writeString(str)
760}
761
762// Rewind sets read and write pos to (0, 0).
763func (v *View) Rewind() {
764	v.writeMutex.Lock()
765	defer v.writeMutex.Unlock()
766
767	v.rewind()
768}
769
770// similar to Rewind but clears lines. Also similar to Clear but doesn't reset
771// viewLines
772func (v *View) Reset() {
773	v.writeMutex.Lock()
774	defer v.writeMutex.Unlock()
775
776	v.rewind()
777	v.lines = nil
778}
779
780// This is for when we've done a restart for the sake of avoiding a flicker and
781// we've reached the end of the new content to display: we need to clear the remaining
782// content from the previous round. We do this by setting v.viewLines to nil so that
783// we just render the new content from v.lines directly
784func (v *View) FlushStaleCells() {
785	v.writeMutex.Lock()
786	defer v.writeMutex.Unlock()
787
788	v.clearViewLines()
789}
790
791func (v *View) rewind() {
792	v.ei.reset()
793
794	if err := v.SetReadPos(0, 0); err != nil {
795		// SetReadPos returns error only if x and y are negative
796		// we are passing 0, 0, thus no error should occur.
797		panic(err)
798	}
799	if err := v.SetWritePos(0, 0); err != nil {
800		// SetWritePos returns error only if x and y are negative
801		// we are passing 0, 0, thus no error should occur.
802		panic(err)
803	}
804}
805
806func containsUpcaseChar(str string) bool {
807	for _, ch := range str {
808		if unicode.IsUpper(ch) {
809			return true
810		}
811	}
812
813	return false
814}
815
816func (v *View) updateSearchPositions() {
817	if v.searcher.searchString != "" {
818		var normalizeRune func(r rune) rune
819		var normalizedSearchStr string
820		// if we have any uppercase characters we'll do a case-sensitive search
821		if containsUpcaseChar(v.searcher.searchString) {
822			normalizedSearchStr = v.searcher.searchString
823			normalizeRune = func(r rune) rune { return r }
824		} else {
825			normalizedSearchStr = strings.ToLower(v.searcher.searchString)
826			normalizeRune = unicode.ToLower
827		}
828
829		v.searcher.searchPositions = []cellPos{}
830		for y, line := range v.lines {
831		lineLoop:
832			for x, _ := range line {
833				if normalizeRune(line[x].chr) == rune(normalizedSearchStr[0]) {
834					for offset := 1; offset < len(normalizedSearchStr); offset++ {
835						if len(line)-1 < x+offset {
836							continue lineLoop
837						}
838						if normalizeRune(line[x+offset].chr) != rune(normalizedSearchStr[offset]) {
839							continue lineLoop
840						}
841					}
842					v.searcher.searchPositions = append(v.searcher.searchPositions, cellPos{x: x, y: y})
843				}
844			}
845		}
846	}
847}
848
849// IsTainted tells us if the view is tainted
850func (v *View) IsTainted() bool {
851	return v.tainted
852}
853
854// draw re-draws the view's contents.
855func (v *View) draw() error {
856	v.writeMutex.Lock()
857	defer v.writeMutex.Unlock()
858
859	v.clearRunes()
860
861	if !v.Visible {
862		return nil
863	}
864
865	v.updateSearchPositions()
866	maxX, maxY := v.Size()
867
868	if v.Wrap {
869		if maxX == 0 {
870			return nil
871		}
872		v.ox = 0
873	}
874	if v.tainted {
875		lineIdx := 0
876		lines := v.lines
877		if v.HasLoader {
878			lines = v.loaderLines()
879		}
880		for i, line := range lines {
881			wrap := 0
882			if v.Wrap {
883				wrap = maxX
884			}
885
886			ls := lineWrap(line, wrap)
887			for j := range ls {
888				vline := viewLine{linesX: j, linesY: i, line: ls[j]}
889
890				if lineIdx > len(v.viewLines)-1 {
891					v.viewLines = append(v.viewLines, vline)
892				} else {
893					v.viewLines[lineIdx] = vline
894				}
895				lineIdx++
896			}
897		}
898		if !v.HasLoader {
899			v.tainted = false
900		}
901	}
902
903	visibleViewLinesHeight := v.viewLineLengthIgnoringTrailingBlankLines()
904	if v.Autoscroll && visibleViewLinesHeight > maxY {
905		v.oy = visibleViewLinesHeight - maxY
906	}
907
908	if len(v.viewLines) == 0 {
909		return nil
910	}
911
912	start := v.oy
913	if start > len(v.viewLines)-1 {
914		start = len(v.viewLines) - 1
915	}
916
917	y := 0
918	for _, vline := range v.viewLines[start:] {
919		if y >= maxY {
920			break
921		}
922		x := 0
923		for j, c := range vline.line {
924			if j < v.ox {
925				continue
926			}
927			if x >= maxX {
928				break
929			}
930
931			fgColor := c.fgColor
932			if fgColor == ColorDefault {
933				fgColor = v.FgColor
934			}
935			bgColor := c.bgColor
936			if bgColor == ColorDefault {
937				bgColor = v.BgColor
938			}
939			if matched, selected := v.isPatternMatchedRune(x, y); matched {
940				if selected {
941					bgColor = ColorCyan
942				} else {
943					bgColor = ColorYellow
944				}
945			}
946
947			if err := v.setRune(x, y, c.chr, fgColor, bgColor); err != nil {
948				return err
949			}
950
951			// Not sure why the previous code was here but it caused problems
952			// when typing wide characters in an editor
953			x += runewidth.RuneWidth(c.chr)
954		}
955		y++
956	}
957	return nil
958}
959
960// if autoscroll is enabled but we only have a single row of cells shown to the
961// user, we don't want to scroll to the final line if it contains no text. So
962// this tells us the view lines height when we ignore any trailing blank lines
963func (v *View) viewLineLengthIgnoringTrailingBlankLines() int {
964	for i := len(v.viewLines) - 1; i >= 0; i-- {
965		if len(v.viewLines[i].line) > 0 {
966			return i + 1
967		}
968	}
969	return 0
970}
971
972func (v *View) isPatternMatchedRune(x, y int) (bool, bool) {
973	searchStringLength := len(v.searcher.searchString)
974	for i, pos := range v.searcher.searchPositions {
975		adjustedY := y + v.oy
976		adjustedX := x + v.ox
977		if adjustedY == pos.y && adjustedX >= pos.x && adjustedX < pos.x+searchStringLength {
978			return true, i == v.searcher.currentSearchIndex
979		}
980	}
981	return false, false
982}
983
984// realPosition returns the position in the internal buffer corresponding to the
985// point (x, y) of the view.
986func (v *View) realPosition(vx, vy int) (x, y int, err error) {
987	vx = v.ox + vx
988	vy = v.oy + vy
989
990	if vx < 0 || vy < 0 {
991		return 0, 0, ErrInvalidPoint
992	}
993
994	if len(v.viewLines) == 0 {
995		return vx, vy, nil
996	}
997
998	if vy < len(v.viewLines) {
999		vline := v.viewLines[vy]
1000		x = vline.linesX + vx
1001		y = vline.linesY
1002	} else {
1003		vline := v.viewLines[len(v.viewLines)-1]
1004		x = vx
1005		y = vline.linesY + vy - len(v.viewLines) + 1
1006	}
1007
1008	return x, y, nil
1009}
1010
1011// clearRunes erases all the cells in the view.
1012func (v *View) clearRunes() {
1013	maxX, maxY := v.Size()
1014	for x := 0; x < maxX; x++ {
1015		for y := 0; y < maxY; y++ {
1016			tcellSetCell(v.x0+x+1, v.y0+y+1, ' ', v.FgColor, v.BgColor, v.outMode)
1017		}
1018	}
1019}
1020
1021// BufferLines returns the lines in the view's internal
1022// buffer.
1023func (v *View) BufferLines() []string {
1024	lines := make([]string, len(v.lines))
1025	for i, l := range v.lines {
1026		str := lineType(l).String()
1027		str = strings.Replace(str, "\x00", " ", -1)
1028		lines[i] = str
1029	}
1030	return lines
1031}
1032
1033// Buffer returns a string with the contents of the view's internal
1034// buffer.
1035func (v *View) Buffer() string {
1036	return linesToString(v.lines)
1037}
1038
1039// ViewBufferLines returns the lines in the view's internal
1040// buffer that is shown to the user.
1041func (v *View) ViewBufferLines() []string {
1042	lines := make([]string, len(v.viewLines))
1043	for i, l := range v.viewLines {
1044		str := lineType(l.line).String()
1045		str = strings.Replace(str, "\x00", " ", -1)
1046		lines[i] = str
1047	}
1048	return lines
1049}
1050
1051// LinesHeight is the count of view lines (i.e. lines excluding wrapping)
1052func (v *View) LinesHeight() int {
1053	return len(v.lines)
1054}
1055
1056// ViewLinesHeight is the count of view lines (i.e. lines including wrapping)
1057func (v *View) ViewLinesHeight() int {
1058	return len(v.viewLines)
1059}
1060
1061// ViewBuffer returns a string with the contents of the view's buffer that is
1062// shown to the user.
1063func (v *View) ViewBuffer() string {
1064	lines := make([][]cell, len(v.viewLines))
1065	for i := range v.viewLines {
1066		lines[i] = v.viewLines[i].line
1067	}
1068
1069	return linesToString(lines)
1070}
1071
1072// Line returns a string with the line of the view's internal buffer
1073// at the position corresponding to the point (x, y).
1074func (v *View) Line(y int) (string, error) {
1075	_, y, err := v.realPosition(0, y)
1076	if err != nil {
1077		return "", err
1078	}
1079
1080	if y < 0 || y >= len(v.lines) {
1081		return "", ErrInvalidPoint
1082	}
1083
1084	return lineType(v.lines[y]).String(), nil
1085}
1086
1087// Word returns a string with the word of the view's internal buffer
1088// at the position corresponding to the point (x, y).
1089func (v *View) Word(x, y int) (string, error) {
1090	x, y, err := v.realPosition(x, y)
1091	if err != nil {
1092		return "", err
1093	}
1094
1095	if x < 0 || y < 0 || y >= len(v.lines) || x >= len(v.lines[y]) {
1096		return "", ErrInvalidPoint
1097	}
1098
1099	str := lineType(v.lines[y]).String()
1100
1101	nl := strings.LastIndexFunc(str[:x], indexFunc)
1102	if nl == -1 {
1103		nl = 0
1104	} else {
1105		nl = nl + 1
1106	}
1107	nr := strings.IndexFunc(str[x:], indexFunc)
1108	if nr == -1 {
1109		nr = len(str)
1110	} else {
1111		nr = nr + x
1112	}
1113	return string(str[nl:nr]), nil
1114}
1115
1116// indexFunc allows to split lines by words taking into account spaces
1117// and 0.
1118func indexFunc(r rune) bool {
1119	return r == ' ' || r == 0
1120}
1121
1122// SetHighlight toggles highlighting of separate lines, for custom lists
1123// or multiple selection in views.
1124func (v *View) SetHighlight(y int, on bool) error {
1125	if y < 0 || y >= len(v.lines) {
1126		err := ErrInvalidPoint
1127		return err
1128	}
1129
1130	line := v.lines[y]
1131	cells := make([]cell, 0)
1132	for _, c := range line {
1133		if on {
1134			c.bgColor = v.SelBgColor
1135			c.fgColor = v.SelFgColor
1136		} else {
1137			c.bgColor = v.BgColor
1138			c.fgColor = v.FgColor
1139		}
1140		cells = append(cells, c)
1141	}
1142	v.tainted = true
1143	v.lines[y] = cells
1144	return nil
1145}
1146
1147func lineWrap(line []cell, columns int) [][]cell {
1148	if columns == 0 {
1149		return [][]cell{line}
1150	}
1151
1152	var n int
1153	var offset int
1154	lines := make([][]cell, 0, 1)
1155	for i := range line {
1156		rw := runewidth.RuneWidth(line[i].chr)
1157		n += rw
1158		if n > columns {
1159			n = rw
1160			lines = append(lines, line[offset:i])
1161			offset = i
1162		}
1163	}
1164
1165	lines = append(lines, line[offset:])
1166	return lines
1167}
1168
1169func linesToString(lines [][]cell) string {
1170	str := make([]string, len(lines))
1171	for i := range lines {
1172		rns := make([]rune, 0, len(lines[i]))
1173		line := lineType(lines[i]).String()
1174		for _, c := range line {
1175			if c != '\x00' {
1176				rns = append(rns, c)
1177			}
1178		}
1179		str[i] = string(rns)
1180	}
1181
1182	return strings.Join(str, "\n")
1183}
1184
1185// GetClickedTabIndex tells us which tab was clicked
1186func (v *View) GetClickedTabIndex(x int) int {
1187	if len(v.Tabs) <= 1 {
1188		return 0
1189	}
1190
1191	charIndex := 0
1192	for i, tab := range v.Tabs {
1193		charIndex += len(tab + " - ")
1194		if x < charIndex {
1195			return i
1196		}
1197	}
1198
1199	return 0
1200}
1201
1202func (v *View) SelectedLineIdx() int {
1203	_, seletedLineIdx := v.SelectedPoint()
1204	return seletedLineIdx
1205}
1206
1207func (v *View) SelectedPoint() (int, int) {
1208	cx, cy := v.Cursor()
1209	ox, oy := v.Origin()
1210	return cx + ox, cy + oy
1211}
1212
1213func (v *View) RenderTextArea() {
1214	v.Clear()
1215	fmt.Fprint(v, v.TextArea.GetContent())
1216	cursorX, cursorY := v.TextArea.GetCursorXY()
1217	prevOriginX, prevOriginY := v.Origin()
1218	width, height := v.InnerWidth(), v.InnerHeight()
1219
1220	newViewCursorX, newOriginX := updatedCursorAndOrigin(prevOriginX, width, cursorX)
1221	newViewCursorY, newOriginY := updatedCursorAndOrigin(prevOriginY, height, cursorY)
1222
1223	_ = v.SetCursor(newViewCursorX, newViewCursorY)
1224	_ = v.SetOrigin(newOriginX, newOriginY)
1225}
1226
1227func updatedCursorAndOrigin(prevOrigin int, size int, cursor int) (int, int) {
1228	var newViewCursor int
1229	newOrigin := prevOrigin
1230
1231	if cursor > prevOrigin+size {
1232		newOrigin = cursor - size
1233		newViewCursor = size
1234	} else if cursor < prevOrigin {
1235		newOrigin = cursor
1236		newViewCursor = 0
1237	} else {
1238		newViewCursor = cursor - prevOrigin
1239	}
1240
1241	return newViewCursor, newOrigin
1242}
1243
1244func (v *View) ClearTextArea() {
1245	v.Clear()
1246
1247	v.writeMutex.Lock()
1248	defer v.writeMutex.Unlock()
1249
1250	v.TextArea.Clear()
1251	_ = v.SetOrigin(0, 0)
1252	_ = v.SetCursor(0, 0)
1253}
1254
1255// only call this function if you don't care where v.wx and v.wy end up
1256func (v *View) OverwriteLines(y int, content string) {
1257	v.writeMutex.Lock()
1258	defer v.writeMutex.Unlock()
1259
1260	// break by newline, then for each line, write it, then add that erase command
1261	v.wx = 0
1262	v.wy = y
1263
1264	lines := strings.Replace(content, "\n", "\x1b[K\n", -1)
1265	v.writeString(lines)
1266}
1267