mirror of
https://github.com/Hilbis/Hilbish
synced 2025-07-01 00:32:03 +00:00
feat: move readline to golibs (#356)
This commit is contained in:
parent
1bb433dc64
commit
5ca858d112
@ -3,6 +3,13 @@
|
||||
## Unreleased
|
||||
### Added
|
||||
- Forward/Right arrow key will fill in hint text (#327)
|
||||
- The readline library adds the ability to create custom instances of the Hilbish
|
||||
line editor. Now, `hilbish.editor` has been changed to a readline instance, instead of just being a table of a few functions to access it.
|
||||
This means the colon operator is now the *preferred* way of accessing its functions,
|
||||
and the dot operator will cause errors in 3.0.
|
||||
Example: `hilbish.editor.getLine()` should be changed to `hilbish.editor:getLine()`
|
||||
before 3.0
|
||||
- Added the `hilbish.editor:read` and `hilbish.editor:log(text)` functions.
|
||||
### Changed
|
||||
- Documentation for Lunacolors has been improved, with more information added.
|
||||
- Values returned by bait hooks will be passed to the `throw` caller
|
||||
|
85
api.go
85
api.go
@ -25,30 +25,31 @@ import (
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
|
||||
//"github.com/arnodel/golua/lib/iolib"
|
||||
"github.com/maxlandon/readline"
|
||||
//"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
var exports = map[string]util.LuaExport{
|
||||
"alias": {hlalias, 2, false},
|
||||
"appendPath": {hlappendPath, 1, false},
|
||||
"complete": {hlcomplete, 2, false},
|
||||
"cwd": {hlcwd, 0, false},
|
||||
"exec": {hlexec, 1, false},
|
||||
"goro": {hlgoro, 1, true},
|
||||
"alias": {hlalias, 2, false},
|
||||
"appendPath": {hlappendPath, 1, false},
|
||||
"complete": {hlcomplete, 2, false},
|
||||
"cwd": {hlcwd, 0, false},
|
||||
"exec": {hlexec, 1, false},
|
||||
"goro": {hlgoro, 1, true},
|
||||
"highlighter": {hlhighlighter, 1, false},
|
||||
"hinter": {hlhinter, 1, false},
|
||||
"hinter": {hlhinter, 1, false},
|
||||
"multiprompt": {hlmultiprompt, 1, false},
|
||||
"prependPath": {hlprependPath, 1, false},
|
||||
"prompt": {hlprompt, 1, true},
|
||||
"inputMode": {hlinputMode, 1, false},
|
||||
"interval": {hlinterval, 2, false},
|
||||
"read": {hlread, 1, false},
|
||||
"timeout": {hltimeout, 2, false},
|
||||
"which": {hlwhich, 1, false},
|
||||
"prompt": {hlprompt, 1, true},
|
||||
"inputMode": {hlinputMode, 1, false},
|
||||
"interval": {hlinterval, 2, false},
|
||||
"read": {hlread, 1, false},
|
||||
"timeout": {hltimeout, 2, false},
|
||||
"which": {hlwhich, 1, false},
|
||||
}
|
||||
|
||||
var hshMod *rt.Table
|
||||
@ -118,9 +119,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
timersModule := timers.loader(rtm)
|
||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule))
|
||||
|
||||
editorModule := editorLoader(rtm)
|
||||
mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule))
|
||||
|
||||
versionModule := rt.NewTable()
|
||||
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch))
|
||||
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()))
|
||||
@ -138,11 +136,11 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if len(value) == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
value := os.Getenv(key)
|
||||
if len(value) == 0 {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func setVimMode(mode string) {
|
||||
@ -194,7 +192,6 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
||||
}
|
||||
|
||||
|
||||
// read(prompt) -> input (string)
|
||||
// Read input from the user, using Hilbish's line editor/input reader.
|
||||
// This is a separate instance from the one Hilbish actually uses.
|
||||
@ -212,7 +209,7 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
// substitute with an empty string
|
||||
prompt = ""
|
||||
}
|
||||
|
||||
|
||||
lualr := &lineReader{
|
||||
rl: readline.NewInstance(),
|
||||
}
|
||||
@ -265,11 +262,13 @@ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case "left":
|
||||
prompt = p
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
case "right": lr.SetRightPrompt(fmtPrompt(p))
|
||||
default: return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
||||
case "left":
|
||||
prompt = p
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
case "right":
|
||||
lr.SetRightPrompt(fmtPrompt(p))
|
||||
default:
|
||||
return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
@ -290,7 +289,7 @@ will look like:
|
||||
user ~ ∆ echo "hey
|
||||
--> ...!"
|
||||
|
||||
so then you get
|
||||
so then you get
|
||||
user ~ ∆ echo "hey
|
||||
--> ...!"
|
||||
hey ...!
|
||||
@ -386,7 +385,7 @@ func appendPath(dir string) {
|
||||
|
||||
// if dir isnt already in $PATH, add it
|
||||
if !strings.Contains(pathenv, dir) {
|
||||
os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
|
||||
os.Setenv("PATH", pathenv+string(os.PathListSeparator)+dir)
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,7 +479,7 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
interval := time.Duration(ms) * time.Millisecond
|
||||
timer := timers.create(timerTimeout, interval, cb)
|
||||
timer.start()
|
||||
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
|
||||
}
|
||||
|
||||
@ -571,7 +570,7 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
|
||||
// if dir isnt already in $PATH, add in
|
||||
if !strings.Contains(pathenv, dir) {
|
||||
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
|
||||
os.Setenv("PATH", dir+string(os.PathListSeparator)+pathenv)
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
@ -625,14 +624,14 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "emacs":
|
||||
unsetVimMode()
|
||||
lr.rl.InputMode = readline.Emacs
|
||||
case "vim":
|
||||
setVimMode("insert")
|
||||
lr.rl.InputMode = readline.Vim
|
||||
default:
|
||||
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
||||
case "emacs":
|
||||
unsetVimMode()
|
||||
lr.rl.InputMode = readline.Emacs
|
||||
case "vim":
|
||||
setVimMode("insert")
|
||||
lr.rl.InputMode = readline.Vim
|
||||
default:
|
||||
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
@ -667,7 +666,9 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
// #example
|
||||
// --This code will highlight all double quoted strings in green.
|
||||
// function hilbish.highlighter(line)
|
||||
// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
//
|
||||
// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
//
|
||||
// end
|
||||
// #example
|
||||
// #param line string
|
||||
|
@ -2,14 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"go/ast"
|
||||
"go/doc"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
md "github.com/atsushinee/go-markdown-generator/doc"
|
||||
@ -27,49 +27,49 @@ menu:
|
||||
`
|
||||
|
||||
type emmyPiece struct {
|
||||
DocPiece *docPiece
|
||||
DocPiece *docPiece
|
||||
Annotations []string
|
||||
Params []string // we only need to know param name to put in function
|
||||
FuncName string
|
||||
Params []string // we only need to know param name to put in function
|
||||
FuncName string
|
||||
}
|
||||
|
||||
type module struct {
|
||||
Types []docPiece
|
||||
Docs []docPiece
|
||||
Fields []docPiece
|
||||
Properties []docPiece
|
||||
Types []docPiece
|
||||
Docs []docPiece
|
||||
Fields []docPiece
|
||||
Properties []docPiece
|
||||
ShortDescription string
|
||||
Description string
|
||||
ParentModule string
|
||||
HasInterfaces bool
|
||||
HasTypes bool
|
||||
Description string
|
||||
ParentModule string
|
||||
HasInterfaces bool
|
||||
HasTypes bool
|
||||
}
|
||||
|
||||
type param struct{
|
||||
type param struct {
|
||||
Name string
|
||||
Type string
|
||||
Doc []string
|
||||
Doc []string
|
||||
}
|
||||
|
||||
type docPiece struct {
|
||||
Doc []string
|
||||
FuncSig string
|
||||
FuncName string
|
||||
Interfacing string
|
||||
Doc []string
|
||||
FuncSig string
|
||||
FuncName string
|
||||
Interfacing string
|
||||
ParentModule string
|
||||
GoFuncName string
|
||||
IsInterface bool
|
||||
IsMember bool
|
||||
IsType bool
|
||||
Fields []docPiece
|
||||
Properties []docPiece
|
||||
Params []param
|
||||
Tags map[string][]tag
|
||||
GoFuncName string
|
||||
IsInterface bool
|
||||
IsMember bool
|
||||
IsType bool
|
||||
Fields []docPiece
|
||||
Properties []docPiece
|
||||
Params []param
|
||||
Tags map[string][]tag
|
||||
}
|
||||
|
||||
type tag struct {
|
||||
id string
|
||||
fields []string
|
||||
id string
|
||||
fields []string
|
||||
startIdx int
|
||||
}
|
||||
|
||||
@ -78,13 +78,14 @@ var interfaceDocs = make(map[string]module)
|
||||
var emmyDocs = make(map[string][]emmyPiece)
|
||||
var typeTable = make(map[string][]string) // [0] = parentMod, [1] = interfaces
|
||||
var prefix = map[string]string{
|
||||
"main": "hl",
|
||||
"hilbish": "hl",
|
||||
"fs": "f",
|
||||
"main": "hl",
|
||||
"hilbish": "hl",
|
||||
"fs": "f",
|
||||
"commander": "c",
|
||||
"bait": "b",
|
||||
"terminal": "term",
|
||||
"snail": "snail",
|
||||
"bait": "b",
|
||||
"terminal": "term",
|
||||
"snail": "snail",
|
||||
"readline": "rl",
|
||||
}
|
||||
|
||||
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||
@ -109,7 +110,7 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||
} else {
|
||||
if tagParts[0] == "example" {
|
||||
exampleIdx := tags["example"][0].startIdx
|
||||
exampleCode := pts[exampleIdx+1:idx]
|
||||
exampleCode := pts[exampleIdx+1 : idx]
|
||||
|
||||
tags["example"][0].fields = exampleCode
|
||||
parts = strings.Split(strings.Replace(strings.Join(parts, "\n"), strings.TrimPrefix(strings.Join(exampleCode, "\n"), "#example\n"), "", -1), "\n")
|
||||
@ -121,7 +122,7 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||
fleds = tagParts[2:]
|
||||
}
|
||||
tags[tagParts[0]] = append(tags[tagParts[0]], tag{
|
||||
id: tagParts[1],
|
||||
id: tagParts[1],
|
||||
fields: fleds,
|
||||
})
|
||||
}
|
||||
@ -138,7 +139,7 @@ func docPieceTag(tagName string, tags map[string][]tag) []docPiece {
|
||||
for _, tag := range tags[tagName] {
|
||||
dps = append(dps, docPiece{
|
||||
FuncName: tag.id,
|
||||
Doc: tag.fields,
|
||||
Doc: tag.fields,
|
||||
})
|
||||
}
|
||||
|
||||
@ -169,16 +170,16 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
|
||||
if strings.HasPrefix(d, "---") {
|
||||
// TODO: document types in lua
|
||||
/*
|
||||
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
|
||||
emmyLinePieces := strings.Split(emmyLine, " ")
|
||||
emmyType := emmyLinePieces[0]
|
||||
if emmyType == "@param" {
|
||||
em.Params = append(em.Params, emmyLinePieces[1])
|
||||
}
|
||||
if emmyType == "@vararg" {
|
||||
em.Params = append(em.Params, "...") // add vararg
|
||||
}
|
||||
em.Annotations = append(em.Annotations, d)
|
||||
emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---"))
|
||||
emmyLinePieces := strings.Split(emmyLine, " ")
|
||||
emmyType := emmyLinePieces[0]
|
||||
if emmyType == "@param" {
|
||||
em.Params = append(em.Params, emmyLinePieces[1])
|
||||
}
|
||||
if emmyType == "@vararg" {
|
||||
em.Params = append(em.Params, "...") // add vararg
|
||||
}
|
||||
em.Annotations = append(em.Annotations, d)
|
||||
*/
|
||||
} else {
|
||||
typeDoc = append(typeDoc, d)
|
||||
@ -191,16 +192,16 @@ func setupDocType(mod string, typ *doc.Type) *docPiece {
|
||||
}
|
||||
parentMod := mod
|
||||
dps := &docPiece{
|
||||
Doc: typeDoc,
|
||||
FuncName: typeName,
|
||||
Interfacing: interfaces,
|
||||
IsInterface: inInterface,
|
||||
IsMember: isMember,
|
||||
IsType: true,
|
||||
Doc: typeDoc,
|
||||
FuncName: typeName,
|
||||
Interfacing: interfaces,
|
||||
IsInterface: inInterface,
|
||||
IsMember: isMember,
|
||||
IsType: true,
|
||||
ParentModule: parentMod,
|
||||
Fields: fields,
|
||||
Properties: properties,
|
||||
Tags: tags,
|
||||
Fields: fields,
|
||||
Properties: properties,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces}
|
||||
@ -221,6 +222,10 @@ func setupDoc(mod string, fun *doc.Func) *docPiece {
|
||||
goto start
|
||||
}
|
||||
|
||||
if prefix[mod] == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) {
|
||||
return nil
|
||||
}
|
||||
@ -248,7 +253,7 @@ start:
|
||||
params[i] = param{
|
||||
Name: p.id,
|
||||
Type: p.fields[0],
|
||||
Doc: p.fields[1:],
|
||||
Doc: p.fields[1:],
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -279,24 +284,24 @@ start:
|
||||
parentMod = mod
|
||||
}
|
||||
dps := &docPiece{
|
||||
Doc: funcdoc,
|
||||
FuncSig: funcsig,
|
||||
FuncName: funcName,
|
||||
Interfacing: interfaces,
|
||||
GoFuncName: strings.ToLower(fun.Name),
|
||||
IsInterface: inInterface,
|
||||
IsMember: isMember,
|
||||
Doc: funcdoc,
|
||||
FuncSig: funcsig,
|
||||
FuncName: funcName,
|
||||
Interfacing: interfaces,
|
||||
GoFuncName: strings.ToLower(fun.Name),
|
||||
IsInterface: inInterface,
|
||||
IsMember: isMember,
|
||||
ParentModule: parentMod,
|
||||
Fields: fields,
|
||||
Properties: properties,
|
||||
Params: params,
|
||||
Tags: tags,
|
||||
Fields: fields,
|
||||
Properties: properties,
|
||||
Params: params,
|
||||
Tags: tags,
|
||||
}
|
||||
if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) {
|
||||
dps.Doc = parts
|
||||
}
|
||||
em.DocPiece = dps
|
||||
|
||||
|
||||
emmyDocs[mod] = append(emmyDocs[mod], em)
|
||||
return dps
|
||||
}
|
||||
@ -326,11 +331,11 @@ provided by Hilbish.
|
||||
os.Mkdir("emmyLuaDocs", 0777)
|
||||
|
||||
dirs := []string{"./", "./util"}
|
||||
filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error {
|
||||
filepath.Walk("golibs/", func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
dirs = append(dirs, "./" + path)
|
||||
dirs = append(dirs, "./"+path)
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -445,13 +450,13 @@ provided by Hilbish.
|
||||
docs[mod] = newDoc
|
||||
} else {
|
||||
docs[mod] = module{
|
||||
Types: filteredTypePieces,
|
||||
Docs: filteredPieces,
|
||||
Types: filteredTypePieces,
|
||||
Docs: filteredPieces,
|
||||
ShortDescription: shortDesc,
|
||||
Description: strings.Join(desc, "\n"),
|
||||
HasInterfaces: hasInterfaces,
|
||||
Properties: docPieceTag("property", tags),
|
||||
Fields: docPieceTag("field", tags),
|
||||
Description: strings.Join(desc, "\n"),
|
||||
HasInterfaces: hasInterfaces,
|
||||
Properties: docPieceTag("property", tags),
|
||||
Fields: docPieceTag("field", tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -466,7 +471,7 @@ provided by Hilbish.
|
||||
for mod, v := range docs {
|
||||
docPath := "docs/api/" + mod + ".md"
|
||||
if v.HasInterfaces {
|
||||
os.Mkdir("docs/api/" + mod, 0777)
|
||||
os.Mkdir("docs/api/"+mod, 0777)
|
||||
os.Remove(docPath) // remove old doc path if it exists
|
||||
docPath = "docs/api/" + mod + "/_index.md"
|
||||
}
|
||||
@ -519,11 +524,11 @@ provided by Hilbish.
|
||||
continue
|
||||
}
|
||||
|
||||
mdTable.SetContent(i - diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
|
||||
mdTable.SetContent(i-diff, 0, fmt.Sprintf(`<a href="#%s">%s</a>`, dps.FuncName, dps.FuncSig))
|
||||
if len(dps.Doc) == 0 {
|
||||
fmt.Printf("WARNING! Function %s on module %s has no documentation!\n", dps.FuncName, modname)
|
||||
} else {
|
||||
mdTable.SetContent(i - diff, 1, dps.Doc[0])
|
||||
mdTable.SetContent(i-diff, 1, dps.Doc[0])
|
||||
}
|
||||
}
|
||||
f.WriteString(mdTable.String())
|
||||
@ -537,7 +542,6 @@ provided by Hilbish.
|
||||
mdTable.SetTitle(0, "")
|
||||
mdTable.SetTitle(1, "")
|
||||
|
||||
|
||||
for i, dps := range modu.Fields {
|
||||
mdTable.SetContent(i, 0, dps.FuncName)
|
||||
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
|
||||
@ -552,7 +556,6 @@ provided by Hilbish.
|
||||
mdTable.SetTitle(0, "")
|
||||
mdTable.SetTitle(1, "")
|
||||
|
||||
|
||||
for i, dps := range modu.Properties {
|
||||
mdTable.SetContent(i, 0, dps.FuncName)
|
||||
mdTable.SetContent(i, 1, strings.Join(dps.Doc, " "))
|
||||
@ -570,7 +573,7 @@ provided by Hilbish.
|
||||
continue
|
||||
}
|
||||
f.WriteString(fmt.Sprintf("<hr>\n<div id='%s'>", dps.FuncName))
|
||||
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(modname + "." + dps.FuncSig, "<", `\<`, -1), func(typ string) string {
|
||||
htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(modname+"."+dps.FuncSig, "<", `\<`, -1), func(typ string) string {
|
||||
typName := typ[1:]
|
||||
typLookup := typeTable[strings.ToLower(typName)]
|
||||
ifaces := typLookup[0] + "." + typLookup[1] + "/"
|
||||
@ -657,7 +660,7 @@ provided by Hilbish.
|
||||
typName := regexp.MustCompile(`\w+`).FindString(typ[1:])
|
||||
typLookup := typeTable[strings.ToLower(typName)]
|
||||
fmt.Printf("%+q, \n", typLookup)
|
||||
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0] + "." + typLookup[1], strings.ToLower(typName))
|
||||
linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0]+"."+typLookup[1], strings.ToLower(typName))
|
||||
return fmt.Sprintf(`<a href="#%s" style="text-decoration: none;">%s</a>`, linkedTyp, typName)
|
||||
})
|
||||
f.WriteString(fmt.Sprintf("#### %s\n", htmlSig))
|
||||
|
@ -229,7 +229,9 @@ Note that to set a highlighter, one has to override this function.
|
||||
```lua
|
||||
--This code will highlight all double quoted strings in green.
|
||||
function hilbish.highlighter(line)
|
||||
return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
|
||||
return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
|
||||
|
||||
end
|
||||
```
|
||||
</div>
|
||||
|
@ -1,124 +0,0 @@
|
||||
---
|
||||
title: Module hilbish.editor
|
||||
description: interactions for Hilbish's line reader
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
The hilbish.editor interface provides functions to
|
||||
directly interact with the line editor in use.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#editor.deleteByAmount">deleteByAmount(amount)</a>|Deletes characters in the line by the given amount.|
|
||||
|<a href="#editor.getLine">getLine() -> string</a>|Returns the current input line.|
|
||||
|<a href="#editor.getVimRegister">getVimRegister(register) -> string</a>|Returns the text that is at the register.|
|
||||
|<a href="#editor.insert">insert(text)</a>|Inserts text into the Hilbish command line.|
|
||||
|<a href="#editor.getChar">getChar() -> string</a>|Reads a keystroke from the user. This is in a format of something like Ctrl-L.|
|
||||
|<a href="#editor.setVimRegister">setVimRegister(register, text)</a>|Sets the vim register at `register` to hold the passed text.|
|
||||
|
||||
<hr>
|
||||
<div id='editor.deleteByAmount'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.deleteByAmount(amount)
|
||||
<a href="#editor.deleteByAmount" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Deletes characters in the line by the given amount.
|
||||
|
||||
#### Parameters
|
||||
`number` **`amount`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.getLine'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getLine() -> string
|
||||
<a href="#editor.getLine" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the current input line.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.getVimRegister'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getVimRegister(register) -> string
|
||||
<a href="#editor.getVimRegister" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Returns the text that is at the register.
|
||||
|
||||
#### Parameters
|
||||
`string` **`register`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.insert'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.insert(text)
|
||||
<a href="#editor.insert" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Inserts text into the Hilbish command line.
|
||||
|
||||
#### Parameters
|
||||
`string` **`text`**
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.getChar'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.getChar() -> string
|
||||
<a href="#editor.getChar" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='editor.setVimRegister'>
|
||||
<h4 class='heading'>
|
||||
hilbish.editor.setVimRegister(register, text)
|
||||
<a href="#editor.setVimRegister" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Sets the vim register at `register` to hold the passed text.
|
||||
|
||||
#### Parameters
|
||||
`string` **`register`**
|
||||
|
||||
|
||||
`string` **`text`**
|
||||
|
||||
|
||||
</div>
|
||||
|
39
docs/api/hilbish/hilbish.processors.md
Normal file
39
docs/api/hilbish/hilbish.processors.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Module hilbish.processors
|
||||
description: No description.
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
<hr>
|
||||
<div id='add'>
|
||||
<h4 class='heading'>
|
||||
hilbish.processors.add()
|
||||
<a href="#add" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div id='execute'>
|
||||
<h4 class='heading'>
|
||||
hilbish.processors.execute()
|
||||
<a href="#execute" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Run all command processors, in order by priority.
|
||||
It returns the processed command (which may be the same as the passed command)
|
||||
and a boolean which states whether to proceed with command execution.
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
66
docs/api/readline.md
Normal file
66
docs/api/readline.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Module readline
|
||||
description: line reader library
|
||||
layout: doc
|
||||
menu:
|
||||
docs:
|
||||
parent: "API"
|
||||
---
|
||||
|
||||
## Introduction
|
||||
The readline module is responsible for reading input from the user.
|
||||
The readline module is what Hilbish uses to read input from the user,
|
||||
including all the interactive features of Hilbish like history search,
|
||||
syntax highlighting, everything. The global Hilbish readline instance
|
||||
is usable at `hilbish.editor`.
|
||||
|
||||
## Functions
|
||||
|||
|
||||
|----|----|
|
||||
|<a href="#New">new() -> @Readline</a>|Creates a new readline instance.|
|
||||
|
||||
<hr>
|
||||
<div id='New'>
|
||||
<h4 class='heading'>
|
||||
readline.new() -> <a href="/Hilbish/docs/api/readline/#readline" style="text-decoration: none;" id="lol">Readline</a>
|
||||
<a href="#New" class='heading-link'>
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</a>
|
||||
</h4>
|
||||
|
||||
Creates a new readline instance.
|
||||
|
||||
#### Parameters
|
||||
This function has no parameters.
|
||||
</div>
|
||||
|
||||
## Types
|
||||
<hr>
|
||||
|
||||
## Readline
|
||||
|
||||
### Methods
|
||||
#### deleteByAmount(amount)
|
||||
Deletes characters in the line by the given amount.
|
||||
|
||||
#### getLine() -> string
|
||||
Returns the current input line.
|
||||
|
||||
#### getVimRegister(register) -> string
|
||||
Returns the text that is at the register.
|
||||
|
||||
#### insert(text)
|
||||
Inserts text into the Hilbish command line.
|
||||
|
||||
#### log(text)
|
||||
Prints a message *before* the prompt without it being interrupted by user input.
|
||||
|
||||
#### read() -> string
|
||||
Reads input from the user.
|
||||
|
||||
#### getChar() -> string
|
||||
Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
|
||||
#### setVimRegister(register, text)
|
||||
Sets the vim register at `register` to hold the passed text.
|
||||
|
128
editor.go
128
editor.go
@ -1,128 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
// #interface editor
|
||||
// interactions for Hilbish's line reader
|
||||
// The hilbish.editor interface provides functions to
|
||||
// directly interact with the line editor in use.
|
||||
func editorLoader(rtm *rt.Runtime) *rt.Table {
|
||||
exports := map[string]util.LuaExport{
|
||||
"insert": {editorInsert, 1, false},
|
||||
"setVimRegister": {editorSetRegister, 1, false},
|
||||
"getVimRegister": {editorGetRegister, 2, false},
|
||||
"getLine": {editorGetLine, 0, false},
|
||||
"readChar": {editorReadChar, 0, false},
|
||||
"deleteByAmount": {editorDeleteByAmount, 1, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// insert(text)
|
||||
// Inserts text into the Hilbish command line.
|
||||
// #param text string
|
||||
func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr.rl.Insert(text)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// setVimRegister(register, text)
|
||||
// Sets the vim register at `register` to hold the passed text.
|
||||
// #param register string
|
||||
// #param text string
|
||||
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr.rl.SetRegisterBuf(register, []rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getVimRegister(register) -> string
|
||||
// Returns the text that is at the register.
|
||||
// #param register string
|
||||
func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := lr.rl.GetFromRegister(register)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getLine() -> string
|
||||
// Returns the current input line.
|
||||
// #returns string
|
||||
func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
buf := lr.rl.GetLine()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// getChar() -> string
|
||||
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
buf := lr.rl.ReadChar()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #interface editor
|
||||
// deleteByAmount(amount)
|
||||
// Deletes characters in the line by the given amount.
|
||||
// #param amount number
|
||||
func editorDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amount, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lr.rl.DeleteByAmount(int(amount))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
@ -7,24 +7,6 @@ local hilbish = {}
|
||||
--- @param cmd string
|
||||
function hilbish.aliases.add(alias, cmd) end
|
||||
|
||||
--- Deletes characters in the line by the given amount.
|
||||
function hilbish.editor.deleteByAmount(amount) end
|
||||
|
||||
--- Returns the current input line.
|
||||
function hilbish.editor.getLine() end
|
||||
|
||||
--- Returns the text that is at the register.
|
||||
function hilbish.editor.getVimRegister(register) end
|
||||
|
||||
--- Inserts text into the Hilbish command line.
|
||||
function hilbish.editor.insert(text) end
|
||||
|
||||
--- Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
function hilbish.editor.getChar() end
|
||||
|
||||
--- Sets the vim register at `register` to hold the passed text.
|
||||
function hilbish.editor.setVimRegister(register, text) end
|
||||
|
||||
--- Return binaries/executables based on the provided parameters.
|
||||
--- This function is meant to be used as a helper in a command completion handler.
|
||||
---
|
||||
|
32
emmyLuaDocs/readline.lua
Normal file
32
emmyLuaDocs/readline.lua
Normal file
@ -0,0 +1,32 @@
|
||||
--- @meta
|
||||
|
||||
local readline = {}
|
||||
|
||||
--- Deletes characters in the line by the given amount.
|
||||
function readline:deleteByAmount(amount) end
|
||||
|
||||
--- Returns the current input line.
|
||||
function readline:getLine() end
|
||||
|
||||
--- Returns the text that is at the register.
|
||||
function readline:getVimRegister(register) end
|
||||
|
||||
--- Inserts text into the Hilbish command line.
|
||||
function readline:insert(text) end
|
||||
|
||||
--- Prints a message *before* the prompt without it being interrupted by user input.
|
||||
function readline:log(text) end
|
||||
|
||||
--- Creates a new readline instance.
|
||||
function readline.new() end
|
||||
|
||||
--- Reads input from the user.
|
||||
function readline:read() end
|
||||
|
||||
--- Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
function readline:getChar() end
|
||||
|
||||
--- Sets the vim register at `register` to hold the passed text.
|
||||
function readline:setVimRegister(register, text) end
|
||||
|
||||
return readline
|
2
go.mod
2
go.mod
@ -30,7 +30,7 @@ require (
|
||||
|
||||
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
|
||||
|
||||
replace github.com/maxlandon/readline => ./readline
|
||||
replace github.com/maxlandon/readline => ./golibs/readline
|
||||
|
||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
|
||||
|
||||
|
@ -54,14 +54,14 @@ var (
|
||||
seqCtrlDelete2 = string([]byte{27, 91, 77})
|
||||
seqAltDelete = string([]byte{27, 91, 51, 59, 51, 126})
|
||||
seqShiftTab = string([]byte{27, 91, 90})
|
||||
seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^["
|
||||
seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^["
|
||||
seqAltB = string([]byte{27, 98})
|
||||
seqAltD = string([]byte{27, 100})
|
||||
seqAltF = string([]byte{27, 102})
|
||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
||||
seqAltBackspace = string([]byte{27, 127})
|
||||
seqPageUp = string([]byte{27, 91, 53, 126})
|
||||
seqPageDown = string([]byte{27, 91, 54, 126})
|
||||
seqPageDown = string([]byte{27, 91, 54, 126})
|
||||
)
|
||||
|
||||
const (
|
||||
@ -76,7 +76,7 @@ const (
|
||||
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
||||
|
||||
seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R"
|
||||
seqHideCursor = "\x1b[?25l"
|
||||
seqHideCursor = "\x1b[?25l"
|
||||
seqUnhideCursor = "\x1b[?25h"
|
||||
|
||||
seqCtrlLeftArrow = "\x1b[1;5D"
|
||||
@ -143,55 +143,94 @@ const (
|
||||
|
||||
// TODO: return whether its actually a sequence or not
|
||||
// remedies the edge case of someone literally typing Ctrl-A for example.
|
||||
func (rl *Instance) ReadChar() string {
|
||||
func (rl *Readline) ReadChar() string {
|
||||
b := make([]byte, 1024)
|
||||
i, _ := os.Stdin.Read(b)
|
||||
r := []rune(string(b))
|
||||
s := string(r[:i])
|
||||
|
||||
switch b[0] {
|
||||
case charCtrlA: return "Ctrl-A"
|
||||
case charCtrlB: return "Ctrl-B"
|
||||
case charCtrlC: return "Ctrl-C"
|
||||
case charEOF: return "Ctrl-D"
|
||||
case charCtrlE: return "Ctrl-E"
|
||||
case charCtrlF: return "Ctrl-F"
|
||||
case charCtrlG: return "Ctrl-G"
|
||||
case charBackspace, charBackspace2: return "Backspace"
|
||||
case charTab: return "Tab"
|
||||
case charCtrlK: return "Ctrl-K"
|
||||
case charCtrlL: return "Ctrl-L"
|
||||
case charCtrlN: return "Ctrl-N"
|
||||
case charCtrlO: return "Ctrl-O"
|
||||
case charCtrlP: return "Ctrl-P"
|
||||
case charCtrlQ: return "Ctrl-Q"
|
||||
case charCtrlR: return "Ctrl-R"
|
||||
case charCtrlS: return "Ctrl-S"
|
||||
case charCtrlT: return "Ctrl-T"
|
||||
case charCtrlU: return "Ctrl-U"
|
||||
case charCtrlV: return "Ctrl-V"
|
||||
case charCtrlW: return "Ctrl-W"
|
||||
case charCtrlX: return "Ctrl-X"
|
||||
case charCtrlY: return "Ctrl-Y"
|
||||
case charCtrlZ: return "Ctrl-Z"
|
||||
case '\r': fallthrough
|
||||
case '\n': return "Enter"
|
||||
case charEscape:
|
||||
switch s {
|
||||
case string(charEscape): return "Escape"
|
||||
case seqUp: return "Up"
|
||||
case seqDown: return "Down"
|
||||
case seqBackwards: return "Left"
|
||||
case seqForwards: return "Right"
|
||||
case seqCtrlLeftArrow: return "Ctrl-Left"
|
||||
case seqCtrlRightArrow: return "Ctrl-Right"
|
||||
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
|
||||
case seqHome, seqHomeSc: return "Home"
|
||||
case seqEnd, seqEndSc: return "End"
|
||||
case seqDelete, seqDelete2: return "Delete"
|
||||
case seqPageUp: return "Page-Up"
|
||||
case seqPageDown: return "Page-Down"
|
||||
}
|
||||
case charCtrlA:
|
||||
return "Ctrl-A"
|
||||
case charCtrlB:
|
||||
return "Ctrl-B"
|
||||
case charCtrlC:
|
||||
return "Ctrl-C"
|
||||
case charEOF:
|
||||
return "Ctrl-D"
|
||||
case charCtrlE:
|
||||
return "Ctrl-E"
|
||||
case charCtrlF:
|
||||
return "Ctrl-F"
|
||||
case charCtrlG:
|
||||
return "Ctrl-G"
|
||||
case charBackspace, charBackspace2:
|
||||
return "Backspace"
|
||||
case charTab:
|
||||
return "Tab"
|
||||
case charCtrlK:
|
||||
return "Ctrl-K"
|
||||
case charCtrlL:
|
||||
return "Ctrl-L"
|
||||
case charCtrlN:
|
||||
return "Ctrl-N"
|
||||
case charCtrlO:
|
||||
return "Ctrl-O"
|
||||
case charCtrlP:
|
||||
return "Ctrl-P"
|
||||
case charCtrlQ:
|
||||
return "Ctrl-Q"
|
||||
case charCtrlR:
|
||||
return "Ctrl-R"
|
||||
case charCtrlS:
|
||||
return "Ctrl-S"
|
||||
case charCtrlT:
|
||||
return "Ctrl-T"
|
||||
case charCtrlU:
|
||||
return "Ctrl-U"
|
||||
case charCtrlV:
|
||||
return "Ctrl-V"
|
||||
case charCtrlW:
|
||||
return "Ctrl-W"
|
||||
case charCtrlX:
|
||||
return "Ctrl-X"
|
||||
case charCtrlY:
|
||||
return "Ctrl-Y"
|
||||
case charCtrlZ:
|
||||
return "Ctrl-Z"
|
||||
case '\r':
|
||||
fallthrough
|
||||
case '\n':
|
||||
return "Enter"
|
||||
case charEscape:
|
||||
switch s {
|
||||
case string(charEscape):
|
||||
return "Escape"
|
||||
case seqUp:
|
||||
return "Up"
|
||||
case seqDown:
|
||||
return "Down"
|
||||
case seqBackwards:
|
||||
return "Left"
|
||||
case seqForwards:
|
||||
return "Right"
|
||||
case seqCtrlLeftArrow:
|
||||
return "Ctrl-Left"
|
||||
case seqCtrlRightArrow:
|
||||
return "Ctrl-Right"
|
||||
case seqCtrlDelete, seqCtrlDelete2:
|
||||
return "Ctrl-Delete"
|
||||
case seqHome, seqHomeSc:
|
||||
return "Home"
|
||||
case seqEnd, seqEndSc:
|
||||
return "End"
|
||||
case seqDelete, seqDelete2:
|
||||
return "Delete"
|
||||
case seqPageUp:
|
||||
return "Page-Up"
|
||||
case seqPageDown:
|
||||
return "Page-Down"
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
@ -4,12 +4,13 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
)
|
||||
|
||||
// initGrid - Grid display details. Called each time we want to be sure to have
|
||||
// a working completion group either immediately, or later on. Generally defered.
|
||||
func (g *CompletionGroup) initGrid(rl *Instance) {
|
||||
func (g *CompletionGroup) initGrid(rl *Readline) {
|
||||
|
||||
// Compute size of each completion item box
|
||||
tcMaxLength := 1
|
||||
@ -44,7 +45,7 @@ func (g *CompletionGroup) initGrid(rl *Instance) {
|
||||
}
|
||||
|
||||
// moveTabGridHighlight - Moves the highlighting for currently selected completion item (grid display)
|
||||
func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabGridHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
g.tcPosX += x
|
||||
g.tcPosY += y
|
||||
@ -96,7 +97,7 @@ func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done boo
|
||||
}
|
||||
|
||||
// writeGrid - A grid completion string
|
||||
func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeGrid(rl *Readline) (comp string) {
|
||||
|
||||
// If group title, print it and adjust offset.
|
||||
if g.Name != "" {
|
||||
@ -127,9 +128,9 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||
|
||||
sugg := g.Suggestions[i]
|
||||
if len(sugg) > GetTermWidth() {
|
||||
sugg = sugg[:GetTermWidth() - 4] + "..."
|
||||
sugg = sugg[:GetTermWidth()-4] + "..."
|
||||
}
|
||||
formatStr := "%-"+cellWidth+"s%s "
|
||||
formatStr := "%-" + cellWidth + "s%s "
|
||||
if g.tcMaxX == 1 {
|
||||
formatStr = "%s%s"
|
||||
}
|
@ -49,7 +49,7 @@ type CompletionGroup struct {
|
||||
}
|
||||
|
||||
// init - The completion group computes and sets all its values, and is then ready to work.
|
||||
func (g *CompletionGroup) init(rl *Instance) {
|
||||
func (g *CompletionGroup) init(rl *Readline) {
|
||||
|
||||
// Details common to all displays
|
||||
g.checkCycle(rl) // Based on the number of groups given to the shell, allows cycling or not
|
||||
@ -70,7 +70,7 @@ func (g *CompletionGroup) init(rl *Instance) {
|
||||
// updateTabFind - When searching through all completion groups (whether it be command history or not),
|
||||
// we ask each of them to filter its own items and return the results to the shell for aggregating them.
|
||||
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
|
||||
func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||
func (g *CompletionGroup) updateTabFind(rl *Readline) {
|
||||
|
||||
suggs := rl.Searcher(rl.search, g.Suggestions)
|
||||
// We perform filter right here, so we create a new completion group, and populate it with our results.
|
||||
@ -97,7 +97,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
||||
}
|
||||
|
||||
// checkCycle - Based on the number of groups given to the shell, allows cycling or not
|
||||
func (g *CompletionGroup) checkCycle(rl *Instance) {
|
||||
func (g *CompletionGroup) checkCycle(rl *Readline) {
|
||||
if len(rl.tcGroups) == 1 {
|
||||
g.allowCycle = true
|
||||
}
|
||||
@ -108,7 +108,7 @@ func (g *CompletionGroup) checkCycle(rl *Instance) {
|
||||
}
|
||||
|
||||
// checkMaxLength - Based on the number of groups given to the shell, check/set MaxLength defaults
|
||||
func (g *CompletionGroup) checkMaxLength(rl *Instance) {
|
||||
func (g *CompletionGroup) checkMaxLength(rl *Readline) {
|
||||
|
||||
// This means the user forgot to set it
|
||||
if g.MaxLength == 0 {
|
||||
@ -147,7 +147,7 @@ func checkNilItems(groups []*CompletionGroup) (checked []*CompletionGroup) {
|
||||
|
||||
// writeCompletion - This function produces a formatted string containing all appropriate items
|
||||
// and according to display settings. This string is then appended to the main completion string.
|
||||
func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeCompletion(rl *Readline) (comp string) {
|
||||
|
||||
// Avoids empty groups in suggestions
|
||||
if len(g.Suggestions) == 0 {
|
||||
@ -169,7 +169,7 @@ func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) {
|
||||
|
||||
// getCurrentCell - The completion groups computes the current cell value,
|
||||
// depending on its display type and its different parameters
|
||||
func (g *CompletionGroup) getCurrentCell(rl *Instance) string {
|
||||
func (g *CompletionGroup) getCurrentCell(rl *Readline) string {
|
||||
|
||||
switch g.DisplayType {
|
||||
case TabDisplayGrid:
|
@ -8,7 +8,7 @@ import (
|
||||
|
||||
// initList - List display details. Because of the way alternative completions
|
||||
// are handled, MaxLength cannot be set when there are alternative completions.
|
||||
func (g *CompletionGroup) initList(rl *Instance) {
|
||||
func (g *CompletionGroup) initList(rl *Readline) {
|
||||
|
||||
// We may only ever have two different
|
||||
// columns: (suggestions, and alternatives)
|
||||
@ -53,7 +53,7 @@ func (g *CompletionGroup) initList(rl *Instance) {
|
||||
|
||||
// moveTabListHighlight - Moves the highlighting for currently selected completion item (list display)
|
||||
// We don't care about the x, because only can have 2 columns of selectable choices (--long and -s)
|
||||
func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabListHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
// We dont' pass to x, because not managed by callers
|
||||
g.tcPosY += x
|
||||
@ -153,7 +153,7 @@ func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done boo
|
||||
}
|
||||
|
||||
// writeList - A list completion string
|
||||
func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeList(rl *Readline) (comp string) {
|
||||
|
||||
// Print group title and adjust offset if there is one.
|
||||
if g.Name != "" {
|
||||
@ -249,7 +249,7 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getListPad() (pad int) {
|
||||
func (rl *Readline) getListPad() (pad int) {
|
||||
for _, group := range rl.tcGroups {
|
||||
if group.DisplayType == TabDisplayList {
|
||||
for i := range group.Suggestions {
|
@ -7,7 +7,7 @@ import (
|
||||
|
||||
// initMap - Map display details. Called each time we want to be sure to have
|
||||
// a working completion group either immediately, or later on. Generally defered.
|
||||
func (g *CompletionGroup) initMap(rl *Instance) {
|
||||
func (g *CompletionGroup) initMap(rl *Readline) {
|
||||
|
||||
// We make the map anyway, especially if we need to use it later
|
||||
if g.Descriptions == nil {
|
||||
@ -35,7 +35,7 @@ func (g *CompletionGroup) initMap(rl *Instance) {
|
||||
}
|
||||
|
||||
// moveTabMapHighlight - Moves the highlighting for currently selected completion item (map display)
|
||||
func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool, next bool) {
|
||||
func (g *CompletionGroup) moveTabMapHighlight(rl *Readline, x, y int) (done bool, next bool) {
|
||||
|
||||
g.tcPosY += x
|
||||
g.tcPosY += y
|
||||
@ -72,7 +72,7 @@ func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool
|
||||
}
|
||||
|
||||
// writeMap - A map or list completion string
|
||||
func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
|
||||
func (g *CompletionGroup) writeMap(rl *Readline) (comp string) {
|
||||
|
||||
if g.Name != "" {
|
||||
// Print group title (changes with line returns depending on type)
|
@ -1,7 +1,7 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
// "fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -28,7 +28,7 @@ func leftMost() []byte {
|
||||
|
||||
var rxRcvCursorPos = regexp.MustCompile("^\x1b([0-9]+);([0-9]+)R$")
|
||||
|
||||
func (rl *Instance) getCursorPos() (x int, y int) {
|
||||
func (rl *Readline) getCursorPos() (x int, y int) {
|
||||
if !rl.EnableGetCursorPos {
|
||||
return -1, -1
|
||||
}
|
||||
@ -143,7 +143,7 @@ func unhideCursor() {
|
||||
print(seqUnhideCursor)
|
||||
}
|
||||
|
||||
func (rl *Instance) backspace(forward bool) {
|
||||
func (rl *Readline) backspace(forward bool) {
|
||||
if len(rl.line) == 0 || rl.pos == 0 {
|
||||
return
|
||||
}
|
||||
@ -151,7 +151,7 @@ func (rl *Instance) backspace(forward bool) {
|
||||
rl.deleteBackspace(forward)
|
||||
}
|
||||
|
||||
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
||||
func (rl *Readline) moveCursorByAdjust(adjust int) {
|
||||
switch {
|
||||
case adjust > 0:
|
||||
rl.pos += adjust
|
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// writeTempFile - This function optionally accepts a filename (generally specified with an extension).
|
||||
func (rl *Instance) writeTempFile(content []byte, filename string) (string, error) {
|
||||
func (rl *Readline) writeTempFile(content []byte, filename string) (string, error) {
|
||||
// The final path to the buffer on disk
|
||||
var path string
|
||||
|
@ -1,9 +1,10 @@
|
||||
//go:build plan9
|
||||
// +build plan9
|
||||
|
||||
package readline
|
||||
|
||||
import "errors"
|
||||
|
||||
func (rl *Instance) launchEditor(multiline []rune) ([]rune, error) {
|
||||
func (rl *Readline) launchEditor(multiline []rune) ([]rune, error) {
|
||||
return rl.line, errors.New("Not currently supported on Plan 9")
|
||||
}
|
@ -15,7 +15,7 @@ const defaultEditor = "vi"
|
||||
// depending on the actions taken by the user within it (eg: x or q! in Vim)
|
||||
// The filename parameter can be used to pass a specific filename.ext pattern,
|
||||
// which might be useful if the editor has builtin filetype plugin functionality.
|
||||
func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
name, err := rl.writeTempFile([]byte(string(multiline)), filename)
|
||||
if err != nil {
|
||||
return multiline, err
|
@ -5,6 +5,6 @@ package readline
|
||||
import "errors"
|
||||
|
||||
// StartEditorWithBuffer - Not implemented on Windows platforms.
|
||||
func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) {
|
||||
return rl.line, errors.New("Not currently supported on Windows")
|
||||
}
|
@ -13,11 +13,11 @@ type EventReturn struct {
|
||||
}
|
||||
|
||||
// AddEvent registers a new keypress handler
|
||||
func (rl *Instance) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) {
|
||||
func (rl *Readline) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) {
|
||||
rl.evtKeyPress[keyPress] = callback
|
||||
}
|
||||
|
||||
// DelEvent deregisters an existing keypress handler
|
||||
func (rl *Instance) DelEvent(keyPress string) {
|
||||
func (rl *Readline) DelEvent(keyPress string) {
|
||||
delete(rl.evtKeyPress, keyPress)
|
||||
}
|
@ -5,13 +5,13 @@ import "regexp"
|
||||
// SetHintText - a nasty function to force writing a new hint text. It does not update helpers, it just renders
|
||||
// them, so the hint will survive until the helpers (thus including the hint) will be updated/recomputed.
|
||||
/*
|
||||
func (rl *Instance) SetHintText(s string) {
|
||||
func (rl *Readline) SetHintText(s string) {
|
||||
rl.hintText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
*/
|
||||
|
||||
func (rl *Instance) getHintText() {
|
||||
func (rl *Readline) getHintText() {
|
||||
|
||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||
// Return if no hints provided by the user/engine
|
||||
@ -27,7 +27,7 @@ func (rl *Instance) getHintText() {
|
||||
}
|
||||
|
||||
// writeHintText - only writes the hint text and computes its offsets.
|
||||
func (rl *Instance) writeHintText() {
|
||||
func (rl *Readline) writeHintText() {
|
||||
if len(rl.hintText) == 0 {
|
||||
//rl.hintY = 0
|
||||
return
|
||||
@ -43,7 +43,7 @@ func (rl *Instance) writeHintText() {
|
||||
|
||||
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
||||
offset += hintLen
|
||||
// rl.hintY = offset
|
||||
// rl.hintY = offset
|
||||
|
||||
hintText := string(wrapped)
|
||||
|
||||
@ -52,12 +52,12 @@ func (rl *Instance) writeHintText() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetHintText() {
|
||||
func (rl *Readline) resetHintText() {
|
||||
//rl.hintY = 0
|
||||
rl.hintText = []rune{}
|
||||
}
|
||||
|
||||
func (rl *Instance) insertHintText() {
|
||||
func (rl *Readline) insertHintText() {
|
||||
if len(rl.hintText) != 0 {
|
||||
// fill in hint text
|
||||
rl.insert(rl.hintText)
|
@ -29,24 +29,24 @@ type History interface {
|
||||
}
|
||||
|
||||
// SetHistoryCtrlR - Set the history source triggered with Ctrl-r combination
|
||||
func (rl *Instance) SetHistoryCtrlR(name string, history History) {
|
||||
func (rl *Readline) SetHistoryCtrlR(name string, history History) {
|
||||
rl.mainHistName = name
|
||||
rl.mainHistory = history
|
||||
}
|
||||
|
||||
// GetHistoryCtrlR - Returns the history source triggered by Ctrl-r
|
||||
func (rl *Instance) GetHistoryCtrlR() History {
|
||||
func (rl *Readline) GetHistoryCtrlR() History {
|
||||
return rl.mainHistory
|
||||
}
|
||||
|
||||
// SetHistoryAltR - Set the history source triggered with Alt-r combination
|
||||
func (rl *Instance) SetHistoryAltR(name string, history History) {
|
||||
func (rl *Readline) SetHistoryAltR(name string, history History) {
|
||||
rl.altHistName = name
|
||||
rl.altHistory = history
|
||||
}
|
||||
|
||||
// GetHistoryAltR - Returns the history source triggered by Alt-r
|
||||
func (rl *Instance) GetHistoryAltR() History {
|
||||
func (rl *Readline) GetHistoryAltR() History {
|
||||
return rl.altHistory
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ func (h *NullHistory) Dump() interface{} {
|
||||
}
|
||||
|
||||
// Browse historic lines:
|
||||
func (rl *Instance) walkHistory(i int) {
|
||||
func (rl *Readline) walkHistory(i int) {
|
||||
var (
|
||||
old, new string
|
||||
dedup bool
|
||||
@ -123,7 +123,7 @@ func (rl *Instance) walkHistory(i int) {
|
||||
|
||||
// When we are exiting the current line buffer to move around
|
||||
// the history, we make buffer the current line
|
||||
if rl.histOffset == 0 && rl.histOffset + i == 1 {
|
||||
if rl.histOffset == 0 && rl.histOffset+i == 1 {
|
||||
rl.lineBuf = string(rl.line)
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ func (rl *Instance) walkHistory(i int) {
|
||||
|
||||
// completeHistory - Populates a CompletionGroup with history and returns it the shell
|
||||
// we populate only one group, so as to pass it to the main completion engine.
|
||||
func (rl *Instance) completeHistory() (hist []*CompletionGroup) {
|
||||
func (rl *Readline) completeHistory() (hist []*CompletionGroup) {
|
||||
|
||||
hist = make([]*CompletionGroup, 1)
|
||||
hist[0] = &CompletionGroup{
|
@ -4,12 +4,12 @@ import "regexp"
|
||||
|
||||
// SetInfoText - a nasty function to force writing a new info text. It does not update helpers, it just renders
|
||||
// them, so the info will survive until the helpers (thus including the info) will be updated/recomputed.
|
||||
func (rl *Instance) SetInfoText(s string) {
|
||||
func (rl *Readline) SetInfoText(s string) {
|
||||
rl.infoText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) getInfoText() {
|
||||
func (rl *Readline) getInfoText() {
|
||||
|
||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||
// Return if no infos provided by the user/engine
|
||||
@ -25,7 +25,7 @@ func (rl *Instance) getInfoText() {
|
||||
}
|
||||
|
||||
// writeInfoText - only writes the info text and computes its offsets.
|
||||
func (rl *Instance) writeInfoText() {
|
||||
func (rl *Readline) writeInfoText() {
|
||||
if len(rl.infoText) == 0 {
|
||||
rl.infoY = 0
|
||||
return
|
||||
@ -50,7 +50,7 @@ func (rl *Instance) writeInfoText() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetInfoText() {
|
||||
func (rl *Readline) resetInfoText() {
|
||||
rl.infoY = 0
|
||||
rl.infoText = []rune{}
|
||||
}
|
@ -5,12 +5,16 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
)
|
||||
|
||||
// Instance is used to encapsulate the parameter group and run time of any given
|
||||
// readline instance so that you can reuse the readline API for multiple entry
|
||||
// captures without having to repeatedly unload configuration.
|
||||
type Instance struct {
|
||||
|
||||
// #type
|
||||
type Readline struct {
|
||||
|
||||
//
|
||||
// Input Modes -------------------------------------------------------------------------------
|
||||
@ -31,13 +35,13 @@ type Instance struct {
|
||||
Multiline bool // If set to true, the shell will have a two-line prompt.
|
||||
MultilinePrompt string // If multiline is true, this is the content of the 2nd line.
|
||||
|
||||
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
||||
rightPrompt string
|
||||
rightPromptLen int
|
||||
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
||||
defaultPrompt []rune
|
||||
promptLen int
|
||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
||||
rightPrompt string
|
||||
rightPromptLen int
|
||||
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
||||
defaultPrompt []rune
|
||||
promptLen int
|
||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||
|
||||
//
|
||||
// Input Line ---------------------------------------------------------------------------------
|
||||
@ -114,9 +118,9 @@ type Instance struct {
|
||||
searchMode FindMode // Used for varying hints, and underlying functions called
|
||||
regexSearch *regexp.Regexp // Holds the current search regex match
|
||||
search string
|
||||
mainHist bool // Which history stdin do we want
|
||||
histInfo []rune // We store a piece of hist info, for dual history sources
|
||||
Searcher func(string, []string) []string
|
||||
mainHist bool // Which history stdin do we want
|
||||
histInfo []rune // We store a piece of hist info, for dual history sources
|
||||
Searcher func(string, []string) []string
|
||||
|
||||
//
|
||||
// History -----------------------------------------------------------------------------------
|
||||
@ -200,17 +204,19 @@ type Instance struct {
|
||||
// concurency
|
||||
mutex sync.Mutex
|
||||
|
||||
ViModeCallback func(ViMode)
|
||||
ViModeCallback func(ViMode)
|
||||
ViActionCallback func(ViAction, []string)
|
||||
|
||||
RawInputCallback func([]rune) // called on all input
|
||||
|
||||
bufferedOut *bufio.Writer
|
||||
|
||||
Loader packagelib.Loader
|
||||
}
|
||||
|
||||
// NewInstance is used to create a readline instance and initialise it with sane defaults.
|
||||
func NewInstance() *Instance {
|
||||
rl := new(Instance)
|
||||
func NewInstance() *Readline {
|
||||
rl := new(Readline)
|
||||
|
||||
// Prompt
|
||||
rl.Multiline = false
|
||||
@ -245,7 +251,9 @@ func NewInstance() *Instance {
|
||||
}
|
||||
|
||||
for _, hay := range haystack {
|
||||
if rl.regexSearch == nil { continue }
|
||||
if rl.regexSearch == nil {
|
||||
continue
|
||||
}
|
||||
if rl.regexSearch.MatchString(hay) {
|
||||
suggs = append(suggs, hay)
|
||||
}
|
||||
@ -256,6 +264,11 @@ func NewInstance() *Instance {
|
||||
|
||||
rl.bufferedOut = bufio.NewWriter(os.Stdout)
|
||||
|
||||
rl.Loader = packagelib.Loader{
|
||||
Name: "readline",
|
||||
Load: rl.luaLoader,
|
||||
}
|
||||
|
||||
// Registers
|
||||
rl.initRegisters()
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
// When the DelayedSyntaxWorker gives us a new line, we need to check if there
|
||||
// is any processing to be made, that all lines match in terms of content.
|
||||
func (rl *Instance) updateLine(line []rune) {
|
||||
func (rl *Readline) updateLine(line []rune) {
|
||||
if len(rl.currentComp) > 0 {
|
||||
|
||||
} else {
|
||||
@ -18,7 +18,7 @@ func (rl *Instance) updateLine(line []rune) {
|
||||
|
||||
// getLine - In many places we need the current line input. We either return the real line,
|
||||
// or the one that includes the current completion candidate, if there is any.
|
||||
func (rl *Instance) GetLine() []rune {
|
||||
func (rl *Readline) GetLine() []rune {
|
||||
if len(rl.currentComp) > 0 {
|
||||
return rl.lineComp
|
||||
}
|
||||
@ -30,7 +30,7 @@ func (rl *Instance) GetLine() []rune {
|
||||
// function is only ever called once, and after having moved back to prompt position
|
||||
// and having printed the line: this is so that at any moment, everyone has the good
|
||||
// values for moving around, synchronized with the update input line.
|
||||
func (rl *Instance) echo() {
|
||||
func (rl *Readline) echo() {
|
||||
|
||||
// Then we print the prompt, and the line,
|
||||
hideCursor()
|
||||
@ -79,7 +79,7 @@ func (rl *Instance) echo() {
|
||||
unhideCursor()
|
||||
}
|
||||
|
||||
func (rl *Instance) insert(r []rune) {
|
||||
func (rl *Readline) insert(r []rune) {
|
||||
for {
|
||||
// I don't really understand why `0` is creaping in at the end of the
|
||||
// array but it only happens with unicode characters.
|
||||
@ -112,11 +112,11 @@ func (rl *Instance) insert(r []rune) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) Insert(t string) {
|
||||
func (rl *Readline) Insert(t string) {
|
||||
rl.insert([]rune(t))
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteX() {
|
||||
func (rl *Readline) deleteX() {
|
||||
switch {
|
||||
case len(rl.line) == 0:
|
||||
return
|
||||
@ -134,7 +134,7 @@ func (rl *Instance) deleteX() {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteBackspace(forward bool) {
|
||||
func (rl *Readline) deleteBackspace(forward bool) {
|
||||
switch {
|
||||
case len(rl.line) == 0:
|
||||
return
|
||||
@ -153,7 +153,7 @@ func (rl *Instance) deleteBackspace(forward bool) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) clearLine() {
|
||||
func (rl *Readline) clearLine() {
|
||||
if len(rl.line) == 0 {
|
||||
return
|
||||
}
|
||||
@ -179,21 +179,21 @@ func (rl *Instance) clearLine() {
|
||||
rl.clearVirtualComp()
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteToBeginning() {
|
||||
func (rl *Readline) deleteToBeginning() {
|
||||
rl.resetVirtualComp(false)
|
||||
// Keep the line length up until the cursor
|
||||
rl.line = rl.line[rl.pos:]
|
||||
rl.pos = 0
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteToEnd() {
|
||||
func (rl *Readline) deleteToEnd() {
|
||||
rl.resetVirtualComp(false)
|
||||
// Keep everything before the cursor
|
||||
rl.line = rl.line[:rl.pos]
|
||||
}
|
||||
|
||||
// @TODO(Renzix): move to emacs sepecific file
|
||||
func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -214,7 +214,7 @@ func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) emacsBackwardWord(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) emacsBackwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
276
golibs/readline/lua.go
Normal file
276
golibs/readline/lua.go
Normal file
@ -0,0 +1,276 @@
|
||||
// line reader library
|
||||
// The readline module is responsible for reading input from the user.
|
||||
// The readline module is what Hilbish uses to read input from the user,
|
||||
// including all the interactive features of Hilbish like history search,
|
||||
// syntax highlighting, everything. The global Hilbish readline instance
|
||||
// is usable at `hilbish.editor`.
|
||||
package readline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var rlMetaKey = rt.StringValue("__readline")
|
||||
|
||||
func (rl *Readline) luaLoader(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
rlMethods := rt.NewTable()
|
||||
rlMethodss := map[string]util.LuaExport{
|
||||
"deleteByAmount": {rlDeleteByAmount, 2, false},
|
||||
"getLine": {rlGetLine, 1, false},
|
||||
"getVimRegister": {rlGetRegister, 2, false},
|
||||
"insert": {rlInsert, 2, false},
|
||||
"read": {rlRead, 1, false},
|
||||
"readChar": {rlReadChar, 1, false},
|
||||
"setVimRegister": {rlSetRegister, 3, false},
|
||||
"log": {rlLog, 2, false},
|
||||
}
|
||||
util.SetExports(rtm, rlMethods, rlMethodss)
|
||||
|
||||
rlMeta := rt.NewTable()
|
||||
rlIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
_, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arg := c.Arg(1)
|
||||
val := rlMethods.Get(arg)
|
||||
|
||||
return c.PushingNext1(t.Runtime, val), nil
|
||||
}
|
||||
|
||||
rlMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(rlIndex, "__index", 2, false)))
|
||||
rtm.SetRegistry(rlMetaKey, rt.TableValue(rlMeta))
|
||||
|
||||
rlFuncs := map[string]util.LuaExport{
|
||||
"new": {rlNew, 0, false},
|
||||
}
|
||||
|
||||
luaRl := rt.NewTable()
|
||||
util.SetExports(rtm, luaRl, rlFuncs)
|
||||
|
||||
return rt.TableValue(luaRl), nil
|
||||
}
|
||||
|
||||
// new() -> @Readline
|
||||
// Creates a new readline instance.
|
||||
func rlNew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
rl := NewInstance()
|
||||
ud := rlUserData(t.Runtime, rl)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.UserDataValue(ud)), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// insert(text)
|
||||
// Inserts text into the Hilbish command line.
|
||||
// #param text string
|
||||
func rlInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.insert([]rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// read() -> string
|
||||
// Reads input from the user.
|
||||
func rlRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inp, err := rl.Readline()
|
||||
if err == EOF {
|
||||
fmt.Println("")
|
||||
return nil, io.EOF
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(inp)), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// setVimRegister(register, text)
|
||||
// Sets the vim register at `register` to hold the passed text.
|
||||
// #param register string
|
||||
// #param text string
|
||||
func rlSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
text, err := c.StringArg(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.SetRegisterBuf(register, []rune(text))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getVimRegister(register) -> string
|
||||
// Returns the text that is at the register.
|
||||
// #param register string
|
||||
func rlGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
register, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := rl.GetFromRegister(register)
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getLine() -> string
|
||||
// Returns the current input line.
|
||||
// #returns string
|
||||
func rlGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := rl.GetLine()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// getChar() -> string
|
||||
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||
func rlReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf := rl.ReadChar()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// deleteByAmount(amount)
|
||||
// Deletes characters in the line by the given amount.
|
||||
// #param amount number
|
||||
func rlDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
amount, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.DeleteByAmount(int(amount))
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// #member
|
||||
// log(text)
|
||||
// Prints a message *before* the prompt without it being interrupted by user input.
|
||||
func rlLog(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl, err := rlArg(c, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logText, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rl.RefreshPromptLog(logText)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func rlArg(c *rt.GoCont, arg int) (*Readline, error) {
|
||||
j, ok := valueToRl(c.Arg(arg))
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("#%d must be a readline", arg+1)
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
func valueToRl(val rt.Value) (*Readline, bool) {
|
||||
u, ok := val.TryUserData()
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
j, ok := u.Value().(*Readline)
|
||||
return j, ok
|
||||
}
|
||||
|
||||
func rlUserData(rtm *rt.Runtime, rl *Readline) *rt.UserData {
|
||||
rlMeta := rtm.Registry(rlMetaKey)
|
||||
return rt.NewUserData(rl, rlMeta.AsTable())
|
||||
}
|
@ -8,20 +8,20 @@ import (
|
||||
|
||||
// SetPrompt will define the readline prompt string.
|
||||
// It also calculates the runes in the string as well as any non-printable escape codes.
|
||||
func (rl *Instance) SetPrompt(s string) {
|
||||
func (rl *Readline) SetPrompt(s string) {
|
||||
rl.mainPrompt = s
|
||||
rl.computePrompt()
|
||||
}
|
||||
|
||||
// SetRightPrompt sets the right prompt.
|
||||
func (rl *Instance) SetRightPrompt(s string) {
|
||||
func (rl *Readline) SetRightPrompt(s string) {
|
||||
rl.rightPrompt = s + " "
|
||||
rl.computePrompt()
|
||||
}
|
||||
|
||||
// RefreshPromptLog - A simple function to print a string message (a log, or more broadly,
|
||||
// an asynchronous event) without bothering the user, and by "pushing" the prompt below the message.
|
||||
func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||
func (rl *Readline) RefreshPromptLog(log string) (err error) {
|
||||
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
@ -73,7 +73,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||
}
|
||||
|
||||
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
|
||||
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||
func (rl *Readline) RefreshPromptInPlace(prompt string) (err error) {
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
// Prompt data intependent
|
||||
if !rl.modeTabCompletion {
|
||||
@ -117,7 +117,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||
// @prompt => If not nil (""), will use this prompt instead of the currently set prompt.
|
||||
// @offset => Used to set the number of lines to go upward, before reprinting. Set to 0 if not used.
|
||||
// @clearLine => If true, will clean the current input line on the next refresh.
|
||||
func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) {
|
||||
func (rl *Readline) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) {
|
||||
|
||||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
@ -166,7 +166,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
|
||||
|
||||
// computePrompt - At any moment, returns an (1st or 2nd line) actualized prompt,
|
||||
// considering all input mode parameters and prompt string values.
|
||||
func (rl *Instance) computePrompt() (prompt []rune) {
|
||||
func (rl *Readline) computePrompt() (prompt []rune) {
|
||||
if rl.Multiline {
|
||||
if rl.MultilinePrompt != "" {
|
||||
rl.realPrompt = []rune(rl.MultilinePrompt)
|
||||
@ -190,11 +190,11 @@ func (rl *Instance) computePrompt() (prompt []rune) {
|
||||
// Strip color escapes
|
||||
rl.promptLen = getRealLength(string(rl.realPrompt))
|
||||
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) {
|
||||
func (rl *Readline) colorizeVimPrompt(p []rune) (cp []rune) {
|
||||
if rl.VimModeColorize {
|
||||
return []rune(fmt.Sprintf("%s%s%s", BOLD, string(p), RESET))
|
||||
}
|
||||
@ -211,8 +211,8 @@ func getRealLength(s string) (l int) {
|
||||
return getWidth([]rune(stripped))
|
||||
}
|
||||
|
||||
func (rl *Instance) echoRightPrompt() {
|
||||
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
|
||||
func (rl *Readline) echoRightPrompt() {
|
||||
if rl.fullX < GetTermWidth()-rl.rightPromptLen-1 {
|
||||
moveCursorForwards(GetTermWidth())
|
||||
moveCursorBackwards(rl.rightPromptLen)
|
||||
print(rl.rightPrompt)
|
@ -2,16 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
@ -2,18 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
||||
// commonly found on UNIX systems.
|
||||
//
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
||||
//
|
||||
// oldState, err := terminal.MakeRaw(0)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer terminal.Restore(0, oldState)
|
||||
package readline
|
||||
|
||||
import (
|
||||
@ -70,4 +61,3 @@ func GetSize(fd int) (width, height int, err error) {
|
||||
}
|
||||
return int(info.Size.X), int(info.Size.Y), nil
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
||||
|
||||
// Readline displays the readline prompt.
|
||||
// It will return a string (user entered data) or an error.
|
||||
func (rl *Instance) Readline() (string, error) {
|
||||
func (rl *Readline) Readline() (string, error) {
|
||||
fd := int(os.Stdin.Fd())
|
||||
state, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
@ -183,7 +183,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
if (rl.pos < len(rl.line)) {
|
||||
if rl.pos < len(rl.line) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
@ -545,7 +545,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||
// editorInput is an unexported function used to determine what mode of text
|
||||
// entry readline is currently configured for and then update the line entries
|
||||
// accordingly.
|
||||
func (rl *Instance) editorInput(r []rune) {
|
||||
func (rl *Readline) editorInput(r []rune) {
|
||||
if len(r) == 0 {
|
||||
return
|
||||
}
|
||||
@ -595,7 +595,7 @@ func (rl *Instance) editorInput(r []rune) {
|
||||
|
||||
// viEscape - In case th user is using Vim input, and the escape sequence has not
|
||||
// been handled by other cases, we dispatch it to Vim and handle a few cases here.
|
||||
func (rl *Instance) viEscape(r []rune) {
|
||||
func (rl *Readline) viEscape(r []rune) {
|
||||
|
||||
// Sometimes the escape sequence is interleaved with another one,
|
||||
// but key strokes might be in the wrong order, so we double check
|
||||
@ -611,7 +611,7 @@ func (rl *Instance) viEscape(r []rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) escapeSeq(r []rune) {
|
||||
func (rl *Readline) escapeSeq(r []rune) {
|
||||
switch string(r) {
|
||||
// Vim escape sequences & dispatching --------------------------------------------------------
|
||||
case string(charEscape):
|
||||
@ -755,11 +755,11 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
rl.updateHelpers()
|
||||
return
|
||||
|
||||
case seqDelete,seqDelete2:
|
||||
case seqDelete, seqDelete2:
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
if (rl.pos < len(rl.line)) {
|
||||
if rl.pos < len(rl.line) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
@ -890,7 +890,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) carridgeReturn() {
|
||||
func (rl *Readline) carridgeReturn() {
|
||||
rl.moveCursorByAdjust(len(rl.line))
|
||||
rl.updateHelpers()
|
||||
rl.clearHelpers()
|
||||
@ -924,7 +924,7 @@ func isMultiline(r []rune) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (rl *Instance) allowMultiline(data []byte) bool {
|
||||
func (rl *Readline) allowMultiline(data []byte) bool {
|
||||
rl.clearHelpers()
|
||||
printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data))
|
||||
for {
|
@ -23,7 +23,7 @@ type registers struct {
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func (rl *Instance) initRegisters() {
|
||||
func (rl *Readline) initRegisters() {
|
||||
rl.registers = ®isters{
|
||||
num: make(map[int][]rune, 10),
|
||||
alpha: make(map[string][]rune, 52),
|
||||
@ -36,7 +36,7 @@ func (rl *Instance) initRegisters() {
|
||||
// the number of Vim iterations and we save the resulting string to the appropriate buffer.
|
||||
// It's the same as saveToRegisterTokenize, but without the need to generate tokenized &
|
||||
// cursor-pos-actualized versions of the input line.
|
||||
func (rl *Instance) saveToRegister(adjust int) {
|
||||
func (rl *Readline) saveToRegister(adjust int) {
|
||||
|
||||
// Get the current cursor position and go the length specified.
|
||||
var begin = rl.pos
|
||||
@ -66,7 +66,7 @@ func (rl *Instance) saveToRegister(adjust int) {
|
||||
// saveToRegisterTokenize - Passing a function that will move around the line in the desired way, we get
|
||||
// the number of Vim iterations and we save the resulting string to the appropriate buffer. Because we
|
||||
// need the cursor position to be really moved around between calls to the jumper, we also need the tokeniser.
|
||||
func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) {
|
||||
func (rl *Readline) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) {
|
||||
|
||||
// The register is going to have to heavily manipulate the cursor position.
|
||||
// Remember the original one first, for the end.
|
||||
@ -104,11 +104,11 @@ func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(toke
|
||||
// saveBufToRegister - Instead of computing the buffer ourselves based on an adjust,
|
||||
// let the caller pass directly this buffer, yet relying on the register system to
|
||||
// determine which register will store the buffer.
|
||||
func (rl *Instance) saveBufToRegister(buffer []rune) {
|
||||
func (rl *Readline) saveBufToRegister(buffer []rune) {
|
||||
rl.SetRegisterBuf(string(rl.registers.currentRegister), buffer)
|
||||
}
|
||||
|
||||
func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) {
|
||||
func (rl *Readline) SetRegisterBuf(reg string, buffer []rune) {
|
||||
// We must make an immutable version of the buffer first.
|
||||
buf := string(buffer)
|
||||
|
||||
@ -141,7 +141,7 @@ func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) {
|
||||
|
||||
// The user asked to paste a buffer onto the line, so we check from which register
|
||||
// we are supposed to select the buffer, and return it to the caller for insertion.
|
||||
func (rl *Instance) pasteFromRegister() (buffer []rune) {
|
||||
func (rl *Readline) pasteFromRegister() (buffer []rune) {
|
||||
|
||||
// When exiting this function the currently selected register is dropped,
|
||||
defer rl.registers.resetRegister()
|
||||
@ -155,7 +155,7 @@ func (rl *Instance) pasteFromRegister() (buffer []rune) {
|
||||
return rl.GetFromRegister(activeRegister)
|
||||
}
|
||||
|
||||
func (rl *Instance) GetFromRegister(reg string) []rune {
|
||||
func (rl *Readline) GetFromRegister(reg string) []rune {
|
||||
// Find the active register, and return its content.
|
||||
num, err := strconv.Atoi(reg)
|
||||
|
||||
@ -264,7 +264,7 @@ func (r *registers) resetRegister() {
|
||||
}
|
||||
|
||||
// The user can show registers completions and insert, no matter the cursor position.
|
||||
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
|
||||
func (rl *Readline) completeRegisters() (groups []*CompletionGroup) {
|
||||
|
||||
// We set the info exceptionally
|
||||
info := BLUE + "-- registers --" + RESET
|
@ -2,7 +2,7 @@ package readline
|
||||
|
||||
// syntaxCompletion - applies syntax highlighting to the current input line.
|
||||
// nothing special to note here, nor any changes envisioned.
|
||||
func (rl *Instance) syntaxCompletion() {
|
||||
func (rl *Readline) syntaxCompletion() {
|
||||
if rl.SyntaxCompleter == nil {
|
||||
return
|
||||
}
|
@ -2,13 +2,14 @@ package readline
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line:
|
||||
// this will not trigger further firltering against the other candidates. Each time this function
|
||||
// is called, any previous candidate is dropped, after being used for moving the cursor around.
|
||||
func (rl *Instance) insertCandidateVirtual(candidate []rune) {
|
||||
func (rl *Readline) insertCandidateVirtual(candidate []rune) {
|
||||
for {
|
||||
// I don't really understand why `0` is creaping in at the end of the
|
||||
// array but it only happens with unicode characters.
|
||||
@ -57,7 +58,7 @@ func (rl *Instance) insertCandidateVirtual(candidate []rune) {
|
||||
// Insert the current completion candidate into the input line.
|
||||
// This candidate might either be the currently selected one (white frame),
|
||||
// or the only candidate available, if the total number of candidates is 1.
|
||||
func (rl *Instance) insertCandidate() {
|
||||
func (rl *Readline) insertCandidate() {
|
||||
|
||||
cur := rl.getCurrentGroup()
|
||||
|
||||
@ -83,7 +84,7 @@ func (rl *Instance) insertCandidate() {
|
||||
}
|
||||
|
||||
// updateVirtualComp - Either insert the current completion candidate virtually, or on the real line.
|
||||
func (rl *Instance) updateVirtualComp() {
|
||||
func (rl *Readline) updateVirtualComp() {
|
||||
cur := rl.getCurrentGroup()
|
||||
if cur != nil {
|
||||
|
||||
@ -118,7 +119,7 @@ func (rl *Instance) updateVirtualComp() {
|
||||
// resetVirtualComp - This function is called before most of our readline key handlers,
|
||||
// and makes sure that the current completion (virtually inserted) is either inserted or dropped,
|
||||
// and that all related parameters are reinitialized.
|
||||
func (rl *Instance) resetVirtualComp(drop bool) {
|
||||
func (rl *Readline) resetVirtualComp(drop bool) {
|
||||
|
||||
// If we don't have a current virtual completion, there's nothing to do.
|
||||
// IMPORTANT: this MUST be first, to avoid nil problems with empty comps.
|
||||
@ -196,7 +197,7 @@ func trimTrailing(comp string) (trimmed string, hadSlash bool) {
|
||||
}
|
||||
|
||||
// viDeleteByAdjustVirtual - Same as viDeleteByAdjust, but for our virtually completed input line.
|
||||
func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
||||
func (rl *Readline) viDeleteByAdjustVirtual(adjust int) {
|
||||
var (
|
||||
newLine []rune
|
||||
backOne bool
|
||||
@ -235,7 +236,7 @@ func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
||||
}
|
||||
|
||||
// viJumpEVirtual - Same as viJumpE, but for our virtually completed input line.
|
||||
func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) {
|
||||
func (rl *Readline) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.lineComp, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -258,7 +259,7 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteVirtual() {
|
||||
func (rl *Readline) deleteVirtual() {
|
||||
switch {
|
||||
case len(rl.lineComp) == 0:
|
||||
return
|
||||
@ -274,7 +275,7 @@ func (rl *Instance) deleteVirtual() {
|
||||
|
||||
// We are done with the current virtual completion candidate.
|
||||
// Get ready for the next one
|
||||
func (rl *Instance) clearVirtualComp() {
|
||||
func (rl *Readline) clearVirtualComp() {
|
||||
rl.line = rl.lineComp
|
||||
rl.currentComp = []rune{}
|
||||
rl.compAddSpace = false
|
@ -28,7 +28,7 @@ const (
|
||||
|
||||
// getTabCompletion - This root function sets up all completion items and engines,
|
||||
// dealing with all search and completion modes. But it does not perform printing.
|
||||
func (rl *Instance) getTabCompletion() {
|
||||
func (rl *Readline) getTabCompletion() {
|
||||
|
||||
// Populate registers if requested.
|
||||
if rl.modeAutoFind && rl.searchMode == RegisterFind {
|
||||
@ -53,7 +53,7 @@ func (rl *Instance) getTabCompletion() {
|
||||
}
|
||||
|
||||
// getRegisterCompletion - Populates and sets up completion for Vim registers.
|
||||
func (rl *Instance) getRegisterCompletion() {
|
||||
func (rl *Readline) getRegisterCompletion() {
|
||||
|
||||
rl.tcGroups = rl.completeRegisters()
|
||||
if len(rl.tcGroups) == 0 {
|
||||
@ -84,7 +84,7 @@ func (rl *Instance) getRegisterCompletion() {
|
||||
}
|
||||
|
||||
// getTabSearchCompletion - Populates and sets up completion for completion search.
|
||||
func (rl *Instance) getTabSearchCompletion() {
|
||||
func (rl *Readline) getTabSearchCompletion() {
|
||||
|
||||
// Get completions from the engine, and make sure there is a current group.
|
||||
rl.getCompletions()
|
||||
@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||
rl.getCurrentGroup()
|
||||
|
||||
// Set the info for this completion mode
|
||||
rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...)
|
||||
rl.infoText = append([]rune("Completion search: "+UNDERLINE+BOLD), rl.tfLine...)
|
||||
|
||||
for _, g := range rl.tcGroups {
|
||||
g.updateTabFind(rl)
|
||||
@ -107,7 +107,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||
}
|
||||
|
||||
// getHistorySearchCompletion - Populates and sets up completion for command history search
|
||||
func (rl *Instance) getHistorySearchCompletion() {
|
||||
func (rl *Readline) getHistorySearchCompletion() {
|
||||
|
||||
// Refresh full list each time
|
||||
rl.tcGroups = rl.completeHistory()
|
||||
@ -142,7 +142,7 @@ func (rl *Instance) getHistorySearchCompletion() {
|
||||
|
||||
// getNormalCompletion - Populates and sets up completion for normal comp mode.
|
||||
// Will automatically cancel the completion mode if there are no candidates.
|
||||
func (rl *Instance) getNormalCompletion() {
|
||||
func (rl *Readline) getNormalCompletion() {
|
||||
|
||||
// Get completions groups, pass delayedTabContext and check nils
|
||||
rl.getCompletions()
|
||||
@ -172,7 +172,7 @@ func (rl *Instance) getNormalCompletion() {
|
||||
// getCompletions - Calls the completion engine/function to yield a list of 0 or more completion groups,
|
||||
// sets up a delayed tab context and passes it on to the tab completion engine function, and ensure no
|
||||
// nil groups/items will pass through. This function is called by different comp search/nav modes.
|
||||
func (rl *Instance) getCompletions() {
|
||||
func (rl *Readline) getCompletions() {
|
||||
|
||||
// If there is no wired tab completion engine, nothing we can do.
|
||||
if rl.TabCompleter == nil {
|
||||
@ -214,7 +214,7 @@ func (rl *Instance) getCompletions() {
|
||||
|
||||
// moveTabCompletionHighlight - This function is in charge of
|
||||
// computing the new position in the current completions liste.
|
||||
func (rl *Instance) moveTabCompletionHighlight(x, y int) {
|
||||
func (rl *Readline) moveTabCompletionHighlight(x, y int) {
|
||||
rl.completionOpen = true
|
||||
g := rl.getCurrentGroup()
|
||||
|
||||
@ -253,7 +253,7 @@ func (rl *Instance) moveTabCompletionHighlight(x, y int) {
|
||||
}
|
||||
|
||||
// writeTabCompletion - Prints all completion groups and their items
|
||||
func (rl *Instance) writeTabCompletion() {
|
||||
func (rl *Readline) writeTabCompletion() {
|
||||
|
||||
// The final completions string to print.
|
||||
var completions string
|
||||
@ -289,7 +289,7 @@ func (rl *Instance) writeTabCompletion() {
|
||||
// cropCompletions - When the user cycles through a completion list longer
|
||||
// than the console MaxTabCompleterRows value, we crop the completions string
|
||||
// so that "global" cycling (across all groups) is printed correctly.
|
||||
func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
func (rl *Readline) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
|
||||
// If we actually fit into the MaxTabCompleterRows, return the comps
|
||||
if rl.tcUsedY < rl.MaxTabCompleterRows {
|
||||
@ -366,7 +366,7 @@ func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getAbsPos() int {
|
||||
func (rl *Readline) getAbsPos() int {
|
||||
var prev int
|
||||
var foundCurrent bool
|
||||
for _, grp := range rl.tcGroups {
|
||||
@ -390,7 +390,7 @@ func (rl *Instance) getAbsPos() int {
|
||||
|
||||
// We pass a special subset of the current input line, so that
|
||||
// completions are available no matter where the cursor is.
|
||||
func (rl *Instance) getCompletionLine() (line []rune, pos int) {
|
||||
func (rl *Readline) getCompletionLine() (line []rune, pos int) {
|
||||
|
||||
pos = rl.pos - len(rl.currentComp)
|
||||
if pos < 0 {
|
||||
@ -409,7 +409,7 @@ func (rl *Instance) getCompletionLine() (line []rune, pos int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
||||
func (rl *Readline) getCurrentGroup() (group *CompletionGroup) {
|
||||
for _, g := range rl.tcGroups {
|
||||
if g.isCurrent && len(g.Suggestions) > 0 {
|
||||
return g
|
||||
@ -431,7 +431,7 @@ func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
||||
|
||||
// cycleNextGroup - Finds either the first non-empty group,
|
||||
// or the next non-empty group after the current one.
|
||||
func (rl *Instance) cycleNextGroup() {
|
||||
func (rl *Readline) cycleNextGroup() {
|
||||
for i, g := range rl.tcGroups {
|
||||
if g.isCurrent {
|
||||
g.isCurrent = false
|
||||
@ -452,7 +452,7 @@ func (rl *Instance) cycleNextGroup() {
|
||||
}
|
||||
|
||||
// cyclePreviousGroup - Same as cycleNextGroup but reverse
|
||||
func (rl *Instance) cyclePreviousGroup() {
|
||||
func (rl *Readline) cyclePreviousGroup() {
|
||||
for i, g := range rl.tcGroups {
|
||||
if g.isCurrent {
|
||||
g.isCurrent = false
|
||||
@ -471,7 +471,7 @@ func (rl *Instance) cyclePreviousGroup() {
|
||||
}
|
||||
|
||||
// Check if we have a single completion candidate
|
||||
func (rl *Instance) hasOneCandidate() bool {
|
||||
func (rl *Readline) hasOneCandidate() bool {
|
||||
if len(rl.tcGroups) == 0 {
|
||||
return false
|
||||
}
|
||||
@ -509,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool {
|
||||
// - The user-specified max completion length
|
||||
// - The terminal lengh
|
||||
// we use this function to prompt for confirmation before printing comps.
|
||||
func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||
func (rl *Readline) promptCompletionConfirm(sentence string) {
|
||||
rl.infoText = []rune(sentence)
|
||||
|
||||
rl.compConfirmWait = true
|
||||
@ -518,7 +518,7 @@ func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
func (rl *Readline) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
for _, group := range rl.tcGroups {
|
||||
comps += len(group.Suggestions)
|
||||
// if group.Name != "" {
|
||||
@ -535,7 +535,7 @@ func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) resetTabCompletion() {
|
||||
func (rl *Readline) resetTabCompletion() {
|
||||
rl.modeTabCompletion = false
|
||||
rl.tabCompletionSelect = false
|
||||
rl.compConfirmWait = false
|
@ -12,7 +12,7 @@ const (
|
||||
RegisterFind
|
||||
)
|
||||
|
||||
func (rl *Instance) backspaceTabFind() {
|
||||
func (rl *Readline) backspaceTabFind() {
|
||||
if len(rl.tfLine) > 0 {
|
||||
rl.tfLine = rl.tfLine[:len(rl.tfLine)-1]
|
||||
}
|
||||
@ -21,7 +21,7 @@ func (rl *Instance) backspaceTabFind() {
|
||||
|
||||
// Filter and refresh (print) a list of completions. The caller should have reset
|
||||
// the virtual completion system before, so that should not clash with this.
|
||||
func (rl *Instance) updateTabFind(r []rune) {
|
||||
func (rl *Readline) updateTabFind(r []rune) {
|
||||
|
||||
rl.tfLine = append(rl.tfLine, r...)
|
||||
|
||||
@ -34,7 +34,7 @@ func (rl *Instance) updateTabFind(r []rune) {
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) resetTabFind() {
|
||||
func (rl *Readline) resetTabFind() {
|
||||
rl.modeTabFind = false
|
||||
// rl.modeAutoFind = false // Added, because otherwise it gets stuck on search completions
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
|
||||
// DelayedTabContext is a custom context interface for async updates to the tab completions
|
||||
type DelayedTabContext struct {
|
||||
rl *Instance
|
||||
rl *Readline
|
||||
Context context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func delayedSyntaxTimer(rl *Instance, i int64) {
|
||||
func delayedSyntaxTimer(rl *Readline, i int64) {
|
||||
if rl.PasswordMask != 0 || rl.DelayedSyntaxWorker == nil {
|
||||
return
|
||||
}
|
@ -5,7 +5,7 @@ type undoItem struct {
|
||||
pos int
|
||||
}
|
||||
|
||||
func (rl *Instance) undoAppendHistory() {
|
||||
func (rl *Readline) undoAppendHistory() {
|
||||
defer func() { rl.viUndoSkipAppend = false }()
|
||||
|
||||
if rl.viUndoSkipAppend {
|
||||
@ -18,7 +18,7 @@ func (rl *Instance) undoAppendHistory() {
|
||||
})
|
||||
}
|
||||
|
||||
func (rl *Instance) undoLast() {
|
||||
func (rl *Readline) undoLast() {
|
||||
var undo undoItem
|
||||
for {
|
||||
if len(rl.viUndoHistory) == 0 {
|
@ -10,7 +10,7 @@ import (
|
||||
// updateHelpers is a key part of the whole refresh process:
|
||||
// it should coordinate reprinting the input line, any Infos and completions
|
||||
// and manage to get back to the current (computed) cursor coordinates
|
||||
func (rl *Instance) updateHelpers() {
|
||||
func (rl *Readline) updateHelpers() {
|
||||
print(seqHideCursor)
|
||||
// Load all Infos & completions before anything.
|
||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText()
|
||||
@ -19,7 +19,9 @@ func (rl *Instance) updateHelpers() {
|
||||
if rl.modeTabCompletion && !rl.completionOpen {
|
||||
rl.getTabCompletion()
|
||||
} else {
|
||||
if rl.completionOpen { rl.completionOpen = false }
|
||||
if rl.completionOpen {
|
||||
rl.completionOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
// We clear everything
|
||||
@ -49,7 +51,7 @@ func getWidth(x []rune) int {
|
||||
}
|
||||
|
||||
// Update reference should be called only once in a "loop" (not Readline(), but key control loop)
|
||||
func (rl *Instance) updateReferences() {
|
||||
func (rl *Readline) updateReferences() {
|
||||
|
||||
// We always need to work with clean data,
|
||||
// since we will have incrementers all around
|
||||
@ -103,7 +105,7 @@ func (rl *Instance) updateReferences() {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetHelpers() {
|
||||
func (rl *Readline) resetHelpers() {
|
||||
rl.modeAutoFind = false
|
||||
|
||||
// Now reset all below-input helpers
|
||||
@ -113,7 +115,7 @@ func (rl *Instance) resetHelpers() {
|
||||
|
||||
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
||||
// and comes back at the prompt.
|
||||
func (rl *Instance) clearHelpers() {
|
||||
func (rl *Readline) clearHelpers() {
|
||||
|
||||
// Now go down to the last line of input
|
||||
moveCursorDown(rl.fullY - rl.posY)
|
||||
@ -132,7 +134,7 @@ func (rl *Instance) clearHelpers() {
|
||||
// renderHelpers - pritns all components (prompt, line, Infos & comps)
|
||||
// and replaces the cursor to its current position. This function never
|
||||
// computes or refreshes any value, except from inside the echo function.
|
||||
func (rl *Instance) renderHelpers() {
|
||||
func (rl *Readline) renderHelpers() {
|
||||
|
||||
// when the instance is in this state we want it to be "below" the user's
|
||||
// input for it to be aligned properly
|
||||
@ -197,14 +199,14 @@ func (rl *Instance) renderHelpers() {
|
||||
moveCursorForwards(rl.posX)
|
||||
}
|
||||
|
||||
func (rl *Instance) bufprintF(format string, a ...any) {
|
||||
func (rl *Readline) bufprintF(format string, a ...any) {
|
||||
fmt.Fprintf(rl.bufferedOut, format, a...)
|
||||
}
|
||||
|
||||
func (rl *Instance) bufprint(text string) {
|
||||
func (rl *Readline) bufprint(text string) {
|
||||
fmt.Fprint(rl.bufferedOut, text)
|
||||
}
|
||||
|
||||
func (rl *Instance) bufflush() {
|
||||
func (rl *Readline) bufflush() {
|
||||
rl.bufferedOut.Flush()
|
||||
}
|
@ -34,6 +34,7 @@ var (
|
||||
)
|
||||
|
||||
type ViAction int
|
||||
|
||||
const (
|
||||
VimActionYank = iota
|
||||
VimActionPaste
|
||||
@ -52,7 +53,7 @@ var (
|
||||
// vi - Apply a key to a Vi action. Note that as in the rest of the code, all cursor movements
|
||||
// have been moved away, and only the rl.pos is adjusted: when echoing the input line, the shell
|
||||
// will compute the new cursor pos accordingly.
|
||||
func (rl *Instance) vi(r rune) {
|
||||
func (rl *Readline) vi(r rune) {
|
||||
activeRegister := string(rl.registers.currentRegister)
|
||||
|
||||
// Check if we are in register mode. If yes, and for some characters,
|
||||
@ -384,7 +385,7 @@ func (rl *Instance) vi(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) getViIterations() int {
|
||||
func (rl *Readline) getViIterations() int {
|
||||
i, _ := strconv.Atoi(rl.viIteration)
|
||||
if i < 1 {
|
||||
i = 1
|
||||
@ -393,7 +394,7 @@ func (rl *Instance) getViIterations() int {
|
||||
return i
|
||||
}
|
||||
|
||||
func (rl *Instance) refreshVimStatus() {
|
||||
func (rl *Readline) refreshVimStatus() {
|
||||
rl.ViModeCallback(rl.modeViMode)
|
||||
rl.computePrompt()
|
||||
rl.updateHelpers()
|
||||
@ -401,7 +402,7 @@ func (rl *Instance) refreshVimStatus() {
|
||||
|
||||
// viInfoMessage - lmorg's way of showing Vim status is to overwrite the info.
|
||||
// Currently not used, as there is a possibility to show the current Vim mode in the prompt.
|
||||
func (rl *Instance) viInfoMessage() {
|
||||
func (rl *Readline) viInfoMessage() {
|
||||
switch rl.modeViMode {
|
||||
case VimKeys:
|
||||
rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||||
@ -421,7 +422,7 @@ func (rl *Instance) viInfoMessage() {
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
@ -436,7 +437,7 @@ func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
||||
return adjust * -1
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
@ -459,7 +460,7 @@ func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
func (rl *Readline) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
@ -472,7 +473,7 @@ func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
||||
func (rl *Readline) viJumpPreviousBrace() (adjust int) {
|
||||
if rl.pos == 0 {
|
||||
return 0
|
||||
}
|
||||
@ -486,7 +487,7 @@ func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpNextBrace() (adjust int) {
|
||||
func (rl *Readline) viJumpNextBrace() (adjust int) {
|
||||
if rl.pos >= len(rl.line)-1 {
|
||||
return 0
|
||||
}
|
||||
@ -500,7 +501,7 @@ func (rl *Instance) viJumpNextBrace() (adjust int) {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (rl *Instance) viJumpBracket() (adjust int) {
|
||||
func (rl *Readline) viJumpBracket() (adjust int) {
|
||||
split, index, pos := tokeniseBrackets(rl.line, rl.pos)
|
||||
switch {
|
||||
case len(split) == 0:
|
@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
// vimDelete -
|
||||
func (rl *Instance) viDelete(r rune) {
|
||||
func (rl *Readline) viDelete(r rune) {
|
||||
|
||||
// We are allowed to type iterations after a delete ('d') command.
|
||||
// in which case we don't exit the delete mode. The next thing typed
|
||||
@ -91,7 +91,7 @@ func (rl *Instance) viDelete(r rune) {
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) viDeleteByAdjust(adjust int) {
|
||||
func (rl *Readline) viDeleteByAdjust(adjust int) {
|
||||
var (
|
||||
newLine []rune
|
||||
backOne bool
|
||||
@ -142,11 +142,11 @@ func (rl *Instance) viDeleteByAdjust(adjust int) {
|
||||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) DeleteByAmount(adjust int) {
|
||||
func (rl *Readline) DeleteByAmount(adjust int) {
|
||||
rl.viDeleteByAdjust(adjust)
|
||||
}
|
||||
|
||||
func (rl *Instance) vimDeleteToken(r rune) bool {
|
||||
func (rl *Readline) vimDeleteToken(r rune) bool {
|
||||
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
||||
pos := int(r) - 48 // convert ASCII to integer
|
||||
if pos > len(tokens) {
|
8
lua.go
8
lua.go
@ -5,16 +5,16 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"hilbish/util"
|
||||
"hilbish/golibs/bait"
|
||||
"hilbish/golibs/commander"
|
||||
"hilbish/golibs/fs"
|
||||
"hilbish/golibs/snail"
|
||||
"hilbish/golibs/terminal"
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib"
|
||||
"github.com/arnodel/golua/lib/debuglib"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var minimalconf = `hilbish.prompt '& '`
|
||||
@ -55,10 +55,10 @@ func luaInit() {
|
||||
|
||||
cmds = commander.New(l)
|
||||
lib.LoadLibs(l, cmds.Loader)
|
||||
|
||||
lib.LoadLibs(l, lr.rl.Loader)
|
||||
|
||||
// Add more paths that Lua can require from
|
||||
_, err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
||||
_, err := util.DoString(l, "package.path = package.path .. "+requirePaths)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
|
||||
}
|
||||
|
28
nature/editor.lua
Normal file
28
nature/editor.lua
Normal file
@ -0,0 +1,28 @@
|
||||
local readline = require 'readline'
|
||||
|
||||
local editor = readline.new()
|
||||
local editorMt = {}
|
||||
|
||||
hilbish.editor = {}
|
||||
|
||||
local function contains(search, needle)
|
||||
for _, p in ipairs(search) do
|
||||
if p == needle then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function editorMt.__index(_, key)
|
||||
if contains({'deleteByAmount', 'getVimRegister', 'getLine', 'insert', 'readChar', 'setVimRegister'}, key) then
|
||||
--editor:log 'The calling method of this function has changed. Please use the colon to call this hilbish.editor function.'
|
||||
end
|
||||
|
||||
return function(...)
|
||||
return editor[key](editor, ...)
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(hilbish.editor, editorMt)
|
@ -28,6 +28,7 @@ require 'nature.vim'
|
||||
require 'nature.runner'
|
||||
require 'nature.hummingbird'
|
||||
require 'nature.abbr'
|
||||
require 'nature.editor'
|
||||
|
||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
||||
if shlvl ~= nil then
|
||||
|
@ -1,7 +0,0 @@
|
||||
// Package readline is a pure-Go re-imagining of the UNIX readline API
|
||||
//
|
||||
// This package is designed to be run independently from murex and at some
|
||||
// point it will be separated into it's own git repository (at a stage when I
|
||||
// am confident that murex will no longer be the primary driver for features,
|
||||
// bugs or other code changes)
|
||||
package readline
|
@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
@ -1,10 +0,0 @@
|
||||
# raw
|
||||
|
||||
This command exists here purely as a lazy feature for me to scan key presses
|
||||
for their corresponding escape codes. It is a useful dev tool for rationalizing
|
||||
what is happening in the different terminal emulators (since documentation
|
||||
regarding what escape codes they send can often be non-existent and some of the
|
||||
more exotic key combinations or modern keyboard functions can have multiple
|
||||
published standards.
|
||||
|
||||
This package is not imported by `readline` and is not required as part of `readline`
|
@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/maxlandon/readline"
|
||||
)
|
||||
|
||||
func main() {
|
||||
readline.MakeRaw(int(os.Stdin.Fd()))
|
||||
|
||||
for {
|
||||
b := make([]byte, 1024)
|
||||
i, err := os.Stdin.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(b[:i])
|
||||
}
|
||||
}
|
75
rl.go
75
rl.go
@ -13,9 +13,10 @@ import (
|
||||
)
|
||||
|
||||
type lineReader struct {
|
||||
rl *readline.Instance
|
||||
rl *readline.Readline
|
||||
fileHist *fileHistory
|
||||
}
|
||||
|
||||
var hinter *rt.Closure
|
||||
var highlighter *rt.Closure
|
||||
|
||||
@ -54,58 +55,64 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||
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"
|
||||
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"
|
||||
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)))
|
||||
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)))
|
||||
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)
|
||||
rt.IntValue(int64(pos))}, term)
|
||||
|
||||
var compGroups []*readline.CompletionGroup
|
||||
if err != nil {
|
||||
@ -175,11 +182,11 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||
itemAliases[itemName] = itemAlias
|
||||
} else if keytyp == rt.IntType {
|
||||
vlStr, ok := lval.TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
if !ok {
|
||||
// TODO: error
|
||||
return
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
} else {
|
||||
// TODO: error
|
||||
return
|
||||
@ -188,20 +195,22 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||
|
||||
var dispType readline.TabDisplayType
|
||||
switch luaCompType.AsString() {
|
||||
case "grid": dispType = readline.TabDisplayGrid
|
||||
case "list": dispType = readline.TabDisplayList
|
||||
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,
|
||||
Aliases: itemAliases,
|
||||
DisplayType: dispType,
|
||||
Aliases: itemAliases,
|
||||
Descriptions: itemDescriptions,
|
||||
ItemDisplays: itemDisplays,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
})
|
||||
|
||||
@ -227,8 +236,8 @@ 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]
|
||||
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 = ""
|
||||
@ -260,16 +269,16 @@ func (lr *lineReader) Resize() {
|
||||
|
||||
// #interface history
|
||||
// command history
|
||||
// The history interface deals with 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},
|
||||
"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},
|
||||
"get": {lr.luaGetHistory, 1, false},
|
||||
"size": {lr.luaSize, 0, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
|
Loading…
x
Reference in New Issue
Block a user