github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/readline/history.go (about)

     1  package readline
     2  
     3  import (
     4  	"errors"
     5  	"strconv"
     6  	"strings"
     7  )
     8  
     9  // History is an interface to allow you to write your own history logging
    10  // tools. eg sqlite backend instead of a file system.
    11  // By default readline will just use the dummyLineHistory interface which only
    12  // logs the history to memory ([]string to be precise).
    13  type History interface {
    14  	// Append takes the line and returns an updated number of lines or an error
    15  	Write(string) (int, error)
    16  
    17  	// GetLine takes the historic line number and returns the line or an error
    18  	GetLine(int) (string, error)
    19  
    20  	// Len returns the number of history lines
    21  	Len() int
    22  
    23  	// Dump returns everything in readline. The return is an interface{} because
    24  	// not all LineHistory implementations will want to structure the history in
    25  	// the same way. And since Dump() is not actually used by the readline API
    26  	// internally, this methods return can be structured in whichever way is most
    27  	// convenient for your own applications (or even just create an empty
    28  	//function which returns `nil` if you don't require Dump() either)
    29  	Dump() interface{}
    30  }
    31  
    32  // ExampleHistory is an example of a LineHistory interface:
    33  type ExampleHistory struct {
    34  	items []string
    35  }
    36  
    37  // Write to history
    38  func (h *ExampleHistory) Write(s string) (int, error) {
    39  	h.items = append(h.items, s)
    40  	return len(h.items), nil
    41  }
    42  
    43  // GetLine returns a line from history
    44  func (h *ExampleHistory) GetLine(i int) (string, error) {
    45  	switch {
    46  	case i < 0:
    47  		return "", errors.New("requested history item out of bounds: < 0")
    48  	case i > h.Len()-1:
    49  		return "", errors.New("requested history item out of bounds: > Len()")
    50  	default:
    51  		return h.items[i], nil
    52  	}
    53  }
    54  
    55  // Len returns the number of lines in history
    56  func (h *ExampleHistory) Len() int {
    57  	return len(h.items)
    58  }
    59  
    60  // Dump returns the entire history
    61  func (h *ExampleHistory) Dump() interface{} {
    62  	return h.items
    63  }
    64  
    65  // NullHistory is a null History interface for when you don't want line
    66  // entries remembered eg password input.
    67  type NullHistory struct{}
    68  
    69  // Write to history
    70  func (h *NullHistory) Write(s string) (int, error) {
    71  	return 0, nil
    72  }
    73  
    74  // GetLine returns a line from history
    75  func (h *NullHistory) GetLine(i int) (string, error) {
    76  	return "", nil
    77  }
    78  
    79  // Len returns the number of lines in history
    80  func (h *NullHistory) Len() int {
    81  	return 0
    82  }
    83  
    84  // Dump returns the entire history
    85  func (h *NullHistory) Dump() interface{} {
    86  	return []string{}
    87  }
    88  
    89  // Browse historic lines
    90  func (rl *Instance) walkHistory(i int) {
    91  	if rl.previewRef == previewRefLine {
    92  		return // don't walk history if [f9] preview open
    93  	}
    94  
    95  	line := rl.line.String()
    96  	line = strings.TrimSpace(line)
    97  	rl._walkHistory(i, line)
    98  }
    99  
   100  func (rl *Instance) _walkHistory(i int, oldLine string) {
   101  	var (
   102  		newLine string
   103  		err     error
   104  	)
   105  
   106  	switch rl.histPos + i {
   107  	case -1, rl.History.Len() + 1:
   108  		return
   109  
   110  	case rl.History.Len():
   111  		rl.clearPrompt()
   112  		rl.histPos += i
   113  		if len(rl.viUndoHistory) > 0 && rl.viUndoHistory[len(rl.viUndoHistory)-1].String() != "" {
   114  			rl.line = rl.lineBuf.Duplicate()
   115  		}
   116  
   117  	default:
   118  		newLine, err = rl.History.GetLine(rl.histPos + i)
   119  		if err != nil {
   120  			rl.resetHelpers()
   121  			print("\r\n" + err.Error() + "\r\n")
   122  			print(rl.prompt)
   123  			return
   124  		}
   125  
   126  		if rl.histPos-i == rl.History.Len() {
   127  			rl.lineBuf = rl.line.Duplicate()
   128  		}
   129  
   130  		rl.histPos += i
   131  		if oldLine == newLine {
   132  			rl._walkHistory(i, newLine)
   133  			return
   134  		}
   135  
   136  		if len(rl.viUndoHistory) > 0 {
   137  			last := rl.viUndoHistory[len(rl.viUndoHistory)-1]
   138  			if !strings.HasPrefix(newLine, strings.TrimSpace(last.String())) {
   139  				rl._walkHistory(i, oldLine)
   140  				return
   141  			}
   142  		}
   143  
   144  		rl.clearPrompt()
   145  
   146  		rl.line = new(UnicodeT)
   147  		rl.line.Set(rl, []rune(newLine))
   148  	}
   149  
   150  	if i > 0 {
   151  		_, y := rl.lineWrapCellLen()
   152  		print(strings.Repeat("\r\n", y))
   153  		rl.line.SetRunePos(rl.line.RuneLen())
   154  	} else {
   155  		rl.line.SetCellPos(rl.termWidth - rl.promptLen - 1)
   156  	}
   157  	print(rl.echoStr())
   158  	print(rl.updateHelpersStr())
   159  }
   160  
   161  func (rl *Instance) autocompleteHistory() ([]string, map[string]string) {
   162  	if rl.AutocompleteHistory != nil {
   163  		rl.tcPrefix = rl.line.String()
   164  		return rl.AutocompleteHistory(rl.tcPrefix)
   165  	}
   166  
   167  	var (
   168  		items []string
   169  		descs = make(map[string]string)
   170  
   171  		line string
   172  		num  string
   173  		err  error
   174  	)
   175  
   176  	rl.tcPrefix = rl.line.String()
   177  	for i := rl.History.Len() - 1; i >= 0; i-- {
   178  		line, err = rl.History.GetLine(i)
   179  		if err != nil {
   180  			continue
   181  		}
   182  
   183  		if !strings.HasPrefix(line, rl.tcPrefix) {
   184  			continue
   185  		}
   186  
   187  		line = strings.Replace(line, "\n", ` `, -1)[rl.line.RuneLen():]
   188  
   189  		if descs[line] != "" {
   190  			continue
   191  		}
   192  
   193  		items = append(items, line)
   194  		num = strconv.Itoa(i)
   195  
   196  		descs[line] = num
   197  	}
   198  
   199  	return items, descs
   200  }