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  }