github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/readline/preview.go (about) 1 package readline 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 ) 8 9 type previewModeT int 10 type previewRefT int 11 12 const ( 13 previewModeClosed previewModeT = 0 14 previewModeOpen previewModeT = 1 15 previewModeAutocomplete previewModeT = 2 16 17 previewRefDefault previewRefT = 0 18 previewRefLine previewRefT = 1 19 ) 20 21 const ( 22 previewHeadingHeight = 3 23 previewPromptHSpace = 3 24 ) 25 26 const ( 27 boxTL = "┏" 28 boxTR = "┓" 29 boxBL = "┗" 30 boxBR = "┛" 31 boxH = "━" 32 boxV = "┃" 33 boxVL = "┠" 34 boxVR = "┨" 35 ) 36 37 const ( 38 headingTL = "╔" 39 headingTR = "╗" 40 headingBL = "╚" 41 headingBR = "╝" 42 headingH = "═" 43 headingV = "║" 44 headingVL = "╟" 45 headingVR = "╢" 46 ) 47 48 const ( 49 glyphScrollUp = "▲" 50 glyphScrollRail = "■" 51 glyphScrollBar = "▣" 52 glyphScrollDown = "▼" 53 ) 54 55 func getScrollBarPos(height, pos, max int) int { 56 height -= 2 57 58 return (height / max) * pos 59 } 60 61 func getPreviewWidth(width int) (preview, forward int) { 62 preview = width - 3 63 64 forward = width - preview 65 forward -= 2 66 return 67 } 68 69 type PreviewSizeT struct { 70 Height int 71 Width int 72 Forward int 73 } 74 75 type previewCacheT struct { 76 item string 77 pos int 78 len int 79 lines []string 80 size *PreviewSizeT 81 } 82 83 func (rl *Instance) getPreviewXY() (*PreviewSizeT, error) { 84 width, height, err := GetSize(int(os.Stdout.Fd())) 85 if err != nil { 86 return nil, err 87 } 88 89 if height == 0 { 90 height = 25 91 } 92 93 if width == 0 { 94 width = 80 95 } 96 97 preview, forward := getPreviewWidth(width) 98 size := &PreviewSizeT{ 99 Height: height - rl.MaxTabCompleterRows - 10, // hintText, multi-line prompts, etc 100 Width: preview, 101 Forward: forward, 102 } 103 104 return size, nil 105 } 106 107 func (rl *Instance) writePreviewStr() string { 108 if rl.previewMode == previewModeClosed { 109 rl.previewCache = nil 110 return "" 111 } 112 113 if rl.previewCancel != nil { 114 rl.previewCancel() 115 } 116 117 var fn PreviewFuncT 118 if rl.previewRef == previewRefLine { 119 fn = rl.PreviewLine 120 } else { 121 if rl.tcr == nil { 122 rl.previewCache = nil 123 return "" 124 } 125 fn = rl.tcr.Preview 126 } 127 128 if fn == nil { 129 rl.previewCache = nil 130 return "" 131 } 132 133 size, err := rl.getPreviewXY() 134 if err != nil || size.Height < 8 || size.Width < 40 { 135 rl.previewCache = nil 136 return "" 137 } 138 139 item := rl.previewItem 140 item = strings.ReplaceAll(item, "\\", "") 141 item = strings.TrimSpace(item) 142 143 go delayedPreviewTimer(rl, fn, size, item) 144 145 return "" 146 } 147 148 const ( 149 curHome = "\x1b[H" 150 curPosSave = "\x1b[s" 151 curPosRestore = "\x1b[u" 152 ) 153 154 func (rl *Instance) previewDrawStr(preview []string, size *PreviewSizeT) (string, error) { 155 var output string 156 157 pf := fmt.Sprintf("%s%%-%ds%s\r\n", boxV, size.Width, boxV) 158 pj := fmt.Sprintf("%s%%-%ds%s\r\n", boxVL, size.Width, boxVR) 159 160 output += curHome 161 162 output += fmt.Sprintf(cursorForwf, size.Forward) 163 hr := strings.Repeat(headingH, size.Width) 164 output += headingTL + hr + headingTR + "\r\n " 165 output += headingV + rl.previewTitleStr(size.Width) + headingV + "\r\n " 166 output += headingBL + hr + headingBR + "\r\n " 167 168 hr = strings.Repeat(boxH, size.Width) 169 output += boxTL + hr + boxTR + "\r\n" 170 171 for i := 0; i <= size.Height; i++ { 172 output += fmt.Sprintf(cursorForwf, size.Forward) 173 174 if i >= len(preview) { 175 blank := strings.Repeat(" ", size.Width) 176 output += boxV + blank + boxV + "\r\n" 177 continue 178 } 179 180 if strings.HasPrefix(preview[i], "─") { 181 output += fmt.Sprintf(pj, preview[i]) 182 } else { 183 output += fmt.Sprintf(pf, preview[i]) 184 } 185 } 186 187 output += fmt.Sprintf(cursorForwf, size.Forward) 188 output += boxBL + hr + boxBR + "\r\n" 189 190 output += rl.previewMoveToPromptStr(size) 191 return output, nil 192 } 193 194 func (rl *Instance) previewTitleStr(width int) string { 195 var title string 196 197 if rl.previewRef == previewRefDefault { 198 title = " Autocomplete Preview" + title 199 } else { 200 title = " Command Line Preview" + title 201 } 202 title += " | [F1] to exit | [ENTER] to commit" 203 204 l := len(title) + 1 205 switch { 206 case l > width: 207 return title[:width-2] + "… " 208 case l == width: 209 return title + " " 210 default: 211 return title + strings.Repeat(" ", width-l+1) 212 } 213 } 214 215 func (rl *Instance) previewMoveToPromptStr(size *PreviewSizeT) string { 216 output := curHome 217 output += moveCursorDownStr(size.Height + previewPromptHSpace + previewHeadingHeight) 218 output += rl.moveCursorFromStartToLinePosStr() 219 return output 220 } 221 222 func (rl *Instance) previewPageUpStr() string { 223 if rl.previewCache == nil { 224 return "" 225 } 226 227 rl.previewCache.pos -= rl.previewCache.len 228 if rl.previewCache.pos < 0 { 229 rl.previewCache.pos = 0 230 } 231 232 output, _ := rl.previewDrawStr(rl.previewCache.lines[rl.previewCache.pos:], rl.previewCache.size) 233 return output 234 } 235 236 func (rl *Instance) previewPageDownStr() string { 237 if rl.previewCache == nil { 238 return "" 239 } 240 241 rl.previewCache.pos += rl.previewCache.len 242 if rl.previewCache.pos > len(rl.previewCache.lines)-rl.previewCache.len-1 { 243 rl.previewCache.pos = len(rl.previewCache.lines) - rl.previewCache.len - 1 244 if rl.previewCache.pos < 0 { 245 rl.previewCache.pos = 0 246 } 247 } 248 249 output, _ := rl.previewDrawStr(rl.previewCache.lines[rl.previewCache.pos:], rl.previewCache.size) 250 return output 251 } 252 253 func (rl *Instance) clearPreviewStr() string { 254 var output string 255 256 if rl.previewMode > previewModeClosed { 257 output = seqRestoreBuffer + curPosRestore 258 output += rl.echoStr() 259 rl.previewMode = previewModeClosed 260 rl.previewRef = previewRefDefault 261 } 262 263 return output 264 }