func (t *Terminal) calculateMargins() { screenWidth := C.MaxX() screenHeight := C.MaxY() for idx, str := range t.margin { if str == "0" { t.marginInt[idx] = 0 } else if strings.HasSuffix(str, "%") { num, _ := strconv.ParseFloat(str[:len(str)-1], 64) var val float64 if idx%2 == 0 { val = float64(screenHeight) } else { val = float64(screenWidth) } t.marginInt[idx] = int(val * num * 0.01) } else { num, _ := strconv.Atoi(str) t.marginInt[idx] = num } } adjust := func(idx1 int, idx2 int, max int, min int) { if max >= min { margin := t.marginInt[idx1] + t.marginInt[idx2] if max-margin < min { desired := max - min t.marginInt[idx1] = desired * t.marginInt[idx1] / margin t.marginInt[idx2] = desired * t.marginInt[idx2] / margin } } } adjust(1, 3, screenWidth, minWidth) adjust(0, 2, screenHeight, minHeight) }
func (t *Terminal) move(y int, x int, clear bool) { x += t.marginInt[3] maxy := C.MaxY() if !t.reverse { y = maxy - y - 1 - t.marginInt[2] } else { y += t.marginInt[0] } if clear { C.MoveAndClear(y, x) } else { C.Move(y, x) } }
// 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, 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.calculateMargins() t.printPrompt() t.placeCursor() C.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) } }() } 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], quoteEntry(item.AsString(t.ansi))) } case actExecuteMulti: if len(t.selected) > 0 { sels := make([]string, len(t.selected)) for i, sel := range t.sortSelected() { sels[i] = quoteEntry(*sel.text) } executeCommand(t.execmap[mapkey], strings.Join(sels, " ")) } else { return doAction(actExecute, mapkey) } 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 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 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-displayWidth([]rune(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) } } }
func (t *Terminal) maxHeight() int { return C.MaxY() - t.marginInt[0] - t.marginInt[2] }