Created
June 30, 2019 07:20
-
-
Save derekschrock/fba5ef095cf50a9db53d2b45494deb1e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From ecbec6b49adee2f2fbd24b8cf9d1fd2e64d07e9f Mon Sep 17 00:00:00 2001 | |
From: Derek Schrock <dereks@lifeofadishwasher.com> | |
Date: Sun, 30 Jun 2019 02:47:27 -0400 | |
Subject: [PATCH] autocomp? | |
--- | |
editors/micro/Makefile | 2 +- | |
editors/micro/distinfo | 6 +- | |
editors/micro/files/patch-autocomp | 2161 ++++++++++++++++++++++++++++ | |
3 files changed, 2165 insertions(+), 4 deletions(-) | |
create mode 100644 editors/micro/files/patch-autocomp | |
diff --git a/editors/micro/Makefile b/editors/micro/Makefile | |
index 7b4f71e01f44..d9d95508a5ec 100644 | |
--- a/editors/micro/Makefile | |
+++ b/editors/micro/Makefile | |
@@ -35,7 +35,7 @@ GH_TUPLE= \ | |
zyedidia:glob:dd4023a:glob/src/github.com/zyedidia/glob \ | |
zyedidia:poller:ab09682:poller/src/github.com/zyedidia/poller \ | |
zyedidia:pty:3036466:pty/src/github.com/zyedidia/pty \ | |
- zyedidia:tcell:208b6e8:tcell/src/github.com/zyedidia/tcell \ | |
+ zyedidia:tcell:869faf86d:tcell/src/github.com/zyedidia/tcell \ | |
zyedidia:terminal:1760577:terminal/src/github.com/zyedidia/terminal \ | |
golang:net:1a68b13:net/src/golang.org/x/net \ | |
golang:text:210eee5:text/src/golang.org/x/text \ | |
diff --git a/editors/micro/distinfo b/editors/micro/distinfo | |
index 1e7e44e76880..03a87810c07f 100644 | |
--- a/editors/micro/distinfo | |
+++ b/editors/micro/distinfo | |
@@ -1,4 +1,4 @@ | |
-TIMESTAMP = 1534863849 | |
+TIMESTAMP = 1561876989 | |
SHA256 (zyedidia-micro-v1.4.1_GH0.tar.gz) = 839b30bb39df45f43ee72583db9fa3b59611228d1b6a65a9e77a999e85b94d01 | |
SIZE (zyedidia-micro-v1.4.1_GH0.tar.gz) = 520409 | |
SHA256 (blang-semver-4a1e882_GH0.tar.gz) = e392b8b07bcc674ac15c0174b48f08528651f21bfbe448b91349825b68bba930 | |
@@ -37,8 +37,8 @@ SHA256 (zyedidia-poller-ab09682_GH0.tar.gz) = 8a6d6dcb4c4e34d37d991c1ba79b5cedec | |
SIZE (zyedidia-poller-ab09682_GH0.tar.gz) = 15914 | |
SHA256 (zyedidia-pty-3036466_GH0.tar.gz) = 2f5daa0b8df213f7d092bc802c0bbde4b5f1873e9b7762f7c400aa4abc2ffec2 | |
SIZE (zyedidia-pty-3036466_GH0.tar.gz) = 5742 | |
-SHA256 (zyedidia-tcell-208b6e8_GH0.tar.gz) = 39fe9c3f97e43bdb61de9893f6e74476e31d3fd8019f8a7b71357f8ab30fc8f9 | |
-SIZE (zyedidia-tcell-208b6e8_GH0.tar.gz) = 167401 | |
+SHA256 (zyedidia-tcell-869faf86d_GH0.tar.gz) = 75458e3d7f3a84f9c61f6ebdc0336f5fa2e62b955a901efd433569e48faf7714 | |
+SIZE (zyedidia-tcell-869faf86d_GH0.tar.gz) = 167269 | |
SHA256 (zyedidia-terminal-1760577_GH0.tar.gz) = bd521e37c4d1b08f9e4e0fc619da5826c94f88589363541be8e517564231f60a | |
SIZE (zyedidia-terminal-1760577_GH0.tar.gz) = 12884 | |
SHA256 (golang-net-1a68b13_GH0.tar.gz) = d57f23fa50027ed9846060ec7e57f02e3f877d8cc6b230c9b2887a1109749529 | |
diff --git a/editors/micro/files/patch-autocomp b/editors/micro/files/patch-autocomp | |
new file mode 100644 | |
index 000000000000..7b7d64dc9c8e | |
--- /dev/null | |
+++ b/editors/micro/files/patch-autocomp | |
@@ -0,0 +1,2161 @@ | |
+--- README.md.orig 2018-07-20 00:24:02 UTC | |
++++ README.md | |
+@@ -66,8 +66,11 @@ You can also check out the website for Micro at https: | |
+ * Easily configurable | |
+ * Macros | |
+ * Common editor things such as undo/redo, line numbers, Unicode support, softwrap... | |
++* Autocomplete | |
++ * See [#174](https://github.com/zyedidia/micro/issues/174) | |
++ * See instructions for enabling below. | |
+ | |
+-Although not yet implemented, I hope to add more features such as autocompletion ([#174](https://github.com/zyedidia/micro/issues/174)) or a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future. | |
++Although not yet implemented, I hope to add more features such as a tree view ([#249](https://github.com/zyedidia/micro/issues/249)) in the future. | |
+ | |
+ # Installation | |
+ | |
+@@ -177,6 +180,20 @@ Many of the Windows terminals don't support more than | |
+ that micro's default colorscheme won't look very good. You can either set | |
+ the colorscheme to `simple`, or download a better terminal emulator, like | |
+ mintty. | |
++ | |
++### Autocomplete | |
++ | |
++* Beta - use at your own risk. | |
++* Please check for duplicate bugs before reporting anything you find. | |
++ | |
++#### All files | |
++ | |
++* Works out common words from text being edited. | |
++* To enable it, press CtrlE to go to command mode, and enter `setlocal autocomplete true`. This will enable autocomplete for the file until closed. The setting will not persist. | |
++ | |
++#### .go files | |
++ | |
++* Uses `gocode` which must be installed and available on the path. | |
+ | |
+ ### Plan9, Cygwin | |
+ | |
+--- cmd/micro/actions.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/actions.go | |
+@@ -721,6 +721,12 @@ func (v *View) InsertNewline(usePlugin bool) bool { | |
+ } | |
+ v.Cursor.LastVisualX = v.Cursor.GetVisualX() | |
+ | |
++ // Allow the completer to access the newline. | |
++ err := v.Completer.Process('\n') | |
++ if err != nil { | |
++ TermMessage(err) | |
++ } | |
++ | |
+ if usePlugin { | |
+ return PostActionCall("InsertNewline", v) | |
+ } | |
+--- cmd/micro/completer.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/completer.go | |
+@@ -0,0 +1,387 @@ | |
++package main | |
++ | |
++import ( | |
++ "fmt" | |
++ "strings" | |
++ | |
++ "github.com/zyedidia/micro/cmd/micro/optionprovider" | |
++ "github.com/zyedidia/tcell" | |
++) | |
++ | |
++// OptionProvider is the signature of a function which returns all of the available options, potentially using the prefix | |
++// data. For example, given input "abc\nab", start offset 4 and end offset 5, then the prefix is "ab", and the result | |
++// should be the option "abc". | |
++// Logger provides logging. Can be satisfied with t.Logf for tests, or LogToMessenger. | |
++type OptionProvider func(logger func(s string, values ...interface{}), buffer []byte, startOffset, endOffset int) (options []optionprovider.Option, startOffsetDelta int, err error) | |
++ | |
++// ContentSetter is the signature of a function which allows the content of a cell to be set. | |
++type ContentSetter func(x int, y int, mainc rune, combc []rune, style tcell.Style) | |
++ | |
++// CompleterEnabledFlagFromView gets whether autocomplete is enabled from the buffer settings. | |
++func CompleterEnabledFlagFromView(v *View) func() bool { | |
++ return func() bool { | |
++ setting, hasSetting := v.Buf.Settings["autocomplete"] | |
++ if !hasSetting { | |
++ return false | |
++ } | |
++ enabled, isBool := setting.(bool) | |
++ if !isBool { | |
++ return false | |
++ } | |
++ return enabled | |
++ } | |
++} | |
++ | |
++// CurrentBytesAndOffsetFromView gets bytes from a view. | |
++func CurrentBytesAndOffsetFromView(v *View) func() (bytes []byte, offset int) { | |
++ return func() (bytes []byte, offset int) { | |
++ bytes = v.Buf.Buffer(false).Bytes() | |
++ offset = ByteOffset(v.Cursor.Loc, v.Buf) | |
++ return | |
++ } | |
++} | |
++ | |
++// LocationOffsetFromView provides the offset of a given location. | |
++func LocationOffsetFromView(v *View) func(Loc) (offset int) { | |
++ return func(l Loc) (offset int) { | |
++ return ByteOffset(l, v.Buf) | |
++ } | |
++} | |
++ | |
++// CurrentLocationFromView gets the current location from a view. | |
++func CurrentLocationFromView(v *View) func() Loc { | |
++ return func() Loc { | |
++ return v.Cursor.Loc | |
++ } | |
++} | |
++ | |
++// ReplaceFromBuffer replaces text in a buffer. | |
++func ReplaceFromBuffer(buf *Buffer) func(from, to Loc, with string) { | |
++ return func(from, to Loc, with string) { | |
++ LogToMessenger()("replacing from %v to %v with %s", from, to, with) | |
++ buf.Replace(from, to, with) | |
++ buf.Cursor.GotoLoc(Loc{X: from.X + len(with), Y: from.Y}) | |
++ } | |
++} | |
++ | |
++// ContentSetterForView sets the content of a cell for the x, y coordinate of a document. | |
++func ContentSetterForView(v *View) ContentSetter { | |
++ return func(x int, y int, mainc rune, combc []rune, style tcell.Style) { | |
++ targetY := y - v.Topline | |
++ targetX := x + v.leftCol + v.lineNumOffset | |
++ screen.SetContent(targetX, targetY, mainc, combc, style) | |
++ } | |
++} | |
++ | |
++// LogToMessenger logs to the global messenger. | |
++func LogToMessenger() func(s string, values ...interface{}) { | |
++ return func(s string, values ...interface{}) { | |
++ messenger.AddLog(fmt.Sprintf(s, values...)) | |
++ } | |
++} | |
++ | |
++// Completer completes code as you type. | |
++type Completer struct { | |
++ // Active is the state which determines whether the completer is active (visible) or not. | |
++ Active bool | |
++ // X stores the X position of the suggestion box | |
++ X int | |
++ // Y stores the Y position of the suggestion box | |
++ Y int | |
++ // Options stores the current list of suggestions. | |
++ Options []optionprovider.Option | |
++ // ActiveIndex store the index of the active option (the one that will be selected). | |
++ ActiveIndex int | |
++ // Activators are insertions that start autocomplete, e.g. a "." or an opening bracket "(". | |
++ Activators map[rune]int | |
++ // Deactivators are insertions that stop autocomplete, e.g. a closing bracket, or a semicolon. | |
++ Deactivators []rune | |
++ // Provider is the provider of completion options, e.g. gocode, or another provider such as a language server. | |
++ Provider OptionProvider | |
++ // Logger is where log messages are written via fmt.Sprintf. | |
++ Logger func(s string, values ...interface{}) | |
++ // CurrentBytesAndOffset is a function which returns the bytes and the current offset position from the current view. | |
++ CurrentBytesAndOffset func() (bytes []byte, offset int) | |
++ // CurrentLocation is a function which returns the current location of the cursor. | |
++ CurrentLocation func() Loc | |
++ // LocationOffset is a function which returns the offset of a given location. | |
++ LocationOffset func(Loc) int | |
++ // Replacer is a function which replaces text. | |
++ Replacer func(from, to Loc, with string) | |
++ // Setter is a function which draws to the console at a given location. | |
++ Setter ContentSetter | |
++ // OptionStyleInactive is the style for completer options which are not currently highlighted. | |
++ OptionStyleInactive tcell.Style | |
++ // OptionStyleActive is the style for completer options which are currently highlighted. | |
++ OptionStyleActive tcell.Style | |
++ // Enabled determines whether the view has the enabled flag set or not. | |
++ Enabled func() bool | |
++ // PreviousLocation stores the last known location of the cursor. | |
++ PreviousLocation Loc | |
++} | |
++ | |
++// defaultActivators sets whether the character should start autocompletion. The value of zero means that | |
++// the character itself is not included in the replacement, -1 means that it is. | |
++var defaultActivators = map[rune]int{ | |
++ '.': 0, | |
++ '(': 0, | |
++ 'a': -1, 'b': -1, 'c': -1, 'd': -1, 'e': -1, 'f': -1, 'g': -1, 'h': -1, 'i': -1, 'j': -1, 'k': -1, 'l': -1, 'm': -1, 'n': -1, 'o': -1, 'p': -1, 'q': -1, 'r': -1, 's': -1, 't': -1, 'u': -1, 'v': -1, 'w': -1, 'x': -1, 'y': -1, 'z': -1, | |
++ 'A': -1, 'B': -1, 'C': -1, 'D': -1, 'E': -1, 'F': -1, 'G': -1, 'H': -1, 'I': -1, 'J': -1, 'K': -1, 'L': -1, 'M': -1, 'N': -1, 'O': -1, 'P': -1, 'Q': -1, 'R': -1, 'S': -1, 'T': -1, 'U': -1, 'V': -1, 'W': -1, 'X': -1, 'Y': -1, 'Z': -1, | |
++} | |
++ | |
++const defaultDeactivators = "), \n." | |
++ | |
++// NewCompleterForView creates a new autocompleter with defaults for writing to the console. | |
++func NewCompleterForView(v *View) *Completer { | |
++ var provider OptionProvider | |
++ | |
++ // Load the provider based on filename. | |
++ fileName := v.Buf.GetName() | |
++ if strings.HasSuffix(fileName, ".go") { | |
++ provider = optionprovider.GoCode | |
++ } else { | |
++ provider = optionprovider.Generic | |
++ } | |
++ | |
++ // If no matching provider was found, we can't autocomplete. | |
++ if provider == nil { | |
++ provider = optionprovider.Noop | |
++ } | |
++ | |
++ return NewCompleter(defaultActivators, []rune(defaultDeactivators), | |
++ provider, | |
++ LogToMessenger(), | |
++ CurrentBytesAndOffsetFromView(v), | |
++ CurrentLocationFromView(v), | |
++ LocationOffsetFromView(v), | |
++ ReplaceFromBuffer(v.Buf), | |
++ ContentSetterForView(v), | |
++ colorscheme["default"].Reverse(true), | |
++ colorscheme["default"], | |
++ CompleterEnabledFlagFromView(v), | |
++ ) | |
++} | |
++ | |
++// NewCompleter creates a new completer with all options exposed. See NewCompleterForView for more common usage. | |
++func NewCompleter(activators map[rune]int, | |
++ deactivators []rune, | |
++ provider OptionProvider, | |
++ logger func(s string, values ...interface{}), | |
++ currentBytesAndOffset func() (bytes []byte, offset int), | |
++ currentLocation func() Loc, | |
++ locationOffset func(Loc) int, | |
++ replacer func(from, to Loc, with string), | |
++ setter ContentSetter, | |
++ optionStyleInactive tcell.Style, | |
++ optionStyleActive tcell.Style, | |
++ enabled func() bool) *Completer { | |
++ return &Completer{ | |
++ Activators: activators, | |
++ Deactivators: deactivators, | |
++ Provider: provider, | |
++ Logger: logger, | |
++ CurrentBytesAndOffset: currentBytesAndOffset, | |
++ CurrentLocation: currentLocation, | |
++ LocationOffset: locationOffset, | |
++ Replacer: replacer, | |
++ Setter: setter, | |
++ OptionStyleInactive: optionStyleInactive, | |
++ OptionStyleActive: optionStyleActive, | |
++ Enabled: enabled, | |
++ } | |
++} | |
++ | |
++// Process handles incoming events from the view and starts looking up via autocomplete. | |
++func (c *Completer) Process(r rune) error { | |
++ if !c.Enabled() { | |
++ return nil | |
++ } | |
++ | |
++ if c.Provider == nil { | |
++ return nil | |
++ } | |
++ | |
++ // Hide the autocomplete view if needed. | |
++ if c.Active && containsRune(c.Deactivators, r) { | |
++ c.Logger("completer.Process: deactivating, because received %v", string(r)) | |
++ c.Active = false | |
++ } | |
++ | |
++ if !c.Active { | |
++ // Check to work out whether we should activate the autocomplete. | |
++ if indexAdjustment, ok := c.Activators[r]; ok { | |
++ c.Logger("completer.Process: activating, because received %v", string(r)) | |
++ c.Active = true | |
++ currentLocation := c.CurrentLocation() | |
++ c.PreviousLocation = currentLocation | |
++ c.X, c.Y = currentLocation.X+indexAdjustment, currentLocation.Y | |
++ c.Logger("completer.Process: SetStartPosition to %d, %d", c.X, c.Y) | |
++ } | |
++ } | |
++ | |
++ if !c.Active { | |
++ // We're not active. | |
++ return nil | |
++ } | |
++ | |
++ // Get options. | |
++ //TODO: We only need the answer by the time Display is called, so we could let the rest of the | |
++ // program continue until we're ready to receive the value by using a go routine or channel. | |
++ bytes, currentOffset := c.CurrentBytesAndOffset() | |
++ startOffset := c.LocationOffset(Loc{X: c.X, Y: c.Y}) | |
++ options, delta, err := c.Provider(c.Logger, bytes, startOffset, currentOffset) | |
++ if err != nil { | |
++ return err | |
++ } | |
++ c.X += delta | |
++ c.Options = options | |
++ c.ActiveIndex = 0 | |
++ // If there are no options, just deactivate. | |
++ if len(options) == 0 { | |
++ c.Logger("completer.Process: Deactivating because there are no options") | |
++ c.Active = false | |
++ } | |
++ return err | |
++} | |
++ | |
++// HandleEvent handles incoming key presses if the completer is active. | |
++// It returns true if it took over the key action, or false if it didn't. | |
++func (c *Completer) HandleEvent(key tcell.Key) bool { | |
++ if !c.Enabled() { | |
++ c.Logger("completer.HandleEvent: not enabled") | |
++ return false | |
++ } | |
++ if !c.Active { | |
++ c.Logger("completer.HandleEvent: not active") | |
++ return false | |
++ } | |
++ | |
++ // Handle selecting various options in the list. | |
++ switch key { | |
++ case tcell.KeyUp: | |
++ if c.ActiveIndex > 0 { | |
++ c.ActiveIndex-- | |
++ } | |
++ break | |
++ case tcell.KeyDown: | |
++ if c.ActiveIndex < len(c.Options)-1 { | |
++ c.ActiveIndex++ | |
++ } | |
++ break | |
++ case tcell.KeyEsc: | |
++ c.Active = false | |
++ break | |
++ case tcell.KeyTab, tcell.KeyEnter: | |
++ // Complete the text. | |
++ if toUse, ok := getOption(c.ActiveIndex, c.Options); ok { | |
++ c.Replacer(Loc{X: c.X, Y: c.Y}, c.CurrentLocation(), toUse) | |
++ } | |
++ c.Active = false | |
++ break | |
++ default: | |
++ // Not part of the keys that the autocomplete menu handles. | |
++ return false | |
++ } | |
++ | |
++ // The completer handled the key. | |
++ return true | |
++} | |
++ | |
++func getOption(i int, options []optionprovider.Option) (toUse string, ok bool) { | |
++ if len(options) == 0 { | |
++ return "", false | |
++ } | |
++ if i > len(options)-1 { | |
++ return "", false | |
++ } | |
++ if i < 0 { | |
++ i = 0 | |
++ } | |
++ return options[i].Text(), true | |
++} | |
++ | |
++// DeactivateIfOutOfBounds for example, if duplicating lines or backspacing past the start of the completion. | |
++func (c *Completer) DeactivateIfOutOfBounds() { | |
++ // Disable autocomplete if we've switched lines (e.g. by duplicating a line, or moving the cursor away) | |
++ // of if the X position is equal to or less than current. | |
++ if !c.Active { | |
++ return | |
++ } | |
++ cur := c.CurrentLocation() | |
++ beforeStart := cur.X < c.X | |
++ movedMoreThanOneXSinceLastCheck := distance(c.PreviousLocation.X, cur.X) > 1 | |
++ c.Logger("completed.DeactivateIfOutOfBounds: Previous loc %v, current loc %v, distance: %v", cur, c.PreviousLocation, distance(c.PreviousLocation.X, cur.X)) | |
++ movedLine := cur.Y != c.Y | |
++ if beforeStart || movedMoreThanOneXSinceLastCheck || movedLine { | |
++ c.Logger("completer.DeactivateIfOutOfBounds: deactivating") | |
++ c.Active = false | |
++ } | |
++ c.PreviousLocation = cur | |
++} | |
++ | |
++func distance(a, b int) int { | |
++ if a == b { | |
++ return 0 | |
++ } | |
++ if a > b { | |
++ return a - b | |
++ } | |
++ return b - a | |
++} | |
++ | |
++// Display the suggestion box. | |
++func (c *Completer) Display() { | |
++ if !c.Enabled() { | |
++ c.Logger("completer.Display: not enabled") | |
++ return | |
++ } | |
++ if !c.Active { | |
++ return | |
++ } | |
++ | |
++ c.Logger("completer.Display: showing %d options", len(c.Options)) | |
++ width := getWidth(c.Options) | |
++ start := c.CurrentLocation() | |
++ for iy, o := range c.Options { | |
++ y := start.Y + iy + 1 // +1 to draw a line below the cursor. | |
++ | |
++ // If it's active, show it differently. | |
++ style := c.OptionStyleInactive | |
++ if c.ActiveIndex == iy { | |
++ style = c.OptionStyleActive | |
++ } | |
++ | |
++ // Draw the runes. | |
++ for ix, r := range padRight(o.Text(), width+1) { | |
++ x := start.X + ix | |
++ c.Setter(x, y, r, nil, style) | |
++ } | |
++ } | |
++} | |
++ | |
++func getWidth(options []optionprovider.Option) (max int) { | |
++ for _, o := range options { | |
++ if l := len(o.Text()); l > max { | |
++ max = l | |
++ } | |
++ } | |
++ return | |
++} | |
++ | |
++func padRight(s string, minSize int) string { | |
++ extra := minSize - len(s) | |
++ if extra > 0 { | |
++ padding := make([]byte, extra) | |
++ return s + string(padding) | |
++ } | |
++ return s | |
++} | |
++ | |
++func containsRune(array []rune, r rune) bool { | |
++ for _, r1 := range array { | |
++ if r1 == r { | |
++ return true | |
++ } | |
++ } | |
++ return false | |
++} | |
+--- cmd/micro/completer_test.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/completer_test.go | |
+@@ -0,0 +1,862 @@ | |
++package main | |
++ | |
++import ( | |
++ "bytes" | |
++ "reflect" | |
++ "testing" | |
++ | |
++ "github.com/zyedidia/micro/cmd/micro/optionprovider" | |
++ "github.com/zyedidia/tcell" | |
++) | |
++ | |
++var noopReplacer = func(from, to Loc, with string) {} | |
++var noopContentSetter = func(x int, y int, mainc rune, combc []rune, style tcell.Style) {} | |
++var enabledFlagSetToTrue = func() bool { return true } | |
++var enabledFlagSetToFalse = func() bool { return false } | |
++ | |
++var optionStyleInactive = tcell.StyleDefault.Reverse(true) | |
++ | |
++const optionStyleActive = tcell.StyleDefault | |
++ | |
++func TestCompleterDoesNothingWhenNotEnabledOrProviderNotSet(t *testing.T) { | |
++ var currentBytesAndOffsetCalled, currentLocationCalled, locationOffsetCalled, providerCalled bool | |
++ | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ currentBytesAndOffsetCalled = true | |
++ return []byte("fmt.Println("), 3 | |
++ } | |
++ currentLocation := func() Loc { | |
++ currentLocationCalled = true | |
++ return Loc{X: 1, Y: 2} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ locationOffsetCalled = true | |
++ return 0 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ providerCalled = true | |
++ return | |
++ } | |
++ | |
++ activators := map[rune]int{ | |
++ '(': 0, | |
++ } | |
++ deactivators := []rune{')', ';'} | |
++ | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToFalse) | |
++ | |
++ // It's not enabled, so nothing should be called. | |
++ err := c.Process('(') | |
++ if err != nil { | |
++ t.Fatalf("not enabled: failed to process with error: %v", err) | |
++ } | |
++ if currentBytesAndOffsetCalled || currentLocationCalled || locationOffsetCalled { | |
++ t.Errorf("not enabled: when disabled, no functions should be called") | |
++ } | |
++ | |
++ // It's enabled, but the provider is nil. | |
++ c.Enabled = enabledFlagSetToTrue | |
++ c.Provider = nil | |
++ | |
++ err = c.Process('(') | |
++ if err != nil { | |
++ t.Fatalf("enabled: failed to process with error: %v", err) | |
++ } | |
++ if currentBytesAndOffsetCalled || currentLocationCalled || locationOffsetCalled || providerCalled { | |
++ t.Errorf("enabled: when disabled, no functions should be called") | |
++ } | |
++} | |
++ | |
++func TestCompleterIsDeactivatedByDeactivatorRunes(t *testing.T) { | |
++ activators := map[rune]int{ | |
++ '(': 0, | |
++ } | |
++ deactivators := []rune{')', ';'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("fmt.Println("), 3 | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 1, Y: 2} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 3 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ options = []optionprovider.Option{ | |
++ optionprovider.New("text", "hint"), | |
++ } | |
++ return | |
++ } | |
++ | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ | |
++ err := c.Process(')') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if c.Active { | |
++ t.Errorf("expected ')' to deactivate the completer, but it didn't") | |
++ } | |
++} | |
++ | |
++func TestCompleterIsActivatedByActivatorRunes(t *testing.T) { | |
++ var providerReceivedBytes []byte | |
++ var providerReceivedOffset int | |
++ | |
++ expectedOptions := []optionprovider.Option{ | |
++ optionprovider.New("text", "hint"), | |
++ optionprovider.New("text1", "hint1"), | |
++ } | |
++ | |
++ activators := map[rune]int{ | |
++ '(': 0, | |
++ } | |
++ deactivators := []rune{')', ';'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("fmt.Println("), 3 | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 1, Y: 2} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 3 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ providerReceivedBytes = buffer | |
++ providerReceivedOffset = currentOffset | |
++ options = expectedOptions | |
++ return | |
++ } | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ err := c.Process('(') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if !c.Active { | |
++ t.Errorf("expected '(' to activate the completer, but it didn't") | |
++ } | |
++ if c.X != 1 && c.Y != 2 { | |
++ t.Errorf("expected activating the completer to set the start position to {1, 2} but got {%v, %v}", c.X, c.Y) | |
++ } | |
++ if !reflect.DeepEqual([]byte("fmt.Println("), providerReceivedBytes) { | |
++ t.Errorf("expected the provider to receive '%v', but got '%v'", "fmt.Println(", string(providerReceivedBytes)) | |
++ } | |
++ if !reflect.DeepEqual(3, providerReceivedOffset) { | |
++ t.Errorf("expected the provider to receive '%v', but got '%v'", "fmt.Println(", string(providerReceivedBytes)) | |
++ } | |
++ if !reflect.DeepEqual(expectedOptions, c.Options) { | |
++ t.Errorf("expected options %v, but got %v", expectedOptions, c.Options) | |
++ } | |
++ if c.ActiveIndex != 0 { | |
++ t.Errorf("expected the active index to be reset to 0 after a refresh, but it was set to %v", c.ActiveIndex) | |
++ } | |
++} | |
++ | |
++func TestCompleterIsDeactivatedByNotReceivingAnyOptions(t *testing.T) { | |
++ activators := map[rune]int{ | |
++ '(': 0, | |
++ } | |
++ deactivators := []rune{')', ';'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("fmt.Print"), len("fmt.Print") | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: len("fmt.Print"), Y: 0} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 0 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ options = []optionprovider.Option{} // No options. | |
++ return | |
++ } | |
++ | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ | |
++ err := c.Process('l') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if c.Active { | |
++ t.Errorf("expected receiving no autocomplete options to deactivate the completer, but it didn't") | |
++ } | |
++} | |
++ | |
++func TestCompleterIsRestartedIfARuneIsAnActivatorAndDeactivator(t *testing.T) { | |
++ activators := map[rune]int{ | |
++ '.': 0, | |
++ } | |
++ deactivators := []rune{'.'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("test test"), 9 | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 9, Y: 0} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 9 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ options = []optionprovider.Option{ | |
++ optionprovider.New("test", "test"), | |
++ } | |
++ return | |
++ } | |
++ | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ | |
++ err := c.Process('.') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if !c.Active { | |
++ t.Errorf("expected '.' to deactivate, then reactivate the completer, but it didn't") | |
++ } | |
++ if c.X != 9 { | |
++ t.Errorf("expected the start position to be reset to x:9, but was %v", c.X) | |
++ } | |
++} | |
++ | |
++func TestCompleterIsNotTriggeredByOtherRunesWhenInactive(t *testing.T) { | |
++ activators := map[rune]int{ | |
++ '(': 0, | |
++ } | |
++ deactivators := []rune{')', ';'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("fmt.Println("), 3 | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 1, Y: 2} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 0 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, delta int, err error) { | |
++ options = []optionprovider.Option{ | |
++ optionprovider.New("text", "hint"), | |
++ } | |
++ return | |
++ } | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ err := c.Process('a') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if c.Active { | |
++ t.Errorf("expected 'a' to do nothing to activate the completer, but the completer was activated") | |
++ } | |
++} | |
++ | |
++func TestCompleterAdjustsStartPositionIfOptionProviderMovesIt(t *testing.T) { | |
++ activators := map[rune]int{ | |
++ '.': 0, | |
++ } | |
++ deactivators := []rune{'.'} | |
++ currentBytesAndOffset := func() (bytes []byte, offset int) { | |
++ return []byte("test test"), 9 | |
++ } | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 9, Y: 0} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 9 | |
++ } | |
++ provider := func(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []optionprovider.Option, startPositionDelta int, err error) { | |
++ options = []optionprovider.Option{ | |
++ optionprovider.New("test", "test"), | |
++ } | |
++ startPositionDelta = -1 | |
++ return | |
++ } | |
++ | |
++ c := NewCompleter(activators, | |
++ deactivators, | |
++ provider, | |
++ t.Logf, | |
++ currentBytesAndOffset, | |
++ currentLocation, | |
++ locationOffset, | |
++ noopReplacer, | |
++ noopContentSetter, | |
++ optionStyleInactive, | |
++ optionStyleActive, | |
++ enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ c.X = 7 | |
++ err := c.Process('e') | |
++ if err != nil { | |
++ t.Fatalf("failed to process with error: %v", err) | |
++ } | |
++ if c.X != 6 { | |
++ t.Errorf("expected the start position to shift by the -1 delta returned by the options, but was %v", c.X) | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventNotEnabled(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToFalse) | |
++ | |
++ handled := c.HandleEvent(tcell.KeyRune) | |
++ if handled { | |
++ t.Error("when the completer is not enabled, handling events should not take place") | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventInactive(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ handled := c.HandleEvent(tcell.KeyRune) | |
++ if handled { | |
++ t.Error("when the completer is inactive, handling events should not take place") | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventKeyUp(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ c.ActiveIndex = 10 | |
++ | |
++ handled := c.HandleEvent(tcell.KeyUp) | |
++ if !handled { | |
++ t.Error("when the completer is active, KeyUp should be handled") | |
++ } | |
++ if c.ActiveIndex != 9 { | |
++ t.Errorf("KeyUp should decrease the active index from 10 to 9, but the result was %v", c.ActiveIndex) | |
++ } | |
++ | |
++ // Check that it's not possible to go before option index zero. | |
++ c.ActiveIndex = 0 | |
++ c.HandleEvent(tcell.KeyUp) | |
++ if c.ActiveIndex != 0 { | |
++ t.Errorf("Once the top of the selections are reached, it shouldn't be possible to go any further, but the result was %v", c.ActiveIndex) | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventKeyDown(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ c.Options = []optionprovider.Option{ | |
++ {H: "hint0", T: "text0"}, | |
++ {H: "hint1", T: "text1"}, | |
++ } | |
++ c.ActiveIndex = 0 | |
++ | |
++ handled := c.HandleEvent(tcell.KeyDown) | |
++ if !handled { | |
++ t.Error("when the completer is active, KeyDown should be handled") | |
++ } | |
++ if c.ActiveIndex != 1 { | |
++ t.Errorf("KeyDown should increase the active index from 0 to 1, but the result was %v", c.ActiveIndex) | |
++ } | |
++ | |
++ // Check that it's not possible to exceed the number of options. | |
++ c.HandleEvent(tcell.KeyDown) | |
++ if c.ActiveIndex != 1 { | |
++ t.Errorf("Once the bottom of the selections are reached, it shouldn't be possible to go any further, but the result was %v", c.ActiveIndex) | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventKeyEscape(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ | |
++ handled := c.HandleEvent(tcell.KeyEscape) | |
++ if !handled { | |
++ t.Error("when the completer is active, KeyEscape should be handled") | |
++ } | |
++ if c.Active { | |
++ t.Error("KeyEscape should stop the completer from being active") | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventKeyTab(t *testing.T) { | |
++ testCompleterHandleEventCompletion(tcell.KeyTab, t) | |
++} | |
++ | |
++func TestCompleterHandleEventKeyEnter(t *testing.T) { | |
++ testCompleterHandleEventCompletion(tcell.KeyEnter, t) | |
++} | |
++ | |
++func testCompleterHandleEventCompletion(key tcell.Key, t *testing.T) { | |
++ expectedFrom := Loc{X: 0, Y: 1} | |
++ expectedTo := Loc{X: 1, Y: 2} | |
++ | |
++ currentLocation := func() Loc { return expectedTo } | |
++ locationOffset := func(Loc) int { | |
++ return 0 | |
++ } | |
++ | |
++ var receivedFrom, receivedTo Loc | |
++ var receivedWith string | |
++ | |
++ replacer := func(from, to Loc, with string) { | |
++ receivedFrom = from | |
++ receivedTo = to | |
++ receivedWith = with | |
++ } | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, currentLocation, locationOffset, replacer, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.X = expectedFrom.X | |
++ c.Y = expectedFrom.Y | |
++ c.Active = true | |
++ c.Options = []optionprovider.Option{ | |
++ {H: "hint0", T: "text0"}, | |
++ {H: "hint1", T: "text1"}, | |
++ } | |
++ c.ActiveIndex = 1 | |
++ | |
++ handled := c.HandleEvent(key) | |
++ if !handled { | |
++ t.Error("when the completer is active, the completion should be handled") | |
++ } | |
++ if expectedFrom != receivedFrom { | |
++ t.Errorf("expected from location %v but got %v", expectedFrom, receivedFrom) | |
++ } | |
++ if expectedTo != receivedTo { | |
++ t.Errorf("expected to location %v but got %v", expectedTo, receivedTo) | |
++ } | |
++ if "text1" != receivedWith { | |
++ t.Errorf("expected to receive a replacement of 'text1', but got %v", receivedWith) | |
++ } | |
++} | |
++ | |
++func TestCompleterHandleEventKeyWhenActive(t *testing.T) { | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.Active = true | |
++ | |
++ handled := c.HandleEvent(tcell.KeyRune) | |
++ if handled { | |
++ t.Error("when the completer is active, KeyRune should have no effect") | |
++ } | |
++} | |
++ | |
++func TestCompleterGetOption(t *testing.T) { | |
++ defaultOptions := []optionprovider.Option{ | |
++ optionprovider.New("text", "hint"), | |
++ optionprovider.New("text1", "hint1"), | |
++ } | |
++ | |
++ tests := []struct { | |
++ index int | |
++ options []optionprovider.Option | |
++ expectedText string | |
++ expectedOK bool | |
++ }{ | |
++ { | |
++ index: -1, | |
++ options: defaultOptions, | |
++ expectedText: "text", // Use the first entry by default. | |
++ expectedOK: true, | |
++ }, | |
++ { | |
++ index: -1, | |
++ options: []optionprovider.Option{}, | |
++ expectedText: "", // If there are no options, don't use anything. | |
++ expectedOK: false, | |
++ }, | |
++ { | |
++ index: 0, | |
++ options: defaultOptions, | |
++ expectedText: "text", | |
++ expectedOK: true, | |
++ }, | |
++ { | |
++ index: 1, | |
++ options: defaultOptions, | |
++ expectedText: "text1", | |
++ expectedOK: true, | |
++ }, | |
++ { | |
++ index: 2, | |
++ options: defaultOptions, | |
++ expectedText: "", | |
++ expectedOK: false, | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ actualText, actualOK := getOption(test.index, test.options) | |
++ if test.expectedText != actualText { | |
++ t.Errorf("for index %v, expected '%v', but got '%v'", test.index, test.expectedText, actualText) | |
++ } | |
++ if test.expectedOK != actualOK { | |
++ t.Errorf("for index %v, expected '%v', but got '%v'", test.index, test.expectedOK, actualOK) | |
++ } | |
++ } | |
++} | |
++ | |
++func TestCompleterDisplayDoesNotWriteToConsoleWhenNotEnabled(t *testing.T) { | |
++ var setterCalled bool | |
++ setter := func(x int, y int, mainc rune, combc []rune, style tcell.Style) { | |
++ setterCalled = true | |
++ } | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, setter, optionStyleInactive, optionStyleActive, enabledFlagSetToFalse) | |
++ | |
++ c.Display() | |
++ if setterCalled { | |
++ t.Error("when the completer is not enabled, expected no content to be written to the screen") | |
++ } | |
++} | |
++ | |
++func TestCompleterDisplayDoesNotWriteToConsoleWhenInactive(t *testing.T) { | |
++ var setterCalled bool | |
++ setter := func(x int, y int, mainc rune, combc []rune, style tcell.Style) { | |
++ setterCalled = true | |
++ } | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, nil, nil, nil, setter, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ | |
++ c.Display() | |
++ if setterCalled { | |
++ t.Error("when the completer is inactive, expected no content to be written to the screen") | |
++ } | |
++} | |
++ | |
++type rs struct { | |
++ Rune rune | |
++ Style tcell.Style | |
++} | |
++ | |
++type displayMap map[Loc]rs | |
++ | |
++func (a displayMap) Eq(b displayMap) bool { | |
++ if len(a) != len(b) { | |
++ return false | |
++ } | |
++ for locA, rsA := range a { | |
++ rsB, ok := b[locA] | |
++ if !ok { | |
++ return false | |
++ } | |
++ if rsA.Rune != rsB.Rune { | |
++ return false | |
++ } | |
++ if rsA.Style != rsB.Style { | |
++ return false | |
++ } | |
++ } | |
++ return true | |
++} | |
++ | |
++func (a displayMap) String() string { | |
++ buf := bytes.NewBuffer([]byte{}) | |
++ | |
++ if len(a) == 0 { | |
++ return "" | |
++ } | |
++ | |
++ minX, maxX := a.X() | |
++ minY, maxY := a.Y() | |
++ | |
++ for y := minY; y <= maxY; y++ { | |
++ for x := minX; x <= maxX; x++ { | |
++ l := Loc{X: x, Y: y} | |
++ // Get the value of the rune. | |
++ v, ok := a[l] | |
++ if ok { | |
++ buf.WriteRune(v.Rune) | |
++ } else { | |
++ buf.WriteRune(' ') | |
++ } | |
++ } | |
++ if y < maxY { | |
++ buf.WriteRune('\n') | |
++ } | |
++ } | |
++ return buf.String() | |
++} | |
++ | |
++func (a displayMap) X() (min, max int) { | |
++ first := true | |
++ for k := range a { | |
++ if first { | |
++ min = k.X | |
++ max = k.X | |
++ first = false | |
++ } | |
++ if k.X < min { | |
++ min = k.X | |
++ } | |
++ if k.X > max { | |
++ max = k.X | |
++ } | |
++ } | |
++ return | |
++} | |
++ | |
++func (a displayMap) Y() (min, max int) { | |
++ first := true | |
++ for k := range a { | |
++ if first { | |
++ min = k.Y | |
++ max = k.Y | |
++ first = false | |
++ } | |
++ if k.Y < min { | |
++ min = k.Y | |
++ } | |
++ if k.Y > max { | |
++ max = k.Y | |
++ } | |
++ } | |
++ return | |
++} | |
++ | |
++func TestDisplayMapString(t *testing.T) { | |
++ m := make(displayMap) | |
++ | |
++ if m.String() != "" { | |
++ t.Errorf("an empty display should be an empty string, but got '%v'", m.String()) | |
++ } | |
++ | |
++ m[Loc{Y: 0, X: 0}] = rs{Rune: 'a'} | |
++ if m.String() != "a" { | |
++ t.Errorf("expected 'a', got '%v'", m.String()) | |
++ } | |
++ m[Loc{Y: 0, X: 2}] = rs{Rune: 'c'} | |
++ if m.String() != "a c" { | |
++ t.Errorf("expected 'a c', got '%v'", m.String()) | |
++ } | |
++} | |
++ | |
++func TestCompleterDisplayRendersOptionsWhenActive(t *testing.T) { | |
++ acs := optionStyleInactive | |
++ act := optionStyleActive | |
++ | |
++ tests := []struct { | |
++ name string | |
++ options []optionprovider.Option | |
++ selectedOptionIndex int | |
++ expected displayMap | |
++ }{ | |
++ { | |
++ name: "no options", | |
++ options: []optionprovider.Option{}, | |
++ selectedOptionIndex: -1, | |
++ expected: displayMap{}, | |
++ }, | |
++ { | |
++ name: "single option", | |
++ options: []optionprovider.Option{ | |
++ optionprovider.New("Text", "Hint"), | |
++ }, | |
++ selectedOptionIndex: -1, | |
++ expected: displayMap{ | |
++ Loc{Y: 1, X: 0}: rs{'T', acs}, Loc{Y: 1, X: 1}: rs{'e', acs}, Loc{Y: 1, X: 2}: rs{'x', acs}, Loc{Y: 1, X: 3}: rs{'t', acs}, Loc{Y: 1, X: 4}: rs{rune(0), acs}, | |
++ }, | |
++ }, | |
++ { | |
++ name: "multiple options", | |
++ options: []optionprovider.Option{ | |
++ optionprovider.New("Text", "Hint"), | |
++ optionprovider.New("Text2", "Hint2"), | |
++ }, | |
++ selectedOptionIndex: -1, | |
++ expected: displayMap{ | |
++ Loc{Y: 1, X: 0}: rs{'T', acs}, Loc{Y: 1, X: 1}: rs{'e', acs}, Loc{Y: 1, X: 2}: rs{'x', acs}, Loc{Y: 1, X: 3}: rs{'t', acs}, Loc{Y: 1, X: 4}: rs{0, acs}, Loc{Y: 1, X: 5}: rs{0, acs}, | |
++ Loc{Y: 2, X: 0}: rs{'T', acs}, Loc{Y: 2, X: 1}: rs{'e', acs}, Loc{Y: 2, X: 2}: rs{'x', acs}, Loc{Y: 2, X: 3}: rs{'t', acs}, Loc{Y: 2, X: 4}: rs{'2', acs}, Loc{Y: 2, X: 5}: rs{0, acs}, | |
++ }, | |
++ }, | |
++ { | |
++ name: "multiple options, last selected", | |
++ options: []optionprovider.Option{ | |
++ optionprovider.New("Text", "Hint"), | |
++ optionprovider.New("Text2", "Hint2"), | |
++ }, | |
++ selectedOptionIndex: 1, | |
++ expected: displayMap{ | |
++ Loc{Y: 1, X: 0}: rs{'T', acs}, Loc{Y: 1, X: 1}: rs{'e', acs}, Loc{Y: 1, X: 2}: rs{'x', acs}, Loc{Y: 1, X: 3}: rs{'t', acs}, Loc{Y: 1, X: 4}: rs{0, acs}, Loc{Y: 1, X: 5}: rs{0, acs}, | |
++ Loc{Y: 2, X: 0}: rs{'T', act}, Loc{Y: 2, X: 1}: rs{'e', act}, Loc{Y: 2, X: 2}: rs{'x', act}, Loc{Y: 2, X: 3}: rs{'t', act}, Loc{Y: 2, X: 4}: rs{'2', act}, Loc{Y: 2, X: 5}: rs{0, act}, | |
++ }, | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ testCompleterDisplayRendersOptionsWhenActive(test.name, test.options, test.selectedOptionIndex, test.expected, t) | |
++ } | |
++} | |
++ | |
++func testCompleterDisplayRendersOptionsWhenActive(name string, | |
++ options []optionprovider.Option, | |
++ activeIndex int, | |
++ expected displayMap, | |
++ t *testing.T) { | |
++ actual := make(displayMap) | |
++ | |
++ currentLocation := func() Loc { | |
++ return Loc{X: 0, Y: 0} | |
++ } | |
++ locationOffset := func(Loc) int { | |
++ return 0 | |
++ } | |
++ | |
++ var setterCalled bool | |
++ setter := func(x int, y int, mainc rune, combc []rune, style tcell.Style) { | |
++ setterCalled = true | |
++ actual[Loc{X: x, Y: y}] = rs{Rune: mainc, Style: style} | |
++ } | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, currentLocation, locationOffset, nil, setter, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ c.Active = true | |
++ c.ActiveIndex = activeIndex | |
++ c.Options = options | |
++ | |
++ c.Display() | |
++ if !expected.Eq(actual) { | |
++ t.Errorf("%s: expected characters '%v', got '%v'", name, expected.String(), actual.String()) | |
++ t.Errorf("%s: expected '%v', got '%v'", name, map[Loc]rs(expected), map[Loc]rs(actual)) | |
++ } | |
++} | |
++ | |
++func TestDeactivateIfOutOfBounds(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ previousLocation Loc | |
++ currentLocation Loc | |
++ currentlyActive bool | |
++ expectedActive bool | |
++ }{ | |
++ { | |
++ name: "No change keeps active", | |
++ previousLocation: Loc{X: 30, Y: 0}, | |
++ currentLocation: Loc{X: 30, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: true, | |
++ }, | |
++ { | |
++ name: "Moving forward retains active", | |
++ previousLocation: Loc{X: 30, Y: 0}, | |
++ currentLocation: Loc{X: 31, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: true, | |
++ }, | |
++ { | |
++ name: "Moving back to the start cancels", | |
++ previousLocation: Loc{X: 31, Y: 0}, | |
++ currentLocation: Loc{X: 30, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: false, | |
++ }, | |
++ { | |
++ name: "Moving before the start cancels", | |
++ previousLocation: Loc{X: 31, Y: 0}, | |
++ currentLocation: Loc{X: 25, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: false, | |
++ }, | |
++ { | |
++ name: "Moving to the line start cancels", | |
++ previousLocation: Loc{X: 31, Y: 0}, | |
++ currentLocation: Loc{X: 0, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: false, | |
++ }, | |
++ { | |
++ name: "Moving more than one character to the right cancels (could be a paste, or mouse click)", | |
++ previousLocation: Loc{X: 31, Y: 0}, | |
++ currentLocation: Loc{X: 35, Y: 0}, | |
++ currentlyActive: true, | |
++ expectedActive: false, | |
++ }, | |
++ { | |
++ name: "Switching lines cancels", | |
++ previousLocation: Loc{X: 30, Y: 0}, | |
++ currentLocation: Loc{X: 31, Y: 1}, | |
++ currentlyActive: true, | |
++ expectedActive: false, | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ currentLocation := func() Loc { | |
++ return test.currentLocation | |
++ } | |
++ c := NewCompleter(nil, nil, nil, t.Logf, nil, currentLocation, nil, nil, nil, optionStyleInactive, optionStyleActive, enabledFlagSetToTrue) | |
++ c.X = test.previousLocation.X | |
++ c.Y = test.previousLocation.Y | |
++ c.PreviousLocation = test.previousLocation | |
++ c.Active = test.currentlyActive | |
++ | |
++ c.DeactivateIfOutOfBounds() | |
++ | |
++ if c.Active != test.expectedActive { | |
++ t.Errorf("%s: expected active to now be %v, but was %v", test.name, test.expectedActive, c.Active) | |
++ } | |
++ } | |
++} | |
+--- cmd/micro/lineArray.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/lineArray.go | |
+@@ -2,6 +2,7 @@ package main | |
+ | |
+ import ( | |
+ "bufio" | |
++ "bytes" | |
+ "io" | |
+ "unicode/utf8" | |
+ | |
+@@ -115,33 +116,31 @@ func NewLineArray(size int64, reader io.Reader) *LineA | |
+ return la | |
+ } | |
+ | |
+-// Returns the String representation of the LineArray | |
+-func (la *LineArray) String() string { | |
+- str := "" | |
++// Buffer gets the lineArray as a bytes.Buffer. | |
++func (la *LineArray) Buffer(useCrlf bool) *bytes.Buffer { | |
++ buf := bytes.NewBuffer([]byte{}) | |
+ for i, l := range la.lines { | |
+- str += string(l.data) | |
++ buf.Write(l.data) | |
+ if i != len(la.lines)-1 { | |
+- str += "\n" | |
++ if useCrlf { | |
++ buf.WriteRune('\r') | |
++ } | |
++ buf.WriteRune('\n') | |
+ } | |
+ } | |
+- return str | |
++ return buf | |
+ } | |
+ | |
+ // SaveString returns the string that should be written to disk when | |
+ // the line array is saved | |
+ // It is the same as string but uses crlf or lf line endings depending | |
+ func (la *LineArray) SaveString(useCrlf bool) string { | |
+- str := "" | |
+- for i, l := range la.lines { | |
+- str += string(l.data) | |
+- if i != len(la.lines)-1 { | |
+- if useCrlf { | |
+- str += "\r" | |
+- } | |
+- str += "\n" | |
+- } | |
+- } | |
+- return str | |
++ return la.Buffer(useCrlf).String() | |
++} | |
++ | |
++// Returns the String representation of the LineArray | |
++func (la *LineArray) String() string { | |
++ return la.SaveString(false) | |
+ } | |
+ | |
+ // NewlineBelow adds a newline below the given line number | |
+--- cmd/micro/linearray_test.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/linearray_test.go | |
+@@ -0,0 +1,116 @@ | |
++package main | |
++ | |
++import ( | |
++ "strings" | |
++ "testing" | |
++) | |
++ | |
++func TestLineArraySubstr(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ input string | |
++ start, end Loc | |
++ expected string | |
++ }{ | |
++ { | |
++ name: "first line", | |
++ input: "abc\ndef", | |
++ start: Loc{Y: 0, X: 0}, | |
++ end: Loc{Y: 0, X: 3}, | |
++ expected: "abc", | |
++ }, | |
++ { | |
++ name: "second line, first char", | |
++ input: "abc\ndef", | |
++ start: Loc{Y: 1, X: 0}, | |
++ end: Loc{Y: 1, X: 1}, | |
++ expected: "d", | |
++ }, | |
++ { | |
++ name: "middle lines lines", | |
++ input: "abc\ndef\nghi", | |
++ start: Loc{Y: 0, X: 0}, | |
++ end: Loc{Y: 1, X: 2}, | |
++ expected: "abc\nde", | |
++ }, | |
++ { | |
++ name: "all lines", | |
++ input: "abc\ndef", | |
++ start: Loc{Y: 0, X: 0}, | |
++ end: Loc{Y: 1, X: 3}, | |
++ expected: "abc\ndef", | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ r := strings.NewReader(test.input) | |
++ la := NewLineArray(10, r) | |
++ actual := la.Substr(test.start, test.end) | |
++ if test.expected != actual { | |
++ t.Errorf("%s: expected '%v', got '%v'", test.name, test.expected, actual) | |
++ } | |
++ } | |
++} | |
++ | |
++func TestLineArrayDeleteLine(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ input string | |
++ line int | |
++ expected string | |
++ }{ | |
++ { | |
++ name: "remove first line", | |
++ input: "abc", | |
++ line: 0, | |
++ expected: "", | |
++ }, | |
++ { | |
++ name: "remove second line", | |
++ input: "abc\ndef", | |
++ line: 1, | |
++ expected: "abc", | |
++ }, | |
++ { | |
++ name: "remove second line of 3", | |
++ input: "abc\ndef\nhij", | |
++ line: 1, | |
++ expected: "abc\nhij", | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ r := strings.NewReader(test.input) | |
++ la := NewLineArray(10, r) | |
++ la.DeleteLine(test.line) | |
++ if la.String() != test.expected { | |
++ t.Errorf("%s: expected '%v', got '%v'", test.name, test.expected, la.String()) | |
++ } | |
++ } | |
++} | |
++ | |
++func TestLineArrayRemove(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ input string | |
++ start, end Loc | |
++ expected string | |
++ }{ | |
++ { | |
++ name: "remove first character", | |
++ input: "abc", | |
++ start: Loc{Y: 0, X: 0}, | |
++ end: Loc{Y: 0, X: 1}, | |
++ expected: "bc", | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ r := strings.NewReader(test.input) | |
++ la := NewLineArray(10, r) | |
++ la.remove(test.start, test.end) | |
++ if la.String() != test.expected { | |
++ t.Errorf("%s: expected '%v', got '%v'", test.name, test.expected, la.String()) | |
++ } | |
++ } | |
++} | |
+--- cmd/micro/loc_test.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/loc_test.go | |
+@@ -0,0 +1,33 @@ | |
++package main | |
++ | |
++import "testing" | |
++ | |
++func TestFromCharPos(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ input string | |
++ charpos int | |
++ expected Loc | |
++ }{ | |
++ { | |
++ name: "first line", | |
++ input: "abc\ndef", | |
++ charpos: 0, | |
++ expected: Loc{Y: 0, X: 0}, | |
++ }, | |
++ { | |
++ name: "secnod line", | |
++ input: "abc\ndef", | |
++ charpos: 5, | |
++ expected: Loc{Y: 1, X: 1}, | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ b := NewBufferFromString(test.input, "/dev/null") | |
++ actual := FromCharPos(test.charpos, b) | |
++ if test.expected != actual { | |
++ t.Errorf("%s: expected '%v', got '%v'", test.name, test.expected, actual) | |
++ } | |
++ } | |
++} | |
+--- cmd/micro/micro.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/micro.go | |
+@@ -12,13 +12,13 @@ import ( | |
+ | |
+ "github.com/go-errors/errors" | |
+ "github.com/mattn/go-isatty" | |
+- "github.com/mitchellh/go-homedir" | |
+- "github.com/yuin/gopher-lua" | |
++ homedir "github.com/mitchellh/go-homedir" | |
++ lua "github.com/yuin/gopher-lua" | |
+ "github.com/zyedidia/clipboard" | |
+ "github.com/zyedidia/micro/cmd/micro/terminfo" | |
+ "github.com/zyedidia/tcell" | |
+ "github.com/zyedidia/tcell/encoding" | |
+- "layeh.com/gopher-luar" | |
++ luar "layeh.com/gopher-luar" | |
+ ) | |
+ | |
+ const ( | |
+--- cmd/micro/optionprovider/generic.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/optionprovider/generic.go | |
+@@ -0,0 +1,140 @@ | |
++package optionprovider | |
++ | |
++import ( | |
++ "regexp" | |
++ "sort" | |
++ "strings" | |
++ "unicode" | |
++) | |
++ | |
++var word = regexp.MustCompile(`[a-zA-Z]+\d*`) | |
++var stopList = map[string]interface{}{ | |
++ "for": false, | |
++ "if": false, | |
++ "then": false, | |
++ "let": false, | |
++ "const": false, | |
++ "when": false, | |
++ "while": false, | |
++ "the": false, | |
++ "to": false, | |
++ "and": false, | |
++} | |
++var maxSuggestions = 10 | |
++ | |
++// Generic is an OptionProvider which provides options to the autocompletion system based on the | |
++// words in the current buffer. It returns a delta of the start index if the start position needs | |
++// to change. | |
++func Generic(logger func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []Option, startDelta int, err error) { | |
++ s := string(buffer) | |
++ | |
++ // Find the best matches. | |
++ prefix := prefix(lastCharacters(s, currentOffset, 10)) | |
++ startDelta = currentOffset - startOffset - len(prefix) | |
++ | |
++ // Calculate common words. | |
++ words := word.FindAllString(s, -1) | |
++ | |
++ // Count words, but remove common syntax and the prefix from the autocomplete list. | |
++ counts := wordCounts(words, map[string]interface{}{prefix: false}) | |
++ orderedWords := orderByFrequencyDesc(counts) | |
++ | |
++ if len(prefix) > 0 { | |
++ // We have a prefix, so write out prefix matches. | |
++ for i := 0; i < len(orderedWords) && len(options) < maxSuggestions; i++ { | |
++ if strings.HasPrefix(orderedWords[i], prefix) { | |
++ options = append(options, New(orderedWords[i], "")) | |
++ } | |
++ } | |
++ return | |
++ } | |
++ | |
++ // Write out all matches. | |
++ for i := 0; i < len(orderedWords) && len(options) < maxSuggestions; i++ { | |
++ options = append(options, New(orderedWords[i], "")) | |
++ } | |
++ | |
++ return | |
++} | |
++ | |
++func lastCharacters(s string, end, charactersBefore int) string { | |
++ if end == 0 { | |
++ return "" | |
++ } | |
++ start := end - charactersBefore | |
++ if start < 0 { | |
++ return s[0:end] | |
++ } | |
++ return s[start:end] | |
++} | |
++ | |
++func prefix(s string) string { | |
++ text := []*unicode.RangeTable{unicode.Letter, unicode.Digit} | |
++ last := 0 | |
++ for i, r := range s { | |
++ if !unicode.IsOneOf(text, r) { | |
++ last = i + 1 | |
++ } | |
++ } | |
++ return s[last:] | |
++} | |
++ | |
++func wordCounts(words []string, additionalStopList map[string]interface{}) (counts map[string]int) { | |
++ counts = make(map[string]int) | |
++ for _, w := range words { | |
++ if !inAnyStopList(w, stopList, additionalStopList) { | |
++ counts[w]++ | |
++ } | |
++ } | |
++ return | |
++} | |
++ | |
++func inAnyStopList(w string, lists ...map[string]interface{}) bool { | |
++ for _, l := range lists { | |
++ if _, inStopList := l[w]; inStopList { | |
++ return true | |
++ } | |
++ } | |
++ return false | |
++} | |
++ | |
++func orderByFrequencyDesc(counts map[string]int) []string { | |
++ cs := NewCountSorter(counts) | |
++ sort.Sort(cs) | |
++ return cs.Keys | |
++} | |
++ | |
++// CountSorter provides a way of sorting map[string]int values (most frequent first). | |
++type CountSorter struct { | |
++ Data map[string]int | |
++ Keys []string | |
++} | |
++ | |
++// NewCountSorter supports the sort.Data interface. The Keys field is populated, and the | |
++// struct can then be sorted. The Keys field then contains the keys, ordered by the count. | |
++func NewCountSorter(data map[string]int) *CountSorter { | |
++ cs := new(CountSorter) | |
++ cs.Data = make(map[string]int) | |
++ cs.Keys = make([]string, len(data)) | |
++ var i int | |
++ for k, v := range data { | |
++ cs.Keys[i] = k | |
++ cs.Data[k] = v | |
++ i++ | |
++ } | |
++ return cs | |
++} | |
++ | |
++func (cs *CountSorter) Len() int { return len(cs.Data) } | |
++func (cs *CountSorter) Less(i, j int) bool { | |
++ countI := cs.Data[cs.Keys[i]] | |
++ countJ := cs.Data[cs.Keys[j]] | |
++ if countI == countJ { | |
++ // Alphabetic sorting. | |
++ // Go's sorting rules mean that capital letters come before lowercase. | |
++ return cs.Keys[i] < cs.Keys[j] | |
++ } | |
++ // Sort by largest count first. | |
++ return countI > countJ | |
++} | |
++func (cs *CountSorter) Swap(i, j int) { cs.Keys[i], cs.Keys[j] = cs.Keys[j], cs.Keys[i] } | |
+--- cmd/micro/optionprovider/generic_test.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/optionprovider/generic_test.go | |
+@@ -0,0 +1,185 @@ | |
++package optionprovider | |
++ | |
++import ( | |
++ "reflect" | |
++ "testing" | |
++) | |
++ | |
++func TestGeneric(t *testing.T) { | |
++ tests := []struct { | |
++ name string | |
++ text string | |
++ from string | |
++ to string | |
++ expected []Option | |
++ expectedDelta int | |
++ }{ | |
++ { | |
++ name: "words are sorted alphabetically if they appear the same number of times", | |
++ text: "fish dog cat ", | |
++ expected: []Option{New("cat", ""), New("dog", ""), New("fish", "")}, | |
++ }, | |
++ { | |
++ name: "capitals preceed lowercase", | |
++ text: "A a B b C c", | |
++ expected: []Option{ | |
++ New("A", ""), New("B", ""), New("C", ""), | |
++ New("a", ""), New("b", ""), New("c", ""), | |
++ }, | |
++ }, | |
++ { | |
++ name: "bare numbers are not included", | |
++ text: "1 2 3 1.23 a", | |
++ expected: []Option{New("a", "")}, | |
++ }, | |
++ { | |
++ name: "words that include a number are included", | |
++ text: "a1", | |
++ expected: []Option{New("a1", "")}, | |
++ }, | |
++ { | |
++ name: "words are ordered by their frequency descending (most common words are first in the list)", | |
++ text: "ccc ccc ccc bb bb a", | |
++ expected: []Option{New("ccc", ""), New("bb", ""), New("a", "")}, | |
++ }, | |
++ { | |
++ name: "prefix matches preceed other matches", | |
++ text: "common common common something", | |
++ from: "common common common ", | |
++ to: "common common common s", | |
++ expected: []Option{New("something", "")}, | |
++ }, | |
++ { | |
++ name: "the autocomplete looks for the previous word boundary to see if you're partway through a word to limit results", | |
++ text: "A AB ABC ABCD", | |
++ from: "A AB ", | |
++ to: "A AB AB", | |
++ expected: []Option{New("ABC", ""), New("ABCD", "")}, | |
++ }, | |
++ { | |
++ name: "realistic example", | |
++ text: `fmt.Println("hello") fmt.P`, | |
++ from: `fmt.Println("hello") fmt.`, | |
++ to: `fmt.Println("hello") fmt.P`, | |
++ expected: []Option{New("Println", "")}, | |
++ }, | |
++ { | |
++ name: "go back further than the start position", | |
++ text: `testing`, | |
++ from: `test`, | |
++ to: `testi`, | |
++ expected: []Option{New("testing", "")}, | |
++ expectedDelta: -4, | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ options, delta, err := Generic(t.Logf, []byte(test.text), len(test.from), len(test.to)) | |
++ if err != nil { | |
++ t.Fatalf("%s: generic complete failed with error %v", test.name, err) | |
++ continue | |
++ } | |
++ if !reflect.DeepEqual(options, test.expected) { | |
++ t.Errorf("%s: expected '%v', got '%v'", test.name, test.expected, options) | |
++ } | |
++ if delta != test.expectedDelta { | |
++ t.Errorf("%s: expected delta %v, got %v", test.name, test.expectedDelta, delta) | |
++ } | |
++ } | |
++} | |
++ | |
++func TestLastCharacters(t *testing.T) { | |
++ tests := []struct { | |
++ input string | |
++ end int | |
++ length int | |
++ expected string | |
++ }{ | |
++ { | |
++ input: "ABC", | |
++ end: 3, | |
++ length: 1, | |
++ expected: "C", | |
++ }, | |
++ { | |
++ input: "ABC", | |
++ end: 3, | |
++ length: 2, | |
++ expected: "BC", | |
++ }, | |
++ { | |
++ input: "ABC", | |
++ end: 3, | |
++ length: 3, | |
++ expected: "ABC", | |
++ }, | |
++ { | |
++ input: "ABC", | |
++ end: 3, | |
++ length: 4, | |
++ expected: "ABC", | |
++ }, | |
++ { | |
++ input: "ABCD", | |
++ end: 3, | |
++ length: 3, | |
++ expected: "ABC", | |
++ }, | |
++ { | |
++ input: "ABCD", | |
++ end: 2, | |
++ length: 6, | |
++ expected: "AB", | |
++ }, | |
++ { | |
++ input: "ABCD", | |
++ end: 0, | |
++ length: 6, | |
++ expected: "", | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ actual := lastCharacters(test.input, test.end, test.length) | |
++ if actual != test.expected { | |
++ t.Errorf("for input '%v', the %v characters before index %v, expected: '%v', but got '%v'", | |
++ test.input, test.length, test.end, test.expected, actual) | |
++ } | |
++ } | |
++} | |
++ | |
++func TestPrefix(t *testing.T) { | |
++ tests := []struct { | |
++ input string | |
++ expected string | |
++ }{ | |
++ { | |
++ input: "fmt.Prin", | |
++ expected: "Prin", | |
++ }, | |
++ { | |
++ input: "Test t", | |
++ expected: "t", | |
++ }, | |
++ { | |
++ input: "1234 123", | |
++ expected: "123", | |
++ }, | |
++ { | |
++ input: "word-connection", | |
++ expected: "connection", | |
++ }, | |
++ { | |
++ input: `"quote`, | |
++ expected: "quote", | |
++ }, | |
++ } | |
++ | |
++ for _, test := range tests { | |
++ actual := prefix(test.input) | |
++ if actual != test.expected { | |
++ t.Errorf("for input '%v', expected: '%v', but got '%v'", | |
++ test.input, test.expected, actual) | |
++ } | |
++ } | |
++} | |
+--- cmd/micro/optionprovider/gocode.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/optionprovider/gocode.go | |
+@@ -0,0 +1,98 @@ | |
++package optionprovider | |
++ | |
++import ( | |
++ "encoding/json" | |
++ "fmt" | |
++ "os/exec" | |
++ "strconv" | |
++) | |
++ | |
++// GoCode is an OptionProvider which provides options to the autocompletion system. | |
++func GoCode(logger func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []Option, startOffsetDelta int, err error) { | |
++ logger("autocompleter.GoCode: received offset %v - '...%v'->'%v'", | |
++ currentOffset, | |
++ string(previousX(buffer, startOffset, 10)), | |
++ string(buffer[startOffset:currentOffset])) | |
++ cmd := exec.Command("gocode", "-f=json", "autocomplete", strconv.Itoa(currentOffset)) | |
++ stdin, err := cmd.StdinPipe() | |
++ if err != nil { | |
++ return | |
++ } | |
++ _, err = stdin.Write(buffer) | |
++ if err != nil { | |
++ return | |
++ } | |
++ err = stdin.Close() | |
++ if err != nil { | |
++ return | |
++ } | |
++ stdoutStderr, err := cmd.CombinedOutput() | |
++ if err != nil { | |
++ if execErr, isExecError := err.(*exec.Error); isExecError { | |
++ if execErr.Err.Error() == exec.ErrNotFound.Error() { | |
++ logger("autocompleter.GoCode: failed to run because GoCode is not on the path, defaulting to Generic") | |
++ return Generic(logger, buffer, startOffset, currentOffset) | |
++ } | |
++ } | |
++ return | |
++ } | |
++ | |
++ // Unmarshal the JSON, it's an awkward format (mixed array) | |
++ // [1, [ { "class": "", "name": "", "type": "" } ]] | |
++ results := []interface{}{} | |
++ err = json.Unmarshal(stdoutStderr, &results) | |
++ if err != nil { | |
++ err = fmt.Errorf("gocode: failed to unmarshal output '%v': %v", string(stdoutStderr), err) | |
++ } | |
++ | |
++ if len(results) > 0 { | |
++ // The number represents how far back the text should go. | |
++ if startOffsetDeltaFloat, isFloat := results[0].(float64); isFloat { | |
++ startOffsetDelta = currentOffset - startOffset - int(startOffsetDeltaFloat) | |
++ } | |
++ | |
++ if firstElement, isArray := results[1].([]interface{}); isArray { | |
++ results = firstElement | |
++ } | |
++ } | |
++ | |
++ // Convert the array of maps into something useful. | |
++ for _, r := range results { | |
++ m, mok := r.(map[string]interface{}) | |
++ if mok { | |
++ options = append(options, mapToOption(m)) | |
++ } | |
++ // Return 10 results at most. | |
++ if len(options) > 10 { | |
++ break | |
++ } | |
++ } | |
++ | |
++ return | |
++} | |
++ | |
++func previousX(b []byte, index, x int) []byte { | |
++ from := 0 | |
++ if index-x > 0 { | |
++ from = index - x | |
++ } | |
++ return b[from:index] | |
++} | |
++ | |
++func mapToOption(m map[string]interface{}) Option { | |
++ // Available values are "class", "name", "type" and "package" | |
++ o := Option{} | |
++ if nv, ok := m["name"]; ok { | |
++ if n, nok := nv.(string); nok { | |
++ // text | |
++ o.T = n | |
++ } | |
++ } | |
++ if tv, ok := m["type"]; ok { | |
++ if t, tok := tv.(string); tok { | |
++ // hint | |
++ o.H = t | |
++ } | |
++ } | |
++ return o | |
++} | |
+--- cmd/micro/optionprovider/noop.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/optionprovider/noop.go | |
+@@ -0,0 +1,8 @@ | |
++package optionprovider | |
++ | |
++var noopOptions = []Option{} | |
++ | |
++// Noop is an option provider that does nothing. | |
++func Noop(l func(s string, values ...interface{}), buffer []byte, startOffset, currentOffset int) (options []Option, startOffsetDelta int, err error) { | |
++ return noopOptions, 0, nil | |
++} | |
+--- cmd/micro/optionprovider/option.go.orig 2019-06-30 06:46:32 UTC | |
++++ cmd/micro/optionprovider/option.go | |
+@@ -0,0 +1,21 @@ | |
++package optionprovider | |
++ | |
++// Option describes a multiple choice selection. | |
++type Option struct { | |
++ T, H string | |
++} | |
++ | |
++// New creates a new option value. | |
++func New(text, hint string) Option { | |
++ return Option{T: text, H: hint} | |
++} | |
++ | |
++// Text is the string that will be inserted. | |
++func (o Option) Text() string { | |
++ return o.T | |
++} | |
++ | |
++// Hint describes the text, e.g. if it's a method. | |
++func (o Option) Hint() string { | |
++ return o.H | |
++} | |
+--- cmd/micro/runtime.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/runtime.go | |
+@@ -1012,7 +1012,7 @@ func runtimeHelpOptionsMd() (*asset, error) { | |
+ return a, nil | |
+ } | |
+ | |
+-var _runtimeHelpPluginsMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x3a\x6b\x8f\xdc\x36\x92\xdf\xf5\x2b\xea\x3a\x38\xb8\x3b\x68\x6b\xb2\xb8\x6f\x03\x78\x01\x3b\x7e\xc4\x7b\x8e\x6d\x78\x9c\x04\x8b\x20\x80\xd8\x52\xa9\x9b\x19\x8a\xd4\x92\xd4\xf4\xf4\x19\xd9\xdf\x7e\xa8\x2a\x92\x52\xf7\x4c\xb2\xb9\x3b\x5c\x80\x78\xd4\x12\x59\x2f\xd6\xbb\xf8\x15\x7c\x34\xd3\x5e\xdb\x50\x55\xdf\xeb\xd6\x3b\x08\xd3\x38\x3a\x1f\x03\xb4\x1e\x55\xd4\x76\x0f\xa3\x2c\x80\xa3\x8e\x07\x50\x10\xf4\x30\x1a\x84\x77\x93\x82\x70\x0a\x11\x87\x1a\x5e\xdd\xa1\x3f\xa5\x75\x70\x50\x01\x54\x35\x28\x6d\x21\xb4\x5e\x8f\x11\x8e\x07\xdd\x1e\x40\x07\xf0\x93\x05\x15\x21\x44\xe5\xe3\x34\xa6\xf7\xe1\xe0\x26\xd3\xc1\x0e\x61\x34\xaa\xc5\x0e\xb4\x85\xaa\xf9\xe7\x55\xdd\x3a\xdb\xeb\xfd\xd5\x40\x64\x5d\x25\x22\xd2\xdf\xf7\x6a\xc0\xc5\x63\x6d\x26\xd5\xd4\x55\xf5\xf9\x80\x1e\x41\xd1\xff\x60\xa7\x61\x87\x1e\x5c\x0f\xad\x32\x66\xa7\xda\x5b\xe8\x27\xdb\x46\xed\x88\x15\xc6\x7c\x72\x13\xb4\xca\x0a\xa7\x48\x78\x4f\x6e\xf2\x99\x8f\xe8\x2a\xa2\xb7\x75\x1d\x12\xd1\x51\x0f\x18\xc0\xc5\x03\x7a\x88\x07\x65\x33\x17\x35\x7c\x3e\x20\x58\x35\x90\xa8\x42\x7b\xc0\x01\x89\xd5\xc6\xd9\xe7\x8c\x6c\x7d\xa7\xf1\xb8\x69\x6a\x78\xed\x7c\x85\xf7\x8a\x85\xa7\x0a\x2d\xe7\xc2\x41\x16\x24\xa1\x82\x78\x40\x98\x02\x7a\x08\xea\x0e\x03\xff\xdc\x4d\x7d\x8f\x1e\x8e\x49\x5e\xd7\x55\xd5\x34\x8d\x99\x54\x55\x80\x39\x7b\xa3\xee\x50\x50\x56\x00\x00\x75\x5d\xf3\x5f\x8f\x71\xf2\x16\x7a\x65\x02\x56\x68\x3b\xda\xc9\xf2\x82\x86\x16\x37\x70\xa7\xbc\x56\x3b\xc3\xb4\x2b\xf0\xd8\xa3\x47\xdb\x22\x44\xc7\xa8\x69\x11\x3f\x28\x41\xa4\x03\xec\x90\x38\xc6\x7b\x6c\xa7\x88\x1d\x38\x5b\x57\x9f\x0f\x3a\x30\x00\x33\xb8\x10\x41\x99\xa3\x3a\x09\xe9\xed\xe4\x3d\xda\xc8\x70\xb6\x17\xe2\xdf\x63\x14\xdd\x6a\xbe\x9d\xfc\x8f\x1a\x8f\xeb\x4d\x03\x2a\x54\x47\x34\xa6\xae\xaa\xe7\xc6\x80\xba\x53\xda\x30\x79\x2a\x9d\x20\x1d\xb2\xd1\x21\x8a\xbe\x10\x8a\x5b\x3c\xed\xb4\xed\xb4\xdd\x07\x08\x98\xe4\xd1\xf3\xa7\x03\x9a\xb1\xae\xaa\xd7\xce\x17\x65\x08\xc4\xd9\xe0\xa6\x50\x40\x6e\x99\x20\x56\x1e\x13\x1c\xec\xf5\x1d\x0a\x60\xbc\x23\xca\xb5\xed\xdd\xa3\x12\xff\x9e\x80\x7c\xf4\x18\xc2\x5a\xb8\xe3\xf5\x22\x7e\xe3\x5a\x65\xe0\x7e\x0b\x27\x78\x26\xef\xaf\x3f\xba\xa0\x59\x2f\x36\xd5\x1f\x9f\x4c\xc0\x85\xc2\x26\x23\x61\xca\xd2\x0e\x05\x3b\xe7\x0c\x92\x26\x8e\xd8\xea\xfe\x44\xc7\x71\x3c\x60\xd2\xd0\x74\x66\xb2\xb1\xda\x21\x78\x24\x6a\x48\x60\xe9\x4c\xdb\xc9\x07\xe7\xc1\x79\xb0\x2e\x82\xea\x63\xda\x37\x1f\x71\xeb\x48\x5b\x23\xd6\x55\xf5\xde\x45\xd2\x49\x32\x04\x26\x6d\x96\xa3\x6b\xdb\xc9\x3f\xdc\x4e\x6e\x60\x87\x68\x0b\x90\xae\x86\xb7\x3d\xcb\xf8\xa8\x6c\xac\xd4\x6c\x97\x3b\xec\x9d\xc7\x0b\xdc\x59\xb1\xb6\x64\x05\xd0\x8c\x1e\x93\x3d\x91\x2d\xbd\xa5\x93\x21\x02\x55\xe0\x7d\x55\x16\x85\xc8\x06\xbb\x24\x13\x8d\xa1\x88\x24\xf1\xb9\xc0\x32\x7b\x9e\xa2\xc5\xcc\x46\x45\x6b\xcc\xa4\xc4\xf6\x33\xfd\x81\x74\xd1\x8a\x03\x98\x02\xf6\x93\x99\x39\x88\x0e\x6e\xad\x3b\x82\xda\xb9\x69\xe1\xef\x58\xae\x76\x66\xaa\x6a\x9c\x25\x05\xff\x30\x62\xf6\x0b\xf3\x62\x02\x86\x1d\x91\x6b\xc9\x0d\x90\x0b\xc3\xa3\x1c\xa2\x0e\xe0\x46\x24\xb6\x94\xed\x98\x03\x8b\xc7\x2a\x7f\x1a\x55\x08\x6c\x06\xe4\x8a\xc4\x00\x13\x7d\xbd\xf3\x10\x30\xb2\x17\x17\x55\x74\xa3\xe8\xd3\x4e\x05\xb6\x59\x16\x5e\xaf\x0d\xc6\xd3\x88\x5b\xde\x90\x7d\x14\x09\x92\x36\xba\xbe\x87\x26\xaa\x5d\x88\x2e\x8c\xaa\xc5\xd0\x80\xb3\xe6\xc4\x6b\xdf\x38\xa0\xcd\x2c\x64\x86\x75\x22\x03\xaa\x84\xd8\xba\xaa\x9e\x3e\x7d\xfa\x7b\x5e\x79\xd6\x6d\xe2\x29\xbb\x9f\x20\x3a\xc6\x8b\x8b\xcd\x47\xc7\x6a\xa3\x2d\x38\xdf\xd1\xf1\x38\x50\x6d\x8b\x41\x5c\x8b\xb6\x96\x9d\xa2\xbf\x65\xd3\x77\x3d\x70\xc4\xa8\xe1\x3b\x42\xcb\xee\x8c\x1c\x05\xac\x69\x31\xb1\x09\x41\xef\xad\x8a\x93\xc7\x40\x4c\x54\x0b\x4a\x3c\x26\xbb\x9f\x02\xb1\xfe\xc6\x3d\x09\x69\x0b\x47\xba\xcd\x75\x55\x7d\x0d\xcd\x87\x9b\xe6\x7a\x76\x98\x72\x80\xb4\x4d\xe8\xf9\x70\x23\x04\xf0\x99\x8a\xdb\x33\x27\xf2\xee\x22\x4d\x4b\x84\xc8\x29\x91\xec\x01\x82\x1a\x10\x54\x10\x64\x6f\x3e\x7c\xb8\x29\xa0\xb7\x10\x1c\x34\x9d\xf2\x47\x6d\x9b\x2d\x34\x47\x6d\x3b\x77\x0c\xf4\x68\xb4\x9d\xee\xe9\xa1\xf7\x88\xbb\xd0\x35\x75\x5d\x6f\x98\x38\x89\x9a\x2f\xb5\x6f\xae\xa1\x75\x36\x2a\x0a\xdd\x44\xd7\xa8\xe2\x21\x9b\xbe\x10\x28\x4b\x27\xaf\x58\x3d\xf9\x20\x19\x04\x1d\x76\x73\x9d\xe5\xe6\x7a\x50\xc6\xf0\x36\xfa\xb0\xe0\x49\x93\x9c\x50\xb0\x4e\xfe\xb3\xda\x35\xd7\xe9\x44\x3a\xbc\xcf\xbe\x37\x7b\x7e\xde\x9b\x5c\x35\x3f\x13\x70\xde\x3b\x60\x08\x68\xf7\x48\x14\x1b\x8c\x81\x0f\x3b\xa0\xed\x80\xbe\xa8\x3d\x86\x4c\x36\xc7\x43\x72\xe2\x12\xb0\x47\xef\x86\x31\x0a\xcd\xef\xf1\xf8\x82\xc3\xe3\x3a\xe2\x7d\xdc\x0a\xb7\x21\x7a\x6d\xf7\x1b\xf8\x5a\x3e\x91\x44\x78\x67\x48\xc6\x95\x02\x6a\xef\xdd\x00\x4a\x4e\xbe\x62\x97\xac\x3a\x52\x29\xc9\x77\x44\x21\x08\xde\x39\xa2\xd7\xde\x0d\xaf\xb5\xc1\xf5\xff\x05\x55\x01\xfb\x06\xe3\x3b\x54\x14\xbd\x7e\x3a\xe8\x88\x6c\x69\xeb\x0d\xbb\xf8\xe6\x3a\xb9\x35\x39\x47\x23\xcb\x48\xef\xd2\xba\x2c\xea\x02\x55\x88\x61\xb8\x6f\xc3\x4f\xce\x77\xdf\x1e\x94\x5f\x87\xe8\x0b\x99\x17\x70\x1f\xf1\x91\xb2\x52\xec\xe7\xc9\xd1\xf9\x8e\x00\xb7\x07\xe5\x55\x1b\xd1\x3f\x61\xe0\x9f\x26\x8b\x37\xd1\xaf\x3d\xa9\x37\x6e\xd2\x9e\x05\x5c\x95\xc1\x24\x4d\xa4\xc7\x42\x29\x6f\x62\x38\xef\x5c\xbb\xe6\x20\xa9\x6d\xdc\xc0\x3b\xd7\x9e\x81\x20\xf1\xd1\x92\x86\x80\x4d\xad\x28\xcd\x4f\x62\xef\x2f\xb5\xc7\x36\x3a\x7f\x5a\x3f\x8a\xdd\x3b\x47\x5e\x9d\x4f\xc8\x92\x9d\xcd\xa1\x8f\x95\x32\x79\x0d\x62\xad\xcb\x90\x18\xfc\xdf\x9c\xb6\x1f\x55\x3c\x84\x75\xa7\x7d\x5d\xd7\x45\x70\x05\x49\xeb\x86\x9d\xb6\x18\x60\x98\x4c\xd4\xe4\x30\x33\x04\x2d\x0a\x4b\x39\x9e\x31\x67\x87\x5c\xa8\xa5\xa4\xf5\x4c\x6f\x96\x54\x1b\x03\xbb\x49\x0e\xc1\xa8\x10\x01\x0d\x0e\x44\xac\xeb\x19\xd2\x96\x40\xc6\xd3\xa8\x29\x60\x9c\x8a\x69\x3f\x09\x17\x2c\xbc\xc1\xf8\x81\xbd\xfd\x9a\x39\x7f\x88\x88\x73\x04\x65\xa6\xa2\x3f\x1e\xff\x31\x21\xa7\x54\x12\x26\x18\xcc\xf3\xae\x7b\x08\x66\x9b\x36\x6a\x1b\xd1\xf7\xaa\xc5\x2f\xbf\x11\xe4\x40\xd6\x3b\x9f\xaf\x40\x11\x3b\x12\x5f\x97\x3e\xc8\xe6\x75\xb3\xd8\xde\xc0\x80\x8a\x83\xc1\x49\x3c\xae\xb6\xf0\xc6\x89\x4b\xbb\x29\x9c\x08\xc4\x8c\x7d\x66\xe9\x71\xc4\x39\x77\xa5\xc5\x12\x1b\x89\x84\xa3\x36\x86\x36\xf0\xb7\xb4\x72\x6f\xdc\x8e\xa4\xb9\x85\xc9\x1a\x0a\x2c\x3a\xa6\xc8\x21\x51\x93\xe2\x9d\x2c\xad\x33\x45\xef\xe8\xcb\x1f\x90\xb5\x95\xd0\xfd\xf5\x8f\x1c\xe6\x7f\x9f\x44\x3e\xcd\x72\x12\x8c\x4f\x5c\xeb\xbc\x56\x7c\x07\x23\x7e\xa1\x6d\xf7\x9f\x78\x5a\xdf\xe2\x69\x5b\xd2\x98\x22\x06\xca\x7f\x03\x34\xb7\x78\x6a\x88\xf9\x46\x16\x34\xbc\xf3\x7b\x75\x8b\xdf\xba\x61\x50\xb6\xe3\x93\xdc\xce\x25\x48\x26\x38\x65\x3a\x1c\x0a\xeb\xba\xfe\xb6\xfc\x24\xd8\x6c\xfe\xc5\xa5\xb5\x02\x29\xe5\xed\x04\x2f\x67\x32\x2c\x5e\x62\x02\x9a\x8c\xa0\x91\xf4\x20\x27\x59\x35\xfc\x10\x10\xbe\x21\x80\x94\x43\x2c\xb1\x46\xc7\xd5\xc0\x7b\x37\xe3\xae\x97\xd4\xa7\x77\xeb\x0b\xd2\x37\xcd\xf5\x39\x79\xcd\xbc\x96\x25\x41\x29\xa4\x90\xba\x10\x83\xc8\x65\xae\x39\xce\x2d\x63\x59\xb1\xf0\xc2\xef\x94\xed\x4c\x11\x61\x3b\x74\x4b\x93\x9a\xec\xf2\x70\x93\x74\x78\xdb\xab\x7b\x6c\x97\x72\x2f\xd2\x56\x7e\x1f\xe0\xe7\x5f\xb2\x63\x59\xe7\xf7\xe8\xbd\xf3\x04\x94\xe4\x05\x0a\xd6\xe1\x80\xc6\x6c\xce\x45\x7e\x66\x4e\xca\xef\x27\xf2\x10\xa1\x86\x4f\x4b\x0e\x64\xc3\x93\x00\x6e\x8a\xe3\x14\x39\xd1\x52\x30\xba\x10\x34\x25\x2e\x8c\xa8\xce\x8e\xfc\x86\xb0\x3c\xc2\xdd\x23\x84\x7d\xa2\x32\x1e\x98\xac\x8c\x24\xa5\x9e\x53\xc0\x70\xc6\x72\x43\x54\x4e\xb6\x4b\xb5\xc1\xc1\xb9\x8e\x9d\x1b\x17\x32\x9d\xc3\x00\xc1\x0d\xe4\xc0\x3c\x27\x5c\xa4\x0f\x9c\xa0\x67\x8e\x60\xad\x6b\xac\xe1\x1f\x13\xfb\xf1\xf2\x7a\xc3\x55\x37\x6b\x50\xd6\x04\xff\xbf\xe6\xfc\x85\x6a\x6f\xf7\xde\x4d\xb6\x63\x19\x5c\x1c\xed\x23\xcc\x66\xeb\xdc\x95\x8d\x05\xd6\x5b\xf2\x68\x64\x76\x77\x78\x09\x6c\x0b\x47\xa5\x23\x47\xde\x2d\x69\xf9\x07\x21\x8d\x7e\xff\x49\x21\x13\xbf\xbb\x13\xb4\xc6\xb1\xb0\x24\x89\x23\x7a\x72\x72\xb9\xe0\x5d\x5c\xb3\x50\x62\x4e\x5c\x7a\x35\x84\xbf\xe1\xc4\xd3\x4f\xb8\x25\x81\x70\x02\x25\x26\xbb\x63\x71\x4e\x21\x17\x41\x12\x58\xbc\xe3\x3c\x1b\xef\x75\x64\xeb\x1c\xbd\x54\xc3\x9c\xc5\xa1\x1f\xb4\x55\x46\x92\x1a\x3d\x0c\xd8\x69\x15\x91\x92\x5d\xcc\xc5\x03\x15\xd9\x44\xe6\x96\xc2\x9a\x3b\xa6\x58\x5b\x72\xb9\xe8\xe6\xce\x42\x3a\xaa\x14\x8b\x12\x66\x21\xbc\x48\x6b\x41\xfd\xf9\x39\x87\xa8\x6c\xa7\x7c\x97\xa0\x14\x3f\xcf\x75\xaf\x94\x84\x35\xcc\x35\xac\xfe\x1d\xa0\xac\x8c\x0b\xc9\x65\x0c\x01\x06\x75\xe2\xd4\x68\x87\x07\x75\xc7\xa2\xb2\xce\x0f\xec\xab\x77\xd8\x2a\xae\x51\x75\x50\x31\x9e\x1a\xc1\xbc\xac\xeb\x8b\x7a\x7c\x46\x3f\xbc\x1a\x26\xa3\xa2\xf3\x7f\x4e\x35\xb6\x73\x91\x99\x0d\x92\x55\xa4\xb9\x86\x1b\x29\x1c\x88\x96\xc7\x54\xaf\x01\xbc\x6f\x71\x8c\xe7\x5a\x21\x7d\x26\xf2\x21\x49\x89\xb3\x9f\x0b\xa3\xa1\xa0\x47\x4a\x57\x0e\x16\x13\xa9\xdc\x43\xe2\x66\x51\x26\xa6\x01\x6d\x89\x44\xbd\xcc\xf1\x16\xd5\xb0\xe2\xfa\xf9\xa2\xc9\x95\x4f\x64\x51\xe9\x2e\xcf\x9a\x90\xb0\xa2\x49\x4b\xed\x51\x7d\x28\x30\x46\xef\xee\x74\x47\xca\x2a\x26\xdf\x6b\x1f\xc4\xce\x39\x58\x67\x4f\x51\x12\xbe\x44\x1b\xe1\x58\xeb\xa2\xf2\x80\xc3\x18\x4f\x8f\xa8\x03\x1f\xdb\x86\xf9\x5e\x76\x3e\xe8\x4b\xe2\x49\x19\x4d\x3b\x83\xe0\x4b\x5d\x53\xa9\xa9\x59\x8b\xdc\x88\x5e\xfa\xa7\x52\x3b\x06\x58\x73\xd1\xb6\x05\x29\xe8\xb6\xd0\x79\xb5\x77\xb6\x37\x27\xce\xe8\xa8\x66\xde\x85\x6e\x0b\xa9\xa2\xdb\x48\x98\x24\xe2\x9b\xa4\x36\x37\x19\x49\x03\xeb\x80\x08\x3b\x34\xee\xb8\x29\x6d\xa0\xe8\xa0\x43\x39\x3b\x24\x96\x16\xa7\xcb\x45\x01\x93\x41\x14\x17\x62\x45\x2f\x1f\x40\xbf\x86\x17\x0f\x3b\x4b\x09\xe0\x03\xdd\x38\x03\x98\x3a\x0a\x70\x87\x3e\x48\xff\x8d\x30\x8b\xa3\x12\xc3\x0b\xd9\x59\x25\xdc\x8e\x4a\x93\x8f\x2e\xac\x8d\x6b\x29\xf3\xdf\x52\xb6\x93\xab\xa8\x0d\xd9\xe2\x65\x5c\xce\x25\x08\xb9\x73\x9d\xbb\x7c\x6a\x8e\x86\x5c\x48\x70\xab\x2b\xa7\xb0\x9f\xd0\x38\x45\x6c\xad\x3f\xe1\x86\x1e\xa5\xcf\x4a\x26\x20\x45\xd2\x8b\x53\xc4\x0f\x7d\x1f\x30\xfe\x21\x19\x78\xaf\x5a\xaa\x7b\x8d\xbe\xc5\x05\xe9\xc5\xd0\xc8\x7e\x58\xd4\xad\x9b\x28\x76\xed\x4e\x94\x8d\x68\x1b\x22\xaa\x8e\xa8\xa4\x82\x27\xa4\xfa\x62\x77\x33\xaa\xa3\x25\x27\xf0\x7e\x99\x15\xb4\x43\xf7\x7c\x99\x18\x6c\xc1\xd9\x9b\xd8\xb9\x29\xa6\x27\xf4\x9e\x9e\x5e\xdd\xeb\x58\x36\x91\x2f\xe5\x74\xa2\xae\xeb\xb3\x8c\xe8\x26\x2a\x1f\xc3\x59\x7c\x48\xe5\x6c\x32\xa7\x87\x91\x0c\x9a\x8c\xb0\x49\x8f\xe8\x7d\x03\x29\xfc\x34\x82\xb9\xe1\x56\xc9\x59\x2f\x75\x69\xef\xe1\x5f\x18\x7c\x29\x54\x73\x37\xf7\xa0\xc6\x11\x4b\xc2\x3e\x53\x33\x87\x81\x26\xf3\x28\xa8\xcf\x93\x04\xc6\x56\x7c\x3e\x65\x90\xa9\x27\x76\x61\xfd\x33\x81\xe5\x10\x48\x40\x67\x9e\xf8\xff\x45\xdc\xff\x2a\x7d\x38\x73\x32\x28\x2a\x24\x6b\x53\xd2\xcc\x3e\x89\x5d\x1c\xa5\x4a\xd8\x51\x1e\x90\xb3\x03\x16\xab\x34\xac\xa4\x29\x9a\x53\xb2\xb7\x64\x6f\xe2\x55\x53\x7b\x53\x72\xe0\x70\x80\xa7\x6d\x53\x17\x19\x60\xca\xf8\xbe\xa6\x75\xf5\xb7\x43\x47\x3e\x2a\xaa\xb3\xfa\x8a\x13\xa8\xdc\x13\xb0\x49\xae\x21\x76\xba\x34\xd9\x7f\x75\x3b\x2e\x76\x93\x33\x2f\x02\x76\xe3\x39\x70\x82\x77\x4b\xe7\xa4\x78\x8b\x0c\x0d\x28\xbc\x06\xc4\x41\x6c\x4b\x41\xa0\xd0\x3a\xf7\x9f\x4a\x2b\x70\xd6\x30\xca\x22\xc5\xd1\x0a\x7a\xb7\xfb\x15\xdb\x18\xaa\xd2\x01\xde\x49\x6d\x3c\xef\xa0\xc0\x0d\x03\x15\x98\x03\xc6\x83\xeb\x48\xab\x16\x13\x87\xb9\x6b\xc0\x29\x89\xc0\xab\x44\x91\x79\xa6\x95\x9a\x60\xea\xf1\x19\x92\x32\x86\x67\x3c\xb9\x7f\x5a\xcd\xb0\xaf\x79\x28\x23\x21\xa5\xa9\xe1\xef\x69\x0b\xb9\x70\xa1\x70\xc1\x6a\xc9\x51\x33\x1a\x51\x96\x6a\x39\xdd\x38\xa0\x19\x21\xba\x51\xb7\x12\x29\x73\x00\x70\xf6\x01\x91\xb4\xa5\xe5\xa2\xe8\xbc\xc5\x33\xb7\xb8\x8b\x0d\xcf\xdd\x70\x3f\xd9\x1a\xde\xa6\x39\x93\x47\xd2\x27\x3a\xff\x3d\x5a\xf4\x9c\xf1\x84\xa8\xdb\xdb\x94\xde\xc9\xe0\x82\x75\x70\x50\xfc\x52\xcd\x13\x34\x50\x77\x4e\x33\x8c\xc9\x07\xca\xa7\x46\xef\x76\x06\x87\x70\xde\x6a\xd6\x7d\x92\xa3\x31\x0f\xc4\x46\x89\xd9\x86\xd2\x0d\xf6\x46\xf4\x86\x64\xf8\xb7\x29\x44\x19\x0c\x3c\x2e\x65\x08\x12\x70\x2a\x8a\xf5\xce\x3e\x89\xa9\x24\x2d\x20\x40\xed\x95\xa6\xe2\xf2\x87\x90\x4d\x75\x71\xec\xdb\x72\xae\x5c\xa8\x2c\xba\xcd\xa9\x8f\xa7\x42\x70\xad\x56\xc5\xa6\xd8\x74\xb9\x54\xdc\x9d\x92\x2d\xce\x94\xd5\x2f\xa6\xbe\xc9\x53\xaf\xd2\xe1\x5c\x00\x6d\x5e\x6b\x83\x9f\x4f\x23\x36\x5b\x68\x3e\xaa\x78\x68\xb6\x55\x43\x61\xa1\xa9\x6b\x19\x6b\xce\x7a\x91\x74\xf7\x11\xf5\x2b\x3e\x00\x97\x3d\xd5\xb9\x51\xad\x3c\x5e\x9f\x37\x5c\xeb\xef\xa5\xc1\xba\x1e\xc2\x9e\x9c\xd9\x59\x3b\xe7\x7c\xe5\x2b\x4a\x3a\xff\xc4\xba\xbf\x63\x78\xef\x3e\x72\x5d\xb1\x4e\xe5\x45\x29\x24\x39\x9f\xe5\x7a\xe7\x62\xd3\xd9\xfa\x2d\x1c\x74\x88\xce\x9f\x3e\x73\xe3\xfd\x41\x6f\x82\x5f\x2f\x7a\x13\x73\xed\xf4\x18\xe8\xe7\x5d\xf7\xce\xed\x1f\x27\xbc\xfa\xea\xab\xaf\xbe\x12\xd7\x5b\x55\x6f\xdc\xa2\x21\x32\x0f\x08\x58\xc7\x6a\x8e\x7f\x6c\x2f\x52\xe1\x5e\x93\x82\xf1\x30\x70\xef\xaa\x87\xe2\xdc\xc8\x10\x2f\x25\x2d\xae\x4c\x0d\xcb\xca\xeb\x8b\x95\x8b\xc1\x18\xbb\xd9\x54\xcc\x64\x81\xfc\x9a\xb4\xfd\x11\x40\x17\xa2\x5b\xad\xb6\xf0\x4d\x02\xfa\x12\x77\xd3\x9e\xec\xdd\xb8\xfd\x9e\x34\x63\x39\xdd\x26\xa5\xd9\x21\x74\xce\xa6\xce\x08\x27\x93\xcc\x62\xb6\xca\xd6\x75\x54\xb9\x3c\x40\x99\x44\xba\x4a\x3c\xc0\x9e\xca\x78\x9e\xf1\xac\xb6\x02\xfd\xc7\xa4\x72\x9f\xdd\x47\xaf\x6d\xfc\x0e\x3d\x66\x46\x2d\xc8\x35\x83\xe8\xd8\xff\x2d\x49\xca\x64\xa6\xc4\x7f\xf4\x64\x1e\xcd\xb7\xd1\x9b\x57\x0d\x69\xb5\x95\xc6\x60\x63\xdc\xbe\xa1\xea\xb5\xca\x1b\x64\x2e\x22\xc1\x9f\xd2\x68\x29\xf7\xed\xa9\x40\x0c\x54\x09\x70\x95\xba\xc4\x97\xb3\x93\x4e\x87\xd1\xa8\x13\x76\x15\x31\x51\x57\xa4\x17\xf0\x9c\xad\x33\xdb\xd3\x1b\x37\x1f\x8a\xd1\x3b\xaf\xfc\xa9\xaa\x38\xbc\xce\x86\x49\x0e\x8d\xc1\x97\xc9\xe1\x3c\xa7\xe2\xa8\x93\x02\xd5\x1c\x90\x52\x1e\xf0\xc6\x55\x97\xc0\xeb\xaa\xba\xd1\xc3\x68\x4e\xa0\x07\xca\xac\x53\x97\xb7\xbd\x25\x79\x9f\xdc\xf4\xa4\x4b\x81\x52\xc6\x81\xb6\xf8\x01\x52\x57\x1d\xcf\x02\xd1\x3c\xb4\x96\x66\xa6\x76\x53\xd4\x06\x9e\x25\xd0\xeb\x95\x76\x57\xf2\x6e\xb5\x49\x4b\xfa\x21\x2e\xbe\xf7\x43\x5c\x6d\xaa\xf4\x89\x12\x03\xee\x52\xd0\x02\xde\x55\x7f\x42\xd5\xf1\x20\x64\x75\xe3\x06\xa4\xa7\x3a\xde\xf3\x16\xdd\xf3\xca\x7f\x3e\x03\xab\x39\x2e\x71\xd2\x07\xb3\x2a\x89\x57\x59\xf1\x1f\x1e\xbb\x70\x13\x48\x1b\xbc\x86\x0b\x58\x48\x51\x86\x36\x3f\x7d\x0a\x2f\x29\x39\xa1\x4a\x22\x87\x7a\x8a\xce\x16\x94\xf7\x8a\xa5\xcc\x79\x77\x5e\x2c\xfe\xfd\x66\x24\x45\xec\x53\x77\xd2\xd9\x3b\xf4\x9c\xaf\x73\xa9\x9a\xc6\x25\xf3\xc0\x3e\x44\x62\xaf\x1f\x62\x9d\xf6\xad\x57\xff\x1e\x56\x92\x15\xa5\xb1\x3d\x91\xe1\x38\x01\xe1\x32\xa2\x74\xe5\x98\x7a\x3e\x0e\x36\x5a\xe2\xe9\xdf\xf2\x06\x72\xe6\x65\xc2\xff\x5d\x1e\x8a\x2e\x8e\x36\x88\x92\xfe\x8e\xc6\x49\x90\x29\x81\x89\x35\xab\xae\xde\xa3\xf2\x54\xf0\x1a\xb3\xd0\xac\x0c\x26\x2c\x40\x13\xae\xb9\xf2\x63\x0d\x51\x56\x6a\x9a\x2a\x67\x1f\x12\x54\xce\x07\xa2\x73\x71\x97\x51\x1b\xe7\x6e\x21\x1e\xbc\x9b\xf6\x07\x20\xcd\xaa\xf7\xae\xa9\xd6\x97\xd7\x79\x50\x85\x13\x37\x5f\x6d\x87\x9e\x99\xd9\x88\x3b\xa9\xfa\x21\x56\xda\x55\x45\xf1\x2a\x8b\xb1\x1a\x54\x3c\xf0\x3f\x57\x9e\x8a\x0d\x17\x2a\x3f\xd9\xa8\x07\xac\x78\x7e\x42\x72\xe5\x07\x39\x2d\x4a\xf4\xf6\x78\x3f\x56\xdc\x0d\x09\x15\x2f\x64\xc1\x12\x63\x9d\x6b\xb9\x34\x48\x93\x4d\x32\x06\xd5\x1e\x92\x05\x2e\x2f\x56\xcc\x81\x3e\x88\xbd\x11\x6f\x55\xe6\xed\xf7\x0e\xe2\x0c\x7c\x2d\xfe\xa2\x63\xdd\xe5\xd4\x8c\x27\xa9\x5b\x08\x27\x1b\xd5\x7d\xfe\xc5\x9d\x6e\xe3\xbc\x5c\x11\x0a\x17\xb7\x8d\xaa\xea\xef\x0b\x0b\xe6\x10\xfe\xbc\xeb\x3e\x89\x00\xd8\xb8\xa4\x67\x2f\x63\xfa\xb3\xc9\xd1\x1c\xb6\x48\x99\xbb\xae\xa2\x80\xef\xa6\x00\xb7\x3c\x0d\x70\x7d\x1a\xd1\xcb\x14\x3d\x23\x3c\x73\x11\xdb\x94\x81\x65\xb7\x22\x80\x40\x31\x3b\x15\x67\x9a\x17\xdb\x73\x49\xd7\x44\x0c\xb1\x11\x29\x4a\xbe\x98\x66\xb3\x4a\x3e\xd5\x43\xd7\x30\xfe\x2d\xfb\x2a\xce\xf0\x96\x4e\x70\x76\x4e\x17\xec\xae\x68\xf7\x6a\x0b\x2b\x22\x81\xfe\x26\x68\xab\x14\x48\x7e\x08\x0f\x44\x14\x5e\x7b\x37\xcc\x03\xc1\xa5\xc0\x3a\xed\x59\x6a\x11\xbd\xdd\x34\x33\x7f\xe5\x3a\x42\x55\x64\xc4\x83\x30\x01\x5a\xc3\x67\xc7\x16\x9c\x5a\x68\x36\xa6\x31\x9c\xca\x2b\xc4\xda\x29\x32\x37\xe4\x05\x97\x0c\xf4\x29\xa5\xdb\xc2\xd9\xf8\x8d\xf4\xa0\x79\xa7\x43\x5c\x12\x5e\x16\x97\x65\x15\xe9\x2c\xd9\xf4\x12\x51\xc8\xb1\x69\x8a\x2e\xdf\x4a\x29\x85\x62\xa9\x86\xab\xea\x86\x0b\x0b\xbe\x43\x93\xae\x72\x38\x30\xa8\xbc\x85\x83\x3b\xe6\x71\xc8\xc5\x48\x45\x32\x9c\x8b\xf1\xc8\x59\xdc\x28\x6a\x26\x55\xee\x4f\x3a\x1e\xd6\x37\x92\x79\xf1\x1b\xb9\xf1\x24\x6f\xe0\x59\x7a\xb8\x9e\xc6\x11\xfd\x3a\x7f\x53\x3e\xf2\x27\xe5\x63\xfe\x02\xcb\x5b\x50\xc2\x7e\x1d\xa6\x5d\x06\xfd\x97\x6d\x7a\x67\xd0\xae\x05\xcf\xe6\xd9\x33\x7e\x60\x5f\x3a\xdf\xc4\xca\x12\x59\x73\x67\x72\x79\xff\x4a\xf1\x60\xa3\x0c\x99\x9e\xc1\x97\xd5\x77\x68\x8c\x23\xb5\xfa\xc9\x79\xd3\xd1\xc3\x6b\xc7\xbf\x5f\x28\xbf\xfa\x6d\xb1\xd7\x63\x98\x0c\x11\xfd\xe5\x37\x71\xfc\x74\x30\x7a\x7b\x47\xf6\x3b\x2a\xed\xc3\xfa\x1c\xfa\x06\x3a\x1e\xe9\xf1\x7f\xba\x5f\x0a\xeb\x6e\x2b\x4d\xd3\xcd\x1c\x09\xf3\x7f\x91\xb2\xa5\x5a\xdb\x80\x3e\xae\x05\xe5\x16\xee\x36\x65\x0d\x4a\xf7\xa5\xfc\x4d\xe2\x92\x95\x17\x72\xe8\x9d\x5b\x2b\xbf\xdf\x5c\x84\xda\x9c\x71\xf2\x27\xde\xb1\x9c\x08\xae\x7a\x61\x3f\x69\x4c\x2d\x3f\x2f\xa6\x6e\xe5\x6b\x16\xf5\x6a\x93\xac\x91\xb4\xf2\x25\xf6\x8a\x44\x35\xe6\xfb\xa4\x0b\x07\xc3\x6e\x28\x7d\xd8\x96\xc2\xb7\x4b\x3b\x1a\x45\x0a\x6d\x5c\xc0\xa4\x85\x86\xf3\xf4\xa6\x80\x5a\x53\x3d\x30\xdf\xf4\x93\xce\x7b\xb9\xc4\xe2\x11\x3c\x8e\x29\xce\x40\x93\x2c\x26\xdf\x18\x6d\x36\x94\x1e\x1c\xd1\x18\x49\x13\x4e\x55\xbe\xcf\x5a\x6e\x1a\x9d\x43\x77\x7d\xaf\x5b\xad\x0c\xb4\x07\x65\x2d\x9a\xea\x67\x4a\x0b\x7f\x59\x1f\x62\x1c\xc3\xf5\xd5\xd5\x5e\xc7\xc3\xb4\x23\x19\xc8\xd5\xd4\xa7\xd8\xe9\xe8\x7c\xc2\xf7\x34\xed\xda\x24\x5b\x95\xdb\xb5\xf0\xbd\xb2\x6a\x8f\x3e\x5f\xb2\xe5\xaa\x92\x9b\x0a\xb0\x9b\xb4\xe1\xbe\x7b\x72\xac\x83\xac\xbc\xa8\xef\xb4\xbd\x73\xb7\x38\xcf\xfe\x9a\xbf\xe6\xf5\x75\x5d\x37\xa5\xe5\x23\x42\x4f\xe3\x64\xdd\xcd\xe3\x8b\x45\x7e\x38\xcb\xbf\x49\x9f\x9b\x65\x47\x41\xca\xcd\x4c\x45\x8f\xb1\x3d\x60\x28\x77\x80\x4b\x7e\x92\xd8\x0c\xb0\x2e\x17\xd8\x52\x04\x9d\xaf\x0d\xa5\xd0\x36\x60\x54\x9c\x39\xe5\xbb\x6e\x91\x2f\xcb\x05\xb9\x2d\x57\xc3\x8b\x53\xd6\x84\x6d\x3a\x53\x6e\xa9\x2f\xd6\x9c\x9d\x4b\x95\x50\xcf\x37\xe7\xf2\xbd\x46\x15\xe1\xcf\x1d\x0e\x37\x8f\x92\x48\x2a\x8a\x04\x1c\xd9\xdc\x91\x6f\x15\xfa\xee\xe9\xa8\x7c\x3c\xcd\x2c\x2e\xea\x6a\x81\x93\xbf\x34\x79\x8c\x4f\x5a\x9b\xe1\xc9\x2d\x0c\x6e\x13\xdb\xdb\x33\x80\x45\xf1\x9c\x4c\xc3\xb8\x35\xac\x8c\x91\x2c\x65\x99\x74\x64\xc9\x65\x5d\xc8\xc9\x65\xc2\x4f\xea\x5e\x90\xd7\xb9\x7c\x5c\x44\xef\x71\xda\x19\x1d\x0e\x73\x2b\x86\x3e\x73\xf7\xab\xc3\x94\x2d\x17\x35\x97\x15\x12\xc2\xd3\x6d\xd1\x69\xe4\x2e\xf9\x32\xde\x3b\x6b\xb4\x45\x58\x47\x07\x6f\x58\xc6\x54\x9f\xf5\xe8\xd5\xce\x9c\x36\x52\x70\x71\x44\x6d\x88\xb6\xfa\xd7\x40\x11\x85\x62\x56\xba\x14\xcc\x71\x32\x27\xdf\x51\x25\x53\xcb\x9a\x31\xd7\x4e\x39\x39\x29\x77\xf7\xec\x79\x19\x43\x80\xab\x9f\xbf\x54\x00\xab\xf7\x6a\xc0\xd5\x35\xac\x64\x0b\x05\xd9\xd5\x96\xde\xbf\x44\xb9\x76\xae\x9d\xa5\xcf\xf3\x2d\x40\xab\x5b\x8e\xe2\xad\x0e\xe4\x7b\xca\x2a\xbe\x32\x98\x4f\x47\x60\x7c\x56\xfb\xb0\xba\x86\x9f\x57\xe3\x29\x1e\x9c\x25\xcf\x48\x3e\x49\xdb\xfd\xea\x17\x5e\xf0\xa3\x0c\x38\x78\x11\x3b\xda\x2f\xc9\x57\xe7\x2f\x84\xfa\x2f\xf5\x37\xf5\x37\x0c\x90\xbf\xfc\xe0\x0d\xbd\x7d\xc4\x8f\x4c\x01\xb3\x8a\x5e\x29\xdf\x1e\xf4\x1d\x5e\xdd\xf1\xee\xfa\xbf\xf4\x38\x43\xf8\x84\xff\x98\xb4\x27\xae\xbf\x94\xd0\xb0\x62\x3d\x27\xc0\x7f\x7d\x46\x5b\xfe\x63\x95\x3e\x49\x14\xa3\x7f\x7f\xa9\x7e\xfb\xa5\x5c\x2b\xb6\xa9\x44\x86\x71\xe2\x29\x25\x5f\xf9\xf9\x1f\x98\x8e\x92\x44\x57\xb1\x7a\x57\x39\x61\x52\xc7\xb3\x93\xcf\xb3\x9e\x8b\x2b\xf5\xac\xb7\x04\xf4\xc4\xa9\xd5\xa0\x6e\x11\xa6\xb1\x93\x31\x19\xb9\xb5\x5c\x99\x3b\x7f\xbb\x5d\x8c\xf5\x58\xf5\x5c\xbf\x84\x15\xe6\x32\x3b\x55\x1c\x4b\xc5\x4a\xf3\xa7\x2a\x0f\x0f\x93\x56\xad\xdf\xb1\x7d\x1c\x74\xb8\x86\xe6\xc7\x57\x9f\x6e\xde\x7e\x78\x0f\xcf\xf2\x41\x35\x1b\xf8\x68\x50\x05\x14\xc2\xc2\xe4\x31\x25\x4b\xd5\xcf\x01\x87\x3b\xf4\x12\x04\xae\xaf\xae\xe4\x67\xed\xfc\xfe\x6a\xc3\xca\x9b\x10\xf2\x5c\xeb\xbf\x03\x00\x00\xff\xff\x7c\x8c\xe6\x56\x53\x31\x00\x00") | |
++var _runtimeHelpPluginsMd = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x7b\x6d\x8f\xdc\x36\x92\xf0\x77\xfd\x8a\x7a\x3a\x78\xe0\xee\xa0\xad\xc9\xe2\xbe\x0d\xe0\x05\x6c\xc7\x76\xbc\xe7\xd8\x86\xc7\x49\x2e\x08\x02\x88\x2d\x95\xba\x99\xa1\x48\x2d\x49\x4d\x4f\x9f\x91\xfd\xed\x87\xaa\x22\x29\x75\xcf\x64\x37\x77\x87\x0b\x90\x8c\x5a\x22\xeb\x8d\xf5\x5e\xcc\x57\xf0\xd1\x4c\x7b\x6d\x43\x55\x7d\xaf\x5b\xef\x20\x4c\xe3\xe8\x7c\x0c\xd0\x7a\x54\x51\xdb\x3d\x8c\xb2\x00\x8e\x3a\x1e\x40\x41\xd0\xc3\x68\x10\xde\x4d\x0a\xc2\x29\x44\x1c\x6a\x78\x75\x87\xfe\x94\xd6\xc1\x41\x05\x50\xd5\xa0\xb4\x85\xd0\x7a\x3d\x46\x38\x1e\x74\x7b\x00\x1d\xc0\x4f\x16\x54\x84\x10\x95\x8f\xd3\x98\xde\x87\x83\x9b\x4c\x07\x3b\x84\xd1\xa8\x16\x3b\xd0\x16\xaa\xe6\x1f\x57\x75\xeb\x6c\xaf\xf7\x57\x03\x91\x75\x95\x88\x48\x7f\xdf\xab\x01\x17\x8f\xb5\x99\x54\x53\x57\xd5\xe7\x03\x7a\x04\x45\xff\x82\x9d\x86\x1d\x7a\x70\x3d\xb4\xca\x98\x9d\x6a\x6f\xa1\x9f\x6c\x1b\xb5\x23\x56\x18\xf3\xc9\x4d\xd0\x2a\x2b\x9c\x22\xe1\x3d\xb9\xc9\x67\x3e\xa2\xab\x88\xde\xd6\x75\x48\x44\x47\x3d\x60\x00\x17\x0f\xe8\x21\x1e\x94\xcd\x5c\xd4\xf0\xf9\x80\x60\xd5\x40\xa2\x0a\xed\x01\x07\x24\x56\x1b\x67\x9f\x33\xb2\xf5\x9d\xc6\xe3\xa6\xa9\xe1\xb5\xf3\x15\xde\x2b\x16\x9e\x2a\xb4\x9c\x0b\x07\x59\x90\x84\x0a\xe2\x01\x61\x0a\xe8\x21\xa8\x3b\x0c\xfc\x73\x37\xf5\x3d\x7a\x38\x26\x79\x5d\x57\x55\xd3\x34\x66\x52\x55\x01\xe6\xec\x8d\xba\x43\x41\x59\x01\x00\xd4\x75\xcd\x7f\x3d\xc6\xc9\x5b\xe8\x95\x09\x58\xa1\xed\x68\x27\xcb\x0b\x1a\x5a\xdc\xc0\x9d\xf2\x5a\xed\x0c\xd3\xae\xc0\x63\x8f\x1e\x6d\x8b\x10\x1d\xa3\xa6\x45\xfc\xa0\x04\x91\x0e\xb0\x43\xe2\x18\xef\xb1\x9d\x22\x76\xe0\x6c\x5d\x7d\x3e\xe8\xc0\x00\xcc\xe0\x42\x04\x65\x8e\xea\x24\xa4\xb7\x93\xf7\x68\x23\xc3\xd9\x5e\x88\x7f\x8f\x51\x74\xab\x79\x39\xf9\x1f\x35\x1e\xd7\x9b\x06\x54\xa8\x8e\x68\x4c\x5d\x55\xcf\x8d\x01\x75\xa7\xb4\x61\xf2\x54\x3a\x41\x3a\x64\xa3\x43\x14\x7d\x21\x14\xb7\x78\xda\x69\xdb\x69\xbb\x0f\x10\x30\xc9\xa3\xe7\x4f\x07\x34\x63\x5d\x55\xaf\x9d\x2f\xca\x10\x88\xb3\xc1\x4d\xa1\x80\xdc\x32\x41\xac\x3c\x26\x38\xd8\xeb\x3b\x14\xc0\x78\x47\x94\x6b\xdb\xbb\x47\x25\xfe\x3d\x01\xf9\xe8\x31\x84\xb5\x70\xc7\xeb\x45\xfc\xc6\xb5\xca\xc0\xfd\x16\x4e\xf0\x4c\xde\x5f\x7f\x74\x41\xb3\x5e\x6c\xaa\x7f\x7e\x32\x01\x17\x0a\x9b\x8c\x84\x29\x4b\x3b\x14\xec\x9c\x33\x48\x9a\x38\x62\xab\xfb\x13\x1d\xc7\xf1\x80\x49\x43\xd3\x99\xc9\xc6\x6a\x87\xe0\x91\xa8\x21\x81\xa5\x33\x6d\x27\x1f\x9c\x07\xe7\xc1\xba\x08\xaa\x8f\x69\xdf\x7c\xc4\xad\x23\x6d\x8d\x58\x57\xd5\x7b\x17\x49\x27\xc9\x10\x98\xb4\x59\x8e\xae\x6d\x27\xff\x70\x3b\xb9\x81\x1d\xa2\x2d\x40\xba\x1a\xde\xf6\x2c\xe3\xa3\xb2\xb1\x52\xb3\x5d\xee\xb0\x77\x1e\x2f\x70\x67\xc5\xda\x92\x15\x40\x33\x7a\x4c\xf6\x44\xb6\xf4\x96\x4e\x86\x08\x54\x81\xf7\x55\x59\x14\x22\x1b\xec\x92\x4c\x34\x86\x22\x92\xc4\xe7\x02\xcb\xec\x79\x8a\x16\x33\x1b\x15\xad\x31\x93\x12\xdb\xcf\xf4\x07\xd2\x45\x2b\x0e\x60\x0a\xd8\x4f\x66\xe6\x20\x3a\xb8\xb5\xee\x08\x6a\xe7\xa6\x85\xbf\x63\xb9\xda\x99\xa9\xaa\x71\x96\x14\xfc\xc3\x88\xd9\x2f\xcc\x8b\x09\x18\x76\x44\xae\x25\x37\x40\x2e\x0c\x8f\x72\x88\x3a\x80\x1b\x91\xd8\x52\xb6\x63\x0e\x2c\x1e\xab\xfc\x69\x54\x21\xb0\x19\x90\x2b\x12\x03\x4c\xf4\xf5\xce\x43\xc0\xc8\x5e\x5c\x54\xd1\x8d\xa2\x4f\x3b\x15\xd8\x66\x59\x78\xbd\x36\x18\x4f\x23\x6e\x79\x43\xf6\x51\x24\x48\xda\xe8\xfa\x1e\x9a\xa8\x76\x21\xba\x30\xaa\x16\x43\x03\xce\x9a\x13\xaf\x7d\xe3\x80\x36\xb3\x90\x19\xd6\x89\x0c\xa8\x12\x62\xeb\xaa\x7a\xfa\xf4\xe9\x1f\x79\xe5\x59\xb7\x89\xa7\xec\x7e\x82\xe8\x18\x2f\x2e\x36\x1f\x1d\xab\x8d\xb6\xe0\x7c\x47\xc7\xe3\x40\xb5\x2d\x06\x71\x2d\xda\x5a\x76\x8a\xfe\x96\x4d\xdf\xf5\xc0\x11\xa3\x86\xef\x08\x2d\xbb\x33\x72\x14\xb0\xa6\xc5\xc4\x26\x04\xbd\xb7\x2a\x4e\x1e\x03\x31\x51\x2d\x28\xf1\x98\xec\x7e\x0a\xc4\xfa\x1b\xf7\x24\xa4\x2d\x1c\xe9\x36\xd7\x55\xf5\x35\x34\x1f\x6e\x9a\xeb\xd9\x61\xca\x01\xd2\x36\xa1\xe7\xc3\x8d\x10\xc0\x67\x2a\x6e\xcf\x9c\xc8\xbb\x8b\x34\x2d\x11\x22\xa7\x44\xb2\x07\x08\x6a\x40\x50\x41\x90\xbd\xf9\xf0\xe1\xa6\x80\xde\x42\x70\xd0\x74\xca\x1f\xb5\x6d\xb6\xd0\x1c\xb5\xed\xdc\x31\xd0\xa3\xd1\x76\xba\xa7\x87\xde\x23\xee\x42\xd7\xd4\x75\xbd\x61\xe2\x24\x6a\x7e\xab\x7d\x73\x0d\xad\xb3\x51\x51\xe8\x26\xba\x46\x15\x0f\xd9\xf4\x85\x40\x59\x3a\x79\xc5\xea\xc9\x07\xc9\x20\xe8\xb0\x9b\xeb\x2c\x37\xd7\x83\x32\x86\xb7\xd1\x87\x05\x4f\x9a\xe4\x84\x82\x75\xf2\x9f\xd5\xae\xb9\x4e\x27\xd2\xe1\x7d\xf6\xbd\xd9\xf3\xf3\xde\xe4\xaa\xf9\x99\x80\xf3\xde\x01\x43\x40\xbb\x47\xa2\xd8\x60\x0c\x7c\xd8\x01\x6d\x07\xf4\x45\xed\x31\x64\xb2\x39\x1e\x92\x13\x97\x80\x3d\x7a\x37\x8c\x51\x68\x7e\x8f\xc7\x17\x1c\x1e\xd7\x11\xef\xe3\x56\xb8\x0d\xd1\x6b\xbb\xdf\xc0\xd7\xf2\x89\x24\xc2\x3b\x43\x32\xae\x14\x50\x7b\xef\x06\x50\x72\xf2\x15\xbb\x64\xd5\x91\x4a\x49\xbe\x23\x0a\x41\xf0\xce\x11\xbd\xf6\x6e\x78\xad\x0d\xae\xff\x37\xa8\x0a\xd8\x37\x18\xdf\xa1\xa2\xe8\xf5\xd3\x41\x47\x64\x4b\x5b\x6f\xd8\xc5\x37\xd7\xc9\xad\xc9\x39\x1a\x59\x46\x7a\x97\xd6\x65\x51\x17\xa8\x42\x0c\xc3\x7d\x1b\x7e\x72\xbe\x7b\x79\x50\x7e\x1d\xa2\x2f\x64\x5e\xc0\x7d\xc4\x47\xca\x4a\xb1\x9f\x27\x47\xe7\x3b\x02\xdc\x1e\x94\x57\x6d\x44\xff\x84\x81\x7f\x9a\x2c\xde\x44\xbf\xf6\xa4\xde\xb8\x49\x7b\x16\x70\x55\x06\x93\x34\x91\x1e\x0b\xa5\xbc\x89\xe1\xbc\x73\xed\x9a\x83\xa4\xb6\x71\x03\xef\x5c\x7b\x06\x82\xc4\x47\x4b\x1a\x02\x36\xb5\xa2\x34\x3f\x89\xbd\x7f\xab\x3d\xb6\xd1\xf9\xd3\xfa\x51\xec\xde\x39\xf2\xea\x7c\x42\x96\xec\x6c\x0e\x7d\xac\x94\xc9\x6b\x10\x6b\x5d\x86\xc4\xe0\xff\xe6\xb4\xfd\xa8\xe2\x21\xac\x3b\xed\xeb\xba\x2e\x82\x2b\x48\x5a\x37\xec\xb4\xc5\x00\xc3\x64\xa2\x26\x87\x99\x21\x68\x51\x58\xca\xf1\x8c\x39\x3b\xe4\x42\x2d\x25\xad\x67\x7a\xb3\xa4\xda\x18\xd8\x4d\x72\x08\x46\x85\x08\x68\x70\x20\x62\x5d\xcf\x90\xb6\x04\x32\x9e\x46\x4d\x01\xe3\x54\x4c\xfb\x49\xb8\x60\xe1\x0d\xc6\x0f\xec\xed\xd7\xcc\xf9\x43\x44\x9c\x23\x28\x33\x15\xfd\xf1\xf8\xf7\x09\x39\xa5\x92\x30\xc1\x60\x9e\x77\xdd\x43\x30\xdb\xb4\x51\xdb\x88\xbe\x57\x2d\x7e\xf9\x9d\x20\x07\xb2\xde\xf9\x7c\x05\x8a\xd8\x91\xf8\xba\xf4\x41\x36\xaf\x9b\xc5\xf6\x06\x06\x54\x1c\x0c\x4e\xe2\x71\xb5\x85\x37\x4e\x5c\xda\x4d\xe1\x44\x20\x66\xec\x33\x4b\x8f\x23\xce\xb9\x2b\x2d\x96\xd8\x48\x24\x1c\xb5\x31\xb4\x81\xbf\xa5\x95\x7b\xe3\x76\x24\xcd\x2d\x4c\xd6\x50\x60\xd1\x31\x45\x0e\x89\x9a\x14\xef\x64\x69\x9d\x29\x7a\x47\x5f\xfe\x09\x59\x5b\x09\xdd\x5f\xff\xc8\x61\xfe\x8f\x49\xe4\xd3\x2c\x27\xc1\xf8\xc4\xb5\xce\x6b\xc5\x77\x30\xe2\x17\xda\x76\xff\x8e\xa7\xf5\x2d\x9e\xb6\x25\x8d\x29\x62\xa0\xfc\x37\x40\x73\x8b\xa7\x86\x98\x6f\x64\x41\xc3\x3b\xbf\x57\xb7\xf8\xd2\x0d\x83\xb2\x1d\x9f\xe4\x76\x2e\x41\x32\xc1\x29\xd3\xe1\x50\x58\xd7\xf5\xcb\xf2\x93\x60\xb3\xf9\x17\x97\xd6\x0a\xa4\x94\xb7\x13\xbc\x9c\xc9\xb0\x78\x89\x09\x68\x32\x82\x46\xd2\x83\x9c\x64\xd5\xf0\x43\x40\xf8\x86\x00\x52\x0e\xb1\xc4\x1a\x1d\x57\x03\xef\xdd\x8c\xbb\x5e\x52\x9f\xde\xad\x2f\x48\xdf\x34\xd7\xe7\xe4\x35\xf3\x5a\x96\x04\xa5\x90\x42\xea\x42\x0c\x22\x97\xb9\xe6\x38\xb7\x8c\x65\xc5\xc2\x0b\xbf\x53\xb6\x33\x45\x84\xed\xd0\x2d\x4d\x6a\xb2\xcb\xc3\x4d\xd2\xe1\x6d\xaf\xee\xb1\x5d\xca\xbd\x48\x5b\xf9\x7d\x80\x5f\x7e\xcd\x8e\x65\x9d\xdf\xa3\xf7\xce\x13\x50\x92\x17\x28\x58\x87\x03\x1a\xb3\x39\x17\xf9\x99\x39\x29\xbf\x9f\xc8\x43\x84\x1a\x3e\x2d\x39\x90\x0d\x4f\x02\xb8\x29\x8e\x53\xe4\x44\x4b\xc1\xe8\x42\xd0\x94\xb8\x30\xa2\x3a\x3b\xf2\x1b\xc2\xf2\x08\x77\x8f\x10\xf6\x89\xca\x78\x60\xb2\x32\x92\x94\x7a\x4e\x01\xc3\x19\xcb\x0d\x51\x39\xd9\x2e\xd5\x06\x07\xe7\x3a\x76\x6e\x5c\xc8\x74\x0e\x03\x04\x37\x90\x03\xf3\x9c\x70\x91\x3e\x70\x82\x9e\x39\x82\xb5\xae\xb1\x86\xbf\x4f\xec\xc7\xcb\xeb\x0d\x57\xdd\xac\x41\x59\x13\xfc\xff\x98\xf3\x17\xaa\xbd\xdd\x7b\x37\xd9\x8e\x65\x70\x71\xb4\x8f\x30\x9b\xad\x73\x57\x36\x16\x58\x6f\xc9\xa3\x91\xd9\xdd\xe1\x25\xb0\x2d\x1c\x95\x8e\x1c\x79\xb7\xa4\xe5\x1f\x84\x34\xfa\xfd\x27\x85\x4c\xfc\xee\x4e\xd0\x1a\xc7\xc2\x92\x24\x8e\xe8\xc9\xc9\xe5\x82\x77\x71\xcd\x42\x89\x39\x71\xe9\xd5\x10\xfe\x86\x13\x4f\x3f\xe1\x96\x04\xc2\x09\x94\x98\xec\x8e\xc5\x39\x85\x5c\x04\x49\x60\xf1\x8e\xf3\x6c\xbc\xd7\x91\xad\x73\xf4\x52\x0d\x73\x16\x87\x7e\xd0\x56\x19\x49\x6a\xf4\x30\x60\xa7\x55\x44\x4a\x76\x31\x17\x0f\x54\x64\x13\x99\x5b\x0a\x6b\xee\x98\x62\x6d\xc9\xe5\xa2\x9b\x3b\x0b\xe9\xa8\x52\x2c\x4a\x98\x85\xf0\x22\xad\x05\xf5\xe7\xe7\x1c\xa2\xb2\x9d\xf2\x5d\x82\x52\xfc\x3c\xd7\xbd\x52\x12\xd6\x30\xd7\xb0\xfa\x0f\x80\xb2\x32\x2e\x24\x97\x31\x04\x18\xd4\x89\x53\xa3\x1d\x1e\xd4\x1d\x8b\xca\x3a\x3f\xb0\xaf\xde\x61\xab\xb8\x46\xd5\x41\xc5\x78\x6a\x04\xf3\xb2\xae\x2f\xea\xf1\x19\xfd\xf0\x6a\x98\x8c\x8a\xce\xff\x39\xd5\xd8\xce\x45\x66\x36\x48\x56\x91\xe6\x1a\x6e\xa4\x70\x20\x5a\x1e\x53\xbd\x06\xf0\xbe\xc5\x31\x9e\x6b\x85\xf4\x99\xc8\x87\x24\x25\xce\x7e\x2e\x8c\x86\x82\x1e\x29\x5d\x39\x58\x4c\xa4\x72\x0f\x89\x9b\x45\x99\x98\x06\xb4\x25\x12\xf5\x32\xc7\x5b\x54\xc3\x8a\xeb\xe7\x8b\x26\x57\x3e\x91\x45\xa5\xbb\x3c\x6b\x42\xc2\x8a\x26\x2d\xb5\x47\xf5\xa1\xc0\x18\xbd\xbb\xd3\x1d\x29\xab\x98\x7c\xaf\x7d\x10\x3b\xe7\x60\x9d\x3d\x45\x49\xf8\x12\x6d\x84\x63\xad\x8b\xca\x03\x0e\x63\x3c\x3d\xa2\x0e\x7c\x6c\x1b\xe6\x7b\xd9\xf9\xa0\x2f\x89\x27\x65\x34\xed\x0c\x82\x2f\x75\x4d\xa5\xa6\x66\x2d\x72\x23\x7a\xe9\x9f\x4a\xed\x18\x60\xcd\x45\xdb\x16\xa4\xa0\xdb\x42\xe7\xd5\xde\xd9\xde\x9c\x38\xa3\xa3\x9a\x79\x17\xba\x2d\xa4\x8a\x6e\x23\x61\x92\x88\x6f\x92\xda\xdc\x64\x24\x0d\xac\x03\x22\xec\xd0\xb8\xe3\xa6\xb4\x81\xa2\x83\x0e\xe5\xec\x90\x58\x5a\x9c\x2e\x17\x05\x4c\x06\x51\x5c\x88\x15\xbd\x7c\x00\xfd\x1a\x5e\x3c\xec\x2c\x25\x80\x0f\x74\xe3\x0c\x60\xea\x28\xc0\x1d\xfa\x20\xfd\x37\xc2\x2c\x8e\x4a\x0c\x2f\x64\x67\x95\x70\x3b\x2a\x4d\x3e\xba\xb0\x36\xae\xa5\xcc\x7f\x4b\xd9\x4e\xae\xa2\x36\x64\x8b\x97\x71\x39\x97\x20\xe4\xce\x75\xee\xf2\xa9\x39\x1a\x72\x21\xc1\xad\xae\x9c\xc2\x7e\x42\xe3\x14\xb1\xb5\xfe\x84\x1b\x7a\x94\x3e\x2b\x99\x80\x14\x49\x2f\x4e\x11\x3f\xf4\x7d\xc0\xf8\x4f\xc9\xc0\x7b\xd5\x52\xdd\x6b\xf4\x2d\x2e\x48\x2f\x86\x46\xf6\xc3\xa2\x6e\xdd\x44\xb1\x6b\x77\xa2\x6c\x44\xdb\x10\x51\x75\x44\x25\x15\x3c\x21\xd5\x17\xbb\x9b\x51\x1d\x2d\x39\x81\xf7\xcb\xac\xa0\x1d\xba\xe7\xcb\xc4\x60\x0b\xce\xde\xc4\xce\x4d\x31\x3d\xa1\xf7\xf4\xf4\xea\x5e\xc7\xb2\x89\x7c\x29\xa7\x13\x75\x5d\x9f\x65\x44\x37\x51\xf9\x18\xce\xe2\x43\x2a\x67\x93\x39\x3d\x8c\x64\xd0\x64\x84\x4d\x7a\x44\xef\x1b\x48\xe1\xa7\x11\xcc\x0d\xb7\x4a\xce\x7a\xa9\x4b\x7b\x0f\xff\xc2\xe0\x4b\xa1\x9a\xbb\xb9\x07\x35\x8e\x58\x12\xf6\x99\x9a\x39\x0c\x34\x99\x47\x41\x7d\x9e\x24\x30\xb6\xe2\xf3\x29\x83\x4c\x3d\xb1\x0b\xeb\x9f\x09\x2c\x87\x40\x02\x3a\xf3\xc4\xff\x27\xe2\xfe\x57\xe9\xc3\x99\x93\x41\x51\x21\x59\x9b\x92\x66\xf6\x49\xec\xe2\x28\x55\xc2\x8e\xf2\x80\x9c\x1d\xb0\x58\xa5\x61\x25\x4d\xd1\x9c\x92\xbd\x25\x7b\x13\xaf\x9a\xda\x9b\x92\x03\x87\x03\x3c\x6d\x9b\xba\xc8\x00\x53\xc6\xf7\x35\xad\xab\x5f\x0e\x1d\xf9\xa8\xa8\xce\xea\x2b\x4e\xa0\x72\x4f\xc0\x26\xb9\x86\xd8\xe9\xd2\x64\xff\xcd\xed\xb8\xd8\x4d\xce\xbc\x08\xd8\x8d\xe7\xc0\x09\xde\x2d\x9d\x93\xe2\x2d\x32\x34\xa0\xf0\x1a\x10\x07\xb1\x2d\x05\x81\x42\xeb\xdc\x7f\x2a\xad\xc0\x59\xc3\x28\x8b\x14\x47\x2b\xe8\xdd\xee\x37\x6c\x63\xa8\x4a\x07\x78\x27\xb5\xf1\xbc\x83\x02\x37\x0c\x54\x60\x0e\x18\x0f\xae\x23\xad\x5a\x4c\x1c\xe6\xae\x01\xa7\x24\x02\xaf\x12\x45\xe6\x99\x56\x6a\x82\xa9\xc7\x67\x48\xca\x18\x9e\xf1\xe4\xfe\x69\x35\xc3\xbe\xe6\xa1\x8c\x84\x94\xa6\x86\x9f\xd3\x16\x72\xe1\x42\xe1\x82\xd5\x92\xa3\x66\x34\xa2\x2c\xd5\x72\xba\x71\x40\x33\x42\x74\xa3\x6e\x25\x52\xe6\x00\xe0\xec\x03\x22\x69\x4b\xcb\x45\xd1\x79\x8b\x67\x6e\x71\x17\x1b\x9e\xbb\xe1\x7e\xb2\x35\xbc\x4d\x73\x26\x8f\xa4\x4f\x74\xfe\x7b\xb4\xe8\x39\xe3\x09\x51\xb7\xb7\x29\xbd\x93\xc1\x05\xeb\xe0\xa0\xf8\xa5\x9a\x27\x68\xa0\xee\x9c\x66\x18\x93\x0f\x94\x4f\x8d\xde\xed\x0c\x0e\xe1\xbc\xd5\xac\xfb\x24\x47\x63\x1e\x88\x8d\x12\xb3\x0d\xa5\x1b\xec\x8d\xe8\x0d\xc9\xf0\x6f\x53\x88\x32\x18\x78\x5c\xca\x10\x24\xe0\x54\x14\xeb\x9d\x7d\x12\x53\x49\x5a\x40\x80\xda\x2b\x4d\xc5\xe5\x0f\x21\x9b\xea\xe2\xd8\xb7\xe5\x5c\xb9\x50\x59\x74\x9b\x53\x1f\x4f\x85\xe0\x5a\xad\x8a\x4d\xb1\xe9\x72\xa9\xb8\x3b\x25\x5b\x9c\x29\xab\x5f\x4c\x7d\x93\xa7\x5e\xa5\xc3\xb9\x00\xda\xbc\xd6\x06\x3f\x9f\x46\x6c\xb6\xd0\x7c\x54\xf1\xd0\x6c\xab\x86\xc2\x42\x53\xd7\x32\xd6\x9c\xf5\x22\xe9\xee\x23\xea\x57\x7c\x00\x2e\x7b\xaa\x73\xa3\x5a\x79\xbc\x3e\x6f\xb8\xd6\xdf\x4b\x83\x75\x3d\x84\x3d\x39\xb3\xb3\x76\xce\xf9\xca\x57\x94\x74\xfe\x89\x75\x3f\x63\x78\xef\x3e\x72\x5d\xb1\x4e\xe5\x45\x29\x24\x39\x9f\xe5\x7a\xe7\x62\xd3\xd9\xfa\x2d\x1c\x74\x88\xce\x9f\x3e\x73\xe3\xfd\x41\x6f\x82\x5f\x2f\x7a\x13\x73\xed\xf4\x18\xe8\xe7\x5d\xf7\xce\xed\x1f\x27\x9c\x05\xfb\x7c\x8a\x2e\x8f\x78\x38\x99\x91\x73\x61\x59\xdf\x69\x25\xe2\x9c\x8f\xf2\x65\x5e\x3a\x0b\xb6\x86\xcf\x0e\x78\x28\xce\x19\x27\x29\xcd\x19\x4c\xf2\x1d\xc9\x24\x58\xfe\xd2\x48\x10\xc0\xce\x7e\x9a\x2c\x36\x69\xc6\xc8\x19\x5c\xab\x3b\x9c\xe7\x78\xae\xb4\xaa\x9a\x82\xba\x7e\xce\x59\x7e\x03\xbd\x51\x6c\x83\x64\x23\x70\x95\x6a\x0d\xa8\x00\xbe\x96\x72\xbc\x75\x36\x7a\x67\xc2\xd9\x58\xb0\xd3\xca\xb8\x3d\xec\x1c\xb7\xe8\xf3\x40\x48\x07\xe8\x74\x18\x8d\x3a\x51\x5a\xf8\x35\x3c\x27\xbd\x7f\x04\xf5\x7f\x70\x2a\xb0\x7c\xf3\xf3\x2c\x89\x90\xda\x00\xa5\xd7\xb5\xc3\xbd\x4e\x73\x8f\x3e\xc5\x6c\x54\x39\x39\x28\xee\x06\xd3\xe0\x9f\x62\x5a\xe9\x8c\x05\x34\xd8\x92\x7d\x71\x75\xc9\x7b\x17\x52\x65\x12\xbb\xae\x90\x1f\xdd\x4c\x52\x2d\x5d\xb9\x40\x9a\x70\x93\x39\x10\x91\xbd\xb5\x1d\xde\x37\xf0\x54\x02\x65\x21\xf3\xa0\xf7\x07\xa3\xf7\x07\xec\x4a\x5f\xf3\x01\x46\xf6\xd0\x75\x55\x66\xdc\x68\xdd\xb4\x97\x81\x8a\xd7\xfb\x7d\x11\x2e\xcb\xb0\x04\x25\xe1\x85\x9d\x3b\xf7\x20\xa8\x08\x86\x69\xbc\xea\xdc\x31\xb3\xb8\x98\x53\x27\x6e\xea\xaa\xfa\xea\xab\xaf\xbe\x92\xa4\xa0\xaa\xde\xb8\x45\xab\x6e\x1e\x5d\xb1\xf7\xab\xe5\x38\xc8\x93\x4b\xef\xe5\x9a\x5c\x1f\x8f\xa9\xf7\xae\x7a\x68\xe8\x1b\x19\x2f\xa7\x74\xda\x95\x79\x76\x59\x79\x7d\xb1\x72\x31\xb2\xe5\x04\x20\x95\xd9\xd9\x54\x7f\x4b\x7e\xf8\x11\x40\x17\x46\xbd\x5a\x6d\xe1\x9b\x04\xf4\x5b\xdc\x4d\x7b\x8a\x44\xc6\xed\xf7\xa4\x1d\xcb\x7b\x17\xe4\xce\x76\x08\x9d\xb3\xa9\x67\xc7\x65\x0e\xb3\x98\xe3\x45\xeb\x3a\xaa\xa9\x1f\xa0\x4c\xc6\xbe\x4a\x3c\xc0\xde\x21\x45\x4b\x8f\xb0\xda\x0a\xf4\x1f\x93\xa6\x7e\x76\x1f\xbd\xb6\xf1\x3b\xf4\x98\x19\xb5\x20\x17\x60\xd8\xe4\xf0\x8c\xa4\x4c\x66\x2a\x49\x47\x4f\x8e\xbb\x79\x19\xbd\x79\xd5\xd0\xc1\x59\x69\x59\x37\xc6\xed\x9b\x2d\x28\xa8\xf2\x06\x99\xd8\x49\x5a\x4a\x05\x9e\x28\x81\x3d\x15\x88\x81\xcc\x9e\x35\x7c\x89\x2f\xe7\xcd\xc5\x22\x2b\x62\xa2\xae\x48\x2f\xe0\xb9\xf8\xa7\xe4\xe9\xdf\xb8\xf9\x50\x8c\xde\x79\xe5\x4f\x55\xc5\x89\xdf\x1c\x32\x28\xd4\x32\xf8\x32\xd3\x9e\x27\xa8\x9c\x0f\x25\xf5\x9b\x53\xa5\xa4\xff\x6f\x5c\x75\x09\xbc\xae\xaa\x1b\xf2\x75\x27\xf2\x78\xce\xc7\x34\x7f\x68\x6f\x49\xde\x27\x37\x3d\xe9\x52\x0a\x27\x83\x6a\x5b\x22\x14\xa9\xab\x8e\x67\x29\xd2\x7c\x9d\x42\xda\xec\xda\x4d\x51\x1b\x78\x96\x40\xaf\x57\xda\x5d\xc9\xbb\xd5\x26\x2d\xe9\x87\xb8\xf8\xde\x0f\x71\xb5\xa9\xd2\x27\x4a\x59\xb9\x7f\x46\x0b\x78\x57\xfd\x09\x55\xc7\x23\xba\xd5\x8d\x1b\x90\x9e\xea\x78\xcf\x5b\x74\xcf\x2b\xff\xf1\x0c\xac\xe6\x8c\x89\xcb\x11\x98\x55\x49\xe2\xdd\x8a\xff\xf0\x40\x90\xdb\x93\xda\xe0\x35\x5c\xc0\x42\xca\x7f\x68\xf3\xd3\xa7\xf0\x2d\xa5\xcd\x54\xe3\xe6\x24\x94\x7c\xbf\x05\xe5\xbd\xf8\x04\xae\x08\xf3\x62\xc9\x3c\x6e\x46\x52\xc4\x3e\xf5\xcd\x9d\xbd\x43\xcf\x95\x24\x37\x51\xd2\x20\x6f\xbe\x4a\x12\x22\xb1\xd7\x0f\xb1\x4e\xfb\xd6\xab\xff\x1f\x56\x92\xaf\xa7\x0b\x25\x44\x86\xe3\xd4\x98\x0b\xdc\xd2\x2f\x66\xea\xf9\x38\xd8\x68\x89\xa7\xff\x97\x37\x50\x9a\x51\xee\x9e\x7c\x97\xc7\xf5\x8b\xa3\x0d\xb3\x1b\x7e\x44\xe3\x24\xfd\x29\x29\x13\x6b\x56\x5d\xbd\x47\xe5\xcd\x89\x53\xd2\x59\xb3\x32\x98\xb0\x00\x4d\xb8\xe6\x9e\x04\x6b\x88\xb2\x52\x6d\x57\x39\x2f\x96\x98\x71\x3e\xaa\x9f\xdb\x0e\x19\xb5\x71\xee\x16\xe2\xc1\xb3\x6b\x26\xcd\xaa\xf7\xae\xa9\xd6\x97\x17\xcd\x50\x85\x13\x8f\x05\x6c\x87\x9e\x99\xd9\x88\x3b\xa9\xfa\x21\x56\xda\x55\x45\xf1\x2a\x8b\xb1\x1a\x54\x3c\xf0\x7f\xae\x3c\x95\xc1\x2e\x54\x7e\xb2\x51\x0f\x58\xf1\x64\x8f\xe4\xca\x0f\x72\x5a\x54\x82\xec\xf1\x7e\xac\xb8\x4f\x17\x2a\x5e\xc8\x82\x25\xc6\x3a\xd7\x72\xd1\x9a\x66\xee\x64\x0c\xaa\x3d\x24\x0b\x5c\x5e\xf9\x99\x53\xd0\x20\xf6\x46\xbc\x55\x99\xb7\x3f\x3a\x88\x33\xf0\xb5\xf8\x8b\x8e\x75\x97\x8b\x06\x9e\xf1\x6f\x21\x9c\x6c\x54\xf7\xf9\x17\xcf\x60\x8c\xf3\x72\x79\x2d\x5c\xdc\x83\xab\xaa\x9f\x17\x16\x2c\xb1\xb4\xeb\x3e\x89\x00\xd8\xb8\x64\x9a\x24\x17\x48\xce\x66\x9a\x73\xd8\x22\x65\xee\xba\x8a\xf2\x04\x37\x05\xb8\xe5\x39\x95\xeb\xd3\xe5\x11\xb9\xdf\x91\x11\x9e\xb9\x88\x6d\xaa\x0d\xb2\x5b\x11\x40\xa0\x98\x9d\x8a\x6b\xa0\x8b\xed\xb9\xd9\xd0\x44\x0c\xb1\x11\x29\x4a\xaa\x91\x6e\x0d\x28\xf9\x54\x0f\x5d\xc3\xf8\xb7\xec\xab\xb8\xf6\x58\x3a\xc1\xd9\x39\x5d\xb0\xbb\xa2\xdd\xab\x2d\xac\x88\x04\xfa\x9b\xa0\xad\x52\x20\xf9\x21\x3c\x10\x51\x78\xed\xdd\x30\x8f\xaa\x97\x02\xeb\xb4\x67\xa9\x45\xf4\x76\xd3\xcc\xfc\x95\x8b\x32\x55\x91\x11\x8f\x68\x05\x28\xa7\x9c\x64\xc1\xa9\xb9\x6b\x63\x1a\x10\xab\xbc\x42\xac\x9d\x22\x73\x43\x5e\x70\xc9\x40\x9f\x8a\x8d\x2d\x9c\x0d\x86\x49\x0f\x9a\x77\x3a\xc4\x25\xe1\x65\x71\x59\x56\x91\xce\x92\x4d\x2f\x11\x85\x1c\x9b\x96\x09\x53\x6e\x61\x94\x3e\x4d\x55\xdd\x70\xc9\xcb\xb7\xbb\xd2\x25\x23\x07\x06\x95\xb7\x70\x70\xc7\x3c\xa8\xbb\x18\xf6\xa5\x84\xf3\x7c\x70\x77\x16\x37\x8a\x9a\x49\xff\xe5\x27\x1d\x0f\xeb\x1b\xa9\x09\xf8\x8d\xdc\xc5\x93\x37\xf0\x2c\x3d\x5c\x4f\xe3\x88\x7e\x9d\xbf\x29\x1f\xf9\x93\xf2\x31\x7f\x81\xe5\xfd\x3c\x61\xbf\x0e\xd3\x2e\x83\xfe\xcb\x36\xbd\x33\x68\xd7\x82\x67\xf3\xec\x19\x3f\xb0\x2f\x9d\xef\x08\x66\x89\xac\xb9\x67\xbe\xbc\x19\xa8\x78\xe4\x56\xc6\x9f\xcf\xe0\xcb\xea\x3b\x34\xc6\x91\x5a\xfd\xe4\xbc\xe9\xe8\xe1\xb5\xe3\xdf\x2f\x94\x5f\xfd\xbe\xd8\xeb\x31\x4c\x86\x88\xfe\xf2\xbb\x38\x7e\x3a\x18\xbd\xbd\x23\xfb\x1d\x95\xf6\x61\x7d\x0e\x7d\x03\x1d\x0f\x9b\xf9\x1f\xdd\x2f\x85\x75\xb7\x95\x76\xfe\x66\x8e\x84\xf9\x9f\xc8\x15\x8e\xb6\x01\x7d\x5c\x0b\xca\x2d\xdc\x6d\xca\x1a\x94\xbe\x60\xf9\x9b\xc4\x25\x2b\x2f\xe4\xd0\x3b\xb7\x56\x7e\xbf\xb9\x08\xb5\x39\xe3\xe4\x4f\xbc\x63\x39\xab\x5e\xf5\xc2\x7e\xd2\x98\x5a\x7e\x5e\xcc\x83\xcb\xd7\x2c\xea\xd5\x26\x59\x23\x69\xe5\xb7\xd8\x2b\x12\xd5\x98\x6f\x3a\x2f\x1c\x0c\xbb\xa1\xf4\x61\x5b\x5a\x32\x5d\xda\xd1\x70\x05\x60\x5c\xc0\xa4\x85\x86\x2b\xc8\xa6\x80\x5a\x53\xa5\x3a\xdf\x41\x95\x99\x50\xb9\x5e\xe5\xb9\xb8\x49\x71\x06\x9a\x64\x31\xf9\x2e\x73\xb3\xa1\xf4\xe0\x88\xc6\x48\x9a\x70\xaa\xf2\x4d\xeb\x72\x07\xee\x1c\xba\xeb\x7b\xdd\x6a\x65\xa0\x3d\x28\x6b\xd1\x54\xbf\x50\x5a\xf8\xeb\xfa\x10\xe3\x18\xae\xaf\xae\xf6\x3a\x1e\xa6\x1d\xc9\x40\x2e\x4d\x3f\xc5\x4e\x47\xe7\x13\xbe\xa7\x69\xd7\x26\xd9\xaa\xdc\xfb\x86\xef\x95\x55\x7b\xf4\xf9\xfa\x37\xf7\x3b\xb8\xdd\x05\xbb\x49\x1b\x9e\x08\x25\xc7\x3a\xc8\xca\x8b\xce\x83\xb6\x77\xee\x16\xe7\xa9\x74\xf3\xd7\xbc\xbe\xae\xeb\xa6\x34\x23\x45\xe8\xe9\xa2\x83\xee\xe6\xc1\xda\x22\x3f\x9c\xe5\xdf\xa4\xcf\xcd\xb2\xd7\x25\xf5\x7a\xa6\xa2\xc7\xd8\x1e\x30\x94\xdb\xe9\x25\x3f\x49\x6c\x06\x58\x97\xab\x95\x29\x82\xce\x17\xda\x52\x68\x1b\x30\x2a\xce\x9c\xf2\x2d\xcc\xc8\xd7\x38\x83\xdc\xe3\xac\xe1\xc5\x29\x6b\xc2\x36\x9d\x29\x0f\x7b\x16\x6b\xce\xce\xa5\x4a\xa8\xe7\x3b\x9d\xf9\xc6\xad\x8a\xf0\xe7\x0e\x87\xdb\x9a\x49\x24\x15\x45\x02\x8e\x6c\x54\x2c\xc6\x83\xf6\xdd\xd3\x51\xf9\x78\x9a\x59\x5c\x74\x7c\x04\x4e\xfe\xd2\xe4\xd2\x93\xb4\x36\xc3\x93\xfb\x41\x3c\xc0\xb0\xb7\x67\x00\x8b\xe2\xb9\x54\xa2\x6a\x1b\xa2\x32\x46\xb2\x94\x65\xd2\x91\x25\x97\x75\x21\x27\x97\x09\x3f\xa9\x7b\x41\x5e\xe7\xf2\x71\x11\xbd\xc7\x69\x67\x74\x38\xcc\x4d\x42\xfa\xcc\x7d\xd9\x0e\x53\xb6\x5c\xd4\x5c\x56\x48\x08\x4f\xf7\x98\xa7\x91\xe7\x37\xcb\x78\xef\xac\xd1\x16\x61\x1d\x1d\xbc\x61\x19\x53\x7d\xd6\xa3\x57\x3b\x73\xda\x48\xc1\xc5\x11\xb5\x21\xda\xea\xdf\x02\x45\x14\x8a\x59\xe9\xba\x3a\xc7\xc9\x9c\x7c\x47\x95\x4c\x2d\x6b\xc6\x5c\x3b\xe5\xe4\xa4\xdc\x2a\xb5\xe7\x65\x0c\x01\xae\x7e\xf9\x52\x01\xac\xde\xab\x01\x57\xd7\xb0\x92\x2d\x14\x64\x57\x5b\x7a\xff\x2d\xca\xff\x10\xa1\x9d\xa5\xcf\xf3\xfd\x54\xab\x5b\x8e\xe2\xad\x0e\xe4\x7b\xca\x2a\xbe\xcc\x9a\x4f\x47\x60\x7c\x56\xfb\xb0\xba\x86\x5f\x56\xe3\x29\x1e\x9c\x25\xcf\x48\x3e\x49\xdb\xfd\xea\x57\x5e\xf0\xa3\x8c\xde\x78\x11\x3b\xda\x2f\xc9\x57\xe7\x2f\x84\xfa\x2f\xf5\x37\xf5\x37\x0c\x90\xbf\xfc\xe0\x0d\xbd\x7d\xc4\x8f\x4c\x01\xb3\x8a\x5e\x29\xdf\x1e\xf4\x1d\x5e\xdd\xf1\xee\xfa\x3f\xf5\x38\x43\xf8\x84\x7f\x9f\xb4\x27\xae\xbf\x94\xd0\xb0\x62\x3d\x27\xc0\x7f\x7d\x46\x5b\xfe\x6d\x95\x3e\x49\x14\xa3\xff\xfe\x5a\xfd\xfe\x6b\xb9\xf0\x6e\x53\x89\x0c\xe3\xc4\xf3\x73\xbe\x8c\xf6\xdf\x30\x1d\x25\x89\xae\x62\xf5\xae\x72\xc2\xa4\x8e\x67\x27\x9f\xa7\x90\x17\xff\xb3\x07\xeb\x2d\x01\x3d\x71\x6a\x35\xa8\x5b\x84\x69\xec\x64\x80\x4b\x6e\x2d\x57\xe6\xce\xdf\x6e\x17\x03\x67\x56\x3d\xd7\x2f\x61\x85\xb9\xcc\x4e\x15\xc7\x52\xb1\xd2\x64\xb4\xca\x63\xed\xa4\x55\xeb\x77\x6c\x1f\x07\x1d\xae\xa1\xf9\xf1\xd5\xa7\x9b\xb7\x1f\xde\xc3\xb3\x7c\x50\xcd\x06\x3e\x1a\x54\x01\x85\xb0\x30\x79\x4c\xc9\x52\xf5\x4b\xc0\xe1\x0e\xbd\x04\x81\xeb\xab\x2b\xf9\x59\x3b\xbf\xbf\xda\xb0\xf2\x26\x84\x3c\x71\xfd\xaf\x00\x00\x00\xff\xff\x99\xf6\x95\xb3\xed\x33\x00\x00") | |
+ | |
+ func runtimeHelpPluginsMdBytes() ([]byte, error) { | |
+ return bindataRead( | |
+--- cmd/micro/settings.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/settings.go | |
+@@ -241,6 +241,7 @@ func DefaultGlobalSettings() map[string]interface{} { | |
+ "tabstospaces": false, | |
+ "termtitle": false, | |
+ "useprimary": true, | |
++ "autocomplete": false, | |
+ } | |
+ } | |
+ | |
+@@ -279,6 +280,7 @@ func DefaultLocalSettings() map[string]interface{} { | |
+ "tabsize": float64(4), | |
+ "tabstospaces": false, | |
+ "useprimary": true, | |
++ "autocomplete": false, | |
+ } | |
+ } | |
+ | |
+--- cmd/micro/shellwords/shellwords.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/shellwords/shellwords.go | |
+@@ -87,8 +87,6 @@ loop: | |
+ backtick = "" | |
+ backQuote = !backQuote | |
+ continue | |
+- backtick = "" | |
+- backQuote = !backQuote | |
+ } | |
+ case ')': | |
+ if !singleQuoted && !doubleQuoted && !backQuote { | |
+@@ -102,8 +100,6 @@ loop: | |
+ backtick = "" | |
+ dollarQuote = !dollarQuote | |
+ continue | |
+- backtick = "" | |
+- dollarQuote = !dollarQuote | |
+ } | |
+ case '(': | |
+ if !singleQuoted && !doubleQuoted && !backQuote { | |
+--- cmd/micro/view.go.orig 2018-07-20 00:24:02 UTC | |
++++ cmd/micro/view.go | |
+@@ -102,6 +102,9 @@ type View struct { | |
+ // The scrollbar | |
+ scrollbar *ScrollBar | |
+ | |
++ // Autocomplete function | |
++ Completer *Completer | |
++ | |
+ // Virtual terminal | |
+ term *Terminal | |
+ } | |
+@@ -151,6 +154,9 @@ func NewViewWidthHeight(buf *Buffer, w, h int) *View { | |
+ } | |
+ } | |
+ | |
++ // Load the autocompleter, based on the filetype. | |
++ v.Completer = NewCompleterForView(v) | |
++ | |
+ return v | |
+ } | |
+ | |
+@@ -506,7 +512,7 @@ func (v *View) MoveToMouseClick(x, y int) { | |
+ v.Cursor.LastVisualX = v.Cursor.GetVisualX() | |
+ } | |
+ | |
+-// Execute actions executes the supplied actions | |
++// ExecuteActions executes the supplied actions | |
+ func (v *View) ExecuteActions(actions []func(*View, bool) bool) bool { | |
+ relocate := false | |
+ readonlyBindingsList := []string{"Delete", "Insert", "Backspace", "Cut", "Play", "Paste", "Move", "Add", "DuplicateLine", "Macro"} | |
+@@ -595,6 +601,12 @@ func (v *View) HandleEvent(event tcell.Event) { | |
+ } | |
+ } | |
+ case *tcell.EventKey: | |
++ // See whether the autocomplete should take over the keys. | |
++ if v.Completer.HandleEvent(e.Key()) { | |
++ // The completer has taken over the key, so break. | |
++ break | |
++ } | |
++ | |
+ // Check first if input is a key binding, if it is we 'eat' the input and don't insert a rune | |
+ isBinding := false | |
+ for key, actions := range bindings { | |
+@@ -623,7 +635,7 @@ func (v *View) HandleEvent(event tcell.Event) { | |
+ | |
+ if !isBinding && e.Key() == tcell.KeyRune { | |
+ // Check viewtype if readonly don't insert a rune (readonly help and log view etc.) | |
+- if v.Type.Readonly == false { | |
++ if !v.Type.Readonly { | |
+ for _, c := range v.Buf.cursors { | |
+ v.SetCursor(c) | |
+ | |
+@@ -641,6 +653,12 @@ func (v *View) HandleEvent(event tcell.Event) { | |
+ v.Buf.Insert(v.Cursor.Loc, string(e.Rune())) | |
+ } | |
+ | |
++ // Allow the completer to access the rune. | |
++ err := v.Completer.Process(e.Rune()) | |
++ if err != nil { | |
++ TermMessage(err) | |
++ } | |
++ | |
+ for pl := range loadedPlugins { | |
+ _, err := Call(pl+".onRune", string(e.Rune()), v) | |
+ if err != nil && !strings.HasPrefix(err.Error(), "function does not exist") { | |
+@@ -733,6 +751,10 @@ func (v *View) HandleEvent(event tcell.Event) { | |
+ // This is (hopefully) a temporary solution | |
+ v.Relocate() | |
+ } | |
++ | |
++ // Check to see whether the cursor has moved out of the autocomplete range, | |
++ // and the completer should exit. | |
++ v.Completer.DeactivateIfOutOfBounds() | |
+ } | |
+ | |
+ func (v *View) mainCursor() bool { | |
+@@ -1075,6 +1097,9 @@ func (v *View) DisplayView() { | |
+ screen.SetContent(v.x, yOffset+i, '|', nil, dividerStyle.Reverse(true)) | |
+ } | |
+ } | |
++ | |
++ // Draw the autocomplete display on top of everything. | |
++ v.Completer.Display() | |
+ } | |
+ | |
+ // ShowMultiCursor will display a cursor at a location | |
+--- runtime/help/plugins.md.orig 2018-07-20 00:24:02 UTC | |
++++ runtime/help/plugins.md | |
+@@ -185,6 +185,16 @@ The possible methods which you can call using the `mes | |
+ * `messenger.Prompt(prompt, historyType string, completionType Completion) (string, bool)` | |
+ * `messenger.AddLog(msg ...interface{})` | |
+ | |
++The Autocompleter is accessible via the `CurView().Completer` variable. To implement an autocompleter as a plugin: | |
++ | |
++* Handle the `onRune` event to decide whether to set the `Completer.Active` flag to true / false. | |
++ * This controls whether the dialog box of options is displayed. | |
++* Also set the `Completer.X` and `Completer.Y` variables. This sets the beginning of the area which would be replaced by an option selected from the autocomplete. | |
++* Add options to `Complete.Options` | |
++* Set the `ActiveIndex` - this sets the highlighed value in the autocomplete list. | |
++ | |
++This is enough to trigger the display of the option list and allow up/down selection of the options. | |
++ | |
+ #### Note | |
+ | |
+ Go function signatures use `.` and lua uses `:` so | |
-- | |
2.22.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment