func calculateSize(base int, size sizeSpec, margin int, minSize int) int { max := base - margin if size.percent { return util.Constrain(int(float64(base)*0.01*size.size), minSize, max) } return util.Constrain(int(size.size), minSize, max) }
func (t *Terminal) constrain() { count := t.merger.Length() height := t.maxItems() diffpos := t.cy - t.offset t.cy = util.Constrain(t.cy, 0, count-1) t.offset = util.Constrain(t.offset, t.cy-height+1, t.cy) // Adjustment if count-t.offset < height { t.offset = util.Max(0, count-height) t.cy = util.Constrain(t.offset+diffpos, 0, count-1) } t.offset = util.Max(0, t.offset) }
func (t *Terminal) constrain() { count := t.merger.Length() height := C.MaxY() - 2 diffpos := t.cy - t.offset t.cy = util.Constrain(t.cy, 0, count-1) if t.cy > t.offset+(height-1) { // Ceil t.offset = t.cy - (height - 1) } else if t.offset > t.cy { // Floor t.offset = t.cy } // Adjustment if count-t.offset < height { t.offset = util.Max(0, count-height) t.cy = util.Constrain(t.offset+diffpos, 0, count-1) } }
// 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) } } }
func (t *Terminal) vset(o int) bool { t.cy = util.Constrain(o, 0, t.merger.Length()-1) return t.cy == o }
func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool) { item := result.item // Overflow text := make([]rune, item.text.Length()) copy(text, item.text.ToRunes()) matchOffsets := []Offset{} var pos *[]int if t.merger.pattern != nil { _, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab) } charOffsets := matchOffsets if pos != nil { charOffsets = make([]Offset, len(*pos)) for idx, p := range *pos { offset := Offset{int32(p), int32(p + 1)} charOffsets[idx] = offset } sort.Sort(ByOrder(charOffsets)) } var maxe int for _, offset := range charOffsets { maxe = util.Max(maxe, int(offset[1])) } offsets := result.colorOffsets(charOffsets, t.theme, col2, attr, current) maxWidth := t.window.Width - 3 maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) if overflow(text, maxWidth) { if t.hscroll { // Stri.. if !overflow(text[:maxe], maxWidth-2) { text, _ = trimRight(text, maxWidth-2) text = append(text, []rune("..")...) } else { // Stri.. if overflow(text[maxe:], 2) { text = append(text[:maxe], []rune("..")...) } // ..ri.. var diff int32 text, diff = trimLeft(text, maxWidth-2) // Transform offsets for idx, offset := range offsets { b, e := offset.offset[0], offset.offset[1] b += 2 - diff e += 2 - diff b = util.Max32(b, 2) offsets[idx].offset[0] = b offsets[idx].offset[1] = util.Max32(b, e) } text = append([]rune(".."), text...) } } else { text, _ = trimRight(text, maxWidth-2) text = append(text, []rune("..")...) for idx, offset := range offsets { offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2)) offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth)) } } } var index int32 var substr string var prefixWidth int maxOffset := int32(len(text)) for _, offset := range offsets { b := util.Constrain32(offset.offset[0], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset) substr, prefixWidth = processTabs(text[index:b], prefixWidth) t.window.CPrint(col1, attr, substr) if b < e { substr, prefixWidth = processTabs(text[b:e], prefixWidth) t.window.CPrint(offset.color, offset.attr, substr) } index = e if index >= maxOffset { break } } if index < maxOffset { substr, _ = processTabs(text[index:], prefixWidth) t.window.CPrint(col1, attr, substr) } }
// 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) notifyOnResize(resizeChan) // Non-portable 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: tui.Clear() tui.Refresh() t.printAll() case reqClose: tui.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: tui.Close() t.printer(string(t.input)) exit(exitOk) case reqQuit: tui.Close() exit(exitInterrupt) } } t.placeCursor() t.mutex.Unlock() }) t.refresh() } }() looping := true for looping { event := tui.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, reqHeader) } 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[tui.DoubleClick], tui.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 == tui.Rune { mapkey = int(event.Char) + int(tui.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 == tui.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) } } }
// 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) } } }
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) { var maxe int for _, offset := range item.offsets { maxe = util.Max(maxe, int(offset[1])) } // Overflow text := make([]rune, len(item.text)) copy(text, item.text) offsets := item.colorOffsets(col2, bold, current) maxWidth := C.MaxX() - 3 - t.marginInt[1] - t.marginInt[3] maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text)) fullWidth := displayWidth(text) if fullWidth > maxWidth { if t.hscroll { // Stri.. matchEndWidth := displayWidth(text[:maxe]) if matchEndWidth <= maxWidth-2 { text, _ = trimRight(text, maxWidth-2) text = append(text, []rune("..")...) } else { // Stri.. if matchEndWidth < fullWidth-2 { text = append(text[:maxe], []rune("..")...) } // ..ri.. var diff int32 text, diff = trimLeft(text, maxWidth-2) // Transform offsets for idx, offset := range offsets { b, e := offset.offset[0], offset.offset[1] b += 2 - diff e += 2 - diff b = util.Max32(b, 2) offsets[idx].offset[0] = b offsets[idx].offset[1] = util.Max32(b, e) } text = append([]rune(".."), text...) } } else { text, _ = trimRight(text, maxWidth-2) text = append(text, []rune("..")...) for idx, offset := range offsets { offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2)) offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth)) } } } var index int32 var substr string var prefixWidth int maxOffset := int32(len(text)) for _, offset := range offsets { b := util.Constrain32(offset.offset[0], index, maxOffset) e := util.Constrain32(offset.offset[1], index, maxOffset) substr, prefixWidth = processTabs(text[index:b], prefixWidth) C.CPrint(col1, bold, substr) if b < e { substr, prefixWidth = processTabs(text[b:e], prefixWidth) C.CPrint(offset.color, offset.bold, substr) } index = e if index >= maxOffset { break } } if index < maxOffset { substr, _ = processTabs(text[index:], prefixWidth) C.CPrint(col1, bold, substr) } }