github.com/elves/elvish@v0.15.0/pkg/edit/prompt.go (about)

     1  package edit
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"os/user"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/elves/elvish/pkg/cli"
    11  	"github.com/elves/elvish/pkg/cli/prompt"
    12  	"github.com/elves/elvish/pkg/eval"
    13  	"github.com/elves/elvish/pkg/eval/vals"
    14  	"github.com/elves/elvish/pkg/eval/vars"
    15  	"github.com/elves/elvish/pkg/fsutil"
    16  	"github.com/elves/elvish/pkg/ui"
    17  )
    18  
    19  //elvdoc:var prompt
    20  //
    21  // See [Prompts](#prompts).
    22  
    23  //elvdoc:var -prompt-eagerness
    24  //
    25  // See [Prompt Eagerness](#prompt-eagerness).
    26  
    27  //elvdoc:var prompt-stale-threshold
    28  //
    29  // See [Stale Prompt](#stale-prompt).
    30  
    31  //elvdoc:var prompt-stale-transformer.
    32  //
    33  // See [Stale Prompt](#stale-prompt).
    34  
    35  //elvdoc:var rprompt
    36  //
    37  // See [Prompts](#prompts).
    38  
    39  //elvdoc:var -rprompt-eagerness
    40  //
    41  // See [Prompt Eagerness](#prompt-eagerness).
    42  
    43  //elvdoc:var rprompt-stale-threshold
    44  //
    45  // See [Stale Prompt](#stale-prompt).
    46  
    47  //elvdoc:var rprompt-stale-transformer.
    48  //
    49  // See [Stale Prompt](#stale-prompt).
    50  
    51  //elvdoc:var rprompt-persistent
    52  //
    53  // See [RPrompt Persistency](#rprompt-persistency).
    54  
    55  func initPrompts(appSpec *cli.AppSpec, nt notifier, ev *eval.Evaler, nb eval.NsBuilder) {
    56  	promptVal, rpromptVal := getDefaultPromptVals()
    57  	initPrompt(&appSpec.Prompt, "prompt", promptVal, nt, ev, nb)
    58  	initPrompt(&appSpec.RPrompt, "rprompt", rpromptVal, nt, ev, nb)
    59  
    60  	rpromptPersistentVar := newBoolVar(false)
    61  	appSpec.RPromptPersistent = func() bool { return rpromptPersistentVar.Get().(bool) }
    62  	nb["rprompt-persistent"] = rpromptPersistentVar
    63  }
    64  
    65  func initPrompt(p *cli.Prompt, name string, val eval.Callable, nt notifier, ev *eval.Evaler, nb eval.NsBuilder) {
    66  	computeVar := vars.FromPtr(&val)
    67  	nb[name] = computeVar
    68  	eagernessVar := newIntVar(5)
    69  	nb["-"+name+"-eagerness"] = eagernessVar
    70  	staleThresholdVar := newFloatVar(0.2)
    71  	nb[name+"-stale-threshold"] = staleThresholdVar
    72  	staleTransformVar := newFnVar(
    73  		eval.NewGoFn("<default stale transform>", defaultStaleTransform))
    74  	nb[name+"-stale-transform"] = staleTransformVar
    75  
    76  	*p = prompt.New(prompt.Config{
    77  		Compute: func() ui.Text {
    78  			return callForStyledText(nt, ev, name, computeVar.Get().(eval.Callable))
    79  		},
    80  		Eagerness: func() int { return eagernessVar.GetRaw().(int) },
    81  		StaleThreshold: func() time.Duration {
    82  			seconds := staleThresholdVar.GetRaw().(float64)
    83  			return time.Duration(seconds * float64(time.Second))
    84  		},
    85  		StaleTransform: func(original ui.Text) ui.Text {
    86  			return callForStyledText(nt, ev, name+" stale transform", staleTransformVar.Get().(eval.Callable), original)
    87  		},
    88  	})
    89  }
    90  
    91  func getDefaultPromptVals() (prompt, rprompt eval.Callable) {
    92  	user, userErr := user.Current()
    93  	isRoot := userErr == nil && user.Uid == "0"
    94  
    95  	username := "???"
    96  	if userErr == nil {
    97  		username = user.Username
    98  	}
    99  	hostname, err := os.Hostname()
   100  	if err != nil {
   101  		hostname = "???"
   102  	}
   103  
   104  	return getDefaultPrompt(isRoot), getDefaultRPrompt(username, hostname)
   105  }
   106  
   107  func getDefaultPrompt(isRoot bool) eval.Callable {
   108  	p := ui.T("> ")
   109  	if isRoot {
   110  		p = ui.T("# ", ui.FgRed)
   111  	}
   112  	return eval.NewGoFn("default prompt", func() ui.Text {
   113  		return ui.Concat(ui.T(fsutil.Getwd()), p)
   114  	})
   115  }
   116  
   117  func getDefaultRPrompt(username, hostname string) eval.Callable {
   118  	rp := ui.T(username+"@"+hostname, ui.Inverse)
   119  	return eval.NewGoFn("default rprompt", func() ui.Text {
   120  		return rp
   121  	})
   122  }
   123  
   124  func defaultStaleTransform(original ui.Text) ui.Text {
   125  	return ui.StyleText(original, ui.Inverse)
   126  }
   127  
   128  // Calls a function with the given arguments and closed input, and concatenates
   129  // its outputs to a styled text. Used to call prompts and stale transformers.
   130  func callForStyledText(nt notifier, ev *eval.Evaler, ctx string, fn eval.Callable, args ...interface{}) ui.Text {
   131  	var (
   132  		result      ui.Text
   133  		resultMutex sync.Mutex
   134  	)
   135  	add := func(v interface{}) {
   136  		resultMutex.Lock()
   137  		defer resultMutex.Unlock()
   138  		newResult, err := result.Concat(v)
   139  		if err != nil {
   140  			nt.notifyf("invalid output type from prompt: %s", vals.Kind(v))
   141  		} else {
   142  			result = newResult.(ui.Text)
   143  		}
   144  	}
   145  
   146  	// Value outputs are concatenated.
   147  	valuesCb := func(ch <-chan interface{}) {
   148  		for v := range ch {
   149  			add(v)
   150  		}
   151  	}
   152  	// Byte output is added to the prompt as a single unstyled text.
   153  	bytesCb := func(r *os.File) {
   154  		allBytes, err := ioutil.ReadAll(r)
   155  		if err != nil {
   156  			nt.notifyf("error reading prompt byte output: %v", err)
   157  		}
   158  		if len(allBytes) > 0 {
   159  			add(ui.ParseSGREscapedText(string(allBytes)))
   160  		}
   161  	}
   162  
   163  	port1, done1, err := eval.PipePort(valuesCb, bytesCb)
   164  	if err != nil {
   165  		nt.notifyf("cannot create pipe for prompt: %v", err)
   166  		return nil
   167  	}
   168  	port2, done2 := makeNotifyPort(nt)
   169  
   170  	err = ev.Call(fn,
   171  		eval.CallCfg{Args: args, From: "[" + ctx + "]"},
   172  		eval.EvalCfg{Ports: []*eval.Port{nil, port1, port2}})
   173  	done1()
   174  	done2()
   175  
   176  	if err != nil {
   177  		nt.notifyError(ctx, err)
   178  	}
   179  	return result
   180  }