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  }