...

Source file src/github.com/c-bata/go-prompt/buffer.go

Documentation: github.com/c-bata/go-prompt

     1  package prompt
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/c-bata/go-prompt/internal/debug"
     7  )
     8  
     9  // Buffer emulates the console buffer.
    10  type Buffer struct {
    11  	workingLines    []string // The working lines. Similar to history
    12  	workingIndex    int
    13  	cursorPosition  int
    14  	cacheDocument   *Document
    15  	preferredColumn int // Remember the original column for the next up/down movement.
    16  	lastKeyStroke   Key
    17  }
    18  
    19  // Text returns string of the current line.
    20  func (b *Buffer) Text() string {
    21  	return b.workingLines[b.workingIndex]
    22  }
    23  
    24  // Document method to return document instance from the current text and cursor position.
    25  func (b *Buffer) Document() (d *Document) {
    26  	if b.cacheDocument == nil ||
    27  		b.cacheDocument.Text != b.Text() ||
    28  		b.cacheDocument.cursorPosition != b.cursorPosition {
    29  		b.cacheDocument = &Document{
    30  			Text:           b.Text(),
    31  			cursorPosition: b.cursorPosition,
    32  		}
    33  	}
    34  	b.cacheDocument.lastKey = b.lastKeyStroke
    35  	return b.cacheDocument
    36  }
    37  
    38  // DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
    39  // So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
    40  func (b *Buffer) DisplayCursorPosition() int {
    41  	return b.Document().DisplayCursorPosition()
    42  }
    43  
    44  // InsertText insert string from current line.
    45  func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
    46  	or := []rune(b.Text())
    47  	oc := b.cursorPosition
    48  
    49  	if overwrite {
    50  		overwritten := string(or[oc : oc+len(v)])
    51  		if strings.Contains(overwritten, "\n") {
    52  			i := strings.IndexAny(overwritten, "\n")
    53  			overwritten = overwritten[:i]
    54  		}
    55  		b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
    56  	} else {
    57  		b.setText(string(or[:oc]) + v + string(or[oc:]))
    58  	}
    59  
    60  	if moveCursor {
    61  		b.cursorPosition += len([]rune(v))
    62  	}
    63  }
    64  
    65  // SetText method to set text and update cursorPosition.
    66  // (When doing this, make sure that the cursor_position is valid for this text.
    67  // text/cursor_position should be consistent at any time, otherwise set a Document instead.)
    68  func (b *Buffer) setText(v string) {
    69  	debug.Assert(b.cursorPosition <= len([]rune(v)), "length of input should be shorter than cursor position")
    70  	b.workingLines[b.workingIndex] = v
    71  }
    72  
    73  // Set cursor position. Return whether it changed.
    74  func (b *Buffer) setCursorPosition(p int) {
    75  	if p > 0 {
    76  		b.cursorPosition = p
    77  	} else {
    78  		b.cursorPosition = 0
    79  	}
    80  }
    81  
    82  func (b *Buffer) setDocument(d *Document) {
    83  	b.cacheDocument = d
    84  	b.setCursorPosition(d.cursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
    85  	b.setText(d.Text)
    86  }
    87  
    88  // CursorLeft move to left on the current line.
    89  func (b *Buffer) CursorLeft(count int) {
    90  	l := b.Document().GetCursorLeftPosition(count)
    91  	b.cursorPosition += l
    92  }
    93  
    94  // CursorRight move to right on the current line.
    95  func (b *Buffer) CursorRight(count int) {
    96  	l := b.Document().GetCursorRightPosition(count)
    97  	b.cursorPosition += l
    98  }
    99  
   100  // CursorUp move cursor to the previous line.
   101  // (for multi-line edit).
   102  func (b *Buffer) CursorUp(count int) {
   103  	orig := b.preferredColumn
   104  	if b.preferredColumn == -1 { // -1 means nil
   105  		orig = b.Document().CursorPositionCol()
   106  	}
   107  	b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
   108  
   109  	// Remember the original column for the next up/down movement.
   110  	b.preferredColumn = orig
   111  }
   112  
   113  // CursorDown move cursor to the next line.
   114  // (for multi-line edit).
   115  func (b *Buffer) CursorDown(count int) {
   116  	orig := b.preferredColumn
   117  	if b.preferredColumn == -1 { // -1 means nil
   118  		orig = b.Document().CursorPositionCol()
   119  	}
   120  	b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
   121  
   122  	// Remember the original column for the next up/down movement.
   123  	b.preferredColumn = orig
   124  }
   125  
   126  // DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
   127  func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
   128  	debug.Assert(count >= 0, "count should be positive")
   129  	r := []rune(b.Text())
   130  
   131  	if b.cursorPosition > 0 {
   132  		start := b.cursorPosition - count
   133  		if start < 0 {
   134  			start = 0
   135  		}
   136  		deleted = string(r[start:b.cursorPosition])
   137  		b.setDocument(&Document{
   138  			Text:           string(r[:start]) + string(r[b.cursorPosition:]),
   139  			cursorPosition: b.cursorPosition - len([]rune(deleted)),
   140  		})
   141  	}
   142  	return
   143  }
   144  
   145  // NewLine means CR.
   146  func (b *Buffer) NewLine(copyMargin bool) {
   147  	if copyMargin {
   148  		b.InsertText("\n"+b.Document().leadingWhitespaceInCurrentLine(), false, true)
   149  	} else {
   150  		b.InsertText("\n", false, true)
   151  	}
   152  }
   153  
   154  // Delete specified number of characters and Return the deleted text.
   155  func (b *Buffer) Delete(count int) (deleted string) {
   156  	r := []rune(b.Text())
   157  	if b.cursorPosition < len(r) {
   158  		deleted = b.Document().TextAfterCursor()[:count]
   159  		b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
   160  	}
   161  	return
   162  }
   163  
   164  // JoinNextLine joins the next line to the current one by deleting the line ending after the current line.
   165  func (b *Buffer) JoinNextLine(separator string) {
   166  	if !b.Document().OnLastLine() {
   167  		b.cursorPosition += b.Document().GetEndOfLinePosition()
   168  		b.Delete(1)
   169  		// Remove spaces
   170  		b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
   171  	}
   172  }
   173  
   174  // SwapCharactersBeforeCursor swaps the last two characters before the cursor.
   175  func (b *Buffer) SwapCharactersBeforeCursor() {
   176  	if b.cursorPosition >= 2 {
   177  		x := b.Text()[b.cursorPosition-2 : b.cursorPosition-1]
   178  		y := b.Text()[b.cursorPosition-1 : b.cursorPosition]
   179  		b.setText(b.Text()[:b.cursorPosition-2] + y + x + b.Text()[b.cursorPosition:])
   180  	}
   181  }
   182  
   183  // NewBuffer is constructor of Buffer struct.
   184  func NewBuffer() (b *Buffer) {
   185  	b = &Buffer{
   186  		workingLines:    []string{""},
   187  		workingIndex:    0,
   188  		preferredColumn: -1, // -1 means nil
   189  	}
   190  	return
   191  }
   192  

View as plain text