github.com/elves/elvish@v0.15.0/pkg/edit/highlight.go (about) 1 package edit 2 3 import ( 4 "os" 5 "os/exec" 6 7 "github.com/elves/elvish/pkg/cli" 8 "github.com/elves/elvish/pkg/edit/highlight" 9 "github.com/elves/elvish/pkg/eval" 10 "github.com/elves/elvish/pkg/fsutil" 11 "github.com/elves/elvish/pkg/parse" 12 ) 13 14 func initHighlighter(appSpec *cli.AppSpec, ev *eval.Evaler) { 15 appSpec.Highlighter = highlight.NewHighlighter(highlight.Config{ 16 Check: func(tree parse.Tree) error { return check(ev, tree) }, 17 HasCommand: func(cmd string) bool { return hasCommand(ev, cmd) }, 18 }) 19 } 20 21 func check(ev *eval.Evaler, tree parse.Tree) error { 22 err := ev.CheckTree(tree, nil) 23 if err == nil { 24 return nil 25 } 26 return err 27 } 28 29 func hasCommand(ev *eval.Evaler, cmd string) bool { 30 if eval.IsBuiltinSpecial[cmd] { 31 return true 32 } 33 if fsutil.DontSearch(cmd) { 34 return isDirOrExecutable(cmd) || hasExternalCommand(cmd) 35 } 36 37 sigil, qname := eval.SplitSigil(cmd) 38 if sigil != "" { 39 // The @ sign is only valid when referring to external commands. 40 return hasExternalCommand(cmd) 41 } 42 43 first, rest := eval.SplitQName(qname) 44 switch { 45 case rest == "": 46 // Unqualified name; try builtin and global. 47 if hasFn(ev.Builtin(), first) || hasFn(ev.Global(), first) { 48 return true 49 } 50 case first == "e:": 51 return hasExternalCommand(rest) 52 case first == "builtin:": 53 if hasFn(ev.Builtin(), rest) { 54 return true 55 } 56 default: 57 // Qualified name. Find the top-level module first. 58 if hasQualifiedFn(ev, first, rest) { 59 return true 60 } 61 } 62 63 // If all failed, it can still be an external command. 64 return hasExternalCommand(cmd) 65 } 66 67 func hasQualifiedFn(ev *eval.Evaler, firstNs string, rest string) bool { 68 if rest == "" { 69 return false 70 } 71 modVal, ok := ev.Global().Index(firstNs) 72 if !ok { 73 modVal, ok = ev.Builtin().Index(firstNs) 74 if !ok { 75 return false 76 } 77 } 78 mod, ok := modVal.(*eval.Ns) 79 if !ok { 80 return false 81 } 82 segs := eval.SplitQNameSegs(rest) 83 for _, seg := range segs[:len(segs)-1] { 84 modVal, ok = mod.Index(seg) 85 if !ok { 86 return false 87 } 88 mod, ok = modVal.(*eval.Ns) 89 if !ok { 90 return false 91 } 92 } 93 return hasFn(mod, segs[len(segs)-1]) 94 } 95 96 func hasFn(ns *eval.Ns, name string) bool { 97 fnVar, ok := ns.Index(name + eval.FnSuffix) 98 if !ok { 99 return false 100 } 101 _, ok = fnVar.(eval.Callable) 102 return ok 103 } 104 105 func isDirOrExecutable(fname string) bool { 106 stat, err := os.Stat(fname) 107 return err == nil && (stat.IsDir() || stat.Mode()&0111 != 0) 108 } 109 110 func hasExternalCommand(cmd string) bool { 111 _, err := exec.LookPath(cmd) 112 return err == nil 113 }