github.com/aretext/aretext@v1.3.0/input/helpers.go (about)

     1  package input
     2  
     3  import (
     4  	"strconv"
     5  	"strings"
     6  	"unicode/utf8"
     7  
     8  	"github.com/gdamore/tcell/v2"
     9  
    10  	"github.com/aretext/aretext/clipboard"
    11  	"github.com/aretext/aretext/input/engine"
    12  )
    13  
    14  func eventKeyToEngineEvent(eventKey *tcell.EventKey) engine.Event {
    15  	if eventKey.Key() == tcell.KeyRune {
    16  		return runeToEngineEvent(eventKey.Rune())
    17  	} else {
    18  		return keyToEngineEvent(eventKey.Key())
    19  	}
    20  }
    21  
    22  func keyToEngineEvent(key tcell.Key) engine.Event {
    23  	return engine.Event(int64(key) << 32)
    24  }
    25  
    26  func runeToEngineEvent(r rune) engine.Event {
    27  	return engine.Event((int64(tcell.KeyRune) << 32) | int64(r))
    28  }
    29  
    30  func engineEventToKey(engineEvent engine.Event) tcell.Key {
    31  	return tcell.Key(engineEvent >> 32)
    32  }
    33  
    34  func engineEventToRune(engineEvent engine.Event) rune {
    35  	return rune(engineEvent & 0xFFFFFFFF)
    36  }
    37  
    38  const (
    39  	captureIdVerbCount = iota
    40  	captureIdObjectCount
    41  	captureIdClipboardPage
    42  	captureIdMatchChar
    43  	captureIdReplaceChar
    44  	captureIdInsertChar
    45  )
    46  
    47  // Pre-compute and share these expressions to reduce number of allocations.
    48  var verbCountExpr, objectCountExpr, clipboardPageExpr, matchCharExpr, replaceCharExpr, insertExpr engine.Expr
    49  
    50  func init() {
    51  	verbCountExpr = engine.OptionExpr{
    52  		Child: engine.CaptureExpr{
    53  			CaptureId: captureIdVerbCount,
    54  			Child: engine.ConcatExpr{
    55  				Children: []engine.Expr{
    56  					engine.EventRangeExpr{
    57  						StartEvent: runeToEngineEvent('1'),
    58  						EndEvent:   runeToEngineEvent('9'),
    59  					},
    60  					engine.StarExpr{
    61  						Child: engine.EventRangeExpr{
    62  							StartEvent: runeToEngineEvent('0'),
    63  							EndEvent:   runeToEngineEvent('9'),
    64  						},
    65  					},
    66  				},
    67  			},
    68  		},
    69  	}
    70  
    71  	objectCountExpr = engine.OptionExpr{
    72  		Child: engine.CaptureExpr{
    73  			CaptureId: captureIdObjectCount,
    74  			Child: engine.ConcatExpr{
    75  				Children: []engine.Expr{
    76  					engine.EventRangeExpr{
    77  						StartEvent: runeToEngineEvent('1'),
    78  						EndEvent:   runeToEngineEvent('9'),
    79  					},
    80  					engine.StarExpr{
    81  						Child: engine.EventRangeExpr{
    82  							StartEvent: runeToEngineEvent('0'),
    83  							EndEvent:   runeToEngineEvent('9'),
    84  						},
    85  					},
    86  				},
    87  			},
    88  		},
    89  	}
    90  
    91  	clipboardPageExpr = engine.OptionExpr{
    92  		Child: engine.ConcatExpr{
    93  			Children: []engine.Expr{
    94  				engine.EventExpr{
    95  					Event: runeToEngineEvent('"'),
    96  				},
    97  				engine.CaptureExpr{
    98  					CaptureId: captureIdClipboardPage,
    99  					Child: engine.EventRangeExpr{
   100  						StartEvent: runeToEngineEvent('a'),
   101  						EndEvent:   runeToEngineEvent('z'),
   102  					},
   103  				},
   104  			},
   105  		},
   106  	}
   107  
   108  	matchCharExpr = engine.CaptureExpr{
   109  		CaptureId: captureIdMatchChar,
   110  		Child: engine.EventRangeExpr{
   111  			StartEvent: runeToEngineEvent(rune(0)),
   112  			EndEvent:   runeToEngineEvent(rune(255)),
   113  		},
   114  	}
   115  
   116  	replaceCharExpr = engine.CaptureExpr{
   117  		CaptureId: captureIdReplaceChar,
   118  		Child: engine.AltExpr{
   119  			Children: []engine.Expr{
   120  				engine.EventRangeExpr{
   121  					StartEvent: runeToEngineEvent(rune(0)),
   122  					EndEvent:   runeToEngineEvent(rune(255)),
   123  				},
   124  				engine.EventExpr{
   125  					Event: keyToEngineEvent(tcell.KeyEnter),
   126  				},
   127  				engine.EventExpr{
   128  					Event: keyToEngineEvent(tcell.KeyTab),
   129  				},
   130  			},
   131  		},
   132  	}
   133  
   134  	insertExpr = engine.CaptureExpr{
   135  		CaptureId: captureIdInsertChar,
   136  		Child: engine.EventRangeExpr{
   137  			StartEvent: runeToEngineEvent(rune(0)),
   138  			EndEvent:   runeToEngineEvent(utf8.MaxRune),
   139  		},
   140  	}
   141  }
   142  
   143  type captureOpts struct {
   144  	count         bool
   145  	clipboardPage bool
   146  	matchChar     bool
   147  	replaceChar   bool
   148  }
   149  
   150  func altExpr(children ...engine.Expr) engine.Expr {
   151  	return engine.AltExpr{Children: children}
   152  }
   153  
   154  func verbCountThenExpr(expr engine.Expr) engine.Expr {
   155  	return engine.ConcatExpr{Children: []engine.Expr{verbCountExpr, expr}}
   156  }
   157  
   158  func runeExpr(r rune) engine.Expr {
   159  	return engine.EventExpr{Event: runeToEngineEvent(r)}
   160  }
   161  
   162  func keyExpr(key tcell.Key) engine.Expr {
   163  	return engine.EventExpr{Event: keyToEngineEvent(key)}
   164  }
   165  
   166  func cmdExpr(verb string, object string, opts captureOpts) engine.Expr {
   167  	expr := engine.ConcatExpr{Children: make([]engine.Expr, 0, len(verb))}
   168  	for _, r := range verb {
   169  		expr.Children = append(expr.Children, engine.EventExpr{
   170  			Event: runeToEngineEvent(r),
   171  		})
   172  	}
   173  
   174  	if object != "" {
   175  		verbExpr := expr
   176  		objExpr := engine.ConcatExpr{Children: make([]engine.Expr, 0, len(object))}
   177  		for _, r := range object {
   178  			objExpr.Children = append(objExpr.Children, engine.EventExpr{
   179  				Event: runeToEngineEvent(r),
   180  			})
   181  		}
   182  
   183  		if opts.count {
   184  			objExpr = engine.ConcatExpr{Children: []engine.Expr{objectCountExpr, objExpr}}
   185  		}
   186  
   187  		expr = engine.ConcatExpr{Children: []engine.Expr{verbExpr, objExpr}}
   188  	}
   189  
   190  	if opts.count {
   191  		expr = engine.ConcatExpr{Children: []engine.Expr{verbCountExpr, expr}}
   192  	}
   193  
   194  	if opts.clipboardPage {
   195  		expr = engine.ConcatExpr{Children: []engine.Expr{clipboardPageExpr, expr}}
   196  	}
   197  
   198  	if opts.matchChar {
   199  		expr = engine.ConcatExpr{Children: []engine.Expr{expr, matchCharExpr}}
   200  	}
   201  
   202  	if opts.replaceChar {
   203  		expr = engine.ConcatExpr{Children: []engine.Expr{expr, replaceCharExpr}}
   204  	}
   205  
   206  	return expr
   207  }
   208  
   209  func capturesToCommandParams(captures map[engine.CaptureId][]engine.Event) CommandParams {
   210  	p := CommandParams{
   211  		Count:         1,
   212  		ClipboardPage: clipboard.PageDefault,
   213  		MatchChar:     '\x00',
   214  		ReplaceChar:   '\x00',
   215  		InsertChar:    '\x00',
   216  	}
   217  	for captureId, captureEvents := range captures {
   218  		switch captureId {
   219  		case captureIdVerbCount, captureIdObjectCount:
   220  			// Multiply here so that if both verb and object count are provided,
   221  			// the total count is the product of the two counts.
   222  			// For example, "2d3w" should delete 2*3=6 words.
   223  			p.Count *= eventsToCount(captureEvents)
   224  		case captureIdClipboardPage:
   225  			p.ClipboardPage = eventsToClipboardPage(captureEvents)
   226  		case captureIdMatchChar:
   227  			p.MatchChar = eventsToChar(captureEvents)
   228  		case captureIdReplaceChar:
   229  			p.ReplaceChar = eventsToReplaceChar(captureEvents)
   230  		case captureIdInsertChar:
   231  			p.InsertChar = eventsToChar(captureEvents)
   232  		}
   233  	}
   234  	return p
   235  }
   236  
   237  func eventsToCount(events []engine.Event) uint64 {
   238  	var sb strings.Builder
   239  	for _, e := range events {
   240  		sb.WriteRune(engineEventToRune(e))
   241  	}
   242  	i, err := strconv.Atoi(sb.String())
   243  	if err != nil || i < 0 {
   244  		return 0
   245  	}
   246  	return uint64(i)
   247  }
   248  
   249  func eventsToClipboardPage(events []engine.Event) clipboard.PageId {
   250  	if len(events) != 1 {
   251  		return clipboard.PageNull
   252  	}
   253  	return clipboard.PageIdForLetter(engineEventToRune(events[0]))
   254  }
   255  
   256  func eventsToChar(events []engine.Event) rune {
   257  	if len(events) != 1 {
   258  		return '\x00'
   259  	}
   260  	return engineEventToRune(events[0])
   261  }
   262  
   263  func eventsToReplaceChar(events []engine.Event) rune {
   264  	if len(events) != 1 {
   265  		return '\x00'
   266  	}
   267  
   268  	switch engineEventToKey(events[0]) {
   269  	case tcell.KeyEnter:
   270  		return '\n'
   271  	case tcell.KeyTab:
   272  		return '\t'
   273  	case tcell.KeyRune:
   274  		return engineEventToRune(events[0])
   275  	default:
   276  		return '\x00'
   277  	}
   278  }