2
2
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:
sammyette 2025-06-15 15:47:53 -04:00 committed by GitHub
parent 1bb433dc64
commit 5ca858d112
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 893 additions and 901 deletions

View File

@ -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
View File

@ -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

View File

@ -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))

View File

@ -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>

View File

@ -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>

View 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
View 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
View File

@ -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
}

View File

@ -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
View 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
View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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:

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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{

View File

@ -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{}
}

View File

@ -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()

View File

@ -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
View 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())
}

View File

@ -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)

View File

@ -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 (

View File

@ -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
}

View File

@ -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 {

View File

@ -23,7 +23,7 @@ type registers struct {
mutex *sync.Mutex
}
func (rl *Instance) initRegisters() {
func (rl *Readline) initRegisters() {
rl.registers = &registers{
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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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:

View File

@ -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
View File

@ -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
View 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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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`

View File

@ -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
View File

@ -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()