mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-13 09:57:51 +08:00
128 lines
2.9 KiB
Go
128 lines
2.9 KiB
Go
package edit
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"src.elv.sh/pkg/cli"
|
|
"src.elv.sh/pkg/edit/highlight"
|
|
"src.elv.sh/pkg/eval"
|
|
"src.elv.sh/pkg/fsutil"
|
|
"src.elv.sh/pkg/parse"
|
|
"src.elv.sh/pkg/ui"
|
|
)
|
|
|
|
func initHighlighter(appSpec *cli.AppSpec, ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
|
|
hl := highlight.NewHighlighter(highlight.Config{
|
|
Check: func(t parse.Tree) (string, error) {
|
|
autofixes, err := ev.CheckTree(t, nil)
|
|
autofix := strings.Join(autofixes, "; ")
|
|
ed.autofix.Store(autofix)
|
|
return autofix, err
|
|
},
|
|
HasCommand: func(cmd string) bool { return hasCommand(ev, cmd) },
|
|
AutofixTip: func(autofix string) ui.Text {
|
|
return bindingTips(ed.ns, "insert:binding",
|
|
bindingTip("autofix: "+autofix, "apply-autofix"),
|
|
bindingTip("autofix first", "smart-enter", "completion:smart-start"))
|
|
},
|
|
})
|
|
appSpec.Highlighter = hl
|
|
ed.applyAutofix = func() {
|
|
code := ed.autofix.Load().(string)
|
|
if code == "" {
|
|
return
|
|
}
|
|
// TODO: Check errors.
|
|
//
|
|
// For now, the autofix snippets are simple enough that we know they'll
|
|
// always succeed.
|
|
ev.Eval(parse.Source{Name: "[autofix]", Code: code}, eval.EvalCfg{})
|
|
hl.InvalidateCache()
|
|
}
|
|
nb.AddGoFn("apply-autofix", ed.applyAutofix)
|
|
}
|
|
|
|
func hasCommand(ev *eval.Evaler, cmd string) bool {
|
|
if eval.IsBuiltinSpecial[cmd] {
|
|
return true
|
|
}
|
|
if fsutil.DontSearch(cmd) {
|
|
return isDirOrExecutable(cmd) || hasExternalCommand(cmd)
|
|
}
|
|
|
|
sigil, qname := eval.SplitSigil(cmd)
|
|
if sigil != "" {
|
|
// The @ sign is only valid when referring to external commands.
|
|
return hasExternalCommand(cmd)
|
|
}
|
|
|
|
first, rest := eval.SplitQName(qname)
|
|
switch {
|
|
case rest == "":
|
|
// Unqualified name; try builtin and global.
|
|
if hasFn(ev.Builtin(), first) || hasFn(ev.Global(), first) {
|
|
return true
|
|
}
|
|
case first == "e:":
|
|
return hasExternalCommand(rest)
|
|
default:
|
|
// Qualified name. Find the top-level module first.
|
|
if hasQualifiedFn(ev, first, rest) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// If all failed, it can still be an external command.
|
|
return hasExternalCommand(cmd)
|
|
}
|
|
|
|
func hasQualifiedFn(ev *eval.Evaler, firstNs string, rest string) bool {
|
|
if rest == "" {
|
|
return false
|
|
}
|
|
modVal, ok := ev.Global().Index(firstNs)
|
|
if !ok {
|
|
modVal, ok = ev.Builtin().Index(firstNs)
|
|
if !ok {
|
|
return false
|
|
}
|
|
}
|
|
mod, ok := modVal.(*eval.Ns)
|
|
if !ok {
|
|
return false
|
|
}
|
|
segs := eval.SplitQNameSegs(rest)
|
|
for _, seg := range segs[:len(segs)-1] {
|
|
modVal, ok = mod.Index(seg)
|
|
if !ok {
|
|
return false
|
|
}
|
|
mod, ok = modVal.(*eval.Ns)
|
|
if !ok {
|
|
return false
|
|
}
|
|
}
|
|
return hasFn(mod, segs[len(segs)-1])
|
|
}
|
|
|
|
func hasFn(ns *eval.Ns, name string) bool {
|
|
fnVar, ok := ns.Index(name + eval.FnSuffix)
|
|
if !ok {
|
|
return false
|
|
}
|
|
_, ok = fnVar.(eval.Callable)
|
|
return ok
|
|
}
|
|
|
|
func isDirOrExecutable(fname string) bool {
|
|
stat, err := os.Stat(fname)
|
|
return err == nil && (stat.IsDir() || fsutil.IsExecutable(stat))
|
|
}
|
|
|
|
func hasExternalCommand(cmd string) bool {
|
|
_, err := exec.LookPath(cmd)
|
|
return err == nil
|
|
}
|