package coreimport ()const ( keyScanBufSize = 1024)// Stdin is used by the Keys struct to read and write keys.// It can be overwritten to use other file descriptors or// custom io.Readers, such as the one used on Windows.varStdinio.ReadCloser = os.Stdinvar rxRcvCursorPos = regexp.MustCompile(`\x1b\[([0-9]+);([0-9]+)R`)// Keys is used read, manage and use keys input by the shell user.typeKeysstruct { buf []byte// Keys read and waiting to be used. matched []rune// Keys that have been successfully matched against a bind. macroKeys []rune// Keys that have been fed by a macro. mustWait bool// Keys are in the stack, but we must still read stdin. waiting bool// Currently waiting for keys on stdin. reading bool// Currently reading keys out of the main loop. keysOnce chan []byte// Passing keys from the main routine. cursor chan []byte// Cursor coordinates has been read on stdin. resize chanbool// Resize events on Windows are sent on stdin. USED IN WINDOWS cfg *inputrc.Config// Configuration file used for meta key settings mutex sync.RWMutex// Concurrency safety}// WaitAvailableKeys waits until an input key is either read from standard input,// or directly returns if the key stack still/already has available keys.func ( *Keys, *inputrc.Config) { .cfg = iflen(.buf) > 0 && !.mustWait {return }// The macro engine might have fed some keysiflen(.macroKeys) > 0 {return } .mutex.Lock() .waiting = true .cursor = make(chan []byte) .mutex.Unlock()deferfunc() { .mutex.Lock() .waiting = false .mutex.Unlock() }()for {// Start reading from os.Stdin in the background. // We will either read keyBuf from user, or an EOF // send by ourselves, because we pause reading. , := .readInputFiltered()if != nil && errors.Is(, io.EOF) {return }iflen() == 0 {continue }switch {case .reading: .keysOnce <- continuedefault:// When convert-meta is on, any meta-prefixed bind should // be stripped and replaced with an escape meta instead.if .cfg != nil && .cfg.GetBool("convert-meta") { = []byte(strutil.ConvertMeta([]rune(string()))) } .mutex.RLock() .buf = append(.buf, ...) .mutex.RUnlock() }return }}// PopKey is used to pop a key off the key stack without// yet marking this key as having matched a bind command.func ( *Keys) ( byte, bool) {switch {caselen(.buf) > 0: = .buf[0] .buf = .buf[1:]caselen(.macroKeys) > 0: = byte(.macroKeys[0]) .macroKeys = .macroKeys[1:]default:returnbyte(0), true }return , false}// PeekKey returns the first key in the stack, without removing it.func ( *Keys) ( byte, bool) {switch {caselen(.buf) > 0: = .buf[0]caselen(.macroKeys) > 0: = byte(.macroKeys[0])default:returnbyte(0), true }return , false}// MatchedKeys is used to indicate how many keys have been evaluated against the shell// commands in the dispatching process (regardless of if a command was matched or not).// This function should normally not be used by external users of the library.func ( *Keys, []byte, ...byte) {iflen() > 0 { .matched = []rune(string()) }iflen() > 0 { .buf = append(, .buf...) } .mustWait = false}// MatchedPrefix is similar to MatchedKeys, except that the provided keys// should not be flushed, since they only matched some binds by prefix and// that we need more keys for an exact match (or failure).func ( *Keys, ...byte) {iflen() == 0 {return } .mutex.Lock()defer .mutex.Unlock()// Our keys are still considered unread, but they have been: // if there is no more keys in the stack, the next blocking // call to WaitAvailableKeys() should block for new keys. .mustWait = len(.buf) == 0 .buf = append(, .buf...) .matched = []rune(string())}// PopForce is used to force-remove a key from the buffer, without marking// it as having matched a bind command. This is used, for example, when the// escape has been handled specially as a Vim escape.func ( *Keys) ( byte, bool) {switch {caselen(.buf) > 0: = .buf[0] .buf = .buf[1:]caselen(.macroKeys) > 0: = byte(.macroKeys[0]) .macroKeys = .macroKeys[1:]default:returnbyte(0), true }// Force the macro recorder to use the matched keys. .mustWait = falsereturn , false}// MacroKeys returns the keys that have matched a given command, and thus can be recorded// as a part of the current macro. This function is different from keys.Caller() in that it// won't return keys that have only matched a prefix, to avoid recording them twice.func ( *Keys) []rune {if .mustWait {returnnil }return .matched}// FlushUsed drops the keys that have matched a given command.func ( *Keys) { .mutex.Lock() .matched = nildefer .mutex.Unlock()}// ReadKey reads keys from stdin like Read(), but immediately// returns them instead of storing them in the stack, along with// an indication on whether this key is an escape/abort one.func ( *Keys) () ( rune, bool) { .mutex.RLock() .keysOnce = make(chan []byte) .reading = true .mutex.RUnlock()deferfunc() { .mutex.RLock() .reading = false .mutex.RUnlock() }()switch {caselen(.macroKeys) > 0: = .macroKeys[0] .macroKeys = .macroKeys[1:]case .waiting: := <-.keysOnce = []rune(string())[0]default: , := .readInputFiltered() = []rune(string())[0] }// Always mark those keys as matched, so that // if the macro engine is recording, it will // capture them .matched = append(.matched, )return , == inputrc.Esc}// Pop removes the first byte in the key stack (first read) and returns it.// It returns either a key and the empty boolean set to false, or if no keys// are present, returns a zero rune and empty set to true.// The key bytes returned by this function are not those that have been// matched against the current command. The keys returned here are only// keys that have not yet been dispatched. (ex: di" will match vim delete-to,// then select-inside, but the quote won't match a command and will be passed// to select-inside. This function Pop() will thus return the quote.)func ( *Keys) () ( byte, bool) {switch {caselen(.buf) > 0: = .buf[0] .buf = .buf[1:]caselen(.macroKeys) > 0: = byte(.macroKeys[0]) .macroKeys = .macroKeys[1:]default:returnbyte(0), true } .matched = append(.matched, rune())return , false}// Caller returns the keys that have matched the command currently being ran.func ( *Keys) () ( []rune) {return .matched}// Feed can be used to directly add keys to the stack.// If begin is true, the keys are added on the top of// the stack, otherwise they are being appended to it.func ( *Keys) ( bool, ...rune) {iflen() == 0 {return } := []rune(string()) .mutex.Lock()defer .mutex.Unlock()if { .macroKeys = append(, .macroKeys...) } else { .macroKeys = append(.macroKeys, ...) }}func ( *Keys) ( []byte) (, []byte) {if !rxRcvCursorPos.Match() {return , } := rxRcvCursorPos.FindAll(, -1) = [len()-1] = rxRcvCursorPos.ReplaceAll(, nil)return}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.