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