github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/readline/tabgrid.go (about) 1 package readline 2 3 import ( 4 "fmt" 5 "strconv" 6 7 "github.com/mattn/go-runewidth" 8 ) 9 10 func (rl *Instance) initTabGrid() { 11 rl.tabMutex.Lock() 12 defer rl.tabMutex.Unlock() 13 14 var suggestions *suggestionsT 15 if rl.modeTabFind { 16 suggestions = newSuggestionsT(rl, rl.tfSuggestions) 17 } else { 18 suggestions = newSuggestionsT(rl, rl.tcSuggestions) 19 } 20 21 rl.tcMaxLength = rl.MinTabItemLength 22 23 for i := 0; i < suggestions.Len(); i++ { 24 l := suggestions.ItemLen(i) 25 if l > rl.tcMaxLength { 26 rl.tcMaxLength = l 27 } 28 } 29 30 if rl.tcMaxLength > rl.MaxTabItemLength && rl.MaxTabItemLength > 0 && rl.MaxTabItemLength > rl.MinTabItemLength { 31 rl.tcMaxLength = rl.MaxTabItemLength 32 } 33 if rl.tcMaxLength == 0 { 34 rl.tcMaxLength = 20 35 } 36 37 rl.tcPosX = 1 38 rl.tcPosY = 1 39 rl.tcMaxX = rl.termWidth / (rl.tcMaxLength + 2) 40 rl.tcOffset = 0 41 42 // avoid a divide by zero error 43 if rl.tcMaxX < 1 { 44 rl.tcMaxX = 1 45 } 46 47 rl.tcMaxY = rl.MaxTabCompleterRows 48 49 // pre-cache 50 max := rl.tcMaxX * rl.tcMaxY 51 if max > len(rl.tcSuggestions) { 52 max = len(rl.tcSuggestions) 53 } 54 subset := rl.tcSuggestions[:max] 55 56 if rl.tcr.HintCache == nil { 57 return 58 } 59 60 go rl.tabHintCache(subset) 61 } 62 63 func (rl *Instance) tabHintCache(subset []string) { 64 hints := rl.tcr.HintCache(rl.tcPrefix, subset) 65 if len(hints) != len(subset) { 66 return 67 } 68 69 rl.tabMutex.Lock() 70 for i := range subset { 71 rl.tcDescriptions[subset[i]] = hints[i] 72 } 73 rl.tabMutex.Unlock() 74 75 } 76 77 func (rl *Instance) moveTabGridHighlight(x, y int) { 78 rl.tabMutex.Lock() 79 defer rl.tabMutex.Unlock() 80 81 var suggestions *suggestionsT 82 if rl.modeTabFind { 83 suggestions = newSuggestionsT(rl, rl.tfSuggestions) 84 } else { 85 suggestions = newSuggestionsT(rl, rl.tcSuggestions) 86 } 87 88 rl.tcPosX += x 89 rl.tcPosY += y 90 91 if rl.tcPosX < 1 { 92 rl.tcPosX = rl.tcMaxX 93 rl.tcPosY-- 94 } 95 96 if rl.tcPosX > rl.tcMaxX { 97 rl.tcPosX = 1 98 rl.tcPosY++ 99 } 100 101 if rl.tcPosY < 1 { 102 rl.tcPosY = rl.tcUsedY 103 } 104 105 if rl.tcPosY > rl.tcUsedY { 106 rl.tcPosY = 1 107 } 108 109 if rl.tcPosY == rl.tcUsedY && (rl.tcMaxX*(rl.tcPosY-1))+rl.tcPosX > suggestions.Len() { 110 if x < 0 { 111 rl.tcPosX = suggestions.Len() - (rl.tcMaxX * (rl.tcPosY - 1)) 112 } 113 114 if x > 0 { 115 rl.tcPosX = 1 116 rl.tcPosY = 1 117 } 118 119 if y < 0 { 120 rl.tcPosY-- 121 } 122 123 if y > 0 { 124 rl.tcPosY = 1 125 } 126 } 127 } 128 129 func (rl *Instance) writeTabGridStr() string { 130 rl.tabMutex.Lock() 131 defer rl.tabMutex.Unlock() 132 133 var suggestions *suggestionsT 134 if rl.modeTabFind { 135 suggestions = newSuggestionsT(rl, rl.tfSuggestions) 136 } else { 137 suggestions = newSuggestionsT(rl, rl.tcSuggestions) 138 } 139 140 iCellWidth := (rl.termWidth / rl.tcMaxX) - 2 141 cellWidth := strconv.Itoa(iCellWidth) 142 143 x := 0 144 y := 1 145 rl.previewItem = "" 146 var output string 147 148 for i := 0; i < suggestions.Len(); i++ { 149 x++ 150 if x > rl.tcMaxX { 151 x = 1 152 y++ 153 if y > rl.tcMaxY { 154 y-- 155 break 156 } else { 157 output += "\r\n" 158 } 159 } 160 161 if x == rl.tcPosX && y == rl.tcPosY { 162 output += seqBgWhite + seqFgBlack 163 rl.previewItem = suggestions.ItemValue(i) 164 } 165 166 value := suggestions.ItemValue(i) 167 caption := cropCaption(value, rl.tcMaxLength, iCellWidth) 168 if caption != value { 169 rl.tcDescriptions[suggestions.ItemLookupValue(i)] = value 170 } 171 172 output += fmt.Sprintf(" %-"+cellWidth+"s %s", caption, seqReset) 173 } 174 175 rl.tcUsedY = y 176 177 return output 178 } 179 180 func cropCaption(caption string, tcMaxLength int, iCellWidth int) string { 181 switch { 182 case iCellWidth == 0: 183 // this condition shouldn't ever happen but lets cover it just in case 184 return "" 185 186 case runewidth.StringWidth(caption) != len(caption): 187 // string length != rune width. So lets not do anything too clever 188 //return runewidth.Truncate(caption, iCellWidth, "…") 189 return runeWidthTruncate(caption, iCellWidth) 190 191 case len(caption) < tcMaxLength, 192 len(caption) < 5, 193 len(caption) <= iCellWidth: 194 return caption 195 196 case len(caption)-iCellWidth+6 < 1: 197 // truncate the end 198 return caption[:iCellWidth-1] + "…" 199 200 case len(caption) > 5+len(caption)-iCellWidth+6: 201 // truncate long lines in the middle 202 return caption[:5] + "…" + caption[len(caption)-iCellWidth+6:] 203 204 default: 205 // edge case reached. lets truncate the most conservative way we can, 206 // just in case 207 return runewidth.Truncate(caption, iCellWidth, "…") 208 } 209 }