github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/insert.go (about)

     1  package edit
     2  
     3  import (
     4  	"io"
     5  	"strings"
     6  	"unicode"
     7  	"unicode/utf8"
     8  
     9  	"github.com/elves/elvish/util"
    10  )
    11  
    12  // Builtins related to insert and command mode.
    13  
    14  type insert struct {
    15  	quotePaste bool
    16  	// The number of consecutive key inserts. Used for abbreviation expansion.
    17  	literalInserts int
    18  	// Indicates whether a key was inserted (via insert-default). A hack for
    19  	// maintaining the inserts field.
    20  	insertedLiteral bool
    21  }
    22  
    23  func (*insert) Mode() ModeType {
    24  	return modeInsert
    25  }
    26  
    27  // Insert mode is the default mode and has an empty mode.
    28  func (ins *insert) ModeLine(width int) *buffer {
    29  	if ins.quotePaste {
    30  		return makeModeLine(" INSERT (quote paste) ", width)
    31  	}
    32  	return nil
    33  }
    34  
    35  type command struct{}
    36  
    37  func (*command) Mode() ModeType {
    38  	return modeCommand
    39  }
    40  
    41  func (*command) ModeLine(width int) *buffer {
    42  	return makeModeLine(" COMMAND ", width)
    43  }
    44  
    45  func startInsert(ed *Editor) {
    46  	ed.mode = &ed.insert
    47  }
    48  
    49  func startCommand(ed *Editor) {
    50  	ed.mode = &ed.command
    51  }
    52  
    53  func killLineLeft(ed *Editor) {
    54  	sol := util.FindLastSOL(ed.line[:ed.dot])
    55  	ed.line = ed.line[:sol] + ed.line[ed.dot:]
    56  	ed.dot = sol
    57  }
    58  
    59  func killLineRight(ed *Editor) {
    60  	eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
    61  	ed.line = ed.line[:ed.dot] + ed.line[eol:]
    62  }
    63  
    64  // NOTE(xiaq): A word is a run of non-space runes. When killing a word,
    65  // trimming spaces are removed as well. Examples:
    66  // "abc  xyz" -> "abc  ", "abc xyz " -> "abc  ".
    67  
    68  func killWordLeft(ed *Editor) {
    69  	if ed.dot == 0 {
    70  		return
    71  	}
    72  	space := strings.LastIndexFunc(
    73  		strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
    74  		unicode.IsSpace) + 1
    75  	ed.line = ed.line[:space] + ed.line[ed.dot:]
    76  	ed.dot = space
    77  }
    78  
    79  // NOTE(xiaq): A small word is either a run of alphanumeric (Unicode category L
    80  // or N) runes or a run of non-alphanumeric runes. This is consistent with vi's
    81  // definition of word, except that "_" is not considered alphanumeric. When
    82  // killing a small word, trimming spaces are removed as well. Examples:
    83  // "abc/~" -> "abc", "~/abc" -> "~/", "abc* " -> "abc"
    84  
    85  func killSmallWordLeft(ed *Editor) {
    86  	left := strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace)
    87  	// The case of left == "" is handled as well.
    88  	r, _ := utf8.DecodeLastRuneInString(left)
    89  	if isAlnum(r) {
    90  		left = strings.TrimRightFunc(left, isAlnum)
    91  	} else {
    92  		left = strings.TrimRightFunc(
    93  			left, func(r rune) bool { return !isAlnum(r) })
    94  	}
    95  	ed.line = left + ed.line[ed.dot:]
    96  	ed.dot = len(left)
    97  }
    98  
    99  func isAlnum(r rune) bool {
   100  	return unicode.IsLetter(r) || unicode.IsNumber(r)
   101  }
   102  
   103  func killRuneLeft(ed *Editor) {
   104  	if ed.dot > 0 {
   105  		_, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
   106  		ed.line = ed.line[:ed.dot-w] + ed.line[ed.dot:]
   107  		ed.dot -= w
   108  	} else {
   109  		ed.flash()
   110  	}
   111  }
   112  
   113  func killRuneRight(ed *Editor) {
   114  	if ed.dot < len(ed.line) {
   115  		_, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
   116  		ed.line = ed.line[:ed.dot] + ed.line[ed.dot+w:]
   117  	} else {
   118  		ed.flash()
   119  	}
   120  }
   121  
   122  func moveDotLeft(ed *Editor) {
   123  	_, w := utf8.DecodeLastRuneInString(ed.line[:ed.dot])
   124  	ed.dot -= w
   125  }
   126  
   127  func moveDotRight(ed *Editor) {
   128  	_, w := utf8.DecodeRuneInString(ed.line[ed.dot:])
   129  	ed.dot += w
   130  }
   131  
   132  func moveDotLeftWord(ed *Editor) {
   133  	if ed.dot == 0 {
   134  		return
   135  	}
   136  	space := strings.LastIndexFunc(
   137  		strings.TrimRightFunc(ed.line[:ed.dot], unicode.IsSpace),
   138  		unicode.IsSpace) + 1
   139  	ed.dot = space
   140  }
   141  
   142  func moveDotRightWord(ed *Editor) {
   143  	// Move to first space
   144  	p := strings.IndexFunc(ed.line[ed.dot:], unicode.IsSpace)
   145  	if p == -1 {
   146  		ed.dot = len(ed.line)
   147  		return
   148  	}
   149  	ed.dot += p
   150  	// Move to first nonspace
   151  	p = strings.IndexFunc(ed.line[ed.dot:], notSpace)
   152  	if p == -1 {
   153  		ed.dot = len(ed.line)
   154  		return
   155  	}
   156  	ed.dot += p
   157  }
   158  
   159  func notSpace(r rune) bool {
   160  	return !unicode.IsSpace(r)
   161  }
   162  
   163  func moveDotSOL(ed *Editor) {
   164  	sol := util.FindLastSOL(ed.line[:ed.dot])
   165  	ed.dot = sol
   166  }
   167  
   168  func moveDotEOL(ed *Editor) {
   169  	eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
   170  	ed.dot = eol
   171  }
   172  
   173  func moveDotUp(ed *Editor) {
   174  	sol := util.FindLastSOL(ed.line[:ed.dot])
   175  	if sol == 0 {
   176  		ed.flash()
   177  		return
   178  	}
   179  	prevEOL := sol - 1
   180  	prevSOL := util.FindLastSOL(ed.line[:prevEOL])
   181  	width := WcWidths(ed.line[sol:ed.dot])
   182  	ed.dot = prevSOL + len(TrimWcWidth(ed.line[prevSOL:prevEOL], width))
   183  }
   184  
   185  func moveDotDown(ed *Editor) {
   186  	eol := util.FindFirstEOL(ed.line[ed.dot:]) + ed.dot
   187  	if eol == len(ed.line) {
   188  		ed.flash()
   189  		return
   190  	}
   191  	nextSOL := eol + 1
   192  	nextEOL := util.FindFirstEOL(ed.line[nextSOL:]) + nextSOL
   193  	sol := util.FindLastSOL(ed.line[:ed.dot])
   194  	width := WcWidths(ed.line[sol:ed.dot])
   195  	ed.dot = nextSOL + len(TrimWcWidth(ed.line[nextSOL:nextEOL], width))
   196  }
   197  
   198  func insertLastWord(ed *Editor) {
   199  	if ed.store == nil {
   200  		ed.addTip("store offline")
   201  		return
   202  	}
   203  	_, lastLine, err := ed.store.LastCmd(-1, "", true)
   204  	if err == nil {
   205  		ed.insertAtDot(lastWord(lastLine))
   206  	} else {
   207  		ed.addTip("db error: %s", err.Error())
   208  	}
   209  }
   210  
   211  func lastWord(s string) string {
   212  	s = strings.TrimRightFunc(s, unicode.IsSpace)
   213  	i := strings.LastIndexFunc(s, unicode.IsSpace) + 1
   214  	return s[i:]
   215  }
   216  
   217  func insertKey(ed *Editor) {
   218  	k := ed.lastKey
   219  	ed.insertAtDot(string(k.Rune))
   220  }
   221  
   222  func returnLine(ed *Editor) {
   223  	ed.nextAction = action{typ: exitReadLine, returnLine: ed.line}
   224  }
   225  
   226  func smartEnter(ed *Editor) {
   227  	if ed.parseErrorAtEnd {
   228  		// There is a parsing error at the end. Insert a newline and copy
   229  		// indents from previous line.
   230  		indent := findLastIndent(ed.line[:ed.dot])
   231  		ed.insertAtDot("\n" + indent)
   232  	} else {
   233  		returnLine(ed)
   234  	}
   235  }
   236  
   237  func findLastIndent(s string) string {
   238  	line := s[util.FindLastSOL(s):]
   239  	trimmed := strings.TrimLeft(line, " \t")
   240  	return line[:len(line)-len(trimmed)]
   241  }
   242  
   243  func returnEOF(ed *Editor) {
   244  	if len(ed.line) == 0 {
   245  		ed.nextAction = action{typ: exitReadLine, returnErr: io.EOF}
   246  	}
   247  }
   248  
   249  func toggleQuotePaste(ed *Editor) {
   250  	ed.insert.quotePaste = !ed.insert.quotePaste
   251  }
   252  
   253  func defaultInsert(ed *Editor) {
   254  	k := ed.lastKey
   255  	if likeChar(k) {
   256  		insertKey(ed)
   257  		// Match abbreviations.
   258  		literals := ed.line[ed.dot-ed.insert.literalInserts-1 : ed.dot]
   259  		for abbr, full := range ed.abbreviations {
   260  			if strings.HasSuffix(literals, abbr) {
   261  				ed.line = ed.line[:ed.dot-len(abbr)] + full + ed.line[ed.dot:]
   262  				ed.dot += len(full) - len(abbr)
   263  				return
   264  			}
   265  		}
   266  		// No match.
   267  		ed.insert.insertedLiteral = true
   268  	} else {
   269  		ed.notify("Unbound: %s", k)
   270  	}
   271  }
   272  
   273  func defaultCommand(ed *Editor) {
   274  	k := ed.lastKey
   275  	ed.notify("Unbound: %s", k)
   276  }
   277  
   278  // likeChar returns if a key looks like a character meant to be input (as
   279  // opposed to a function key).
   280  func likeChar(k Key) bool {
   281  	return k.Mod == 0 && k.Rune > 0 && unicode.IsGraphic(k.Rune)
   282  }