completion.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. package edit
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. "reflect"
  7. "strings"
  8. "sync"
  9. "unicode/utf8"
  10. "src.elv.sh/pkg/cli"
  11. "src.elv.sh/pkg/cli/modes"
  12. "src.elv.sh/pkg/cli/tk"
  13. "src.elv.sh/pkg/edit/complete"
  14. "src.elv.sh/pkg/eval"
  15. "src.elv.sh/pkg/eval/errs"
  16. "src.elv.sh/pkg/eval/vals"
  17. "src.elv.sh/pkg/eval/vars"
  18. "src.elv.sh/pkg/parse"
  19. "src.elv.sh/pkg/persistent/hash"
  20. "src.elv.sh/pkg/strutil"
  21. "src.elv.sh/pkg/ui"
  22. )
  23. type complexCandidateOpts struct {
  24. CodeSuffix string
  25. Display any
  26. }
  27. func (*complexCandidateOpts) SetDefaultOptions() {}
  28. func complexCandidate(fm *eval.Frame, opts complexCandidateOpts, stem string) (complexItem, error) {
  29. var display ui.Text
  30. switch displayOpt := opts.Display.(type) {
  31. case nil:
  32. // Leave display = nil
  33. case string:
  34. display = ui.T(displayOpt)
  35. case ui.Text:
  36. display = displayOpt
  37. default:
  38. return complexItem{}, errs.BadValue{What: "&display",
  39. Valid: "string or styled", Actual: vals.ReprPlain(displayOpt)}
  40. }
  41. return complexItem{
  42. Stem: stem,
  43. CodeSuffix: opts.CodeSuffix,
  44. Display: display,
  45. }, nil
  46. }
  47. func completionStart(app cli.App, bindings tk.Bindings, ev *eval.Evaler, cfg complete.Config, smart bool) {
  48. codeArea, ok := focusedCodeArea(app)
  49. if !ok {
  50. return
  51. }
  52. buf := codeArea.CopyState().Buffer
  53. result, err := complete.Complete(
  54. complete.CodeBuffer{Content: buf.Content, Dot: buf.Dot}, ev, cfg)
  55. if err != nil {
  56. app.Notify(modes.ErrorText(err))
  57. return
  58. }
  59. if smart {
  60. prefix := ""
  61. for i, item := range result.Items {
  62. if i == 0 {
  63. prefix = item.ToInsert
  64. continue
  65. }
  66. prefix = commonPrefix(prefix, item.ToInsert)
  67. if prefix == "" {
  68. break
  69. }
  70. }
  71. if prefix != "" {
  72. insertedPrefix := false
  73. codeArea.MutateState(func(s *tk.CodeAreaState) {
  74. rep := s.Buffer.Content[result.Replace.From:result.Replace.To]
  75. if len(prefix) > len(rep) && strings.HasPrefix(prefix, rep) {
  76. s.Pending = tk.PendingCode{
  77. Content: prefix,
  78. From: result.Replace.From, To: result.Replace.To}
  79. s.ApplyPending()
  80. insertedPrefix = true
  81. }
  82. })
  83. if insertedPrefix {
  84. return
  85. }
  86. }
  87. }
  88. w, err := modes.NewCompletion(app, modes.CompletionSpec{
  89. Name: result.Name, Replace: result.Replace, Items: result.Items,
  90. Filter: filterSpec, Bindings: bindings,
  91. })
  92. if w != nil {
  93. app.PushAddon(w)
  94. }
  95. if err != nil {
  96. app.Notify(modes.ErrorText(err))
  97. }
  98. }
  99. func initCompletion(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
  100. bindingVar := newBindingVar(emptyBindingsMap)
  101. bindings := newMapBindings(ed, ev, bindingVar)
  102. matcherMapVar := newMapVar(vals.EmptyMap)
  103. argGeneratorMapVar := newMapVar(vals.EmptyMap)
  104. cfg := func() complete.Config {
  105. return complete.Config{
  106. Filterer: adaptMatcherMap(
  107. ed, ev, matcherMapVar.Get().(vals.Map)),
  108. ArgGenerator: adaptArgGeneratorMap(
  109. ev, argGeneratorMapVar.Get().(vals.Map)),
  110. }
  111. }
  112. generateForSudo := func(args []string) ([]complete.RawItem, error) {
  113. return complete.GenerateForSudo(args, ev, cfg())
  114. }
  115. nb.AddGoFns(map[string]any{
  116. "complete-filename": wrapArgGenerator(complete.GenerateFileNames),
  117. "complete-getopt": completeGetopt,
  118. "complete-sudo": wrapArgGenerator(generateForSudo),
  119. "complex-candidate": complexCandidate,
  120. "match-prefix": wrapMatcher(strings.HasPrefix),
  121. "match-subseq": wrapMatcher(strutil.HasSubseq),
  122. "match-substr": wrapMatcher(strings.Contains),
  123. })
  124. app := ed.app
  125. nb.AddNs("completion",
  126. eval.BuildNsNamed("edit:completion").
  127. AddVars(map[string]vars.Var{
  128. "arg-completer": argGeneratorMapVar,
  129. "binding": bindingVar,
  130. "matcher": matcherMapVar,
  131. }).
  132. AddGoFns(map[string]any{
  133. "accept": func() { listingAccept(app) },
  134. "smart-start": func() { completionStart(app, bindings, ev, cfg(), true) },
  135. "start": func() { completionStart(app, bindings, ev, cfg(), false) },
  136. "up": func() { listingUp(app) },
  137. "down": func() { listingDown(app) },
  138. "up-cycle": func() { listingUpCycle(app) },
  139. "down-cycle": func() { listingDownCycle(app) },
  140. "left": func() { listingLeft(app) },
  141. "right": func() { listingRight(app) },
  142. }))
  143. }
  144. // A wrapper type implementing Elvish value methods.
  145. type complexItem complete.ComplexItem
  146. func (c complexItem) Index(k any) (any, bool) {
  147. switch k {
  148. case "stem":
  149. return c.Stem, true
  150. case "code-suffix":
  151. return c.CodeSuffix, true
  152. case "display":
  153. return c.Display, true
  154. }
  155. return nil, false
  156. }
  157. func (c complexItem) IterateKeys(f func(any) bool) {
  158. vals.Feed(f, "stem", "code-suffix", "display")
  159. }
  160. func (c complexItem) Kind() string { return "map" }
  161. func (c complexItem) Equal(a any) bool {
  162. rhs, ok := a.(complexItem)
  163. return ok && c.Stem == rhs.Stem &&
  164. c.CodeSuffix == rhs.CodeSuffix && reflect.DeepEqual(c.Display, rhs.Display)
  165. }
  166. func (c complexItem) Hash() uint32 {
  167. h := hash.DJBInit
  168. h = hash.DJBCombine(h, hash.String(c.Stem))
  169. h = hash.DJBCombine(h, hash.String(c.CodeSuffix))
  170. // TODO: Add c.Display
  171. return h
  172. }
  173. func (c complexItem) Repr(indent int) string {
  174. // TODO(xiaq): Pretty-print when indent >= 0
  175. return fmt.Sprintf("(edit:complex-candidate %s &code-suffix=%s &display=%s)",
  176. parse.Quote(c.Stem), parse.Quote(c.CodeSuffix), vals.Repr(c.Display, indent+1))
  177. }
  178. type wrappedArgGenerator func(*eval.Frame, ...string) error
  179. // Wraps an ArgGenerator into a function that can be then passed to
  180. // eval.NewGoFn.
  181. func wrapArgGenerator(gen complete.ArgGenerator) wrappedArgGenerator {
  182. return func(fm *eval.Frame, args ...string) error {
  183. rawItems, err := gen(args)
  184. if err != nil {
  185. return err
  186. }
  187. out := fm.ValueOutput()
  188. for _, rawItem := range rawItems {
  189. var v any
  190. switch rawItem := rawItem.(type) {
  191. case complete.ComplexItem:
  192. v = complexItem(rawItem)
  193. case complete.PlainItem:
  194. v = string(rawItem)
  195. default:
  196. v = rawItem
  197. }
  198. err := out.Put(v)
  199. if err != nil {
  200. return err
  201. }
  202. }
  203. return nil
  204. }
  205. }
  206. func commonPrefix(s1, s2 string) string {
  207. for i, r := range s1 {
  208. if s2 == "" {
  209. break
  210. }
  211. r2, n2 := utf8.DecodeRuneInString(s2)
  212. if r2 != r {
  213. return s1[:i]
  214. }
  215. s2 = s2[n2:]
  216. }
  217. return s1
  218. }
  219. // The type for a native Go matcher. This is not equivalent to the Elvish
  220. // counterpart, which streams input and output. This is because we can actually
  221. // afford calling a Go function for each item, so omitting the streaming
  222. // behavior makes the implementation simpler.
  223. //
  224. // Native Go matchers are wrapped into Elvish matchers, but never the other way
  225. // around.
  226. //
  227. // This type is satisfied by strings.Contains and strings.HasPrefix; they are
  228. // wrapped into match-substr and match-prefix respectively.
  229. type matcher func(text, seed string) bool
  230. type matcherOpts struct {
  231. IgnoreCase bool
  232. SmartCase bool
  233. }
  234. func (*matcherOpts) SetDefaultOptions() {}
  235. type wrappedMatcher func(fm *eval.Frame, opts matcherOpts, seed string, inputs eval.Inputs) error
  236. func wrapMatcher(m matcher) wrappedMatcher {
  237. return func(fm *eval.Frame, opts matcherOpts, seed string, inputs eval.Inputs) error {
  238. out := fm.ValueOutput()
  239. var errOut error
  240. if opts.IgnoreCase || (opts.SmartCase && seed == strings.ToLower(seed)) {
  241. if opts.IgnoreCase {
  242. seed = strings.ToLower(seed)
  243. }
  244. inputs(func(v any) {
  245. if errOut != nil {
  246. return
  247. }
  248. errOut = out.Put(m(strings.ToLower(vals.ToString(v)), seed))
  249. })
  250. } else {
  251. inputs(func(v any) {
  252. if errOut != nil {
  253. return
  254. }
  255. errOut = out.Put(m(vals.ToString(v), seed))
  256. })
  257. }
  258. return errOut
  259. }
  260. }
  261. // Adapts $edit:completion:matcher into a Filterer.
  262. func adaptMatcherMap(nt notifier, ev *eval.Evaler, m vals.Map) complete.Filterer {
  263. return func(ctxName, seed string, rawItems []complete.RawItem) []complete.RawItem {
  264. matcher, ok := lookupFn(m, ctxName)
  265. if !ok {
  266. nt.notifyf(
  267. "matcher for %s not a function, falling back to prefix matching", ctxName)
  268. }
  269. if matcher == nil {
  270. return complete.FilterPrefix(ctxName, seed, rawItems)
  271. }
  272. input := make(chan any)
  273. stopInputFeeder := make(chan struct{})
  274. defer close(stopInputFeeder)
  275. // Feed a string representing all raw candidates to the input channel.
  276. go func() {
  277. defer close(input)
  278. for _, rawItem := range rawItems {
  279. select {
  280. case input <- rawItem.String():
  281. case <-stopInputFeeder:
  282. return
  283. }
  284. }
  285. }()
  286. // TODO: Supply the Chan component of port 2.
  287. port1, collect, err := eval.CapturePort()
  288. if err != nil {
  289. nt.notifyf("cannot create pipe to run completion matcher: %v", err)
  290. return nil
  291. }
  292. err = ev.Call(matcher,
  293. eval.CallCfg{Args: []any{seed}, From: "[editor matcher]"},
  294. eval.EvalCfg{Ports: []*eval.Port{
  295. // TODO: Supply the Chan component of port 2.
  296. {Chan: input, File: eval.DevNull}, port1, {File: os.Stderr}}})
  297. outputs := collect()
  298. if err != nil {
  299. nt.notifyError("matcher", err)
  300. // Continue with whatever values have been output
  301. }
  302. if len(outputs) != len(rawItems) {
  303. nt.notifyf(
  304. "matcher has output %v values, not equal to %v inputs",
  305. len(outputs), len(rawItems))
  306. }
  307. filtered := []complete.RawItem{}
  308. for i := 0; i < len(rawItems) && i < len(outputs); i++ {
  309. if vals.Bool(outputs[i]) {
  310. filtered = append(filtered, rawItems[i])
  311. }
  312. }
  313. return filtered
  314. }
  315. }
  316. func adaptArgGeneratorMap(ev *eval.Evaler, m vals.Map) complete.ArgGenerator {
  317. return func(args []string) ([]complete.RawItem, error) {
  318. gen, ok := lookupFn(m, args[0])
  319. if !ok {
  320. return nil, fmt.Errorf("arg completer for %s not a function", args[0])
  321. }
  322. if gen == nil {
  323. return complete.GenerateFileNames(args)
  324. }
  325. argValues := make([]any, len(args))
  326. for i, arg := range args {
  327. argValues[i] = arg
  328. }
  329. var output []complete.RawItem
  330. var outputMutex sync.Mutex
  331. collect := func(item complete.RawItem) {
  332. outputMutex.Lock()
  333. defer outputMutex.Unlock()
  334. output = append(output, item)
  335. }
  336. valueCb := func(ch <-chan any) {
  337. for v := range ch {
  338. switch v := v.(type) {
  339. case string:
  340. collect(complete.PlainItem(v))
  341. case complexItem:
  342. collect(complete.ComplexItem(v))
  343. default:
  344. collect(complete.PlainItem(vals.ToString(v)))
  345. }
  346. }
  347. }
  348. bytesCb := func(r *os.File) {
  349. buffered := bufio.NewReader(r)
  350. for {
  351. line, err := buffered.ReadString('\n')
  352. if line != "" {
  353. collect(complete.PlainItem(strutil.ChopLineEnding(line)))
  354. }
  355. if err != nil {
  356. break
  357. }
  358. }
  359. }
  360. port1, done, err := eval.PipePort(valueCb, bytesCb)
  361. if err != nil {
  362. panic(err)
  363. }
  364. err = ev.Call(gen,
  365. eval.CallCfg{Args: argValues, From: "[editor arg generator]"},
  366. eval.EvalCfg{Ports: []*eval.Port{
  367. // TODO: Supply the Chan component of port 2.
  368. nil, port1, {File: os.Stderr}}})
  369. done()
  370. return output, err
  371. }
  372. }
  373. func lookupFn(m vals.Map, ctxName string) (eval.Callable, bool) {
  374. val, ok := m.Index(ctxName)
  375. if !ok {
  376. val, ok = m.Index("")
  377. }
  378. if !ok {
  379. // No matcher, but not an error either
  380. return nil, true
  381. }
  382. fn, ok := val.(eval.Callable)
  383. if !ok {
  384. return nil, false
  385. }
  386. return fn, true
  387. }