Skip to content

History Sources

maxlandon edited this page May 29, 2023 · 8 revisions

Introduction

The console uses the underlying readline history mechanism for providing command history in your application.

  • Each menu has a distinct list of command history sources. You can add as many sources as you want.
  • By default, each menu comes with an in-memory command history, wiped when the application exits.
  • The behavior for history search/completion and movements can be customized with the readline keymaps configuration.

The console library offers a method to create a history source from a file, shown below. Note that by default, the in-memory history source is not removed in the call below: the following appends to the list of histories for the given menu.

func main() {
    menu := app.CurrentMenu()
        
    // "local history" is the title of the history, used when completing/searching it.
    menu.AddHistorySourceFile("file history", "/path/to/.example-history")

    // Add a custom source, like the one implemented in the next section
    menu.AddHistorySource("custom history", new(ClientHistory))
}

Custom sources

Taking the menu example from the menus page, we here consider that our application has a local (main) menu, and a remote (client) menu, which might be used when connecting to a remote host. Therefore, we might want to have a history source with a file located on this host.

For this, readline offers the History interface, which allows you to implement a custom history:

type History interface {
	// Append takes the line and returns an updated number of lines or an error
	Write(string) (int, error)

	// GetLine takes the historic line number and returns the line or an error
	GetLine(int) (string, error)

	// Len returns the number of history lines
	Len() int

	// Dump returns everything in readline. The return is an interface{} because
	// not all LineHistory implementations will want to structure the history in
	// the same way. And since Dump() is not actually used by the readline API
	// internally, this methods return can be structured in whichever way is most
	// convenient for your own applications (or even just create an empty
	//function which returns `nil` if you don't require Dump() either)
	Dump() interface{}
}

An example implementation using gRPC could look like this:

// ClientHistory - Writes and queries only the Client's history
type ClientHistory struct {
	LinesSinceStart int // Keeps count of line since session
	items           []string
}

// Write - Sends the last command to the server for saving
func (h *ClientHistory) Write(s string) (int, error) {

	res, err := transport.RPC.AddToHistory(context.Background(),
		&clientpb.AddCmdHistoryRequest{Line: s})
	if err != nil {
		return 0, err
	}

	// The server sent us back the whole user history,
	// so we give it to the user history (the latter never
	// actually uses its Write() method.
	UserHist.cache = res.Lines

	h.items = append(h.items, s)
	return len(h.items), nil
}

// GetLine returns a line from history
func (h *ClientHistory) GetLine(i int) (string, error) {
	if len(h.items) == 0 {
		return "", nil
	}
	return h.items[i], nil
}

// Len returns the number of lines in history
func (h *ClientHistory) Len() int {
	return len(h.items)
}

// Dump returns the entire history
func (h *ClientHistory) Dump() interface{} {
	return h.items
}