github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/cmds/core/elvish/edit/history/history.go (about)

     1  package history
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  
    10  	"github.com/u-root/u-root/cmds/core/elvish/edit/eddefs"
    11  	"github.com/u-root/u-root/cmds/core/elvish/edit/ui"
    12  	"github.com/u-root/u-root/cmds/core/elvish/eval"
    13  	"github.com/u-root/u-root/cmds/core/elvish/eval/vals"
    14  	"github.com/u-root/u-root/cmds/core/elvish/eval/vars"
    15  	"github.com/u-root/u-root/cmds/core/elvish/store"
    16  	"github.com/u-root/u-root/cmds/core/elvish/util"
    17  )
    18  
    19  var logger = util.GetLogger("[edit/history] ")
    20  
    21  // Command history mode.
    22  
    23  type hist struct {
    24  	ed      eddefs.Editor
    25  	mutex   sync.RWMutex
    26  	fuser   *Fuser
    27  	binding eddefs.BindingMap
    28  
    29  	// Non-persistent state.
    30  	walker    *Walker
    31  	bufferLen int
    32  }
    33  
    34  func Init(ed eddefs.Editor, ns eval.Ns) {
    35  	hist := &hist{ed: ed, binding: eddefs.EmptyBindingMap}
    36  	fuser, err := NewFuser(store.NewCmdHistory())
    37  	if err != nil {
    38  		fmt.Fprintln(os.Stderr, "Failed to initialize command history; disabled.")
    39  	} else {
    40  		hist.fuser = fuser
    41  		ed.AddAfterReadline(hist.appendHistory)
    42  	}
    43  	hl := &histlist{}
    44  	histlistBinding := eddefs.EmptyBindingMap
    45  
    46  	historyNs := eval.Ns{
    47  		"binding": vars.FromPtr(&hist.binding),
    48  		"list":    vars.NewRo(List{&hist.mutex}),
    49  	}
    50  	historyNs.AddBuiltinFns("edit:history:", map[string]interface{}{
    51  		"start":        hist.start,
    52  		"up":           hist.up,
    53  		"down":         hist.down,
    54  		"down-or-quit": hist.downOrQuit,
    55  		"default":      hist.defaultFn,
    56  
    57  		"fast-forward": hist.fuser.FastForward,
    58  	})
    59  
    60  	histlistNs := eval.Ns{
    61  		"binding": vars.FromPtr(&histlistBinding),
    62  	}
    63  	histlistNs.AddBuiltinFns("edit:histlist:", map[string]interface{}{
    64  		"start": func() {
    65  			hl.start(ed, hist.fuser, histlistBinding)
    66  		},
    67  		"toggle-dedup":            func() { hl.toggleDedup(ed) },
    68  		"toggle-case-sensitivity": func() { hl.toggleCaseSensitivity(ed) },
    69  	})
    70  
    71  	ns.AddNs("history", historyNs)
    72  	ns.AddNs("histlist", histlistNs)
    73  	// TODO(xiaq): Rename and put in edit:history
    74  	ns.AddBuiltinFn("edit:", "command-history", hist.commandHistory)
    75  }
    76  
    77  func (h *hist) Teardown() {
    78  	h.walker = nil
    79  	h.bufferLen = 0
    80  }
    81  
    82  func (h *hist) Binding(k ui.Key) eval.Callable {
    83  	return h.binding.GetOrDefault(k)
    84  }
    85  
    86  func (h *hist) ModeLine() ui.Renderer {
    87  	return ui.NewModeLineRenderer(
    88  		fmt.Sprintf(" HISTORY #%d ", h.walker.CurrentSeq()), "")
    89  }
    90  
    91  func (h *hist) Replacement() (int, int, string) {
    92  	begin := len(h.walker.Prefix())
    93  	return begin, h.bufferLen, h.walker.CurrentCmd()[begin:]
    94  }
    95  
    96  func (hist *hist) start() {
    97  	ed := hist.ed
    98  	if hist.fuser == nil {
    99  		ed.Notify("history offline")
   100  		return
   101  	}
   102  
   103  	buffer, dot := ed.Buffer()
   104  	prefix := buffer[:dot]
   105  	walker := hist.fuser.Walker(prefix)
   106  	_, _, err := walker.Prev()
   107  
   108  	if err == nil {
   109  		hist.walker = walker
   110  		hist.bufferLen = len(buffer)
   111  		ed.SetMode(hist)
   112  	} else {
   113  		ed.AddTip("no matching history item")
   114  	}
   115  }
   116  
   117  func (hist *hist) up() {
   118  	_, _, err := hist.walker.Prev()
   119  	if err != nil {
   120  		hist.ed.Notify("%s", err)
   121  	}
   122  }
   123  
   124  func (hist *hist) down() {
   125  	_, _, err := hist.walker.Next()
   126  	if err != nil {
   127  		hist.ed.Notify("%s", err)
   128  	}
   129  }
   130  
   131  func (hist *hist) downOrQuit() {
   132  	_, _, err := hist.walker.Next()
   133  	if err != nil {
   134  		hist.ed.SetModeInsert()
   135  	}
   136  }
   137  
   138  func (hist *hist) defaultFn() {
   139  	newBuffer := hist.walker.CurrentCmd()
   140  	hist.ed.SetBuffer(newBuffer, len(newBuffer))
   141  	hist.ed.SetModeInsert()
   142  	hist.ed.SetAction(eddefs.ReprocessKey)
   143  }
   144  
   145  func (hist *hist) appendHistory(line string) {
   146  	// Do not add empty commands or commands with leading spaces to
   147  	// TODO: Make this customizable.
   148  	if line == "" || strings.HasPrefix(line, " ") {
   149  		return
   150  	}
   151  
   152  	if hist.fuser != nil {
   153  		hist.mutex.Lock()
   154  		go func() {
   155  			err := hist.fuser.AddCmd(line)
   156  			hist.mutex.Unlock()
   157  			if err != nil {
   158  				logger.Printf("Failed to AddCmd %q: %v", line, err)
   159  			}
   160  		}()
   161  	}
   162  }
   163  
   164  func (hist *hist) commandHistory(fm *eval.Frame, args ...int) {
   165  	var limit, start, end int
   166  
   167  	out := fm.OutputChan()
   168  	cmds, err := hist.fuser.AllCmds()
   169  	if err != nil {
   170  		return
   171  	}
   172  
   173  	if len(args) > 0 {
   174  		limit = args[0]
   175  	}
   176  
   177  	total := len(cmds)
   178  	switch {
   179  	case limit > 0:
   180  		start = 0
   181  		end = limit
   182  		if limit > total {
   183  			end = total
   184  		}
   185  	case limit < 0:
   186  		start = limit + total
   187  		if start < 0 {
   188  			start = 0
   189  		}
   190  		end = total
   191  	default:
   192  		start = 0
   193  		end = total
   194  	}
   195  
   196  	for i := start; i < end; i++ {
   197  		out <- vals.MakeMapFromKV(
   198  			"id", strconv.Itoa(i),
   199  			"cmd", cmds[i],
   200  		)
   201  	}
   202  }