mirror of
				https://github.com/sammy-ette/Hilbish
				synced 2025-08-10 02:52:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			328 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			328 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 
 | |
| 	"hilbish/util"
 | |
| 
 | |
| 	rt "github.com/arnodel/golua/runtime"
 | |
| 	"github.com/maxlandon/readline"
 | |
| 	"github.com/sahilm/fuzzy"
 | |
| )
 | |
| 
 | |
| type lineReader struct {
 | |
| 	rl *readline.Instance
 | |
| 	fileHist *fileHistory
 | |
| }
 | |
| var hinter *rt.Closure
 | |
| var highlighter *rt.Closure
 | |
| 
 | |
| func newLineReader(prompt string, noHist bool) *lineReader {
 | |
| 	rl := readline.NewInstance()
 | |
| 	lr := &lineReader{
 | |
| 		rl: rl,
 | |
| 	}
 | |
| 
 | |
| 	regexSearcher := rl.Searcher
 | |
| 	rl.Searcher = func(needle string, haystack []string) []string {
 | |
| 		fz, _ := util.DoString(l, "return hilbish.opts.fuzzy")
 | |
| 		fuzz, ok := fz.TryBool()
 | |
| 		if !fuzz || !ok {
 | |
| 			return regexSearcher(needle, haystack)
 | |
| 		}
 | |
| 
 | |
| 		matches := fuzzy.Find(needle, haystack)
 | |
| 		suggs := make([]string, 0)
 | |
| 
 | |
| 		for _, match := range matches {
 | |
| 			suggs = append(suggs, match.Str)
 | |
| 		}
 | |
| 
 | |
| 		return suggs
 | |
| 	}
 | |
| 
 | |
| 	// we don't mind hilbish.read rl instances having completion,
 | |
| 	// but it cant have shared history
 | |
| 	if !noHist {
 | |
| 		lr.fileHist = newFileHistory(defaultHistPath)
 | |
| 		rl.SetHistoryCtrlR("History", &luaHistory{})
 | |
| 		rl.HistoryAutoWrite = false
 | |
| 	}
 | |
| 	rl.ShowVimMode = false
 | |
| 	rl.ViModeCallback = func(mode readline.ViMode) {
 | |
| 		modeStr := ""
 | |
| 		switch mode {
 | |
| 			case readline.VimKeys: modeStr = "normal"
 | |
| 			case readline.VimInsert: modeStr = "insert"
 | |
| 			case readline.VimDelete: modeStr = "delete"
 | |
| 			case readline.VimReplaceOnce, readline.VimReplaceMany: modeStr = "replace"
 | |
| 		}
 | |
| 		setVimMode(modeStr)
 | |
| 	}
 | |
| 	rl.ViActionCallback = func(action readline.ViAction, args []string) {
 | |
| 		actionStr := ""
 | |
| 		switch action {
 | |
| 			case readline.VimActionPaste: actionStr = "paste"
 | |
| 			case readline.VimActionYank: actionStr = "yank"
 | |
| 		}
 | |
| 		hooks.Emit("hilbish.vimAction", actionStr, args)
 | |
| 	}
 | |
| 	rl.HintText = func(line []rune, pos int) []rune {
 | |
| 		hinter := hshMod.Get(rt.StringValue("hinter"))
 | |
| 		retVal, err := rt.Call1(l.MainThread(), hinter,
 | |
| 		rt.StringValue(string(line)), rt.IntValue(int64(pos)))
 | |
| 		if err != nil {
 | |
| 			fmt.Println(err)
 | |
| 			return []rune{}
 | |
| 		}
 | |
| 		
 | |
| 		hintText := ""
 | |
| 		if luaStr, ok := retVal.TryString(); ok {
 | |
| 			hintText = luaStr
 | |
| 		}
 | |
| 		
 | |
| 		return []rune(hintText)
 | |
| 	}
 | |
| 	rl.SyntaxHighlighter = func(line []rune) string {
 | |
| 		highlighter := hshMod.Get(rt.StringValue("highlighter"))
 | |
| 		retVal, err := rt.Call1(l.MainThread(), highlighter,
 | |
| 		rt.StringValue(string(line)))
 | |
| 		if err != nil {
 | |
| 			fmt.Println(err)
 | |
| 			return string(line)
 | |
| 		}
 | |
| 		
 | |
| 		highlighted := ""
 | |
| 		if luaStr, ok := retVal.TryString(); ok {
 | |
| 			highlighted = luaStr
 | |
| 		}
 | |
| 		
 | |
| 		return highlighted
 | |
| 	}
 | |
| 	rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
 | |
| 		term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
 | |
| 		compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler"))
 | |
| 		err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
 | |
| 		rt.IntValue(int64(pos))}, term)
 | |
| 
 | |
| 		var compGroups []*readline.CompletionGroup
 | |
| 		if err != nil {
 | |
| 			return "", compGroups
 | |
| 		}
 | |
| 
 | |
| 		luaCompGroups := term.Get(0)
 | |
| 		luaPrefix := term.Get(1)
 | |
| 
 | |
| 		if luaCompGroups.Type() != rt.TableType {
 | |
| 			return "", compGroups
 | |
| 		}
 | |
| 
 | |
| 		groups := luaCompGroups.AsTable()
 | |
| 		// prefix is optional
 | |
| 		pfx, _ := luaPrefix.TryString()
 | |
| 
 | |
| 		util.ForEach(groups, func(key rt.Value, val rt.Value) {
 | |
| 			if key.Type() != rt.IntType || val.Type() != rt.TableType {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			valTbl := val.AsTable()
 | |
| 			luaCompType := valTbl.Get(rt.StringValue("type"))
 | |
| 			luaCompItems := valTbl.Get(rt.StringValue("items"))
 | |
| 
 | |
| 			if luaCompType.Type() != rt.StringType || luaCompItems.Type() != rt.TableType {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			items := []string{}
 | |
| 			itemDescriptions := make(map[string]string)
 | |
| 
 | |
| 			util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
 | |
| 				if keytyp := lkey.Type(); keytyp == rt.StringType {
 | |
| 					// ['--flag'] = {'description', '--flag-alias'}
 | |
| 					itemName, ok := lkey.TryString()
 | |
| 					vlTbl, okk := lval.TryTable()
 | |
| 					if !ok && !okk {
 | |
| 						// TODO: error
 | |
| 						return
 | |
| 					}
 | |
| 
 | |
| 					items = append(items, itemName)
 | |
| 					itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
 | |
| 					if !ok {
 | |
| 						// TODO: error
 | |
| 						return
 | |
| 					}
 | |
| 					itemDescriptions[itemName] = itemDescription
 | |
| 				} else if keytyp == rt.IntType {
 | |
| 					vlStr, ok := lval.TryString()
 | |
| 						if !ok {
 | |
| 							// TODO: error
 | |
| 							return
 | |
| 						}
 | |
| 						items = append(items, vlStr)
 | |
| 				} else {
 | |
| 					// TODO: error
 | |
| 					return
 | |
| 				}
 | |
| 			})
 | |
| 
 | |
| 			var dispType readline.TabDisplayType
 | |
| 			switch luaCompType.AsString() {
 | |
| 				case "grid": dispType = readline.TabDisplayGrid
 | |
| 				case "list": dispType = readline.TabDisplayList
 | |
| 				// need special cases, will implement later
 | |
| 				//case "map": dispType = readline.TabDisplayMap
 | |
| 			}
 | |
| 
 | |
| 			compGroups = append(compGroups, &readline.CompletionGroup{
 | |
| 				DisplayType: dispType,
 | |
| 				Descriptions: itemDescriptions,
 | |
| 				Suggestions: items,
 | |
| 				TrimSlash: false,
 | |
| 				NoSpace: true,
 | |
| 			})
 | |
| 		})
 | |
| 
 | |
| 		return pfx, compGroups
 | |
| 	}
 | |
| 
 | |
| 	return lr
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) Read() (string, error) {
 | |
| 	hooks.Emit("command.precmd", nil)
 | |
| 	s, err := lr.rl.Readline()
 | |
| 	// this is so dumb
 | |
| 	if err == readline.EOF {
 | |
| 		fmt.Println("")
 | |
| 		return "", io.EOF
 | |
| 	}
 | |
| 
 | |
| 	return s, err // might get another error
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) SetPrompt(p string) {
 | |
| 	halfPrompt := strings.Split(p, "\n")
 | |
| 	if len(halfPrompt) > 1 {
 | |
| 		lr.rl.Multiline = true
 | |
| 		lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt) - 1], "\n"))
 | |
| 		lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt) - 1:][0]
 | |
| 	} else {
 | |
| 		lr.rl.Multiline = false
 | |
| 		lr.rl.MultilinePrompt = ""
 | |
| 		lr.rl.SetPrompt(p)
 | |
| 	}
 | |
| 	if initialized && !running {
 | |
| 		lr.rl.RefreshPromptInPlace("")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) SetRightPrompt(p string) {
 | |
| 	lr.rl.SetRightPrompt(p)
 | |
| 	if initialized && !running {
 | |
| 		lr.rl.RefreshPromptInPlace("")
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) AddHistory(cmd string) {
 | |
| 	lr.fileHist.Write(cmd)
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) ClearInput() {
 | |
| 	return
 | |
| }
 | |
| 
 | |
| func (lr *lineReader) Resize() {
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // command history
 | |
| // The history interface deals with command history. 
 | |
| // This includes the ability to override functions to change the main
 | |
| // method of saving history.
 | |
| func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
 | |
| 	lrLua := map[string]util.LuaExport{
 | |
| 		"add": {lr.luaAddHistory, 1, false},
 | |
| 		"all": {lr.luaAllHistory, 0, false},
 | |
| 		"clear": {lr.luaClearHistory, 0, false},
 | |
| 		"get": {lr.luaGetHistory, 1, false},
 | |
| 		"size": {lr.luaSize, 0, false},
 | |
| 	}
 | |
| 
 | |
| 	mod := rt.NewTable()
 | |
| 	util.SetExports(rtm, mod, lrLua)
 | |
| 
 | |
| 	return mod
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // add(cmd)
 | |
| // Adds a command to the history.
 | |
| // #param cmd string
 | |
| func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
 | |
| 	if err := c.Check1Arg(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	cmd, err := c.StringArg(0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	lr.AddHistory(cmd)
 | |
| 
 | |
| 	return c.Next(), nil
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // size() -> number
 | |
| // Returns the amount of commands in the history.
 | |
| // #eturns number
 | |
| func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
 | |
| 	return c.PushingNext1(t.Runtime, rt.IntValue(int64(lr.fileHist.Len()))), nil
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // get(index)
 | |
| // Retrieves a command from the history based on the `index`.
 | |
| // #param index number
 | |
| func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
 | |
| 	if err := c.Check1Arg(); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	idx, err := c.IntArg(0)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	cmd, _ := lr.fileHist.GetLine(int(idx))
 | |
| 
 | |
| 	return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // all() -> table
 | |
| // Retrieves all history as a table.
 | |
| // #returns table
 | |
| func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
 | |
| 	tbl := rt.NewTable()
 | |
| 	size := lr.fileHist.Len()
 | |
| 
 | |
| 	for i := 1; i < size; i++ {
 | |
| 		cmd, _ := lr.fileHist.GetLine(i)
 | |
| 		tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd))
 | |
| 	}
 | |
| 
 | |
| 	return c.PushingNext1(t.Runtime, rt.TableValue(tbl)), nil
 | |
| }
 | |
| 
 | |
| // #interface history
 | |
| // clear()
 | |
| // Deletes all commands from the history.
 | |
| func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
 | |
| 	lr.fileHist.clear()
 | |
| 	return c.Next(), nil
 | |
| }
 |