github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/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 "src.elv.sh/pkg/cli" 11 "src.elv.sh/pkg/cli/prompt" 12 "src.elv.sh/pkg/eval" 13 "src.elv.sh/pkg/eval/vals" 14 "src.elv.sh/pkg/eval/vars" 15 "src.elv.sh/pkg/fsutil" 16 "src.elv.sh/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 }