func executeCommand(template string, current string) { command := strings.Replace(template, "{}", fmt.Sprintf("%q", current), -1) cmd := exec.Command("sh", "-c", command) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr C.Endwin() cmd.Run() C.Refresh() }
func (t *Terminal) executeCommand(template string, items []*Item) { command := replacePlaceholder(template, t.ansi, t.delimiter, string(t.input), items) cmd := util.ExecCommand(command) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr C.Endwin() cmd.Run() t.refresh() }
func executeCommand(template string, replacement string) { command := strings.Replace(template, "{}", replacement, -1) cmd := util.ExecCommand(command) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr C.Endwin() cmd.Run() C.Refresh() }
func executeCommand(template string, replacement string) { command := strings.Replace(template, "{}", replacement, -1) shell := os.Getenv("SHELL") if len(shell) == 0 { shell = "sh" } cmd := exec.Command(shell, "-c", command) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr C.Endwin() cmd.Run() C.Refresh() }
// Loop is called to start Terminal I/O func (t *Terminal) Loop() { <-t.startChan { // Late initialization intChan := make(chan os.Signal, 1) signal.Notify(intChan, os.Interrupt, os.Kill) go func() { <-intChan t.reqBox.Set(reqQuit, nil) }() resizeChan := make(chan os.Signal, 1) signal.Notify(resizeChan, syscall.SIGWINCH) go func() { for { <-resizeChan t.reqBox.Set(reqRedraw, nil) } }() t.mutex.Lock() t.initFunc() t.calculateMargins() t.printPrompt() t.placeCursor() C.Refresh() t.printInfo() t.printHeader() t.mutex.Unlock() go func() { timer := time.NewTimer(initialDelay) <-timer.C t.reqBox.Set(reqRefresh, nil) }() // Keep the spinner spinning go func() { for { t.mutex.Lock() reading := t.reading t.mutex.Unlock() if !reading { break } time.Sleep(spinnerDuration) t.reqBox.Set(reqInfo, nil) } }() } exit := func(code int) { if code <= exitNoMatch && t.history != nil { t.history.append(string(t.input)) } os.Exit(code) } go func() { for { t.reqBox.Wait(func(events *util.Events) { defer events.Clear() t.mutex.Lock() for req := range *events { switch req { case reqPrompt: t.printPrompt() if t.inlineInfo { t.printInfo() } case reqInfo: t.printInfo() case reqList: t.printList() case reqHeader: t.printHeader() case reqRefresh: t.suppress = false case reqRedraw: C.Clear() C.Endwin() C.Refresh() t.printAll() case reqClose: C.Close() if t.output() { exit(exitOk) } exit(exitNoMatch) case reqQuit: C.Close() exit(exitInterrupt) } } t.placeCursor() t.mutex.Unlock() }) t.refresh() } }() looping := true for looping { event := C.GetChar() t.mutex.Lock() previousInput := t.input events := []util.EventType{reqPrompt} req := func(evts ...util.EventType) { for _, event := range evts { events = append(events, event) if event == reqClose || event == reqQuit { looping = false } } } selectItem := func(item *Item) bool { if _, found := t.selected[item.index]; !found { t.selected[item.index] = selectedItem{time.Now(), item.StringPtr(t.ansi)} return true } return false } toggleY := func(y int) { item := t.merger.Get(y) if !selectItem(item) { delete(t.selected, item.index) } } toggle := func() { if t.cy < t.merger.Length() { toggleY(t.cy) req(reqInfo) } } for key, ret := range t.expect { if keyMatch(key, event) { t.pressed = ret req(reqClose) break } } var doAction func(actionType, int) bool doAction = func(action actionType, mapkey int) bool { switch action { case actIgnore: case actExecute: if t.cy >= 0 && t.cy < t.merger.Length() { item := t.merger.Get(t.cy) executeCommand(t.execmap[mapkey], item.AsString(t.ansi)) } case actInvalid: t.mutex.Unlock() return false case actToggleSort: t.sort = !t.sort t.eventBox.Set(EvtSearchNew, t.sort) t.mutex.Unlock() return false case actBeginningOfLine: t.cx = 0 case actBackwardChar: if t.cx > 0 { t.cx-- } case actAbort: req(reqQuit) case actDeleteChar: t.delChar() case actDeleteCharEOF: if !t.delChar() && t.cx == 0 { req(reqQuit) } case actEndOfLine: t.cx = len(t.input) case actCancel: if len(t.input) == 0 { req(reqQuit) } else { t.yanked = t.input t.input = []rune{} t.cx = 0 } case actForwardChar: if t.cx < len(t.input) { t.cx++ } case actBackwardDeleteChar: if t.cx > 0 { t.input = append(t.input[:t.cx-1], t.input[t.cx:]...) t.cx-- } case actSelectAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { item := t.merger.Get(i) selectItem(item) } req(reqList, reqInfo) } case actDeselectAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { item := t.merger.Get(i) delete(t.selected, item.index) } req(reqList, reqInfo) } case actToggle: if t.multi && t.merger.Length() > 0 { toggle() req(reqList) } case actToggleAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { toggleY(i) } req(reqList, reqInfo) } case actToggleDown: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(-1) req(reqList) } case actToggleUp: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(1) req(reqList) } case actDown: t.vmove(-1) req(reqList) case actUp: t.vmove(1) req(reqList) case actAccept: req(reqClose) case actClearScreen: req(reqRedraw) case actUnixLineDiscard: if t.cx > 0 { t.yanked = copySlice(t.input[:t.cx]) t.input = t.input[t.cx:] t.cx = 0 } case actUnixWordRubout: if t.cx > 0 { t.rubout("\\s\\S") } case actBackwardKillWord: if t.cx > 0 { t.rubout("[^[:alnum:]][[:alnum:]]") } case actYank: suffix := copySlice(t.input[t.cx:]) t.input = append(append(t.input[:t.cx], t.yanked...), suffix...) t.cx += len(t.yanked) case actPageUp: t.vmove(t.maxItems() - 1) req(reqList) case actPageDown: t.vmove(-(t.maxItems() - 1)) req(reqList) case actBackwardWord: t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1 case actForwardWord: t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 case actKillWord: ncx := t.cx + findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 if ncx > t.cx { t.yanked = copySlice(t.input[t.cx:ncx]) t.input = append(t.input[:t.cx], t.input[ncx:]...) } case actKillLine: if t.cx < len(t.input) { t.yanked = copySlice(t.input[t.cx:]) t.input = t.input[:t.cx] } case actRune: prefix := copySlice(t.input[:t.cx]) t.input = append(append(prefix, event.Char), t.input[t.cx:]...) t.cx++ case actPreviousHistory: if t.history != nil { t.history.override(string(t.input)) t.input = []rune(t.history.previous()) t.cx = len(t.input) } case actNextHistory: if t.history != nil { t.history.override(string(t.input)) t.input = []rune(t.history.next()) t.cx = len(t.input) } case actMouse: me := event.MouseEvent mx, my := me.X, me.Y if me.S != 0 { // Scroll if t.merger.Length() > 0 { if t.multi && me.Mod { toggle() } t.vmove(me.S) req(reqList) } } else if mx >= t.marginInt[3] && mx < C.MaxX()-t.marginInt[1] && my >= t.marginInt[0] && my < C.MaxY()-t.marginInt[2] { mx -= t.marginInt[3] my -= t.marginInt[0] mx = util.Constrain(mx-len(t.prompt), 0, len(t.input)) if !t.reverse { my = t.maxHeight() - my - 1 } min := 2 + len(t.header) if t.inlineInfo { min-- } if me.Double { // Double-click if my >= min { if t.vset(t.offset+my-min) && t.cy < t.merger.Length() { return doAction(t.keymap[C.DoubleClick], C.DoubleClick) } } } else if me.Down { if my == 0 && mx >= 0 { // Prompt t.cx = mx } else if my >= min { // List if t.vset(t.offset+my-min) && t.multi && me.Mod { toggle() } req(reqList) } } } } return true } action := t.keymap[event.Type] mapkey := event.Type if event.Type == C.Rune { mapkey = int(event.Char) + int(C.AltZ) if act, prs := t.keymap[mapkey]; prs { action = act } } if !doAction(action, mapkey) { continue } changed := string(previousInput) != string(t.input) t.mutex.Unlock() // Must be unlocked before touching reqBox if changed { t.eventBox.Set(EvtSearchNew, t.sort) } for _, event := range events { t.reqBox.Set(event, nil) } } }
// Loop is called to start Terminal I/O func (t *Terminal) Loop() { <-t.startChan { // Late initialization t.mutex.Lock() t.initFunc() t.printPrompt() t.placeCursor() C.Refresh() t.printInfo() t.mutex.Unlock() go func() { timer := time.NewTimer(initialDelay) <-timer.C t.reqBox.Set(reqRefresh, nil) }() resizeChan := make(chan os.Signal, 1) signal.Notify(resizeChan, syscall.SIGWINCH) go func() { for { <-resizeChan t.reqBox.Set(reqRedraw, nil) } }() } go func() { for { t.reqBox.Wait(func(events *util.Events) { defer events.Clear() t.mutex.Lock() for req := range *events { switch req { case reqPrompt: t.printPrompt() case reqInfo: t.printInfo() case reqList: t.printList() case reqRefresh: t.suppress = false case reqRedraw: C.Clear() C.Endwin() C.Refresh() t.printAll() case reqClose: C.Close() t.output() os.Exit(0) case reqQuit: C.Close() os.Exit(1) } } t.placeCursor() t.mutex.Unlock() }) t.refresh() } }() looping := true for looping { event := C.GetChar() t.mutex.Lock() previousInput := t.input events := []util.EventType{reqPrompt} req := func(evts ...util.EventType) { for _, event := range evts { events = append(events, event) if event == reqClose || event == reqQuit { looping = false } } } toggle := func() { if t.cy < t.merger.Length() { item := t.merger.Get(t.cy) if _, found := t.selected[item.text]; !found { var strptr *string if item.origText != nil { strptr = item.origText } else { strptr = item.text } t.selected[item.text] = selectedItem{time.Now(), strptr} } else { delete(t.selected, item.text) } req(reqInfo) } } switch event.Type { case C.Invalid: t.mutex.Unlock() continue case C.CtrlA: t.cx = 0 case C.CtrlB: if t.cx > 0 { t.cx-- } case C.CtrlC, C.CtrlG, C.CtrlQ, C.ESC: req(reqQuit) case C.CtrlD: if !t.delChar() && t.cx == 0 { req(reqQuit) } case C.CtrlE: t.cx = len(t.input) case C.CtrlF: if t.cx < len(t.input) { t.cx++ } case C.CtrlH: if t.cx > 0 { t.input = append(t.input[:t.cx-1], t.input[t.cx:]...) t.cx-- } case C.Tab: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(-1) req(reqList) } case C.BTab: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(1) req(reqList) } case C.CtrlJ, C.CtrlN: t.vmove(-1) req(reqList) case C.CtrlK, C.CtrlP: t.vmove(1) req(reqList) case C.CtrlM: req(reqClose) case C.CtrlL: req(reqRedraw) case C.CtrlU: if t.cx > 0 { t.yanked = copySlice(t.input[:t.cx]) t.input = t.input[t.cx:] t.cx = 0 } case C.CtrlW: if t.cx > 0 { t.rubout("\\s\\S") } case C.AltBS: if t.cx > 0 { t.rubout("[^[:alnum:]][[:alnum:]]") } case C.CtrlY: suffix := copySlice(t.input[t.cx:]) t.input = append(append(t.input[:t.cx], t.yanked...), suffix...) t.cx += len(t.yanked) case C.Del: t.delChar() case C.PgUp: t.vmove(maxItems() - 1) req(reqList) case C.PgDn: t.vmove(-(maxItems() - 1)) req(reqList) case C.AltB: t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1 case C.AltF: t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 case C.AltD: ncx := t.cx + findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 if ncx > t.cx { t.yanked = copySlice(t.input[t.cx:ncx]) t.input = append(t.input[:t.cx], t.input[ncx:]...) } case C.Rune: prefix := copySlice(t.input[:t.cx]) t.input = append(append(prefix, event.Char), t.input[t.cx:]...) t.cx++ case C.Mouse: me := event.MouseEvent mx, my := util.Constrain(me.X-len(t.prompt), 0, len(t.input)), me.Y if !t.reverse { my = C.MaxY() - my - 1 } if me.S != 0 { // Scroll if t.merger.Length() > 0 { if t.multi && me.Mod { toggle() } t.vmove(me.S) req(reqList) } } else if me.Double { // Double-click if my >= 2 { if t.vset(my-2) && t.cy < t.merger.Length() { req(reqClose) } } } else if me.Down { if my == 0 && mx >= 0 { // Prompt t.cx = mx } else if my >= 2 { // List if t.vset(t.offset+my-2) && t.multi && me.Mod { toggle() } req(reqList) } } } changed := string(previousInput) != string(t.input) t.mutex.Unlock() // Must be unlocked before touching reqBox if changed { t.eventBox.Set(EvtSearchNew, nil) } for _, event := range events { t.reqBox.Set(event, nil) } } }
// Loop is called to start Terminal I/O func (t *Terminal) Loop() { // prof := profile.Start(profile.ProfilePath("/tmp/")) <-t.startChan { // Late initialization intChan := make(chan os.Signal, 1) signal.Notify(intChan, os.Interrupt, os.Kill, syscall.SIGTERM) go func() { <-intChan t.reqBox.Set(reqQuit, nil) }() resizeChan := make(chan os.Signal, 1) signal.Notify(resizeChan, syscall.SIGWINCH) go func() { for { <-resizeChan t.reqBox.Set(reqRedraw, nil) } }() t.mutex.Lock() t.initFunc() t.resizeWindows() t.printPrompt() t.placeCursor() t.refresh() t.printInfo() t.printHeader() t.mutex.Unlock() go func() { timer := time.NewTimer(t.initDelay) <-timer.C t.reqBox.Set(reqRefresh, nil) }() // Keep the spinner spinning go func() { for { t.mutex.Lock() reading := t.reading t.mutex.Unlock() if !reading { break } time.Sleep(spinnerDuration) t.reqBox.Set(reqInfo, nil) } }() } if t.hasPreviewWindow() { go func() { for { var request *Item t.previewBox.Wait(func(events *util.Events) { for req, value := range *events { switch req { case reqPreviewEnqueue: request = value.(*Item) } } events.Clear() }) if request != nil { command := replacePlaceholder(t.preview.command, t.ansi, t.delimiter, string(t.input), []*Item{request}) cmd := util.ExecCommand(command) out, _ := cmd.CombinedOutput() t.reqBox.Set(reqPreviewDisplay, string(out)) } else { t.reqBox.Set(reqPreviewDisplay, "") } } }() } exit := func(code int) { if code <= exitNoMatch && t.history != nil { t.history.append(string(t.input)) } // prof.Stop() os.Exit(code) } go func() { var focused *Item for { t.reqBox.Wait(func(events *util.Events) { defer events.Clear() t.mutex.Lock() for req, value := range *events { switch req { case reqPrompt: t.printPrompt() if t.inlineInfo { t.printInfo() } case reqInfo: t.printInfo() case reqList: t.printList() cnt := t.merger.Length() var currentFocus *Item if cnt > 0 && cnt > t.cy { currentFocus = t.currentItem() } else { currentFocus = nil } if currentFocus != focused { focused = currentFocus if t.isPreviewEnabled() { t.previewBox.Set(reqPreviewEnqueue, focused) } } case reqJump: if t.merger.Length() == 0 { t.jumping = jumpDisabled } t.printList() case reqHeader: t.printHeader() case reqRefresh: t.suppress = false case reqRedraw: C.Clear() C.Endwin() C.Refresh() t.printAll() case reqClose: C.Close() if t.output() { exit(exitOk) } exit(exitNoMatch) case reqPreviewDisplay: t.previewer.text = value.(string) t.previewer.lines = strings.Count(t.previewer.text, "\n") t.previewer.offset = 0 t.printPreview() case reqPreviewRefresh: t.printPreview() case reqPrintQuery: C.Close() t.printer(string(t.input)) exit(exitOk) case reqQuit: C.Close() exit(exitInterrupt) } } t.placeCursor() t.mutex.Unlock() }) t.refresh() } }() looping := true for looping { event := C.GetChar() t.mutex.Lock() previousInput := t.input events := []util.EventType{reqPrompt} req := func(evts ...util.EventType) { for _, event := range evts { events = append(events, event) if event == reqClose || event == reqQuit { looping = false } } } selectItem := func(item *Item) bool { if _, found := t.selected[item.Index()]; !found { t.selected[item.Index()] = selectedItem{time.Now(), item} return true } return false } toggleY := func(y int) { item := t.merger.Get(y).item if !selectItem(item) { delete(t.selected, item.Index()) } } toggle := func() { if t.cy < t.merger.Length() { toggleY(t.cy) req(reqInfo) } } scrollPreview := func(amount int) { t.previewer.offset = util.Constrain( t.previewer.offset+amount, 0, t.previewer.lines-1) req(reqPreviewRefresh) } for key, ret := range t.expect { if keyMatch(key, event) { t.pressed = ret t.reqBox.Set(reqClose, nil) t.mutex.Unlock() return } } var doAction func(actionType, int) bool doAction = func(action actionType, mapkey int) bool { switch action { case actIgnore: case actExecute: if t.cy >= 0 && t.cy < t.merger.Length() { t.executeCommand(t.execmap[mapkey], []*Item{t.currentItem()}) } case actExecuteMulti: if len(t.selected) > 0 { sels := make([]*Item, len(t.selected)) for i, sel := range t.sortSelected() { sels[i] = sel.item } t.executeCommand(t.execmap[mapkey], sels) } else { return doAction(actExecute, mapkey) } case actInvalid: t.mutex.Unlock() return false case actTogglePreview: if t.hasPreviewWindow() { t.previewer.enabled = !t.previewer.enabled t.resizeWindows() cnt := t.merger.Length() if t.previewer.enabled && cnt > 0 && cnt > t.cy { t.previewBox.Set(reqPreviewEnqueue, t.currentItem()) } req(reqList, reqInfo) } case actToggleSort: t.sort = !t.sort t.eventBox.Set(EvtSearchNew, t.sort) t.mutex.Unlock() return false case actPreviewUp: if t.isPreviewEnabled() { scrollPreview(-1) } case actPreviewDown: if t.isPreviewEnabled() { scrollPreview(1) } case actPreviewPageUp: if t.isPreviewEnabled() { scrollPreview(-t.pwindow.Height) } case actPreviewPageDown: if t.isPreviewEnabled() { scrollPreview(t.pwindow.Height) } case actBeginningOfLine: t.cx = 0 case actBackwardChar: if t.cx > 0 { t.cx-- } case actPrintQuery: req(reqPrintQuery) case actAbort: req(reqQuit) case actDeleteChar: t.delChar() case actDeleteCharEOF: if !t.delChar() && t.cx == 0 { req(reqQuit) } case actEndOfLine: t.cx = len(t.input) case actCancel: if len(t.input) == 0 { req(reqQuit) } else { t.yanked = t.input t.input = []rune{} t.cx = 0 } case actForwardChar: if t.cx < len(t.input) { t.cx++ } case actBackwardDeleteChar: if t.cx > 0 { t.input = append(t.input[:t.cx-1], t.input[t.cx:]...) t.cx-- } case actSelectAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { item := t.merger.Get(i).item selectItem(item) } req(reqList, reqInfo) } case actDeselectAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { item := t.merger.Get(i) delete(t.selected, item.Index()) } req(reqList, reqInfo) } case actToggle: if t.multi && t.merger.Length() > 0 { toggle() req(reqList) } case actToggleAll: if t.multi { for i := 0; i < t.merger.Length(); i++ { toggleY(i) } req(reqList, reqInfo) } case actToggleIn: if t.reverse { return doAction(actToggleUp, mapkey) } return doAction(actToggleDown, mapkey) case actToggleOut: if t.reverse { return doAction(actToggleDown, mapkey) } return doAction(actToggleUp, mapkey) case actToggleDown: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(-1) req(reqList) } case actToggleUp: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(1) req(reqList) } case actDown: t.vmove(-1) req(reqList) case actUp: t.vmove(1) req(reqList) case actAccept: req(reqClose) case actClearScreen: req(reqRedraw) case actUnixLineDiscard: if t.cx > 0 { t.yanked = copySlice(t.input[:t.cx]) t.input = t.input[t.cx:] t.cx = 0 } case actUnixWordRubout: if t.cx > 0 { t.rubout("\\s\\S") } case actBackwardKillWord: if t.cx > 0 { t.rubout("[^[:alnum:]][[:alnum:]]") } case actYank: suffix := copySlice(t.input[t.cx:]) t.input = append(append(t.input[:t.cx], t.yanked...), suffix...) t.cx += len(t.yanked) case actPageUp: t.vmove(t.maxItems() - 1) req(reqList) case actPageDown: t.vmove(-(t.maxItems() - 1)) req(reqList) case actJump: t.jumping = jumpEnabled req(reqJump) case actJumpAccept: t.jumping = jumpAcceptEnabled req(reqJump) case actBackwardWord: t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1 case actForwardWord: t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 case actKillWord: ncx := t.cx + findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 if ncx > t.cx { t.yanked = copySlice(t.input[t.cx:ncx]) t.input = append(t.input[:t.cx], t.input[ncx:]...) } case actKillLine: if t.cx < len(t.input) { t.yanked = copySlice(t.input[t.cx:]) t.input = t.input[:t.cx] } case actRune: prefix := copySlice(t.input[:t.cx]) t.input = append(append(prefix, event.Char), t.input[t.cx:]...) t.cx++ case actPreviousHistory: if t.history != nil { t.history.override(string(t.input)) t.input = []rune(t.history.previous()) t.cx = len(t.input) } case actNextHistory: if t.history != nil { t.history.override(string(t.input)) t.input = []rune(t.history.next()) t.cx = len(t.input) } case actMouse: me := event.MouseEvent mx, my := me.X, me.Y if me.S != 0 { // Scroll if t.window.Enclose(my, mx) && t.merger.Length() > 0 { if t.multi && me.Mod { toggle() } t.vmove(me.S) req(reqList) } else if t.isPreviewEnabled() && t.pwindow.Enclose(my, mx) { scrollPreview(-me.S) } } else if t.window.Enclose(my, mx) { mx -= t.window.Left my -= t.window.Top mx = util.Constrain(mx-displayWidth([]rune(t.prompt)), 0, len(t.input)) if !t.reverse { my = t.window.Height - my - 1 } min := 2 + len(t.header) if t.inlineInfo { min-- } if me.Double { // Double-click if my >= min { if t.vset(t.offset+my-min) && t.cy < t.merger.Length() { return doAction(t.keymap[C.DoubleClick], C.DoubleClick) } } } else if me.Down { if my == 0 && mx >= 0 { // Prompt t.cx = mx } else if my >= min { // List if t.vset(t.offset+my-min) && t.multi && me.Mod { toggle() } req(reqList) } } } } return true } changed := false mapkey := event.Type if t.jumping == jumpDisabled { action := t.keymap[mapkey] if mapkey == C.Rune { mapkey = int(event.Char) + int(C.AltZ) if act, prs := t.keymap[mapkey]; prs { action = act } } if !doAction(action, mapkey) { continue } // Truncate the query if it's too long if len(t.input) > maxPatternLength { t.input = t.input[:maxPatternLength] t.cx = util.Constrain(t.cx, 0, maxPatternLength) } changed = string(previousInput) != string(t.input) } else { if mapkey == C.Rune { if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() { t.cy = idx + t.offset if t.jumping == jumpAcceptEnabled { req(reqClose) } } } t.jumping = jumpDisabled req(reqList) } t.mutex.Unlock() // Must be unlocked before touching reqBox if changed { t.eventBox.Set(EvtSearchNew, t.sort) } for _, event := range events { t.reqBox.Set(event, nil) } } }