mirror of
https://github.com/Hilbis/Hilbish
synced 2025-07-01 16:52:03 +00:00
chore: update branch
This commit is contained in:
commit
3f53f31fb9
23
CHANGELOG.md
23
CHANGELOG.md
@ -3,7 +3,30 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
- Forward/Right arrow key will fill in hint text (#327)
|
- 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.
|
||||||
|
- `yarn` threading library (See the docs)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Documentation for Lunacolors has been improved, with more information added.
|
||||||
- Values returned by bait hooks will be passed to the `throw` caller
|
- Values returned by bait hooks will be passed to the `throw` caller
|
||||||
|
- `display` property to completion groups entries to style completion entries when type is `list`.
|
||||||
|
example:
|
||||||
|
```lua
|
||||||
|
local cg = {
|
||||||
|
items = {
|
||||||
|
'list item 1',
|
||||||
|
['--command-flag-here'] = {'this does a thing', '--the-flag-alias'},
|
||||||
|
['--styled-command-flag-here'] = {'this does a thing', '--the-flag-alias', display = lunacolors.blue '--styled-command-flag-here'}
|
||||||
|
},
|
||||||
|
type = 'list'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## [2.3.4] - 2024-12-28
|
## [2.3.4] - 2024-12-28
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -6,7 +6,7 @@ vars:
|
|||||||
PREFIX: '{{default "/usr/local" .PREFIX}}'
|
PREFIX: '{{default "/usr/local" .PREFIX}}'
|
||||||
bindir__: '{{.PREFIX}}/bin'
|
bindir__: '{{.PREFIX}}/bin'
|
||||||
BINDIR: '{{default .bindir__ .BINDIR}}'
|
BINDIR: '{{default .bindir__ .BINDIR}}'
|
||||||
libdir__: '{{.PREFIX}}/share/hilbish'
|
libdir__: ''
|
||||||
LIBDIR: '{{default .libdir__ .LIBDIR}}'
|
LIBDIR: '{{default .libdir__ .LIBDIR}}'
|
||||||
goflags__: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"'
|
goflags__: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}}"'
|
||||||
GOFLAGS: '{{default .goflags__ .GOFLAGS}}'
|
GOFLAGS: '{{default .goflags__ .GOFLAGS}}'
|
||||||
|
92
api.go
92
api.go
@ -25,30 +25,31 @@ import (
|
|||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
|
||||||
"github.com/arnodel/golua/lib/packagelib"
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
|
||||||
//"github.com/arnodel/golua/lib/iolib"
|
//"github.com/arnodel/golua/lib/iolib"
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
//"mvdan.cc/sh/v3/interp"
|
//"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exports = map[string]util.LuaExport{
|
var exports = map[string]util.LuaExport{
|
||||||
"alias": {hlalias, 2, false},
|
"alias": {hlalias, 2, false},
|
||||||
"appendPath": {hlappendPath, 1, false},
|
"appendPath": {hlappendPath, 1, false},
|
||||||
"complete": {hlcomplete, 2, false},
|
"complete": {hlcomplete, 2, false},
|
||||||
"cwd": {hlcwd, 0, false},
|
"cwd": {hlcwd, 0, false},
|
||||||
"exec": {hlexec, 1, false},
|
"exec": {hlexec, 1, false},
|
||||||
"goro": {hlgoro, 1, true},
|
"goro": {hlgoro, 1, true},
|
||||||
"highlighter": {hlhighlighter, 1, false},
|
"highlighter": {hlhighlighter, 1, false},
|
||||||
"hinter": {hlhinter, 1, false},
|
"hinter": {hlhinter, 1, false},
|
||||||
"multiprompt": {hlmultiprompt, 1, false},
|
"multiprompt": {hlmultiprompt, 1, false},
|
||||||
"prependPath": {hlprependPath, 1, false},
|
"prependPath": {hlprependPath, 1, false},
|
||||||
"prompt": {hlprompt, 1, true},
|
"prompt": {hlprompt, 1, true},
|
||||||
"inputMode": {hlinputMode, 1, false},
|
"inputMode": {hlinputMode, 1, false},
|
||||||
"interval": {hlinterval, 2, false},
|
"interval": {hlinterval, 2, false},
|
||||||
"read": {hlread, 1, false},
|
"read": {hlread, 1, false},
|
||||||
"timeout": {hltimeout, 2, false},
|
"timeout": {hltimeout, 2, false},
|
||||||
"which": {hlwhich, 1, false},
|
"which": {hlwhich, 1, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
var hshMod *rt.Table
|
var hshMod *rt.Table
|
||||||
@ -61,7 +62,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
|
|
||||||
util.SetExports(rtm, mod, exports)
|
util.SetExports(rtm, mod, exports)
|
||||||
hshMod = mod
|
if hshMod == nil {
|
||||||
|
hshMod = mod
|
||||||
|
}
|
||||||
|
|
||||||
host, _ := os.Hostname()
|
host, _ := os.Hostname()
|
||||||
username := curuser.Username
|
username := curuser.Username
|
||||||
@ -101,7 +104,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
// hilbish.completion table
|
// hilbish.completion table
|
||||||
hshcomp := completionLoader(rtm)
|
hshcomp := completionLoader(rtm)
|
||||||
// TODO: REMOVE "completion" AND ONLY USE "completions" WITH AN S
|
// TODO: REMOVE "completion" AND ONLY USE "completions" WITH AN S
|
||||||
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
|
||||||
mod.Set(rt.StringValue("completions"), rt.TableValue(hshcomp))
|
mod.Set(rt.StringValue("completions"), rt.TableValue(hshcomp))
|
||||||
|
|
||||||
// hilbish.runner table
|
// hilbish.runner table
|
||||||
@ -118,9 +120,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
timersModule := timers.loader(rtm)
|
timersModule := timers.loader(rtm)
|
||||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule))
|
mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule))
|
||||||
|
|
||||||
editorModule := editorLoader(rtm)
|
|
||||||
mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule))
|
|
||||||
|
|
||||||
versionModule := rt.NewTable()
|
versionModule := rt.NewTable()
|
||||||
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch))
|
util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch))
|
||||||
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()))
|
util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion()))
|
||||||
@ -131,18 +130,18 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
|||||||
pluginModule := moduleLoader(rtm)
|
pluginModule := moduleLoader(rtm)
|
||||||
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
|
||||||
|
|
||||||
sinkModule := util.SinkLoader(l)
|
sinkModule := util.SinkLoader(rtm)
|
||||||
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
|
mod.Set(rt.StringValue("sink"), rt.TableValue(sinkModule))
|
||||||
|
|
||||||
return rt.TableValue(mod), nil
|
return rt.TableValue(mod), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getenv(key, fallback string) string {
|
func getenv(key, fallback string) string {
|
||||||
value := os.Getenv(key)
|
value := os.Getenv(key)
|
||||||
if len(value) == 0 {
|
if len(value) == 0 {
|
||||||
return fallback
|
return fallback
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func setVimMode(mode string) {
|
func setVimMode(mode string) {
|
||||||
@ -194,7 +193,6 @@ func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// read(prompt) -> input (string)
|
// read(prompt) -> input (string)
|
||||||
// Read input from the user, using Hilbish's line editor/input reader.
|
// Read input from the user, using Hilbish's line editor/input reader.
|
||||||
// This is a separate instance from the one Hilbish actually uses.
|
// This is a separate instance from the one Hilbish actually uses.
|
||||||
@ -212,7 +210,7 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// substitute with an empty string
|
// substitute with an empty string
|
||||||
prompt = ""
|
prompt = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
lualr := &lineReader{
|
lualr := &lineReader{
|
||||||
rl: readline.NewInstance(),
|
rl: readline.NewInstance(),
|
||||||
}
|
}
|
||||||
@ -265,11 +263,13 @@ func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch typ {
|
switch typ {
|
||||||
case "left":
|
case "left":
|
||||||
prompt = p
|
prompt = p
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
lr.SetPrompt(fmtPrompt(prompt))
|
||||||
case "right": lr.SetRightPrompt(fmtPrompt(p))
|
case "right":
|
||||||
default: return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
lr.SetRightPrompt(fmtPrompt(p))
|
||||||
|
default:
|
||||||
|
return nil, errors.New("expected prompt type to be right or left, got " + typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -290,7 +290,7 @@ will look like:
|
|||||||
user ~ ∆ echo "hey
|
user ~ ∆ echo "hey
|
||||||
--> ...!"
|
--> ...!"
|
||||||
|
|
||||||
so then you get
|
so then you get
|
||||||
user ~ ∆ echo "hey
|
user ~ ∆ echo "hey
|
||||||
--> ...!"
|
--> ...!"
|
||||||
hey ...!
|
hey ...!
|
||||||
@ -386,7 +386,7 @@ func appendPath(dir string) {
|
|||||||
|
|
||||||
// if dir isnt already in $PATH, add it
|
// if dir isnt already in $PATH, add it
|
||||||
if !strings.Contains(pathenv, dir) {
|
if !strings.Contains(pathenv, dir) {
|
||||||
os.Setenv("PATH", pathenv + string(os.PathListSeparator) + dir)
|
os.Setenv("PATH", pathenv+string(os.PathListSeparator)+dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,7 +480,7 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
interval := time.Duration(ms) * time.Millisecond
|
interval := time.Duration(ms) * time.Millisecond
|
||||||
timer := timers.create(timerTimeout, interval, cb)
|
timer := timers.create(timerTimeout, interval, cb)
|
||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,7 +571,7 @@ func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
|
|
||||||
// if dir isnt already in $PATH, add in
|
// if dir isnt already in $PATH, add in
|
||||||
if !strings.Contains(pathenv, dir) {
|
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
|
return c.Next(), nil
|
||||||
@ -625,14 +625,14 @@ func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case "emacs":
|
case "emacs":
|
||||||
unsetVimMode()
|
unsetVimMode()
|
||||||
lr.rl.InputMode = readline.Emacs
|
lr.rl.InputMode = readline.Emacs
|
||||||
case "vim":
|
case "vim":
|
||||||
setVimMode("insert")
|
setVimMode("insert")
|
||||||
lr.rl.InputMode = readline.Vim
|
lr.rl.InputMode = readline.Vim
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Next(), nil
|
return c.Next(), nil
|
||||||
@ -667,7 +667,9 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
// #example
|
// #example
|
||||||
// --This code will highlight all double quoted strings in green.
|
// --This code will highlight all double quoted strings in green.
|
||||||
// function hilbish.highlighter(line)
|
// 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
|
// end
|
||||||
// #example
|
// #example
|
||||||
// #param line string
|
// #param line string
|
||||||
|
@ -72,7 +72,7 @@ type docPiece struct {
|
|||||||
type tag struct {
|
type tag struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Fields []string `json:"fields"`
|
Fields []string `json:"fields"`
|
||||||
StartIdx int `json:"startIdx`
|
StartIdx int `json:"startIdx"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var docs = make(map[string]module)
|
var docs = make(map[string]module)
|
||||||
@ -86,6 +86,8 @@ var prefix = map[string]string{
|
|||||||
"bait": "b",
|
"bait": "b",
|
||||||
"terminal": "term",
|
"terminal": "term",
|
||||||
"snail": "snail",
|
"snail": "snail",
|
||||||
|
"readline": "rl",
|
||||||
|
"yarn": "yarn",
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
func getTagsAndDocs(docs string) (map[string][]tag, []string) {
|
||||||
@ -222,6 +224,10 @@ func setupDoc(mod string, fun *doc.Func) *docPiece {
|
|||||||
goto start
|
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) {
|
if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
"\tprint 'Goodbye Hilbish!'",
|
"\tprint 'Goodbye Hilbish!'",
|
||||||
"end)"
|
"end)"
|
||||||
],
|
],
|
||||||
"StartIdx": 5
|
"startIdx": 5
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -70,7 +70,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"hook."
|
"hook."
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cb",
|
"id": "cb",
|
||||||
@ -88,7 +88,7 @@
|
|||||||
"is",
|
"is",
|
||||||
"thrown."
|
"thrown."
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"event"
|
"event"
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cb",
|
"id": "cb",
|
||||||
@ -163,7 +163,7 @@
|
|||||||
"is",
|
"is",
|
||||||
"thrown."
|
"thrown."
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -203,14 +203,14 @@
|
|||||||
"the",
|
"the",
|
||||||
"hook"
|
"hook"
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "table\u003cfunction\u003e",
|
"id": "table\u003cfunction\u003e",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -268,7 +268,7 @@
|
|||||||
"bait.release('event', hookCallback)",
|
"bait.release('event', hookCallback)",
|
||||||
"-- and now hookCallback will no longer be ran for the event."
|
"-- and now hookCallback will no longer be ran for the event."
|
||||||
],
|
],
|
||||||
"StartIdx": 7
|
"startIdx": 7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -285,7 +285,7 @@
|
|||||||
"is",
|
"is",
|
||||||
"on"
|
"on"
|
||||||
],
|
],
|
||||||
"StartIdx": 4
|
"startIdx": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "catcher",
|
"id": "catcher",
|
||||||
@ -296,7 +296,7 @@
|
|||||||
"to",
|
"to",
|
||||||
"remove"
|
"remove"
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -351,7 +351,7 @@
|
|||||||
"\tprint('Hello ' .. greetTo)",
|
"\tprint('Hello ' .. greetTo)",
|
||||||
"end)"
|
"end)"
|
||||||
],
|
],
|
||||||
"StartIdx": 5
|
"startIdx": 5
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -365,7 +365,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"hook."
|
"hook."
|
||||||
],
|
],
|
||||||
"StartIdx": 1
|
"startIdx": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "args",
|
"id": "args",
|
||||||
@ -379,7 +379,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"hook."
|
"hook."
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"to",
|
"to",
|
||||||
"remove."
|
"remove."
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@
|
|||||||
"\tsinks.out:writeln('Hello ' .. name)",
|
"\tsinks.out:writeln('Hello ' .. name)",
|
||||||
"end)"
|
"end)"
|
||||||
],
|
],
|
||||||
"StartIdx": 6
|
"startIdx": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -110,7 +110,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"command"
|
"command"
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cb",
|
"id": "cb",
|
||||||
@ -122,7 +122,7 @@
|
|||||||
"command",
|
"command",
|
||||||
"invocation"
|
"invocation"
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@
|
|||||||
{
|
{
|
||||||
"id": "table",
|
"id": "table",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
46
defs/fs.json
46
defs/fs.json
@ -44,14 +44,14 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 4
|
"startIdx": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -96,14 +96,14 @@
|
|||||||
"name",
|
"name",
|
||||||
"of."
|
"of."
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 4
|
"startIdx": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@
|
|||||||
"directory",
|
"directory",
|
||||||
"to."
|
"to."
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -186,14 +186,14 @@
|
|||||||
"directory",
|
"directory",
|
||||||
"for."
|
"for."
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "string",
|
"id": "string",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 4
|
"startIdx": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -240,7 +240,7 @@
|
|||||||
"print(matches)",
|
"print(matches)",
|
||||||
"-- -\u003e {'init.lua', 'code.lua'}"
|
"-- -\u003e {'init.lua', 'code.lua'}"
|
||||||
],
|
],
|
||||||
"StartIdx": 6
|
"startIdx": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -254,7 +254,7 @@
|
|||||||
"files",
|
"files",
|
||||||
"with."
|
"with."
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
@ -269,7 +269,7 @@
|
|||||||
"that",
|
"that",
|
||||||
"match."
|
"match."
|
||||||
],
|
],
|
||||||
"StartIdx": 4
|
"startIdx": 4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -307,7 +307,7 @@
|
|||||||
"print(fs.join(hilbish.userDir.config, 'hilbish'))",
|
"print(fs.join(hilbish.userDir.config, 'hilbish'))",
|
||||||
"-- -\u003e '/home/user/.config/hilbish' on Linux"
|
"-- -\u003e '/home/user/.config/hilbish' on Linux"
|
||||||
],
|
],
|
||||||
"StartIdx": 5
|
"startIdx": 5
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -320,7 +320,7 @@
|
|||||||
"join",
|
"join",
|
||||||
"together"
|
"together"
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
@ -331,7 +331,7 @@
|
|||||||
"joined",
|
"joined",
|
||||||
"path."
|
"path."
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -385,7 +385,7 @@
|
|||||||
"-- foo directory. If recursive is false in this case, it will fail.",
|
"-- foo directory. If recursive is false in this case, it will fail.",
|
||||||
"fs.mkdir('./foo/bar', true)"
|
"fs.mkdir('./foo/bar', true)"
|
||||||
],
|
],
|
||||||
"StartIdx": 6
|
"startIdx": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -398,7 +398,7 @@
|
|||||||
"the",
|
"the",
|
||||||
"directory"
|
"directory"
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "recursive",
|
"id": "recursive",
|
||||||
@ -414,7 +414,7 @@
|
|||||||
"provided",
|
"provided",
|
||||||
"name"
|
"name"
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -435,12 +435,12 @@
|
|||||||
{
|
{
|
||||||
"id": "File",
|
"id": "File",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "File",
|
"id": "File",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -469,14 +469,14 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"StartIdx": 2
|
"startIdx": 2
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "table",
|
"id": "table",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -524,7 +524,7 @@
|
|||||||
"}",
|
"}",
|
||||||
"]]--"
|
"]]--"
|
||||||
],
|
],
|
||||||
"StartIdx": 10
|
"startIdx": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -533,14 +533,14 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"StartIdx": 7
|
"startIdx": 7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"returns": [
|
"returns": [
|
||||||
{
|
{
|
||||||
"id": "table",
|
"id": "table",
|
||||||
"fields": [],
|
"fields": [],
|
||||||
"StartIdx": 8
|
"startIdx": 8
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
282
defs/readline.json
Normal file
282
defs/readline.json
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
{
|
||||||
|
"name": "readline",
|
||||||
|
"shortDescription": "line reader library",
|
||||||
|
"description": "The readline module is responsible for reading input from the user.\nThe readline module is what Hilbish uses to read input from the user,\nincluding all the interactive features of Hilbish like history search,\nsyntax highlighting, everything. The global Hilbish readline instance\nis usable at `hilbish.editor`.",
|
||||||
|
"properties": [],
|
||||||
|
"fields": [],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "Readline",
|
||||||
|
"description": [],
|
||||||
|
"parent": "readline",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": false,
|
||||||
|
"isType": true,
|
||||||
|
"tags": {
|
||||||
|
"type": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"docs": [
|
||||||
|
{
|
||||||
|
"name": "DeleteByAmount",
|
||||||
|
"description": [
|
||||||
|
"Deletes characters in the line by the given amount."
|
||||||
|
],
|
||||||
|
"signature": "deleteByAmount(amount)",
|
||||||
|
"goFuncName": "rldeletebyamount",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"Name": "amount",
|
||||||
|
"Type": "number",
|
||||||
|
"Doc": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"param": [
|
||||||
|
{
|
||||||
|
"id": "amount",
|
||||||
|
"fields": [
|
||||||
|
"number"
|
||||||
|
],
|
||||||
|
"startIdx": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GetLine",
|
||||||
|
"description": [
|
||||||
|
"Returns the current input line."
|
||||||
|
],
|
||||||
|
"signature": "getLine() -\u003e string",
|
||||||
|
"goFuncName": "rlgetline",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returns": [
|
||||||
|
{
|
||||||
|
"id": "string",
|
||||||
|
"fields": [],
|
||||||
|
"startIdx": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GetRegister",
|
||||||
|
"description": [
|
||||||
|
"Returns the text that is at the register."
|
||||||
|
],
|
||||||
|
"signature": "getVimRegister(register) -\u003e string",
|
||||||
|
"goFuncName": "rlgetregister",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"Name": "register",
|
||||||
|
"Type": "string",
|
||||||
|
"Doc": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"param": [
|
||||||
|
{
|
||||||
|
"id": "register",
|
||||||
|
"fields": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"startIdx": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Insert",
|
||||||
|
"description": [
|
||||||
|
"Inserts text into the Hilbish command line."
|
||||||
|
],
|
||||||
|
"signature": "insert(text)",
|
||||||
|
"goFuncName": "rlinsert",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"Name": "text",
|
||||||
|
"Type": "string",
|
||||||
|
"Doc": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"param": [
|
||||||
|
{
|
||||||
|
"id": "text",
|
||||||
|
"fields": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"startIdx": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Log",
|
||||||
|
"description": [
|
||||||
|
"Prints a message *before* the prompt without it being interrupted by user input."
|
||||||
|
],
|
||||||
|
"signature": "log(text)",
|
||||||
|
"goFuncName": "rllog",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "New",
|
||||||
|
"description": [
|
||||||
|
"Creates a new readline instance."
|
||||||
|
],
|
||||||
|
"signature": "new() -\u003e @Readline",
|
||||||
|
"goFuncName": "rlnew",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": false,
|
||||||
|
"isType": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Read",
|
||||||
|
"description": [
|
||||||
|
"Reads input from the user."
|
||||||
|
],
|
||||||
|
"signature": "read() -\u003e string",
|
||||||
|
"goFuncName": "rlread",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ReadChar",
|
||||||
|
"description": [
|
||||||
|
"Reads a keystroke from the user. This is in a format of something like Ctrl-L."
|
||||||
|
],
|
||||||
|
"signature": "getChar() -\u003e string",
|
||||||
|
"goFuncName": "rlreadchar",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SetRegister",
|
||||||
|
"description": [
|
||||||
|
"Sets the vim register at `register` to hold the passed text."
|
||||||
|
],
|
||||||
|
"signature": "setVimRegister(register, text)",
|
||||||
|
"goFuncName": "rlsetregister",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": true,
|
||||||
|
"isType": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"Name": "register",
|
||||||
|
"Type": "string",
|
||||||
|
"Doc": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "text",
|
||||||
|
"Type": "string",
|
||||||
|
"Doc": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": {
|
||||||
|
"member": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"param": [
|
||||||
|
{
|
||||||
|
"id": "register",
|
||||||
|
"fields": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"startIdx": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "text",
|
||||||
|
"fields": [
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
{
|
{
|
||||||
"id": "",
|
"id": "",
|
||||||
"fields": null,
|
"fields": null,
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@
|
|||||||
{
|
{
|
||||||
"id": "",
|
"id": "",
|
||||||
"fields": null,
|
"fields": null,
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -72,7 +72,7 @@
|
|||||||
"absolute",
|
"absolute",
|
||||||
"path."
|
"path."
|
||||||
],
|
],
|
||||||
"StartIdx": 5
|
"startIdx": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@
|
|||||||
{
|
{
|
||||||
"id": "",
|
"id": "",
|
||||||
"fields": null,
|
"fields": null,
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"param": [
|
"param": [
|
||||||
@ -124,14 +124,14 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
"string"
|
"string"
|
||||||
],
|
],
|
||||||
"StartIdx": 3
|
"startIdx": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "streams",
|
"id": "streams",
|
||||||
"fields": [
|
"fields": [
|
||||||
"table"
|
"table"
|
||||||
],
|
],
|
||||||
"StartIdx": 0
|
"startIdx": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
40
defs/yarn.json
Normal file
40
defs/yarn.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "yarn",
|
||||||
|
"shortDescription": "multi threading library",
|
||||||
|
"description": "Yarn is a simple multithreading library. Threads are individual Lua states,\nso they do NOT share the same environment as the code that runs the thread.\nBait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.\n\nExample:\n\n```lua\nlocal yarn = require 'yarn'\n\n-- calling t will run the yarn thread.\nlocal t = yarn.thread(print)\nt 'printing from another lua state!'\n```",
|
||||||
|
"properties": [],
|
||||||
|
"fields": [],
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"name": "Thread",
|
||||||
|
"description": [],
|
||||||
|
"parent": "yarn",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": false,
|
||||||
|
"isType": true,
|
||||||
|
"tags": {
|
||||||
|
"type": [
|
||||||
|
{
|
||||||
|
"id": "",
|
||||||
|
"fields": null,
|
||||||
|
"startIdx": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"docs": [
|
||||||
|
{
|
||||||
|
"name": "thread",
|
||||||
|
"description": [
|
||||||
|
"Creates a new, fresh Yarn thread.",
|
||||||
|
"`fun` is the function that will run in the thread."
|
||||||
|
],
|
||||||
|
"signature": "thread(fun) -\u003e @Thread",
|
||||||
|
"goFuncName": "yarnthread",
|
||||||
|
"isInterface": false,
|
||||||
|
"isMember": false,
|
||||||
|
"isType": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Module hilbish
|
title: Module hilbish
|
||||||
description: the core Hilbish API
|
description:
|
||||||
layout: doc
|
layout: doc
|
||||||
menu:
|
menu:
|
||||||
docs:
|
docs:
|
||||||
@ -9,8 +9,7 @@ menu:
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
The Hilbish module includes the core API, containing
|
|
||||||
interfaces and functions which directly relate to shell functionality.
|
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
@ -87,53 +86,6 @@ interfaces and functions which directly relate to shell functionality.
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Static module fields
|
|
||||||
|
|
||||||
``` =html
|
|
||||||
<div class='relative overflow-x-auto sm:rounded-lg my-4'>
|
|
||||||
<table class='w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400'>
|
|
||||||
<tbody>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>ver</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>The version of Hilbish</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>goVersion</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>The version of Go that Hilbish was compiled with</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>user</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Username of the user</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>host</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Hostname of the machine</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>dataDir</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Directory for Hilbish data files, including the docs and default modules</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>interactive</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Is Hilbish in an interactive shell?</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>login</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Is Hilbish the login shell?</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>vimMode</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Current Vim input mode of Hilbish (will be nil if not in Vim input mode)</td>
|
|
||||||
</tr>
|
|
||||||
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>exitCode</td>
|
|
||||||
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Exit code of the last executed command</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
``` =html
|
``` =html
|
||||||
@ -351,7 +303,9 @@ Note that to set a highlighter, one has to override this function.
|
|||||||
```lua
|
```lua
|
||||||
--This code will highlight all double quoted strings in green.
|
--This code will highlight all double quoted strings in green.
|
||||||
function hilbish.highlighter(line)
|
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
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
96
docs/api/readline.md
Normal file
96
docs/api/readline.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<div class='relative overflow-x-auto sm:rounded-lg my-4'>
|
||||||
|
<table class='w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400'>
|
||||||
|
<tbody>
|
||||||
|
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
||||||
|
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'><a href="#New">new() -> @Readline</a></td>
|
||||||
|
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Creates a new readline instance.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<hr class='my-4 text-neutral-400 dark:text-neutral-600'>
|
||||||
|
<div id='New'>
|
||||||
|
<h4 class='text-xl font-medium mb-2'>
|
||||||
|
readline.new() -> @Readline
|
||||||
|
<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.
|
||||||
|
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<hr class='my-4 text-neutral-400 dark:text-neutral-600'>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
73
docs/api/yarn.md
Normal file
73
docs/api/yarn.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
title: Module yarn
|
||||||
|
description: multi threading library
|
||||||
|
layout: doc
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "API"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Yarn is a simple multithreading library. Threads are individual Lua states,
|
||||||
|
so they do NOT share the same environment as the code that runs the thread.
|
||||||
|
Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local yarn = require 'yarn'
|
||||||
|
|
||||||
|
-- calling t will run the yarn thread.
|
||||||
|
local t = yarn.thread(print)
|
||||||
|
t 'printing from another lua state!'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<div class='relative overflow-x-auto sm:rounded-lg my-4'>
|
||||||
|
<table class='w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400'>
|
||||||
|
<tbody>
|
||||||
|
<tr class='bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200'>
|
||||||
|
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'><a href="#thread">thread(fun) -> @Thread</a></td>
|
||||||
|
<td class='p-3 font-medium text-black whitespace-nowrap dark:text-white'>Creates a new, fresh Yarn thread.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<hr class='my-4 text-neutral-400 dark:text-neutral-600'>
|
||||||
|
<div id='thread'>
|
||||||
|
<h4 class='text-xl font-medium mb-2'>
|
||||||
|
yarn.thread(fun) -> @Thread
|
||||||
|
<a href="#thread" class='heading-link'>
|
||||||
|
<i class="fas fa-paperclip"></i>
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Creates a new, fresh Yarn thread.
|
||||||
|
`fun` is the function that will run in the thread.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
This function has no parameters.
|
||||||
|
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
``` =html
|
||||||
|
<hr class='my-4 text-neutral-400 dark:text-neutral-600'>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Thread
|
||||||
|
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
@ -56,6 +56,50 @@ return {cg, cg2}, prefix
|
|||||||
Which looks like this:
|
Which looks like this:
|
||||||
{{< video src="https://safe.saya.moe/t4CiLK6dgPbD.mp4" >}}
|
{{< video src="https://safe.saya.moe/t4CiLK6dgPbD.mp4" >}}
|
||||||
|
|
||||||
|
# Completion Group Types
|
||||||
|
### grid
|
||||||
|
Grid is the simplest completion group type. All items are strings and when
|
||||||
|
completion is done is displayed in a grid based on size.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
items = {'just', 'a bunch', 'of items', 'here', 'hehe'},
|
||||||
|
type = 'grid'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### list
|
||||||
|
The list completion group type displays in a list. A list item can either be a string, or a table for additional display options.
|
||||||
|
A completion alias can be specified either as the `2nd` entry in the options table
|
||||||
|
or te `alias` key.
|
||||||
|
|
||||||
|
A description can optionally be displayed for a list item, which is either the `1st`
|
||||||
|
entry or the `description` key.
|
||||||
|
|
||||||
|
Lastly, list entries can be styled. This is done with the `display` key. If this is present, this
|
||||||
|
overrides what the completion item *looks* like.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```lua
|
||||||
|
{
|
||||||
|
items = {
|
||||||
|
['--flag'] = {
|
||||||
|
description = 'this flag nukes the bri ish',
|
||||||
|
alias = '--bye-bri-ish',
|
||||||
|
display = lunacolors.format('--{blue}fl{red}ag')
|
||||||
|
},
|
||||||
|
['--flag2'] = {
|
||||||
|
'make pizza', -- description
|
||||||
|
'--pizzuh', -- alias
|
||||||
|
display = lunacolors.yellow '--pizzuh'
|
||||||
|
},
|
||||||
|
'--flag3'
|
||||||
|
},
|
||||||
|
type = 'list'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
# Completion Handler
|
# Completion Handler
|
||||||
Like most parts of Hilbish, it's made to be extensible and
|
Like most parts of Hilbish, it's made to be extensible and
|
||||||
customizable. The default handler for completions in general can
|
customizable. The default handler for completions in general can
|
||||||
|
@ -76,3 +76,8 @@ of an exact match.
|
|||||||
#### Default: `true`
|
#### Default: `true`
|
||||||
If this is enabled, when a background job is finished,
|
If this is enabled, when a background job is finished,
|
||||||
a [notification](../notifications) will be sent.
|
a [notification](../notifications) will be sent.
|
||||||
|
|
||||||
|
### `processorSkipList`
|
||||||
|
#### Value: `table`
|
||||||
|
#### Default: `{}`
|
||||||
|
A table listing the names of command processors to skip.
|
||||||
|
@ -18,7 +18,7 @@ In other usage, you may want to use a format string instead of having
|
|||||||
multiple nested functions for different styles. This is where the format
|
multiple nested functions for different styles. This is where the format
|
||||||
function comes in. You can used named keywords to style a section of text.
|
function comes in. You can used named keywords to style a section of text.
|
||||||
|
|
||||||
The list of arguments are:
|
The list of arguments are:
|
||||||
Colors:
|
Colors:
|
||||||
- black
|
- black
|
||||||
- red
|
- red
|
||||||
@ -28,14 +28,17 @@ Colors:
|
|||||||
- magenta
|
- magenta
|
||||||
- cyan
|
- cyan
|
||||||
- white
|
- white
|
||||||
|
|
||||||
Styles:
|
Styles:
|
||||||
|
- reset
|
||||||
- bold
|
- bold
|
||||||
- dim
|
- dim
|
||||||
- italic
|
- italic
|
||||||
- underline
|
- underline
|
||||||
- invert
|
- invert
|
||||||
|
|
||||||
For the colors, there are background and bright variants. The background
|
For the colors, there are background and bright variants. Background color
|
||||||
color variants have a suffix of `Bg` and bright has a prefix of `bright`.
|
variants have a `Bg` suffix, while bright variants use the `bright` prefix.
|
||||||
Note that appropriate camel casing has to be applied to them. So bright
|
These can also be combined. Note that appropriate camel casing must be applied.
|
||||||
blue would be `brightBlue` and background cyan would be `cyanBg`.
|
For example, bright blue would be written as `brightBlue`, a cyan background as
|
||||||
|
`cyanBg`, and combining them would result in `brightBlueBg`.
|
||||||
|
128
editor.go
128
editor.go
@ -1,128 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hilbish/util"
|
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// interactions for Hilbish's line reader
|
|
||||||
// The hilbish.editor interface provides functions to
|
|
||||||
// directly interact with the line editor in use.
|
|
||||||
func editorLoader(rtm *rt.Runtime) *rt.Table {
|
|
||||||
exports := map[string]util.LuaExport{
|
|
||||||
"insert": {editorInsert, 1, false},
|
|
||||||
"setVimRegister": {editorSetRegister, 1, false},
|
|
||||||
"getVimRegister": {editorGetRegister, 2, false},
|
|
||||||
"getLine": {editorGetLine, 0, false},
|
|
||||||
"readChar": {editorReadChar, 0, false},
|
|
||||||
"deleteByAmount": {editorDeleteByAmount, 1, false},
|
|
||||||
}
|
|
||||||
|
|
||||||
mod := rt.NewTable()
|
|
||||||
util.SetExports(rtm, mod, exports)
|
|
||||||
|
|
||||||
return mod
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// insert(text)
|
|
||||||
// Inserts text into the Hilbish command line.
|
|
||||||
// #param text string
|
|
||||||
func editorInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
text, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lr.rl.Insert(text)
|
|
||||||
|
|
||||||
return c.Next(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// setVimRegister(register, text)
|
|
||||||
// Sets the vim register at `register` to hold the passed text.
|
|
||||||
// #param register string
|
|
||||||
// #param text string
|
|
||||||
func editorSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
register, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
text, err := c.StringArg(1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lr.rl.SetRegisterBuf(register, []rune(text))
|
|
||||||
|
|
||||||
return c.Next(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// getVimRegister(register) -> string
|
|
||||||
// Returns the text that is at the register.
|
|
||||||
// #param register string
|
|
||||||
func editorGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
register, err := c.StringArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := lr.rl.GetFromRegister(register)
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// getLine() -> string
|
|
||||||
// Returns the current input line.
|
|
||||||
// #returns string
|
|
||||||
func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
buf := lr.rl.GetLine()
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// getChar() -> string
|
|
||||||
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
|
||||||
func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
buf := lr.rl.ReadChar()
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #interface editor
|
|
||||||
// deleteByAmount(amount)
|
|
||||||
// Deletes characters in the line by the given amount.
|
|
||||||
// #param amount number
|
|
||||||
func editorDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
amount, err := c.IntArg(0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lr.rl.DeleteByAmount(int(amount))
|
|
||||||
|
|
||||||
return c.Next(), nil
|
|
||||||
}
|
|
10
exec.go
10
exec.go
@ -29,12 +29,12 @@ func handleLua(input string) (string, uint8, error) {
|
|||||||
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
|
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
|
||||||
if err != nil && noexecute {
|
if err != nil && noexecute {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
/* if lerr, ok := err.(*lua.ApiError); ok {
|
/* if lerr, ok := err.(*lua.ApiError); ok {
|
||||||
if perr, ok := lerr.Cause.(*parse.Error); ok {
|
if perr, ok := lerr.Cause.(*parse.Error); ok {
|
||||||
print(perr.Pos.Line == parse.EOF)
|
print(perr.Pos.Line == parse.EOF)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
*/
|
||||||
*/
|
|
||||||
return cmdString, 125, err
|
return cmdString, 125, err
|
||||||
}
|
}
|
||||||
// And if there's no syntax errors and -n isnt provided, run
|
// And if there's no syntax errors and -n isnt provided, run
|
||||||
|
2
go.mod
2
go.mod
@ -30,7 +30,7 @@ require (
|
|||||||
|
|
||||||
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73
|
replace 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
|
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
|
||||||
|
|
||||||
|
@ -96,12 +96,23 @@ func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path = util.ExpandHome(strings.TrimSpace(path))
|
path = util.ExpandHome(strings.TrimSpace(path))
|
||||||
|
oldWd, _ := os.Getwd()
|
||||||
|
|
||||||
|
abspath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
err = os.Chdir(path)
|
err = os.Chdir(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
util.DoString(t.Runtime, fmt.Sprintf(`
|
||||||
|
local bait = require 'bait'
|
||||||
|
bait.throw('hilbish.cd', '%s', '%s')
|
||||||
|
`, abspath, oldWd))
|
||||||
|
|
||||||
return c.Next(), err
|
return c.Next(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,14 +54,14 @@ var (
|
|||||||
seqCtrlDelete2 = string([]byte{27, 91, 77})
|
seqCtrlDelete2 = string([]byte{27, 91, 77})
|
||||||
seqAltDelete = string([]byte{27, 91, 51, 59, 51, 126})
|
seqAltDelete = string([]byte{27, 91, 51, 59, 51, 126})
|
||||||
seqShiftTab = string([]byte{27, 91, 90})
|
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})
|
seqAltB = string([]byte{27, 98})
|
||||||
seqAltD = string([]byte{27, 100})
|
seqAltD = string([]byte{27, 100})
|
||||||
seqAltF = string([]byte{27, 102})
|
seqAltF = string([]byte{27, 102})
|
||||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
||||||
seqAltBackspace = string([]byte{27, 127})
|
seqAltBackspace = string([]byte{27, 127})
|
||||||
seqPageUp = string([]byte{27, 91, 53, 126})
|
seqPageUp = string([]byte{27, 91, 53, 126})
|
||||||
seqPageDown = string([]byte{27, 91, 54, 126})
|
seqPageDown = string([]byte{27, 91, 54, 126})
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -76,7 +76,7 @@ const (
|
|||||||
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
||||||
|
|
||||||
seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R"
|
seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R"
|
||||||
seqHideCursor = "\x1b[?25l"
|
seqHideCursor = "\x1b[?25l"
|
||||||
seqUnhideCursor = "\x1b[?25h"
|
seqUnhideCursor = "\x1b[?25h"
|
||||||
|
|
||||||
seqCtrlLeftArrow = "\x1b[1;5D"
|
seqCtrlLeftArrow = "\x1b[1;5D"
|
||||||
@ -143,55 +143,94 @@ const (
|
|||||||
|
|
||||||
// TODO: return whether its actually a sequence or not
|
// TODO: return whether its actually a sequence or not
|
||||||
// remedies the edge case of someone literally typing Ctrl-A for example.
|
// 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)
|
b := make([]byte, 1024)
|
||||||
i, _ := os.Stdin.Read(b)
|
i, _ := os.Stdin.Read(b)
|
||||||
r := []rune(string(b))
|
r := []rune(string(b))
|
||||||
s := string(r[:i])
|
s := string(r[:i])
|
||||||
|
|
||||||
switch b[0] {
|
switch b[0] {
|
||||||
case charCtrlA: return "Ctrl-A"
|
case charCtrlA:
|
||||||
case charCtrlB: return "Ctrl-B"
|
return "Ctrl-A"
|
||||||
case charCtrlC: return "Ctrl-C"
|
case charCtrlB:
|
||||||
case charEOF: return "Ctrl-D"
|
return "Ctrl-B"
|
||||||
case charCtrlE: return "Ctrl-E"
|
case charCtrlC:
|
||||||
case charCtrlF: return "Ctrl-F"
|
return "Ctrl-C"
|
||||||
case charCtrlG: return "Ctrl-G"
|
case charEOF:
|
||||||
case charBackspace, charBackspace2: return "Backspace"
|
return "Ctrl-D"
|
||||||
case charTab: return "Tab"
|
case charCtrlE:
|
||||||
case charCtrlK: return "Ctrl-K"
|
return "Ctrl-E"
|
||||||
case charCtrlL: return "Ctrl-L"
|
case charCtrlF:
|
||||||
case charCtrlN: return "Ctrl-N"
|
return "Ctrl-F"
|
||||||
case charCtrlO: return "Ctrl-O"
|
case charCtrlG:
|
||||||
case charCtrlP: return "Ctrl-P"
|
return "Ctrl-G"
|
||||||
case charCtrlQ: return "Ctrl-Q"
|
case charBackspace, charBackspace2:
|
||||||
case charCtrlR: return "Ctrl-R"
|
return "Backspace"
|
||||||
case charCtrlS: return "Ctrl-S"
|
case charTab:
|
||||||
case charCtrlT: return "Ctrl-T"
|
return "Tab"
|
||||||
case charCtrlU: return "Ctrl-U"
|
case charCtrlK:
|
||||||
case charCtrlV: return "Ctrl-V"
|
return "Ctrl-K"
|
||||||
case charCtrlW: return "Ctrl-W"
|
case charCtrlL:
|
||||||
case charCtrlX: return "Ctrl-X"
|
return "Ctrl-L"
|
||||||
case charCtrlY: return "Ctrl-Y"
|
case charCtrlN:
|
||||||
case charCtrlZ: return "Ctrl-Z"
|
return "Ctrl-N"
|
||||||
case '\r': fallthrough
|
case charCtrlO:
|
||||||
case '\n': return "Enter"
|
return "Ctrl-O"
|
||||||
case charEscape:
|
case charCtrlP:
|
||||||
switch s {
|
return "Ctrl-P"
|
||||||
case string(charEscape): return "Escape"
|
case charCtrlQ:
|
||||||
case seqUp: return "Up"
|
return "Ctrl-Q"
|
||||||
case seqDown: return "Down"
|
case charCtrlR:
|
||||||
case seqBackwards: return "Left"
|
return "Ctrl-R"
|
||||||
case seqForwards: return "Right"
|
case charCtrlS:
|
||||||
case seqCtrlLeftArrow: return "Ctrl-Left"
|
return "Ctrl-S"
|
||||||
case seqCtrlRightArrow: return "Ctrl-Right"
|
case charCtrlT:
|
||||||
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
|
return "Ctrl-T"
|
||||||
case seqHome, seqHomeSc: return "Home"
|
case charCtrlU:
|
||||||
case seqEnd, seqEndSc: return "End"
|
return "Ctrl-U"
|
||||||
case seqDelete, seqDelete2: return "Delete"
|
case charCtrlV:
|
||||||
case seqPageUp: return "Page-Up"
|
return "Ctrl-V"
|
||||||
case seqPageDown: return "Page-Down"
|
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
|
return s
|
@ -4,12 +4,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initGrid - Grid display details. Called each time we want to be sure to have
|
// 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.
|
// 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
|
// Compute size of each completion item box
|
||||||
tcMaxLength := 1
|
tcMaxLength := 1
|
||||||
@ -44,7 +45,7 @@ func (g *CompletionGroup) initGrid(rl *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// moveTabGridHighlight - Moves the highlighting for currently selected completion item (grid display)
|
// 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.tcPosX += x
|
||||||
g.tcPosY += y
|
g.tcPosY += y
|
||||||
@ -96,7 +97,7 @@ func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeGrid - A grid completion string
|
// 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 group title, print it and adjust offset.
|
||||||
if g.Name != "" {
|
if g.Name != "" {
|
||||||
@ -127,9 +128,9 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
|||||||
|
|
||||||
sugg := g.Suggestions[i]
|
sugg := g.Suggestions[i]
|
||||||
if len(sugg) > GetTermWidth() {
|
if len(sugg) > GetTermWidth() {
|
||||||
sugg = sugg[:GetTermWidth() - 4] + "..."
|
sugg = sugg[:GetTermWidth()-4] + "..."
|
||||||
}
|
}
|
||||||
formatStr := "%-"+cellWidth+"s%s "
|
formatStr := "%-" + cellWidth + "s%s "
|
||||||
if g.tcMaxX == 1 {
|
if g.tcMaxX == 1 {
|
||||||
formatStr = "%s%s"
|
formatStr = "%s%s"
|
||||||
}
|
}
|
@ -14,6 +14,7 @@ type CompletionGroup struct {
|
|||||||
Suggestions []string
|
Suggestions []string
|
||||||
Aliases map[string]string // A candidate has an alternative name (ex: --long, -l option flags)
|
Aliases map[string]string // A candidate has an alternative name (ex: --long, -l option flags)
|
||||||
Descriptions map[string]string // Items descriptions
|
Descriptions map[string]string // Items descriptions
|
||||||
|
ItemDisplays map[string]string // What to display the item as (can be used for styling items)
|
||||||
DisplayType TabDisplayType // Map, list or normal
|
DisplayType TabDisplayType // Map, list or normal
|
||||||
MaxLength int // Each group can be limited in the number of comps offered
|
MaxLength int // Each group can be limited in the number of comps offered
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ type CompletionGroup struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init - The completion group computes and sets all its values, and is then ready to work.
|
// 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
|
// Details common to all displays
|
||||||
g.checkCycle(rl) // Based on the number of groups given to the shell, allows cycling or not
|
g.checkCycle(rl) // Based on the number of groups given to the shell, allows cycling or not
|
||||||
@ -69,7 +70,7 @@ func (g *CompletionGroup) init(rl *Instance) {
|
|||||||
// updateTabFind - When searching through all completion groups (whether it be command history or not),
|
// 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.
|
// 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.
|
// 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)
|
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.
|
// We perform filter right here, so we create a new completion group, and populate it with our results.
|
||||||
@ -96,7 +97,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkCycle - Based on the number of groups given to the shell, allows cycling or not
|
// 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 {
|
if len(rl.tcGroups) == 1 {
|
||||||
g.allowCycle = true
|
g.allowCycle = true
|
||||||
}
|
}
|
||||||
@ -107,7 +108,7 @@ func (g *CompletionGroup) checkCycle(rl *Instance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkMaxLength - Based on the number of groups given to the shell, check/set MaxLength defaults
|
// 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
|
// This means the user forgot to set it
|
||||||
if g.MaxLength == 0 {
|
if g.MaxLength == 0 {
|
||||||
@ -146,7 +147,7 @@ func checkNilItems(groups []*CompletionGroup) (checked []*CompletionGroup) {
|
|||||||
|
|
||||||
// writeCompletion - This function produces a formatted string containing all appropriate items
|
// 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.
|
// 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
|
// Avoids empty groups in suggestions
|
||||||
if len(g.Suggestions) == 0 {
|
if len(g.Suggestions) == 0 {
|
||||||
@ -168,7 +169,7 @@ func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) {
|
|||||||
|
|
||||||
// getCurrentCell - The completion groups computes the current cell value,
|
// getCurrentCell - The completion groups computes the current cell value,
|
||||||
// depending on its display type and its different parameters
|
// 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 {
|
switch g.DisplayType {
|
||||||
case TabDisplayGrid:
|
case TabDisplayGrid:
|
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
// initList - List display details. Because of the way alternative completions
|
// initList - List display details. Because of the way alternative completions
|
||||||
// are handled, MaxLength cannot be set when there are 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
|
// We may only ever have two different
|
||||||
// columns: (suggestions, and alternatives)
|
// 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)
|
// 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)
|
// 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
|
// We dont' pass to x, because not managed by callers
|
||||||
g.tcPosY += x
|
g.tcPosY += x
|
||||||
@ -153,7 +153,7 @@ func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeList - A list completion string
|
// 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.
|
// Print group title and adjust offset if there is one.
|
||||||
if g.Name != "" {
|
if g.Name != "" {
|
||||||
@ -217,6 +217,11 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
|||||||
alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces
|
alt = strings.Repeat(" ", maxLengthAlt+1) // + 2 to keep account of spaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
styledSugg, ok := g.ItemDisplays[item]
|
||||||
|
if ok {
|
||||||
|
sugg = fmt.Sprintf("\r%s%-"+cellWidth+"s", highlight(y, 0), fmtEscape(styledSugg))
|
||||||
|
}
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
description := g.Descriptions[g.Suggestions[i]]
|
description := g.Descriptions[g.Suggestions[i]]
|
||||||
if len(description) > maxDescWidth {
|
if len(description) > maxDescWidth {
|
||||||
@ -244,7 +249,7 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) getListPad() (pad int) {
|
func (rl *Readline) getListPad() (pad int) {
|
||||||
for _, group := range rl.tcGroups {
|
for _, group := range rl.tcGroups {
|
||||||
if group.DisplayType == TabDisplayList {
|
if group.DisplayType == TabDisplayList {
|
||||||
for i := range group.Suggestions {
|
for i := range group.Suggestions {
|
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
// initMap - Map display details. Called each time we want to be sure to have
|
// 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.
|
// 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
|
// We make the map anyway, especially if we need to use it later
|
||||||
if g.Descriptions == nil {
|
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)
|
// 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 += x
|
||||||
g.tcPosY += y
|
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
|
// 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 != "" {
|
if g.Name != "" {
|
||||||
// Print group title (changes with line returns depending on type)
|
// Print group title (changes with line returns depending on type)
|
@ -1,7 +1,7 @@
|
|||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "fmt"
|
// "fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -28,7 +28,7 @@ func leftMost() []byte {
|
|||||||
|
|
||||||
var rxRcvCursorPos = regexp.MustCompile("^\x1b([0-9]+);([0-9]+)R$")
|
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 {
|
if !rl.EnableGetCursorPos {
|
||||||
return -1, -1
|
return -1, -1
|
||||||
}
|
}
|
||||||
@ -143,7 +143,7 @@ func unhideCursor() {
|
|||||||
print(seqUnhideCursor)
|
print(seqUnhideCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) backspace(forward bool) {
|
func (rl *Readline) backspace(forward bool) {
|
||||||
if len(rl.line) == 0 || rl.pos == 0 {
|
if len(rl.line) == 0 || rl.pos == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ func (rl *Instance) backspace(forward bool) {
|
|||||||
rl.deleteBackspace(forward)
|
rl.deleteBackspace(forward)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
func (rl *Readline) moveCursorByAdjust(adjust int) {
|
||||||
switch {
|
switch {
|
||||||
case adjust > 0:
|
case adjust > 0:
|
||||||
rl.pos += adjust
|
rl.pos += adjust
|
@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// writeTempFile - This function optionally accepts a filename (generally specified with an extension).
|
// 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
|
// The final path to the buffer on disk
|
||||||
var path string
|
var path string
|
||||||
|
|
@ -1,9 +1,10 @@
|
|||||||
|
//go:build plan9
|
||||||
// +build plan9
|
// +build plan9
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import "errors"
|
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")
|
return rl.line, errors.New("Not currently supported on Plan 9")
|
||||||
}
|
}
|
@ -15,7 +15,7 @@ const defaultEditor = "vi"
|
|||||||
// depending on the actions taken by the user within it (eg: x or q! in Vim)
|
// 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,
|
// 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.
|
// 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)
|
name, err := rl.writeTempFile([]byte(string(multiline)), filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return multiline, err
|
return multiline, err
|
@ -5,6 +5,6 @@ package readline
|
|||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
// StartEditorWithBuffer - Not implemented on Windows platforms.
|
// 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")
|
return rl.line, errors.New("Not currently supported on Windows")
|
||||||
}
|
}
|
@ -13,11 +13,11 @@ type EventReturn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddEvent registers a new keypress handler
|
// 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
|
rl.evtKeyPress[keyPress] = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
// DelEvent deregisters an existing keypress handler
|
// DelEvent deregisters an existing keypress handler
|
||||||
func (rl *Instance) DelEvent(keyPress string) {
|
func (rl *Readline) DelEvent(keyPress string) {
|
||||||
delete(rl.evtKeyPress, keyPress)
|
delete(rl.evtKeyPress, keyPress)
|
||||||
}
|
}
|
@ -5,13 +5,13 @@ import "regexp"
|
|||||||
// SetHintText - a nasty function to force writing a new hint text. It does not update helpers, it just renders
|
// 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.
|
// 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.hintText = []rune(s)
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (rl *Instance) getHintText() {
|
func (rl *Readline) getHintText() {
|
||||||
|
|
||||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||||
// Return if no hints provided by the user/engine
|
// 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.
|
// writeHintText - only writes the hint text and computes its offsets.
|
||||||
func (rl *Instance) writeHintText() {
|
func (rl *Readline) writeHintText() {
|
||||||
if len(rl.hintText) == 0 {
|
if len(rl.hintText) == 0 {
|
||||||
//rl.hintY = 0
|
//rl.hintY = 0
|
||||||
return
|
return
|
||||||
@ -43,7 +43,7 @@ func (rl *Instance) writeHintText() {
|
|||||||
|
|
||||||
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
||||||
offset += hintLen
|
offset += hintLen
|
||||||
// rl.hintY = offset
|
// rl.hintY = offset
|
||||||
|
|
||||||
hintText := string(wrapped)
|
hintText := string(wrapped)
|
||||||
|
|
||||||
@ -52,12 +52,12 @@ func (rl *Instance) writeHintText() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) resetHintText() {
|
func (rl *Readline) resetHintText() {
|
||||||
//rl.hintY = 0
|
//rl.hintY = 0
|
||||||
rl.hintText = []rune{}
|
rl.hintText = []rune{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) insertHintText() {
|
func (rl *Readline) insertHintText() {
|
||||||
if len(rl.hintText) != 0 {
|
if len(rl.hintText) != 0 {
|
||||||
// fill in hint text
|
// fill in hint text
|
||||||
rl.insert(rl.hintText)
|
rl.insert(rl.hintText)
|
@ -29,24 +29,24 @@ type History interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetHistoryCtrlR - Set the history source triggered with Ctrl-r combination
|
// 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.mainHistName = name
|
||||||
rl.mainHistory = history
|
rl.mainHistory = history
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHistoryCtrlR - Returns the history source triggered by Ctrl-r
|
// GetHistoryCtrlR - Returns the history source triggered by Ctrl-r
|
||||||
func (rl *Instance) GetHistoryCtrlR() History {
|
func (rl *Readline) GetHistoryCtrlR() History {
|
||||||
return rl.mainHistory
|
return rl.mainHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHistoryAltR - Set the history source triggered with Alt-r combination
|
// 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.altHistName = name
|
||||||
rl.altHistory = history
|
rl.altHistory = history
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHistoryAltR - Returns the history source triggered by Alt-r
|
// GetHistoryAltR - Returns the history source triggered by Alt-r
|
||||||
func (rl *Instance) GetHistoryAltR() History {
|
func (rl *Readline) GetHistoryAltR() History {
|
||||||
return rl.altHistory
|
return rl.altHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ func (h *NullHistory) Dump() interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Browse historic lines:
|
// Browse historic lines:
|
||||||
func (rl *Instance) walkHistory(i int) {
|
func (rl *Readline) walkHistory(i int) {
|
||||||
var (
|
var (
|
||||||
old, new string
|
old, new string
|
||||||
dedup bool
|
dedup bool
|
||||||
@ -123,7 +123,7 @@ func (rl *Instance) walkHistory(i int) {
|
|||||||
|
|
||||||
// When we are exiting the current line buffer to move around
|
// When we are exiting the current line buffer to move around
|
||||||
// the history, we make buffer the current line
|
// 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)
|
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
|
// 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.
|
// 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 = make([]*CompletionGroup, 1)
|
||||||
hist[0] = &CompletionGroup{
|
hist[0] = &CompletionGroup{
|
@ -4,12 +4,12 @@ import "regexp"
|
|||||||
|
|
||||||
// SetInfoText - a nasty function to force writing a new info text. It does not update helpers, it just renders
|
// 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.
|
// 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.infoText = []rune(s)
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) getInfoText() {
|
func (rl *Readline) getInfoText() {
|
||||||
|
|
||||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||||
// Return if no infos provided by the user/engine
|
// 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.
|
// writeInfoText - only writes the info text and computes its offsets.
|
||||||
func (rl *Instance) writeInfoText() {
|
func (rl *Readline) writeInfoText() {
|
||||||
if len(rl.infoText) == 0 {
|
if len(rl.infoText) == 0 {
|
||||||
rl.infoY = 0
|
rl.infoY = 0
|
||||||
return
|
return
|
||||||
@ -50,7 +50,7 @@ func (rl *Instance) writeInfoText() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) resetInfoText() {
|
func (rl *Readline) resetInfoText() {
|
||||||
rl.infoY = 0
|
rl.infoY = 0
|
||||||
rl.infoText = []rune{}
|
rl.infoText = []rune{}
|
||||||
}
|
}
|
@ -5,12 +5,16 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Instance is used to encapsulate the parameter group and run time of any given
|
// 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
|
// readline instance so that you can reuse the readline API for multiple entry
|
||||||
// captures without having to repeatedly unload configuration.
|
// captures without having to repeatedly unload configuration.
|
||||||
type Instance struct {
|
|
||||||
|
// #type
|
||||||
|
type Readline struct {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Input Modes -------------------------------------------------------------------------------
|
// Input Modes -------------------------------------------------------------------------------
|
||||||
@ -31,13 +35,13 @@ type Instance struct {
|
|||||||
Multiline bool // If set to true, the shell will have a two-line prompt.
|
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.
|
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
|
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
||||||
rightPrompt string
|
rightPrompt string
|
||||||
rightPromptLen int
|
rightPromptLen int
|
||||||
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
||||||
defaultPrompt []rune
|
defaultPrompt []rune
|
||||||
promptLen int
|
promptLen int
|
||||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||||
|
|
||||||
//
|
//
|
||||||
// Input Line ---------------------------------------------------------------------------------
|
// Input Line ---------------------------------------------------------------------------------
|
||||||
@ -114,9 +118,9 @@ type Instance struct {
|
|||||||
searchMode FindMode // Used for varying hints, and underlying functions called
|
searchMode FindMode // Used for varying hints, and underlying functions called
|
||||||
regexSearch *regexp.Regexp // Holds the current search regex match
|
regexSearch *regexp.Regexp // Holds the current search regex match
|
||||||
search string
|
search string
|
||||||
mainHist bool // Which history stdin do we want
|
mainHist bool // Which history stdin do we want
|
||||||
histInfo []rune // We store a piece of hist info, for dual history sources
|
histInfo []rune // We store a piece of hist info, for dual history sources
|
||||||
Searcher func(string, []string) []string
|
Searcher func(string, []string) []string
|
||||||
|
|
||||||
//
|
//
|
||||||
// History -----------------------------------------------------------------------------------
|
// History -----------------------------------------------------------------------------------
|
||||||
@ -200,17 +204,19 @@ type Instance struct {
|
|||||||
// concurency
|
// concurency
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
|
||||||
ViModeCallback func(ViMode)
|
ViModeCallback func(ViMode)
|
||||||
ViActionCallback func(ViAction, []string)
|
ViActionCallback func(ViAction, []string)
|
||||||
|
|
||||||
RawInputCallback func([]rune) // called on all input
|
RawInputCallback func([]rune) // called on all input
|
||||||
|
|
||||||
bufferedOut *bufio.Writer
|
bufferedOut *bufio.Writer
|
||||||
|
|
||||||
|
Loader packagelib.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInstance is used to create a readline instance and initialise it with sane defaults.
|
// NewInstance is used to create a readline instance and initialise it with sane defaults.
|
||||||
func NewInstance() *Instance {
|
func NewInstance() *Readline {
|
||||||
rl := new(Instance)
|
rl := new(Readline)
|
||||||
|
|
||||||
// Prompt
|
// Prompt
|
||||||
rl.Multiline = false
|
rl.Multiline = false
|
||||||
@ -245,7 +251,9 @@ func NewInstance() *Instance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, hay := range haystack {
|
for _, hay := range haystack {
|
||||||
if rl.regexSearch == nil { continue }
|
if rl.regexSearch == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if rl.regexSearch.MatchString(hay) {
|
if rl.regexSearch.MatchString(hay) {
|
||||||
suggs = append(suggs, hay)
|
suggs = append(suggs, hay)
|
||||||
}
|
}
|
||||||
@ -256,6 +264,11 @@ func NewInstance() *Instance {
|
|||||||
|
|
||||||
rl.bufferedOut = bufio.NewWriter(os.Stdout)
|
rl.bufferedOut = bufio.NewWriter(os.Stdout)
|
||||||
|
|
||||||
|
rl.Loader = packagelib.Loader{
|
||||||
|
Name: "readline",
|
||||||
|
Load: rl.luaLoader,
|
||||||
|
}
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
rl.initRegisters()
|
rl.initRegisters()
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
// When the DelayedSyntaxWorker gives us a new line, we need to check if there
|
// 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.
|
// 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 {
|
if len(rl.currentComp) > 0 {
|
||||||
|
|
||||||
} else {
|
} 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,
|
// 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.
|
// 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 {
|
if len(rl.currentComp) > 0 {
|
||||||
return rl.lineComp
|
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
|
// 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
|
// 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.
|
// 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,
|
// Then we print the prompt, and the line,
|
||||||
hideCursor()
|
hideCursor()
|
||||||
@ -79,7 +79,7 @@ func (rl *Instance) echo() {
|
|||||||
unhideCursor()
|
unhideCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) insert(r []rune) {
|
func (rl *Readline) insert(r []rune) {
|
||||||
for {
|
for {
|
||||||
// I don't really understand why `0` is creaping in at the end of the
|
// I don't really understand why `0` is creaping in at the end of the
|
||||||
// array but it only happens with unicode characters.
|
// array but it only happens with unicode characters.
|
||||||
@ -112,11 +112,11 @@ func (rl *Instance) insert(r []rune) {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) Insert(t string) {
|
func (rl *Readline) Insert(t string) {
|
||||||
rl.insert([]rune(t))
|
rl.insert([]rune(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteX() {
|
func (rl *Readline) deleteX() {
|
||||||
switch {
|
switch {
|
||||||
case len(rl.line) == 0:
|
case len(rl.line) == 0:
|
||||||
return
|
return
|
||||||
@ -134,7 +134,7 @@ func (rl *Instance) deleteX() {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteBackspace(forward bool) {
|
func (rl *Readline) deleteBackspace(forward bool) {
|
||||||
switch {
|
switch {
|
||||||
case len(rl.line) == 0:
|
case len(rl.line) == 0:
|
||||||
return
|
return
|
||||||
@ -153,7 +153,7 @@ func (rl *Instance) deleteBackspace(forward bool) {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) clearLine() {
|
func (rl *Readline) clearLine() {
|
||||||
if len(rl.line) == 0 {
|
if len(rl.line) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -179,21 +179,21 @@ func (rl *Instance) clearLine() {
|
|||||||
rl.clearVirtualComp()
|
rl.clearVirtualComp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteToBeginning() {
|
func (rl *Readline) deleteToBeginning() {
|
||||||
rl.resetVirtualComp(false)
|
rl.resetVirtualComp(false)
|
||||||
// Keep the line length up until the cursor
|
// Keep the line length up until the cursor
|
||||||
rl.line = rl.line[rl.pos:]
|
rl.line = rl.line[rl.pos:]
|
||||||
rl.pos = 0
|
rl.pos = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteToEnd() {
|
func (rl *Readline) deleteToEnd() {
|
||||||
rl.resetVirtualComp(false)
|
rl.resetVirtualComp(false)
|
||||||
// Keep everything before the cursor
|
// Keep everything before the cursor
|
||||||
rl.line = rl.line[:rl.pos]
|
rl.line = rl.line[:rl.pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO(Renzix): move to emacs sepecific file
|
// @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)
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||||
if len(split) == 0 {
|
if len(split) == 0 {
|
||||||
return
|
return
|
||||||
@ -214,7 +214,7 @@ func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
|||||||
return
|
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)
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||||
if len(split) == 0 {
|
if len(split) == 0 {
|
||||||
return
|
return
|
276
golibs/readline/lua.go
Normal file
276
golibs/readline/lua.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
// line reader library
|
||||||
|
// The readline module is responsible for reading input from the user.
|
||||||
|
// The readline module is what Hilbish uses to read input from the user,
|
||||||
|
// including all the interactive features of Hilbish like history search,
|
||||||
|
// syntax highlighting, everything. The global Hilbish readline instance
|
||||||
|
// is usable at `hilbish.editor`.
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rlMetaKey = rt.StringValue("__readline")
|
||||||
|
|
||||||
|
func (rl *Readline) luaLoader(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
rlMethods := rt.NewTable()
|
||||||
|
rlMethodss := map[string]util.LuaExport{
|
||||||
|
"deleteByAmount": {rlDeleteByAmount, 2, false},
|
||||||
|
"getLine": {rlGetLine, 1, false},
|
||||||
|
"getVimRegister": {rlGetRegister, 2, false},
|
||||||
|
"insert": {rlInsert, 2, false},
|
||||||
|
"read": {rlRead, 1, false},
|
||||||
|
"readChar": {rlReadChar, 1, false},
|
||||||
|
"setVimRegister": {rlSetRegister, 3, false},
|
||||||
|
"log": {rlLog, 2, false},
|
||||||
|
}
|
||||||
|
util.SetExports(rtm, rlMethods, rlMethodss)
|
||||||
|
|
||||||
|
rlMeta := rt.NewTable()
|
||||||
|
rlIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
_, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := c.Arg(1)
|
||||||
|
val := rlMethods.Get(arg)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rlMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(rlIndex, "__index", 2, false)))
|
||||||
|
rtm.SetRegistry(rlMetaKey, rt.TableValue(rlMeta))
|
||||||
|
|
||||||
|
rlFuncs := map[string]util.LuaExport{
|
||||||
|
"new": {rlNew, 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
luaRl := rt.NewTable()
|
||||||
|
util.SetExports(rtm, luaRl, rlFuncs)
|
||||||
|
|
||||||
|
return rt.TableValue(luaRl), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// new() -> @Readline
|
||||||
|
// Creates a new readline instance.
|
||||||
|
func rlNew(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
rl := NewInstance()
|
||||||
|
ud := rlUserData(t.Runtime, rl)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.UserDataValue(ud)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// insert(text)
|
||||||
|
// Inserts text into the Hilbish command line.
|
||||||
|
// #param text string
|
||||||
|
func rlInsert(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.insert([]rune(text))
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// read() -> string
|
||||||
|
// Reads input from the user.
|
||||||
|
func rlRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inp, err := rl.Readline()
|
||||||
|
if err == EOF {
|
||||||
|
fmt.Println("")
|
||||||
|
return nil, io.EOF
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(inp)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// setVimRegister(register, text)
|
||||||
|
// Sets the vim register at `register` to hold the passed text.
|
||||||
|
// #param register string
|
||||||
|
// #param text string
|
||||||
|
func rlSetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(3); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
register, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text, err := c.StringArg(2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.SetRegisterBuf(register, []rune(text))
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// getVimRegister(register) -> string
|
||||||
|
// Returns the text that is at the register.
|
||||||
|
// #param register string
|
||||||
|
func rlGetRegister(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
register, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := rl.GetFromRegister(register)
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// getLine() -> string
|
||||||
|
// Returns the current input line.
|
||||||
|
// #returns string
|
||||||
|
func rlGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := rl.GetLine()
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// getChar() -> string
|
||||||
|
// Reads a keystroke from the user. This is in a format of something like Ctrl-L.
|
||||||
|
func rlReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf := rl.ReadChar()
|
||||||
|
|
||||||
|
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// deleteByAmount(amount)
|
||||||
|
// Deletes characters in the line by the given amount.
|
||||||
|
// #param amount number
|
||||||
|
func rlDeleteByAmount(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, err := c.IntArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.DeleteByAmount(int(amount))
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// #member
|
||||||
|
// log(text)
|
||||||
|
// Prints a message *before* the prompt without it being interrupted by user input.
|
||||||
|
func rlLog(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := rlArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logText, err := c.StringArg(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.RefreshPromptLog(logText)
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rlArg(c *rt.GoCont, arg int) (*Readline, error) {
|
||||||
|
j, ok := valueToRl(c.Arg(arg))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("#%d must be a readline", arg+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToRl(val rt.Value) (*Readline, bool) {
|
||||||
|
u, ok := val.TryUserData()
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
j, ok := u.Value().(*Readline)
|
||||||
|
return j, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func rlUserData(rtm *rt.Runtime, rl *Readline) *rt.UserData {
|
||||||
|
rlMeta := rtm.Registry(rlMetaKey)
|
||||||
|
return rt.NewUserData(rl, rlMeta.AsTable())
|
||||||
|
}
|
@ -8,20 +8,20 @@ import (
|
|||||||
|
|
||||||
// SetPrompt will define the readline prompt string.
|
// SetPrompt will define the readline prompt string.
|
||||||
// It also calculates the runes in the string as well as any non-printable escape codes.
|
// 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.mainPrompt = s
|
||||||
rl.computePrompt()
|
rl.computePrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRightPrompt sets the right prompt.
|
// SetRightPrompt sets the right prompt.
|
||||||
func (rl *Instance) SetRightPrompt(s string) {
|
func (rl *Readline) SetRightPrompt(s string) {
|
||||||
rl.rightPrompt = s + " "
|
rl.rightPrompt = s + " "
|
||||||
rl.computePrompt()
|
rl.computePrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshPromptLog - A simple function to print a string message (a log, or more broadly,
|
// 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.
|
// 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.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
if !rl.modeTabCompletion {
|
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.
|
// 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.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
// Prompt data intependent
|
// Prompt data intependent
|
||||||
if !rl.modeTabCompletion {
|
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.
|
// @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.
|
// @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.
|
// @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.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
if !rl.modeTabCompletion {
|
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,
|
// computePrompt - At any moment, returns an (1st or 2nd line) actualized prompt,
|
||||||
// considering all input mode parameters and prompt string values.
|
// 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.Multiline {
|
||||||
if rl.MultilinePrompt != "" {
|
if rl.MultilinePrompt != "" {
|
||||||
rl.realPrompt = []rune(rl.MultilinePrompt)
|
rl.realPrompt = []rune(rl.MultilinePrompt)
|
||||||
@ -190,11 +190,11 @@ func (rl *Instance) computePrompt() (prompt []rune) {
|
|||||||
// Strip color escapes
|
// Strip color escapes
|
||||||
rl.promptLen = getRealLength(string(rl.realPrompt))
|
rl.promptLen = getRealLength(string(rl.realPrompt))
|
||||||
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) {
|
func (rl *Readline) colorizeVimPrompt(p []rune) (cp []rune) {
|
||||||
if rl.VimModeColorize {
|
if rl.VimModeColorize {
|
||||||
return []rune(fmt.Sprintf("%s%s%s", BOLD, string(p), RESET))
|
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))
|
return getWidth([]rune(stripped))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) echoRightPrompt() {
|
func (rl *Readline) echoRightPrompt() {
|
||||||
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
|
if rl.fullX < GetTermWidth()-rl.rightPromptLen-1 {
|
||||||
moveCursorForwards(GetTermWidth())
|
moveCursorForwards(GetTermWidth())
|
||||||
moveCursorBackwards(rl.rightPromptLen)
|
moveCursorBackwards(rl.rightPromptLen)
|
||||||
print(rl.rightPrompt)
|
print(rl.rightPrompt)
|
@ -2,16 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// 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
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
@ -2,18 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
// +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
|
package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -70,4 +61,3 @@ func GetSize(fd int) (width, height int, err error) {
|
|||||||
}
|
}
|
||||||
return int(info.Size.X), int(info.Size.Y), nil
|
return int(info.Size.X), int(info.Size.Y), nil
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
|||||||
|
|
||||||
// Readline displays the readline prompt.
|
// Readline displays the readline prompt.
|
||||||
// It will return a string (user entered data) or an error.
|
// 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())
|
fd := int(os.Stdin.Fd())
|
||||||
state, err := MakeRaw(fd)
|
state, err := MakeRaw(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,7 +183,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||||||
if rl.modeTabFind {
|
if rl.modeTabFind {
|
||||||
rl.backspaceTabFind()
|
rl.backspaceTabFind()
|
||||||
} else {
|
} else {
|
||||||
if (rl.pos < len(rl.line)) {
|
if rl.pos < len(rl.line) {
|
||||||
rl.deleteBackspace(true)
|
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
|
// editorInput is an unexported function used to determine what mode of text
|
||||||
// entry readline is currently configured for and then update the line entries
|
// entry readline is currently configured for and then update the line entries
|
||||||
// accordingly.
|
// accordingly.
|
||||||
func (rl *Instance) editorInput(r []rune) {
|
func (rl *Readline) editorInput(r []rune) {
|
||||||
if len(r) == 0 {
|
if len(r) == 0 {
|
||||||
return
|
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
|
// 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.
|
// 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,
|
// Sometimes the escape sequence is interleaved with another one,
|
||||||
// but key strokes might be in the wrong order, so we double check
|
// 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) {
|
switch string(r) {
|
||||||
// Vim escape sequences & dispatching --------------------------------------------------------
|
// Vim escape sequences & dispatching --------------------------------------------------------
|
||||||
case string(charEscape):
|
case string(charEscape):
|
||||||
@ -755,11 +755,11 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
return
|
return
|
||||||
|
|
||||||
case seqDelete,seqDelete2:
|
case seqDelete, seqDelete2:
|
||||||
if rl.modeTabFind {
|
if rl.modeTabFind {
|
||||||
rl.backspaceTabFind()
|
rl.backspaceTabFind()
|
||||||
} else {
|
} else {
|
||||||
if (rl.pos < len(rl.line)) {
|
if rl.pos < len(rl.line) {
|
||||||
rl.deleteBackspace(true)
|
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.moveCursorByAdjust(len(rl.line))
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
@ -924,7 +924,7 @@ func isMultiline(r []rune) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) allowMultiline(data []byte) bool {
|
func (rl *Readline) allowMultiline(data []byte) bool {
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data))
|
printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data))
|
||||||
for {
|
for {
|
@ -23,7 +23,7 @@ type registers struct {
|
|||||||
mutex *sync.Mutex
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) initRegisters() {
|
func (rl *Readline) initRegisters() {
|
||||||
rl.registers = ®isters{
|
rl.registers = ®isters{
|
||||||
num: make(map[int][]rune, 10),
|
num: make(map[int][]rune, 10),
|
||||||
alpha: make(map[string][]rune, 52),
|
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.
|
// 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 &
|
// It's the same as saveToRegisterTokenize, but without the need to generate tokenized &
|
||||||
// cursor-pos-actualized versions of the input line.
|
// 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.
|
// Get the current cursor position and go the length specified.
|
||||||
var begin = rl.pos
|
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
|
// 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
|
// 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.
|
// 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.
|
// The register is going to have to heavily manipulate the cursor position.
|
||||||
// Remember the original one first, for the end.
|
// 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,
|
// 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
|
// let the caller pass directly this buffer, yet relying on the register system to
|
||||||
// determine which register will store the buffer.
|
// 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)
|
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.
|
// We must make an immutable version of the buffer first.
|
||||||
buf := string(buffer)
|
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
|
// 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.
|
// 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,
|
// When exiting this function the currently selected register is dropped,
|
||||||
defer rl.registers.resetRegister()
|
defer rl.registers.resetRegister()
|
||||||
@ -155,7 +155,7 @@ func (rl *Instance) pasteFromRegister() (buffer []rune) {
|
|||||||
return rl.GetFromRegister(activeRegister)
|
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.
|
// Find the active register, and return its content.
|
||||||
num, err := strconv.Atoi(reg)
|
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.
|
// 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
|
// We set the info exceptionally
|
||||||
info := BLUE + "-- registers --" + RESET
|
info := BLUE + "-- registers --" + RESET
|
@ -2,7 +2,7 @@ package readline
|
|||||||
|
|
||||||
// syntaxCompletion - applies syntax highlighting to the current input line.
|
// syntaxCompletion - applies syntax highlighting to the current input line.
|
||||||
// nothing special to note here, nor any changes envisioned.
|
// nothing special to note here, nor any changes envisioned.
|
||||||
func (rl *Instance) syntaxCompletion() {
|
func (rl *Readline) syntaxCompletion() {
|
||||||
if rl.SyntaxCompleter == nil {
|
if rl.SyntaxCompleter == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
@ -2,13 +2,14 @@ package readline
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line:
|
// 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
|
// 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.
|
// 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 {
|
for {
|
||||||
// I don't really understand why `0` is creaping in at the end of the
|
// I don't really understand why `0` is creaping in at the end of the
|
||||||
// array but it only happens with unicode characters.
|
// 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.
|
// Insert the current completion candidate into the input line.
|
||||||
// This candidate might either be the currently selected one (white frame),
|
// This candidate might either be the currently selected one (white frame),
|
||||||
// or the only candidate available, if the total number of candidates is 1.
|
// or the only candidate available, if the total number of candidates is 1.
|
||||||
func (rl *Instance) insertCandidate() {
|
func (rl *Readline) insertCandidate() {
|
||||||
|
|
||||||
cur := rl.getCurrentGroup()
|
cur := rl.getCurrentGroup()
|
||||||
|
|
||||||
@ -83,7 +84,7 @@ func (rl *Instance) insertCandidate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateVirtualComp - Either insert the current completion candidate virtually, or on the real line.
|
// updateVirtualComp - Either insert the current completion candidate virtually, or on the real line.
|
||||||
func (rl *Instance) updateVirtualComp() {
|
func (rl *Readline) updateVirtualComp() {
|
||||||
cur := rl.getCurrentGroup()
|
cur := rl.getCurrentGroup()
|
||||||
if cur != nil {
|
if cur != nil {
|
||||||
|
|
||||||
@ -118,7 +119,7 @@ func (rl *Instance) updateVirtualComp() {
|
|||||||
// resetVirtualComp - This function is called before most of our readline key handlers,
|
// 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 makes sure that the current completion (virtually inserted) is either inserted or dropped,
|
||||||
// and that all related parameters are reinitialized.
|
// 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.
|
// 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.
|
// 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.
|
// viDeleteByAdjustVirtual - Same as viDeleteByAdjust, but for our virtually completed input line.
|
||||||
func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
func (rl *Readline) viDeleteByAdjustVirtual(adjust int) {
|
||||||
var (
|
var (
|
||||||
newLine []rune
|
newLine []rune
|
||||||
backOne bool
|
backOne bool
|
||||||
@ -235,7 +236,7 @@ func (rl *Instance) viDeleteByAdjustVirtual(adjust int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// viJumpEVirtual - Same as viJumpE, but for our virtually completed input line.
|
// 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)
|
split, index, pos := tokeniser(rl.lineComp, rl.pos)
|
||||||
if len(split) == 0 {
|
if len(split) == 0 {
|
||||||
return
|
return
|
||||||
@ -258,7 +259,7 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteVirtual() {
|
func (rl *Readline) deleteVirtual() {
|
||||||
switch {
|
switch {
|
||||||
case len(rl.lineComp) == 0:
|
case len(rl.lineComp) == 0:
|
||||||
return
|
return
|
||||||
@ -274,7 +275,7 @@ func (rl *Instance) deleteVirtual() {
|
|||||||
|
|
||||||
// We are done with the current virtual completion candidate.
|
// We are done with the current virtual completion candidate.
|
||||||
// Get ready for the next one
|
// Get ready for the next one
|
||||||
func (rl *Instance) clearVirtualComp() {
|
func (rl *Readline) clearVirtualComp() {
|
||||||
rl.line = rl.lineComp
|
rl.line = rl.lineComp
|
||||||
rl.currentComp = []rune{}
|
rl.currentComp = []rune{}
|
||||||
rl.compAddSpace = false
|
rl.compAddSpace = false
|
@ -28,7 +28,7 @@ const (
|
|||||||
|
|
||||||
// getTabCompletion - This root function sets up all completion items and engines,
|
// getTabCompletion - This root function sets up all completion items and engines,
|
||||||
// dealing with all search and completion modes. But it does not perform printing.
|
// 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.
|
// Populate registers if requested.
|
||||||
if rl.modeAutoFind && rl.searchMode == RegisterFind {
|
if rl.modeAutoFind && rl.searchMode == RegisterFind {
|
||||||
@ -53,7 +53,7 @@ func (rl *Instance) getTabCompletion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getRegisterCompletion - Populates and sets up completion for Vim registers.
|
// getRegisterCompletion - Populates and sets up completion for Vim registers.
|
||||||
func (rl *Instance) getRegisterCompletion() {
|
func (rl *Readline) getRegisterCompletion() {
|
||||||
|
|
||||||
rl.tcGroups = rl.completeRegisters()
|
rl.tcGroups = rl.completeRegisters()
|
||||||
if len(rl.tcGroups) == 0 {
|
if len(rl.tcGroups) == 0 {
|
||||||
@ -84,7 +84,7 @@ func (rl *Instance) getRegisterCompletion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getTabSearchCompletion - Populates and sets up completion for completion search.
|
// 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.
|
// Get completions from the engine, and make sure there is a current group.
|
||||||
rl.getCompletions()
|
rl.getCompletions()
|
||||||
@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
|||||||
rl.getCurrentGroup()
|
rl.getCurrentGroup()
|
||||||
|
|
||||||
// Set the info for this completion mode
|
// 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 {
|
for _, g := range rl.tcGroups {
|
||||||
g.updateTabFind(rl)
|
g.updateTabFind(rl)
|
||||||
@ -107,7 +107,7 @@ func (rl *Instance) getTabSearchCompletion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getHistorySearchCompletion - Populates and sets up completion for command history search
|
// getHistorySearchCompletion - Populates and sets up completion for command history search
|
||||||
func (rl *Instance) getHistorySearchCompletion() {
|
func (rl *Readline) getHistorySearchCompletion() {
|
||||||
|
|
||||||
// Refresh full list each time
|
// Refresh full list each time
|
||||||
rl.tcGroups = rl.completeHistory()
|
rl.tcGroups = rl.completeHistory()
|
||||||
@ -142,7 +142,7 @@ func (rl *Instance) getHistorySearchCompletion() {
|
|||||||
|
|
||||||
// getNormalCompletion - Populates and sets up completion for normal comp mode.
|
// getNormalCompletion - Populates and sets up completion for normal comp mode.
|
||||||
// Will automatically cancel the completion mode if there are no candidates.
|
// 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
|
// Get completions groups, pass delayedTabContext and check nils
|
||||||
rl.getCompletions()
|
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,
|
// 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
|
// 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.
|
// 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 there is no wired tab completion engine, nothing we can do.
|
||||||
if rl.TabCompleter == nil {
|
if rl.TabCompleter == nil {
|
||||||
@ -214,7 +214,7 @@ func (rl *Instance) getCompletions() {
|
|||||||
|
|
||||||
// moveTabCompletionHighlight - This function is in charge of
|
// moveTabCompletionHighlight - This function is in charge of
|
||||||
// computing the new position in the current completions liste.
|
// 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
|
rl.completionOpen = true
|
||||||
g := rl.getCurrentGroup()
|
g := rl.getCurrentGroup()
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ func (rl *Instance) moveTabCompletionHighlight(x, y int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writeTabCompletion - Prints all completion groups and their items
|
// writeTabCompletion - Prints all completion groups and their items
|
||||||
func (rl *Instance) writeTabCompletion() {
|
func (rl *Readline) writeTabCompletion() {
|
||||||
|
|
||||||
// The final completions string to print.
|
// The final completions string to print.
|
||||||
var completions string
|
var completions string
|
||||||
@ -289,7 +289,7 @@ func (rl *Instance) writeTabCompletion() {
|
|||||||
// cropCompletions - When the user cycles through a completion list longer
|
// cropCompletions - When the user cycles through a completion list longer
|
||||||
// than the console MaxTabCompleterRows value, we crop the completions string
|
// than the console MaxTabCompleterRows value, we crop the completions string
|
||||||
// so that "global" cycling (across all groups) is printed correctly.
|
// 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 we actually fit into the MaxTabCompleterRows, return the comps
|
||||||
if rl.tcUsedY < rl.MaxTabCompleterRows {
|
if rl.tcUsedY < rl.MaxTabCompleterRows {
|
||||||
@ -366,7 +366,7 @@ func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) getAbsPos() int {
|
func (rl *Readline) getAbsPos() int {
|
||||||
var prev int
|
var prev int
|
||||||
var foundCurrent bool
|
var foundCurrent bool
|
||||||
for _, grp := range rl.tcGroups {
|
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
|
// We pass a special subset of the current input line, so that
|
||||||
// completions are available no matter where the cursor is.
|
// 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)
|
pos = rl.pos - len(rl.currentComp)
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
@ -409,7 +409,7 @@ func (rl *Instance) getCompletionLine() (line []rune, pos int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
func (rl *Readline) getCurrentGroup() (group *CompletionGroup) {
|
||||||
for _, g := range rl.tcGroups {
|
for _, g := range rl.tcGroups {
|
||||||
if g.isCurrent && len(g.Suggestions) > 0 {
|
if g.isCurrent && len(g.Suggestions) > 0 {
|
||||||
return g
|
return g
|
||||||
@ -431,7 +431,7 @@ func (rl *Instance) getCurrentGroup() (group *CompletionGroup) {
|
|||||||
|
|
||||||
// cycleNextGroup - Finds either the first non-empty group,
|
// cycleNextGroup - Finds either the first non-empty group,
|
||||||
// or the next non-empty group after the current one.
|
// or the next non-empty group after the current one.
|
||||||
func (rl *Instance) cycleNextGroup() {
|
func (rl *Readline) cycleNextGroup() {
|
||||||
for i, g := range rl.tcGroups {
|
for i, g := range rl.tcGroups {
|
||||||
if g.isCurrent {
|
if g.isCurrent {
|
||||||
g.isCurrent = false
|
g.isCurrent = false
|
||||||
@ -452,7 +452,7 @@ func (rl *Instance) cycleNextGroup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// cyclePreviousGroup - Same as cycleNextGroup but reverse
|
// cyclePreviousGroup - Same as cycleNextGroup but reverse
|
||||||
func (rl *Instance) cyclePreviousGroup() {
|
func (rl *Readline) cyclePreviousGroup() {
|
||||||
for i, g := range rl.tcGroups {
|
for i, g := range rl.tcGroups {
|
||||||
if g.isCurrent {
|
if g.isCurrent {
|
||||||
g.isCurrent = false
|
g.isCurrent = false
|
||||||
@ -471,7 +471,7 @@ func (rl *Instance) cyclePreviousGroup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a single completion candidate
|
// Check if we have a single completion candidate
|
||||||
func (rl *Instance) hasOneCandidate() bool {
|
func (rl *Readline) hasOneCandidate() bool {
|
||||||
if len(rl.tcGroups) == 0 {
|
if len(rl.tcGroups) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -509,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool {
|
|||||||
// - The user-specified max completion length
|
// - The user-specified max completion length
|
||||||
// - The terminal lengh
|
// - The terminal lengh
|
||||||
// we use this function to prompt for confirmation before printing comps.
|
// 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.infoText = []rune(sentence)
|
||||||
|
|
||||||
rl.compConfirmWait = true
|
rl.compConfirmWait = true
|
||||||
@ -518,7 +518,7 @@ func (rl *Instance) promptCompletionConfirm(sentence string) {
|
|||||||
rl.renderHelpers()
|
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 {
|
for _, group := range rl.tcGroups {
|
||||||
comps += len(group.Suggestions)
|
comps += len(group.Suggestions)
|
||||||
// if group.Name != "" {
|
// if group.Name != "" {
|
||||||
@ -535,7 +535,7 @@ func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) resetTabCompletion() {
|
func (rl *Readline) resetTabCompletion() {
|
||||||
rl.modeTabCompletion = false
|
rl.modeTabCompletion = false
|
||||||
rl.tabCompletionSelect = false
|
rl.tabCompletionSelect = false
|
||||||
rl.compConfirmWait = false
|
rl.compConfirmWait = false
|
@ -12,7 +12,7 @@ const (
|
|||||||
RegisterFind
|
RegisterFind
|
||||||
)
|
)
|
||||||
|
|
||||||
func (rl *Instance) backspaceTabFind() {
|
func (rl *Readline) backspaceTabFind() {
|
||||||
if len(rl.tfLine) > 0 {
|
if len(rl.tfLine) > 0 {
|
||||||
rl.tfLine = rl.tfLine[:len(rl.tfLine)-1]
|
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
|
// 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.
|
// 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...)
|
rl.tfLine = append(rl.tfLine, r...)
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ func (rl *Instance) updateTabFind(r []rune) {
|
|||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) resetTabFind() {
|
func (rl *Readline) resetTabFind() {
|
||||||
rl.modeTabFind = false
|
rl.modeTabFind = false
|
||||||
// rl.modeAutoFind = false // Added, because otherwise it gets stuck on search completions
|
// rl.modeAutoFind = false // Added, because otherwise it gets stuck on search completions
|
||||||
|
|
@ -7,12 +7,12 @@ import (
|
|||||||
|
|
||||||
// DelayedTabContext is a custom context interface for async updates to the tab completions
|
// DelayedTabContext is a custom context interface for async updates to the tab completions
|
||||||
type DelayedTabContext struct {
|
type DelayedTabContext struct {
|
||||||
rl *Instance
|
rl *Readline
|
||||||
Context context.Context
|
Context context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func delayedSyntaxTimer(rl *Instance, i int64) {
|
func delayedSyntaxTimer(rl *Readline, i int64) {
|
||||||
if rl.PasswordMask != 0 || rl.DelayedSyntaxWorker == nil {
|
if rl.PasswordMask != 0 || rl.DelayedSyntaxWorker == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ type undoItem struct {
|
|||||||
pos int
|
pos int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) undoAppendHistory() {
|
func (rl *Readline) undoAppendHistory() {
|
||||||
defer func() { rl.viUndoSkipAppend = false }()
|
defer func() { rl.viUndoSkipAppend = false }()
|
||||||
|
|
||||||
if rl.viUndoSkipAppend {
|
if rl.viUndoSkipAppend {
|
||||||
@ -18,7 +18,7 @@ func (rl *Instance) undoAppendHistory() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) undoLast() {
|
func (rl *Readline) undoLast() {
|
||||||
var undo undoItem
|
var undo undoItem
|
||||||
for {
|
for {
|
||||||
if len(rl.viUndoHistory) == 0 {
|
if len(rl.viUndoHistory) == 0 {
|
@ -10,7 +10,7 @@ import (
|
|||||||
// updateHelpers is a key part of the whole refresh process:
|
// updateHelpers is a key part of the whole refresh process:
|
||||||
// it should coordinate reprinting the input line, any Infos and completions
|
// it should coordinate reprinting the input line, any Infos and completions
|
||||||
// and manage to get back to the current (computed) cursor coordinates
|
// and manage to get back to the current (computed) cursor coordinates
|
||||||
func (rl *Instance) updateHelpers() {
|
func (rl *Readline) updateHelpers() {
|
||||||
print(seqHideCursor)
|
print(seqHideCursor)
|
||||||
// Load all Infos & completions before anything.
|
// Load all Infos & completions before anything.
|
||||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText()
|
// 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 {
|
if rl.modeTabCompletion && !rl.completionOpen {
|
||||||
rl.getTabCompletion()
|
rl.getTabCompletion()
|
||||||
} else {
|
} else {
|
||||||
if rl.completionOpen { rl.completionOpen = false }
|
if rl.completionOpen {
|
||||||
|
rl.completionOpen = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We clear everything
|
// 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)
|
// 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,
|
// We always need to work with clean data,
|
||||||
// since we will have incrementers all around
|
// 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
|
rl.modeAutoFind = false
|
||||||
|
|
||||||
// Now reset all below-input helpers
|
// Now reset all below-input helpers
|
||||||
@ -113,7 +115,7 @@ func (rl *Instance) resetHelpers() {
|
|||||||
|
|
||||||
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
||||||
// and comes back at the prompt.
|
// and comes back at the prompt.
|
||||||
func (rl *Instance) clearHelpers() {
|
func (rl *Readline) clearHelpers() {
|
||||||
|
|
||||||
// Now go down to the last line of input
|
// Now go down to the last line of input
|
||||||
moveCursorDown(rl.fullY - rl.posY)
|
moveCursorDown(rl.fullY - rl.posY)
|
||||||
@ -132,7 +134,7 @@ func (rl *Instance) clearHelpers() {
|
|||||||
// renderHelpers - pritns all components (prompt, line, Infos & comps)
|
// renderHelpers - pritns all components (prompt, line, Infos & comps)
|
||||||
// and replaces the cursor to its current position. This function never
|
// and replaces the cursor to its current position. This function never
|
||||||
// computes or refreshes any value, except from inside the echo function.
|
// 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
|
// when the instance is in this state we want it to be "below" the user's
|
||||||
// input for it to be aligned properly
|
// input for it to be aligned properly
|
||||||
@ -197,14 +199,14 @@ func (rl *Instance) renderHelpers() {
|
|||||||
moveCursorForwards(rl.posX)
|
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...)
|
fmt.Fprintf(rl.bufferedOut, format, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) bufprint(text string) {
|
func (rl *Readline) bufprint(text string) {
|
||||||
fmt.Fprint(rl.bufferedOut, text)
|
fmt.Fprint(rl.bufferedOut, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) bufflush() {
|
func (rl *Readline) bufflush() {
|
||||||
rl.bufferedOut.Flush()
|
rl.bufferedOut.Flush()
|
||||||
}
|
}
|
@ -34,6 +34,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ViAction int
|
type ViAction int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VimActionYank = iota
|
VimActionYank = iota
|
||||||
VimActionPaste
|
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
|
// 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
|
// 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.
|
// will compute the new cursor pos accordingly.
|
||||||
func (rl *Instance) vi(r rune) {
|
func (rl *Readline) vi(r rune) {
|
||||||
activeRegister := string(rl.registers.currentRegister)
|
activeRegister := string(rl.registers.currentRegister)
|
||||||
|
|
||||||
// Check if we are in register mode. If yes, and for some characters,
|
// 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)
|
i, _ := strconv.Atoi(rl.viIteration)
|
||||||
if i < 1 {
|
if i < 1 {
|
||||||
i = 1
|
i = 1
|
||||||
@ -393,7 +394,7 @@ func (rl *Instance) getViIterations() int {
|
|||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) refreshVimStatus() {
|
func (rl *Readline) refreshVimStatus() {
|
||||||
rl.ViModeCallback(rl.modeViMode)
|
rl.ViModeCallback(rl.modeViMode)
|
||||||
rl.computePrompt()
|
rl.computePrompt()
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
@ -401,7 +402,7 @@ func (rl *Instance) refreshVimStatus() {
|
|||||||
|
|
||||||
// viInfoMessage - lmorg's way of showing Vim status is to overwrite the info.
|
// 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.
|
// 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 {
|
switch rl.modeViMode {
|
||||||
case VimKeys:
|
case VimKeys:
|
||||||
rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||||||
@ -421,7 +422,7 @@ func (rl *Instance) viInfoMessage() {
|
|||||||
rl.renderHelpers()
|
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)
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||||
switch {
|
switch {
|
||||||
case len(split) == 0:
|
case len(split) == 0:
|
||||||
@ -436,7 +437,7 @@ func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) {
|
|||||||
return adjust * -1
|
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)
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||||
if len(split) == 0 {
|
if len(split) == 0 {
|
||||||
return
|
return
|
||||||
@ -459,7 +460,7 @@ func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) {
|
|||||||
return
|
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)
|
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||||
switch {
|
switch {
|
||||||
case len(split) == 0:
|
case len(split) == 0:
|
||||||
@ -472,7 +473,7 @@ func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
func (rl *Readline) viJumpPreviousBrace() (adjust int) {
|
||||||
if rl.pos == 0 {
|
if rl.pos == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -486,7 +487,7 @@ func (rl *Instance) viJumpPreviousBrace() (adjust int) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) viJumpNextBrace() (adjust int) {
|
func (rl *Readline) viJumpNextBrace() (adjust int) {
|
||||||
if rl.pos >= len(rl.line)-1 {
|
if rl.pos >= len(rl.line)-1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -500,7 +501,7 @@ func (rl *Instance) viJumpNextBrace() (adjust int) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) viJumpBracket() (adjust int) {
|
func (rl *Readline) viJumpBracket() (adjust int) {
|
||||||
split, index, pos := tokeniseBrackets(rl.line, rl.pos)
|
split, index, pos := tokeniseBrackets(rl.line, rl.pos)
|
||||||
switch {
|
switch {
|
||||||
case len(split) == 0:
|
case len(split) == 0:
|
@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// vimDelete -
|
// vimDelete -
|
||||||
func (rl *Instance) viDelete(r rune) {
|
func (rl *Readline) viDelete(r rune) {
|
||||||
|
|
||||||
// We are allowed to type iterations after a delete ('d') command.
|
// 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
|
// 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 (
|
var (
|
||||||
newLine []rune
|
newLine []rune
|
||||||
backOne bool
|
backOne bool
|
||||||
@ -142,11 +142,11 @@ func (rl *Instance) viDeleteByAdjust(adjust int) {
|
|||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) DeleteByAmount(adjust int) {
|
func (rl *Readline) DeleteByAmount(adjust int) {
|
||||||
rl.viDeleteByAdjust(adjust)
|
rl.viDeleteByAdjust(adjust)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) vimDeleteToken(r rune) bool {
|
func (rl *Readline) vimDeleteToken(r rune) bool {
|
||||||
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
tokens, _, _ := tokeniseSplitSpaces(rl.line, 0)
|
||||||
pos := int(r) - 48 // convert ASCII to integer
|
pos := int(r) - 48 // convert ASCII to integer
|
||||||
if pos > len(tokens) {
|
if pos > len(tokens) {
|
147
golibs/yarn/yarn.go
Normal file
147
golibs/yarn/yarn.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// multi threading library
|
||||||
|
// Yarn is a simple multithreading library. Threads are individual Lua states,
|
||||||
|
// so they do NOT share the same environment as the code that runs the thread.
|
||||||
|
// Bait and Commanders are shared though, so you *can* throw hooks from 1 thread to another.
|
||||||
|
/*
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local yarn = require 'yarn'
|
||||||
|
|
||||||
|
-- calling t will run the yarn thread.
|
||||||
|
local t = yarn.thread(print)
|
||||||
|
t 'printing from another lua state!'
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
package yarn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hilbish/util"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var yarnMetaKey = rt.StringValue("hshyarn")
|
||||||
|
var globalSpool *Yarn
|
||||||
|
|
||||||
|
type Yarn struct {
|
||||||
|
initializer func(*rt.Runtime)
|
||||||
|
Loader packagelib.Loader
|
||||||
|
}
|
||||||
|
|
||||||
|
// #type
|
||||||
|
type Thread struct {
|
||||||
|
rtm *rt.Runtime
|
||||||
|
f rt.Callable
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(init func(*rt.Runtime)) *Yarn {
|
||||||
|
yrn := &Yarn{
|
||||||
|
initializer: init,
|
||||||
|
}
|
||||||
|
yrn.Loader = packagelib.Loader{
|
||||||
|
Load: yrn.loaderFunc,
|
||||||
|
Name: "yarn",
|
||||||
|
}
|
||||||
|
|
||||||
|
globalSpool = yrn
|
||||||
|
|
||||||
|
return yrn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *Yarn) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
|
yarnMeta := rt.NewTable()
|
||||||
|
yarnMeta.Set(rt.StringValue("__call"), rt.FunctionValue(rt.NewGoFunction(yarnrun, "__call", 1, true)))
|
||||||
|
rtm.SetRegistry(yarnMetaKey, rt.TableValue(yarnMeta))
|
||||||
|
|
||||||
|
exports := map[string]util.LuaExport{
|
||||||
|
"thread": {
|
||||||
|
Function: yarnthread,
|
||||||
|
ArgNum: 1,
|
||||||
|
Variadic: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
|
|
||||||
|
return rt.TableValue(mod), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *Yarn) init(th *Thread) {
|
||||||
|
y.initializer(th.rtm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// thread(fun) -> @Thread
|
||||||
|
// Creates a new, fresh Yarn thread.
|
||||||
|
// `fun` is the function that will run in the thread.
|
||||||
|
func yarnthread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fun, err := c.CallableArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
yrn := &Thread{
|
||||||
|
rtm: rt.New(os.Stdout),
|
||||||
|
f: fun,
|
||||||
|
}
|
||||||
|
globalSpool.init(yrn)
|
||||||
|
|
||||||
|
return c.PushingNext(t.Runtime, rt.UserDataValue(yarnUserData(t.Runtime, yrn))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func yarnrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
yrn, err := yarnArg(c, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
yrn.Run(c.Etc())
|
||||||
|
|
||||||
|
return c.Next(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *Thread) Run(args []rt.Value) {
|
||||||
|
go func() {
|
||||||
|
term := rt.NewTerminationWith(y.rtm.MainThread().CurrentCont(), 0, true)
|
||||||
|
err := rt.Call(y.rtm.MainThread(), rt.FunctionValue(y.f), args, term)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func yarnArg(c *rt.GoCont, arg int) (*Thread, error) {
|
||||||
|
j, ok := valueToYarn(c.Arg(arg))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("#%d must be a yarn thread", arg+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToYarn(val rt.Value) (*Thread, bool) {
|
||||||
|
u, ok := val.TryUserData()
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
j, ok := u.Value().(*Thread)
|
||||||
|
return j, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func yarnUserData(rtm *rt.Runtime, t *Thread) *rt.UserData {
|
||||||
|
yarnMeta := rtm.Registry(yarnMetaKey)
|
||||||
|
return rt.NewUserData(t, yarnMeta.AsTable())
|
||||||
|
}
|
97
lua.go
97
lua.go
@ -5,60 +5,31 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"hilbish/util"
|
|
||||||
"hilbish/golibs/bait"
|
"hilbish/golibs/bait"
|
||||||
"hilbish/golibs/commander"
|
"hilbish/golibs/commander"
|
||||||
"hilbish/golibs/fs"
|
"hilbish/golibs/fs"
|
||||||
"hilbish/golibs/snail"
|
"hilbish/golibs/snail"
|
||||||
"hilbish/golibs/terminal"
|
"hilbish/golibs/terminal"
|
||||||
|
"hilbish/golibs/yarn"
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
rt "github.com/arnodel/golua/runtime"
|
|
||||||
"github.com/arnodel/golua/lib"
|
"github.com/arnodel/golua/lib"
|
||||||
"github.com/arnodel/golua/lib/debuglib"
|
"github.com/arnodel/golua/lib/debuglib"
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
var minimalconf = `hilbish.prompt '& '`
|
var minimalconf = `hilbish.prompt '& '`
|
||||||
|
|
||||||
func luaInit() {
|
func luaInit() {
|
||||||
l = rt.New(os.Stdout)
|
l = rt.New(os.Stdout)
|
||||||
l.PushContext(rt.RuntimeContextDef{
|
|
||||||
MessageHandler: debuglib.Traceback,
|
|
||||||
})
|
|
||||||
lib.LoadAll(l)
|
|
||||||
|
|
||||||
lib.LoadLibs(l, hilbishLoader)
|
loadLibs(l)
|
||||||
// yes this is stupid, i know
|
|
||||||
util.DoString(l, "hilbish = require 'hilbish'")
|
|
||||||
|
|
||||||
lib.LoadLibs(l, fs.Loader)
|
yarnPool := yarn.New(yarnloadLibs)
|
||||||
lib.LoadLibs(l, terminal.Loader)
|
lib.LoadLibs(l, yarnPool.Loader)
|
||||||
lib.LoadLibs(l, snail.Loader)
|
|
||||||
|
|
||||||
cmds = commander.New(l)
|
|
||||||
lib.LoadLibs(l, cmds.Loader)
|
|
||||||
|
|
||||||
hooks = bait.New(l)
|
|
||||||
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
|
|
||||||
fmt.Println("Error in `error` hook handler:", err)
|
|
||||||
hooks.Off(event, handler)
|
|
||||||
})
|
|
||||||
|
|
||||||
lib.LoadLibs(l, hooks.Loader)
|
|
||||||
|
|
||||||
// Add Ctrl-C handler
|
|
||||||
hooks.On("signal.sigint", func(...interface{}) rt.Value {
|
|
||||||
if !interactive {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
return rt.NilValue
|
|
||||||
})
|
|
||||||
|
|
||||||
lr.rl.RawInputCallback = func(r []rune) {
|
|
||||||
hooks.Emit("hilbish.rawInput", string(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add more paths that Lua can require from
|
// 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 {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
|
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
|
||||||
}
|
}
|
||||||
@ -74,6 +45,60 @@ func luaInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadLibs(r *rt.Runtime) {
|
||||||
|
r.PushContext(rt.RuntimeContextDef{
|
||||||
|
MessageHandler: debuglib.Traceback,
|
||||||
|
})
|
||||||
|
lib.LoadAll(r)
|
||||||
|
|
||||||
|
lib.LoadLibs(r, hilbishLoader)
|
||||||
|
// yes this is stupid, i know
|
||||||
|
util.DoString(r, "hilbish = require 'hilbish'")
|
||||||
|
|
||||||
|
hooks = bait.New(r)
|
||||||
|
hooks.SetRecoverer(func(event string, handler *bait.Listener, err interface{}) {
|
||||||
|
fmt.Println("Error in `error` hook handler:", err)
|
||||||
|
hooks.Off(event, handler)
|
||||||
|
})
|
||||||
|
lib.LoadLibs(r, hooks.Loader)
|
||||||
|
|
||||||
|
// Add Ctrl-C handler
|
||||||
|
hooks.On("signal.sigint", func(...interface{}) rt.Value {
|
||||||
|
if !interactive {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
return rt.NilValue
|
||||||
|
})
|
||||||
|
|
||||||
|
lr.rl.RawInputCallback = func(rn []rune) {
|
||||||
|
hooks.Emit("hilbish.rawInput", string(rn))
|
||||||
|
}
|
||||||
|
|
||||||
|
lib.LoadLibs(r, fs.Loader)
|
||||||
|
lib.LoadLibs(r, terminal.Loader)
|
||||||
|
lib.LoadLibs(r, snail.Loader)
|
||||||
|
|
||||||
|
cmds = commander.New(r)
|
||||||
|
lib.LoadLibs(r, cmds.Loader)
|
||||||
|
lib.LoadLibs(l, lr.rl.Loader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yarnloadLibs(r *rt.Runtime) {
|
||||||
|
r.PushContext(rt.RuntimeContextDef{
|
||||||
|
MessageHandler: debuglib.Traceback,
|
||||||
|
})
|
||||||
|
lib.LoadAll(r)
|
||||||
|
|
||||||
|
lib.LoadLibs(r, hilbishLoader)
|
||||||
|
lib.LoadLibs(r, hooks.Loader)
|
||||||
|
lib.LoadLibs(r, fs.Loader)
|
||||||
|
lib.LoadLibs(r, terminal.Loader)
|
||||||
|
lib.LoadLibs(r, snail.Loader)
|
||||||
|
lib.LoadLibs(r, cmds.Loader)
|
||||||
|
lib.LoadLibs(l, lr.rl.Loader)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func runConfig(confpath string) {
|
func runConfig(confpath string) {
|
||||||
if !interactive {
|
if !interactive {
|
||||||
return
|
return
|
||||||
|
@ -25,5 +25,4 @@ commander.register('cd', function (args, sinks)
|
|||||||
end
|
end
|
||||||
|
|
||||||
bait.throw('cd', path, oldPath)
|
bait.throw('cd', path, oldPath)
|
||||||
bait.throw('hilbish.cd', absPath, oldPath)
|
|
||||||
end)
|
end)
|
||||||
|
28
nature/editor.lua
Normal file
28
nature/editor.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
local readline = require 'readline'
|
||||||
|
|
||||||
|
local editor = readline.new()
|
||||||
|
local editorMt = {}
|
||||||
|
|
||||||
|
hilbish.editor = {}
|
||||||
|
|
||||||
|
local function contains(search, needle)
|
||||||
|
for _, p in ipairs(search) do
|
||||||
|
if p == needle then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function editorMt.__index(_, key)
|
||||||
|
if contains({'deleteByAmount', 'getVimRegister', 'getLine', 'insert', 'readChar', 'setVimRegister'}, key) then
|
||||||
|
--editor:log 'The calling method of this function has changed. Please use the colon to call this hilbish.editor function.'
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
return editor[key](editor, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(hilbish.editor, editorMt)
|
10
nature/env.lua
Normal file
10
nature/env.lua
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
env = {}
|
||||||
|
|
||||||
|
setmetatable(env, {
|
||||||
|
__index = function(_, k)
|
||||||
|
return os.getenv(k)
|
||||||
|
end,
|
||||||
|
__newindex = function(_, k, v)
|
||||||
|
os.setenv(k, tostring(v))
|
||||||
|
end
|
||||||
|
})
|
@ -3,6 +3,7 @@ local bait = require 'bait'
|
|||||||
local snail = require 'snail'
|
local snail = require 'snail'
|
||||||
|
|
||||||
hilbish.snail = snail.new()
|
hilbish.snail = snail.new()
|
||||||
|
hilbish.snail:run 'true' -- to "initialize" snail
|
||||||
bait.catch('hilbish.cd', function(path)
|
bait.catch('hilbish.cd', function(path)
|
||||||
hilbish.snail:dir(path)
|
hilbish.snail:dir(path)
|
||||||
end)
|
end)
|
||||||
|
@ -19,6 +19,7 @@ table.insert(package.searchers, function(module)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
require 'nature.hilbish'
|
require 'nature.hilbish'
|
||||||
|
require 'nature.processors'
|
||||||
|
|
||||||
require 'nature.commands'
|
require 'nature.commands'
|
||||||
require 'nature.completions'
|
require 'nature.completions'
|
||||||
@ -26,7 +27,9 @@ require 'nature.opts'
|
|||||||
require 'nature.vim'
|
require 'nature.vim'
|
||||||
require 'nature.runner'
|
require 'nature.runner'
|
||||||
require 'nature.hummingbird'
|
require 'nature.hummingbird'
|
||||||
|
require 'nature.env'
|
||||||
require 'nature.abbr'
|
require 'nature.abbr'
|
||||||
|
require 'nature.editor'
|
||||||
|
|
||||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
local shlvl = tonumber(os.getenv 'SHLVL')
|
||||||
if shlvl ~= nil then
|
if shlvl ~= nil then
|
||||||
@ -35,36 +38,6 @@ else
|
|||||||
os.setenv('SHLVL', '0')
|
os.setenv('SHLVL', '0')
|
||||||
end
|
end
|
||||||
|
|
||||||
do
|
|
||||||
local virt_G = { }
|
|
||||||
|
|
||||||
setmetatable(_G, {
|
|
||||||
__index = function (_, key)
|
|
||||||
local got_virt = virt_G[key]
|
|
||||||
if got_virt ~= nil then
|
|
||||||
return got_virt
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(key) == 'string' then
|
|
||||||
virt_G[key] = os.getenv(key)
|
|
||||||
end
|
|
||||||
return virt_G[key]
|
|
||||||
end,
|
|
||||||
|
|
||||||
__newindex = function (_, key, value)
|
|
||||||
if type(value) == 'string' then
|
|
||||||
os.setenv(key, value)
|
|
||||||
virt_G[key] = value
|
|
||||||
else
|
|
||||||
if type(virt_G[key]) == 'string' then
|
|
||||||
os.setenv(key, '')
|
|
||||||
end
|
|
||||||
virt_G[key] = value
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
do
|
||||||
local startSearchPath = hilbish.userDir.data .. '/hilbish/start/?/init.lua;'
|
local startSearchPath = hilbish.userDir.data .. '/hilbish/start/?/init.lua;'
|
||||||
.. hilbish.userDir.data .. '/hilbish/start/?.lua'
|
.. hilbish.userDir.data .. '/hilbish/start/?.lua'
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
|
|
||||||
local oldShRunner = hilbish.runner.sh
|
hilbish.processors.add {
|
||||||
function hilbish.runner.sh(input)
|
name = 'hilbish.autocd',
|
||||||
local res = oldShRunner(input)
|
func = function(path)
|
||||||
|
if hilbish.opts.autocd then
|
||||||
|
local ok, stat = pcall(fs.stat, path)
|
||||||
|
if ok and stat.isDir then
|
||||||
|
local oldPath = hilbish.cwd()
|
||||||
|
|
||||||
if res.exit ~= 0 and hilbish.opts.autocd then
|
local absPath = fs.abs(path)
|
||||||
local ok, stat = pcall(fs.stat, res.input)
|
fs.cd(path)
|
||||||
if ok and stat.isDir then
|
|
||||||
-- discard here to not append the cd, which will be in history
|
bait.throw('cd', path, oldPath)
|
||||||
local _, exitCode, err = hilbish.runner.sh('cd ' .. res.input)
|
bait.throw('hilbish.cd', absPath, oldPath)
|
||||||
res.exitCode = exitCode
|
|
||||||
res.err = err
|
end
|
||||||
|
return {
|
||||||
|
continue = not ok
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
continue = true
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
}
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
@ -2,7 +2,7 @@ hilbish.opts = {}
|
|||||||
|
|
||||||
local function setupOpt(name, default)
|
local function setupOpt(name, default)
|
||||||
hilbish.opts[name] = default
|
hilbish.opts[name] = default
|
||||||
pcall(require, 'nature.opts.' .. name)
|
local ok, err = pcall(require, 'nature.opts.' .. name)
|
||||||
end
|
end
|
||||||
|
|
||||||
local defaultOpts = {
|
local defaultOpts = {
|
||||||
@ -15,7 +15,8 @@ The nice lil shell for {blue}Lua{reset} fanatics!
|
|||||||
fuzzy = false,
|
fuzzy = false,
|
||||||
notifyJobFinish = true,
|
notifyJobFinish = true,
|
||||||
crimmas = true,
|
crimmas = true,
|
||||||
tips = true
|
tips = true,
|
||||||
|
processorSkipList = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for optsName, default in pairs(defaultOpts) do
|
for optsName, default in pairs(defaultOpts) do
|
||||||
|
57
nature/processors.lua
Normal file
57
nature/processors.lua
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
-- @module hilbish.processors
|
||||||
|
|
||||||
|
hilbish.processors = {
|
||||||
|
list = {},
|
||||||
|
sorted = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hilbish.processors.add(processor)
|
||||||
|
if not processor.name then
|
||||||
|
error 'processor is missing name'
|
||||||
|
end
|
||||||
|
|
||||||
|
if not processor.func then
|
||||||
|
error 'processor is missing function'
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(hilbish.processors.list, processor)
|
||||||
|
table.sort(hilbish.processors.list, function(a, b) return a.priority < b.priority end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function contains(search, needle)
|
||||||
|
for _, p in ipairs(search) do
|
||||||
|
if p == needle then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- 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.
|
||||||
|
function hilbish.processors.execute(command, opts)
|
||||||
|
opts = opts or {}
|
||||||
|
opts.skip = opts.skip or {}
|
||||||
|
|
||||||
|
local continue = true
|
||||||
|
local history
|
||||||
|
for _, processor in ipairs(hilbish.processors.list) do
|
||||||
|
if not contains(opts.skip, processor.name) then
|
||||||
|
local processed = processor.func(command)
|
||||||
|
if processed.history ~= nil then history = processed.history end
|
||||||
|
if processed.command then command = processed.command end
|
||||||
|
if not processed.continue then
|
||||||
|
continue = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
command = command,
|
||||||
|
continue = continue,
|
||||||
|
history = history
|
||||||
|
}
|
||||||
|
end
|
@ -122,12 +122,22 @@ end
|
|||||||
-- @param input string
|
-- @param input string
|
||||||
-- @param priv bool
|
-- @param priv bool
|
||||||
function hilbish.runner.run(input, priv)
|
function hilbish.runner.run(input, priv)
|
||||||
local command = hilbish.aliases.resolve(input)
|
bait.throw('command.preprocess', input)
|
||||||
bait.throw('command.preexec', input, command)
|
local processed = hilbish.processors.execute(input, {
|
||||||
|
skip = hilbish.opts.processorSkipList
|
||||||
|
})
|
||||||
|
priv = processed.history ~= nil and (not processed.history) or priv
|
||||||
|
if not processed.continue then
|
||||||
|
finishExec(0, '', true)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local command = hilbish.aliases.resolve(processed.command)
|
||||||
|
bait.throw('command.preexec', processed.command, command)
|
||||||
|
|
||||||
::rerun::
|
::rerun::
|
||||||
local runner = hilbish.runner.get(currentRunner)
|
local runner = hilbish.runner.get(currentRunner)
|
||||||
local ok, out = pcall(runner.run, input)
|
local ok, out = pcall(runner.run, processed.command)
|
||||||
if not ok then
|
if not ok then
|
||||||
io.stderr:write(out .. '\n')
|
io.stderr:write(out .. '\n')
|
||||||
finishExec(124, out.input, priv)
|
finishExec(124, out.input, priv)
|
||||||
@ -135,9 +145,9 @@ function hilbish.runner.run(input, priv)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if out.continue then
|
if out.continue then
|
||||||
local contInput = continuePrompt(input, out.newline)
|
local contInput = continuePrompt(processed.command, out.newline)
|
||||||
if contInput then
|
if contInput then
|
||||||
input = contInput
|
processed.command = contInput
|
||||||
goto rerun
|
goto rerun
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
// Package readline is a pure-Go re-imagining of the UNIX readline API
|
|
||||||
//
|
|
||||||
// This package is designed to be run independently from murex and at some
|
|
||||||
// point it will be separated into it's own git repository (at a stage when I
|
|
||||||
// am confident that murex will no longer be the primary driver for features,
|
|
||||||
// bugs or other code changes)
|
|
||||||
package readline
|
|
@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
@ -1,10 +0,0 @@
|
|||||||
# raw
|
|
||||||
|
|
||||||
This command exists here purely as a lazy feature for me to scan key presses
|
|
||||||
for their corresponding escape codes. It is a useful dev tool for rationalizing
|
|
||||||
what is happening in the different terminal emulators (since documentation
|
|
||||||
regarding what escape codes they send can often be non-existent and some of the
|
|
||||||
more exotic key combinations or modern keyboard functions can have multiple
|
|
||||||
published standards.
|
|
||||||
|
|
||||||
This package is not imported by `readline` and is not required as part of `readline`
|
|
@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
readline.MakeRaw(int(os.Stdin.Fd()))
|
|
||||||
|
|
||||||
for {
|
|
||||||
b := make([]byte, 1024)
|
|
||||||
i, err := os.Stdin.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(b[:i])
|
|
||||||
}
|
|
||||||
}
|
|
98
rl.go
98
rl.go
@ -13,9 +13,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type lineReader struct {
|
type lineReader struct {
|
||||||
rl *readline.Instance
|
rl *readline.Readline
|
||||||
fileHist *fileHistory
|
fileHist *fileHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
var hinter *rt.Closure
|
var hinter *rt.Closure
|
||||||
var highlighter *rt.Closure
|
var highlighter *rt.Closure
|
||||||
|
|
||||||
@ -54,58 +55,64 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||||||
rl.ViModeCallback = func(mode readline.ViMode) {
|
rl.ViModeCallback = func(mode readline.ViMode) {
|
||||||
modeStr := ""
|
modeStr := ""
|
||||||
switch mode {
|
switch mode {
|
||||||
case readline.VimKeys: modeStr = "normal"
|
case readline.VimKeys:
|
||||||
case readline.VimInsert: modeStr = "insert"
|
modeStr = "normal"
|
||||||
case readline.VimDelete: modeStr = "delete"
|
case readline.VimInsert:
|
||||||
case readline.VimReplaceOnce, readline.VimReplaceMany: modeStr = "replace"
|
modeStr = "insert"
|
||||||
|
case readline.VimDelete:
|
||||||
|
modeStr = "delete"
|
||||||
|
case readline.VimReplaceOnce, readline.VimReplaceMany:
|
||||||
|
modeStr = "replace"
|
||||||
}
|
}
|
||||||
setVimMode(modeStr)
|
setVimMode(modeStr)
|
||||||
}
|
}
|
||||||
rl.ViActionCallback = func(action readline.ViAction, args []string) {
|
rl.ViActionCallback = func(action readline.ViAction, args []string) {
|
||||||
actionStr := ""
|
actionStr := ""
|
||||||
switch action {
|
switch action {
|
||||||
case readline.VimActionPaste: actionStr = "paste"
|
case readline.VimActionPaste:
|
||||||
case readline.VimActionYank: actionStr = "yank"
|
actionStr = "paste"
|
||||||
|
case readline.VimActionYank:
|
||||||
|
actionStr = "yank"
|
||||||
}
|
}
|
||||||
hooks.Emit("hilbish.vimAction", actionStr, args)
|
hooks.Emit("hilbish.vimAction", actionStr, args)
|
||||||
}
|
}
|
||||||
rl.HintText = func(line []rune, pos int) []rune {
|
rl.HintText = func(line []rune, pos int) []rune {
|
||||||
hinter := hshMod.Get(rt.StringValue("hinter"))
|
hinter := hshMod.Get(rt.StringValue("hinter"))
|
||||||
retVal, err := rt.Call1(l.MainThread(), 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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return []rune{}
|
return []rune{}
|
||||||
}
|
}
|
||||||
|
|
||||||
hintText := ""
|
hintText := ""
|
||||||
if luaStr, ok := retVal.TryString(); ok {
|
if luaStr, ok := retVal.TryString(); ok {
|
||||||
hintText = luaStr
|
hintText = luaStr
|
||||||
}
|
}
|
||||||
|
|
||||||
return []rune(hintText)
|
return []rune(hintText)
|
||||||
}
|
}
|
||||||
rl.SyntaxHighlighter = func(line []rune) string {
|
rl.SyntaxHighlighter = func(line []rune) string {
|
||||||
highlighter := hshMod.Get(rt.StringValue("highlighter"))
|
highlighter := hshMod.Get(rt.StringValue("highlighter"))
|
||||||
retVal, err := rt.Call1(l.MainThread(), highlighter,
|
retVal, err := rt.Call1(l.MainThread(), highlighter,
|
||||||
rt.StringValue(string(line)))
|
rt.StringValue(string(line)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return string(line)
|
return string(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
highlighted := ""
|
highlighted := ""
|
||||||
if luaStr, ok := retVal.TryString(); ok {
|
if luaStr, ok := retVal.TryString(); ok {
|
||||||
highlighted = luaStr
|
highlighted = luaStr
|
||||||
}
|
}
|
||||||
|
|
||||||
return highlighted
|
return highlighted
|
||||||
}
|
}
|
||||||
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
||||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
||||||
compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler"))
|
compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler"))
|
||||||
err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)),
|
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
|
var compGroups []*readline.CompletionGroup
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -138,10 +145,15 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||||||
|
|
||||||
items := []string{}
|
items := []string{}
|
||||||
itemDescriptions := make(map[string]string)
|
itemDescriptions := make(map[string]string)
|
||||||
|
itemDisplays := make(map[string]string)
|
||||||
|
itemAliases := make(map[string]string)
|
||||||
|
|
||||||
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
util.ForEach(luaCompItems.AsTable(), func(lkey rt.Value, lval rt.Value) {
|
||||||
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
if keytyp := lkey.Type(); keytyp == rt.StringType {
|
||||||
|
// TODO: remove in 3.0
|
||||||
// ['--flag'] = {'description', '--flag-alias'}
|
// ['--flag'] = {'description', '--flag-alias'}
|
||||||
|
// OR
|
||||||
|
// ['--flag'] = {description = '', alias = '', display = ''}
|
||||||
itemName, ok := lkey.TryString()
|
itemName, ok := lkey.TryString()
|
||||||
vlTbl, okk := lval.TryTable()
|
vlTbl, okk := lval.TryTable()
|
||||||
if !ok && !okk {
|
if !ok && !okk {
|
||||||
@ -151,18 +163,30 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||||||
|
|
||||||
items = append(items, itemName)
|
items = append(items, itemName)
|
||||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||||
|
if !ok {
|
||||||
|
// if we can't get it by number index, try by string key
|
||||||
|
itemDescription, _ = vlTbl.Get(rt.StringValue("description")).TryString()
|
||||||
|
}
|
||||||
|
itemDescriptions[itemName] = itemDescription
|
||||||
|
|
||||||
|
// display
|
||||||
|
if itemDisplay, ok := vlTbl.Get(rt.StringValue("display")).TryString(); ok {
|
||||||
|
itemDisplays[itemName] = itemDisplay
|
||||||
|
}
|
||||||
|
|
||||||
|
itemAlias, ok := vlTbl.Get(rt.IntValue(2)).TryString()
|
||||||
|
if !ok {
|
||||||
|
// if we can't get it by number index, try by string key
|
||||||
|
itemAlias, _ = vlTbl.Get(rt.StringValue("alias")).TryString()
|
||||||
|
}
|
||||||
|
itemAliases[itemName] = itemAlias
|
||||||
|
} else if keytyp == rt.IntType {
|
||||||
|
vlStr, ok := lval.TryString()
|
||||||
if !ok {
|
if !ok {
|
||||||
// TODO: error
|
// TODO: error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
itemDescriptions[itemName] = itemDescription
|
items = append(items, vlStr)
|
||||||
} else if keytyp == rt.IntType {
|
|
||||||
vlStr, ok := lval.TryString()
|
|
||||||
if !ok {
|
|
||||||
// TODO: error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
items = append(items, vlStr)
|
|
||||||
} else {
|
} else {
|
||||||
// TODO: error
|
// TODO: error
|
||||||
return
|
return
|
||||||
@ -171,18 +195,22 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||||||
|
|
||||||
var dispType readline.TabDisplayType
|
var dispType readline.TabDisplayType
|
||||||
switch luaCompType.AsString() {
|
switch luaCompType.AsString() {
|
||||||
case "grid": dispType = readline.TabDisplayGrid
|
case "grid":
|
||||||
case "list": dispType = readline.TabDisplayList
|
dispType = readline.TabDisplayGrid
|
||||||
|
case "list":
|
||||||
|
dispType = readline.TabDisplayList
|
||||||
// need special cases, will implement later
|
// need special cases, will implement later
|
||||||
//case "map": dispType = readline.TabDisplayMap
|
//case "map": dispType = readline.TabDisplayMap
|
||||||
}
|
}
|
||||||
|
|
||||||
compGroups = append(compGroups, &readline.CompletionGroup{
|
compGroups = append(compGroups, &readline.CompletionGroup{
|
||||||
DisplayType: dispType,
|
DisplayType: dispType,
|
||||||
|
Aliases: itemAliases,
|
||||||
Descriptions: itemDescriptions,
|
Descriptions: itemDescriptions,
|
||||||
Suggestions: items,
|
ItemDisplays: itemDisplays,
|
||||||
TrimSlash: false,
|
Suggestions: items,
|
||||||
NoSpace: true,
|
TrimSlash: false,
|
||||||
|
NoSpace: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -208,8 +236,8 @@ func (lr *lineReader) SetPrompt(p string) {
|
|||||||
halfPrompt := strings.Split(p, "\n")
|
halfPrompt := strings.Split(p, "\n")
|
||||||
if len(halfPrompt) > 1 {
|
if len(halfPrompt) > 1 {
|
||||||
lr.rl.Multiline = true
|
lr.rl.Multiline = true
|
||||||
lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt) - 1], "\n"))
|
lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt)-1], "\n"))
|
||||||
lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt) - 1:][0]
|
lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt)-1:][0]
|
||||||
} else {
|
} else {
|
||||||
lr.rl.Multiline = false
|
lr.rl.Multiline = false
|
||||||
lr.rl.MultilinePrompt = ""
|
lr.rl.MultilinePrompt = ""
|
||||||
@ -241,16 +269,16 @@ func (lr *lineReader) Resize() {
|
|||||||
|
|
||||||
// #interface history
|
// #interface history
|
||||||
// command 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
|
// This includes the ability to override functions to change the main
|
||||||
// method of saving history.
|
// method of saving history.
|
||||||
func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
|
func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
|
||||||
lrLua := map[string]util.LuaExport{
|
lrLua := map[string]util.LuaExport{
|
||||||
"add": {lr.luaAddHistory, 1, false},
|
"add": {lr.luaAddHistory, 1, false},
|
||||||
"all": {lr.luaAllHistory, 0, false},
|
"all": {lr.luaAllHistory, 0, false},
|
||||||
"clear": {lr.luaClearHistory, 0, false},
|
"clear": {lr.luaClearHistory, 0, false},
|
||||||
"get": {lr.luaGetHistory, 1, false},
|
"get": {lr.luaGetHistory, 1, false},
|
||||||
"size": {lr.luaSize, 0, false},
|
"size": {lr.luaSize, 0, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
|
13
vars.go
13
vars.go
@ -2,16 +2,16 @@ package main
|
|||||||
|
|
||||||
// String vars that are free to be changed at compile time
|
// String vars that are free to be changed at compile time
|
||||||
var (
|
var (
|
||||||
defaultHistDir = ""
|
defaultHistDir = ""
|
||||||
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
||||||
|
|
||||||
prompt string
|
prompt string
|
||||||
multilinePrompt = "> "
|
multilinePrompt = "> "
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version info
|
// Version info
|
||||||
var (
|
var (
|
||||||
ver = "v2.4.0"
|
ver = "v3.0.0"
|
||||||
releaseName = "Moonflower"
|
releaseName = "Moonflower"
|
||||||
|
|
||||||
gitCommit string
|
gitCommit string
|
||||||
@ -20,10 +20,9 @@ var (
|
|||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
var (
|
var (
|
||||||
running bool // Is a command currently running
|
running bool // Is a command currently running
|
||||||
interactive bool
|
interactive bool
|
||||||
login bool // Are we the login shell?
|
login bool // Are we the login shell?
|
||||||
noexecute bool // Should we run Lua or only report syntax errors
|
noexecute bool // Should we run Lua or only report syntax errors
|
||||||
initialized bool
|
initialized bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user