diff --git a/CHANGELOG.md b/CHANGELOG.md index b80824b0..8e82fdd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ## Unreleased ### Added - Forward/Right arrow key will fill in hint text (#327) +- The readline library adds the ability to create custom instances of the Hilbish +line editor. Now, `hilbish.editor` has been changed to a readline instance, instead of just being a table of a few functions to access it. +This means the colon operator is now the *preferred* way of accessing its functions, +and the dot operator will cause errors in 3.0. +Example: `hilbish.editor.getLine()` should be changed to `hilbish.editor:getLine()` +before 3.0 +- Added the `hilbish.editor:read` and `hilbish.editor:log(text)` functions. ### Changed - Documentation for Lunacolors has been improved, with more information added. - Values returned by bait hooks will be passed to the `throw` caller diff --git a/api.go b/api.go index 8800f723..3d0ba262 100644 --- a/api.go +++ b/api.go @@ -121,9 +121,6 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) { timersModule := timers.loader(rtm) mod.Set(rt.StringValue("timers"), rt.TableValue(timersModule)) - editorModule := editorLoader(rtm) - mod.Set(rt.StringValue("editor"), rt.TableValue(editorModule)) - versionModule := rt.NewTable() util.SetField(rtm, versionModule, "branch", rt.StringValue(gitBranch)) util.SetField(rtm, versionModule, "full", rt.StringValue(getVersion())) diff --git a/cmd/docgen/docgen.go b/cmd/docgen/docgen.go index 1521e0e0..4961a648 100644 --- a/cmd/docgen/docgen.go +++ b/cmd/docgen/docgen.go @@ -2,14 +2,14 @@ package main import ( "fmt" - "path/filepath" "go/ast" "go/doc" "go/parser" "go/token" + "os" + "path/filepath" "regexp" "strings" - "os" "sync" md "github.com/atsushinee/go-markdown-generator/doc" @@ -27,49 +27,49 @@ menu: ` type emmyPiece struct { - DocPiece *docPiece + DocPiece *docPiece Annotations []string - Params []string // we only need to know param name to put in function - FuncName string + Params []string // we only need to know param name to put in function + FuncName string } type module struct { - Types []docPiece - Docs []docPiece - Fields []docPiece - Properties []docPiece + Types []docPiece + Docs []docPiece + Fields []docPiece + Properties []docPiece ShortDescription string - Description string - ParentModule string - HasInterfaces bool - HasTypes bool + Description string + ParentModule string + HasInterfaces bool + HasTypes bool } -type param struct{ +type param struct { Name string Type string - Doc []string + Doc []string } type docPiece struct { - Doc []string - FuncSig string - FuncName string - Interfacing string + Doc []string + FuncSig string + FuncName string + Interfacing string ParentModule string - GoFuncName string - IsInterface bool - IsMember bool - IsType bool - Fields []docPiece - Properties []docPiece - Params []param - Tags map[string][]tag + GoFuncName string + IsInterface bool + IsMember bool + IsType bool + Fields []docPiece + Properties []docPiece + Params []param + Tags map[string][]tag } type tag struct { - id string - fields []string + id string + fields []string startIdx int } @@ -78,13 +78,14 @@ var interfaceDocs = make(map[string]module) var emmyDocs = make(map[string][]emmyPiece) var typeTable = make(map[string][]string) // [0] = parentMod, [1] = interfaces var prefix = map[string]string{ - "main": "hl", - "hilbish": "hl", - "fs": "f", + "main": "hl", + "hilbish": "hl", + "fs": "f", "commander": "c", - "bait": "b", - "terminal": "term", - "snail": "snail", + "bait": "b", + "terminal": "term", + "snail": "snail", + "readline": "rl", } func getTagsAndDocs(docs string) (map[string][]tag, []string) { @@ -109,7 +110,7 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) { } else { if tagParts[0] == "example" { exampleIdx := tags["example"][0].startIdx - exampleCode := pts[exampleIdx+1:idx] + exampleCode := pts[exampleIdx+1 : idx] tags["example"][0].fields = exampleCode parts = strings.Split(strings.Replace(strings.Join(parts, "\n"), strings.TrimPrefix(strings.Join(exampleCode, "\n"), "#example\n"), "", -1), "\n") @@ -121,7 +122,7 @@ func getTagsAndDocs(docs string) (map[string][]tag, []string) { fleds = tagParts[2:] } tags[tagParts[0]] = append(tags[tagParts[0]], tag{ - id: tagParts[1], + id: tagParts[1], fields: fleds, }) } @@ -138,7 +139,7 @@ func docPieceTag(tagName string, tags map[string][]tag) []docPiece { for _, tag := range tags[tagName] { dps = append(dps, docPiece{ FuncName: tag.id, - Doc: tag.fields, + Doc: tag.fields, }) } @@ -169,16 +170,16 @@ func setupDocType(mod string, typ *doc.Type) *docPiece { if strings.HasPrefix(d, "---") { // TODO: document types in lua /* - emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) - emmyLinePieces := strings.Split(emmyLine, " ") - emmyType := emmyLinePieces[0] - if emmyType == "@param" { - em.Params = append(em.Params, emmyLinePieces[1]) - } - if emmyType == "@vararg" { - em.Params = append(em.Params, "...") // add vararg - } - em.Annotations = append(em.Annotations, d) + emmyLine := strings.TrimSpace(strings.TrimPrefix(d, "---")) + emmyLinePieces := strings.Split(emmyLine, " ") + emmyType := emmyLinePieces[0] + if emmyType == "@param" { + em.Params = append(em.Params, emmyLinePieces[1]) + } + if emmyType == "@vararg" { + em.Params = append(em.Params, "...") // add vararg + } + em.Annotations = append(em.Annotations, d) */ } else { typeDoc = append(typeDoc, d) @@ -191,16 +192,16 @@ func setupDocType(mod string, typ *doc.Type) *docPiece { } parentMod := mod dps := &docPiece{ - Doc: typeDoc, - FuncName: typeName, - Interfacing: interfaces, - IsInterface: inInterface, - IsMember: isMember, - IsType: true, + Doc: typeDoc, + FuncName: typeName, + Interfacing: interfaces, + IsInterface: inInterface, + IsMember: isMember, + IsType: true, ParentModule: parentMod, - Fields: fields, - Properties: properties, - Tags: tags, + Fields: fields, + Properties: properties, + Tags: tags, } typeTable[strings.ToLower(typeName)] = []string{parentMod, interfaces} @@ -221,6 +222,10 @@ func setupDoc(mod string, fun *doc.Func) *docPiece { goto start } + if prefix[mod] == "" { + return nil + } + if (!strings.HasPrefix(fun.Name, prefix[mod]) && tags["interface"] == nil) || (strings.ToLower(fun.Name) == "loader" && tags["interface"] == nil) { return nil } @@ -248,7 +253,7 @@ start: params[i] = param{ Name: p.id, Type: p.fields[0], - Doc: p.fields[1:], + Doc: p.fields[1:], } } } @@ -279,24 +284,24 @@ start: parentMod = mod } dps := &docPiece{ - Doc: funcdoc, - FuncSig: funcsig, - FuncName: funcName, - Interfacing: interfaces, - GoFuncName: strings.ToLower(fun.Name), - IsInterface: inInterface, - IsMember: isMember, + Doc: funcdoc, + FuncSig: funcsig, + FuncName: funcName, + Interfacing: interfaces, + GoFuncName: strings.ToLower(fun.Name), + IsInterface: inInterface, + IsMember: isMember, ParentModule: parentMod, - Fields: fields, - Properties: properties, - Params: params, - Tags: tags, + Fields: fields, + Properties: properties, + Params: params, + Tags: tags, } if strings.HasSuffix(dps.GoFuncName, strings.ToLower("loader")) { dps.Doc = parts } em.DocPiece = dps - + emmyDocs[mod] = append(emmyDocs[mod], em) return dps } @@ -326,11 +331,11 @@ provided by Hilbish. os.Mkdir("emmyLuaDocs", 0777) dirs := []string{"./", "./util"} - filepath.Walk("golibs/", func (path string, info os.FileInfo, err error) error { + filepath.Walk("golibs/", func(path string, info os.FileInfo, err error) error { if !info.IsDir() { return nil } - dirs = append(dirs, "./" + path) + dirs = append(dirs, "./"+path) return nil }) @@ -445,13 +450,13 @@ provided by Hilbish. docs[mod] = newDoc } else { docs[mod] = module{ - Types: filteredTypePieces, - Docs: filteredPieces, + Types: filteredTypePieces, + Docs: filteredPieces, ShortDescription: shortDesc, - Description: strings.Join(desc, "\n"), - HasInterfaces: hasInterfaces, - Properties: docPieceTag("property", tags), - Fields: docPieceTag("field", tags), + Description: strings.Join(desc, "\n"), + HasInterfaces: hasInterfaces, + Properties: docPieceTag("property", tags), + Fields: docPieceTag("field", tags), } } } @@ -466,7 +471,7 @@ provided by Hilbish. for mod, v := range docs { docPath := "docs/api/" + mod + ".md" if v.HasInterfaces { - os.Mkdir("docs/api/" + mod, 0777) + os.Mkdir("docs/api/"+mod, 0777) os.Remove(docPath) // remove old doc path if it exists docPath = "docs/api/" + mod + "/_index.md" } @@ -519,11 +524,11 @@ provided by Hilbish. continue } - mdTable.SetContent(i - diff, 0, fmt.Sprintf(`%s`, dps.FuncName, dps.FuncSig)) + mdTable.SetContent(i-diff, 0, fmt.Sprintf(`%s`, dps.FuncName, dps.FuncSig)) if len(dps.Doc) == 0 { fmt.Printf("WARNING! Function %s on module %s has no documentation!\n", dps.FuncName, modname) } else { - mdTable.SetContent(i - diff, 1, dps.Doc[0]) + mdTable.SetContent(i-diff, 1, dps.Doc[0]) } } f.WriteString(mdTable.String()) @@ -537,7 +542,6 @@ provided by Hilbish. mdTable.SetTitle(0, "") mdTable.SetTitle(1, "") - for i, dps := range modu.Fields { mdTable.SetContent(i, 0, dps.FuncName) mdTable.SetContent(i, 1, strings.Join(dps.Doc, " ")) @@ -552,7 +556,6 @@ provided by Hilbish. mdTable.SetTitle(0, "") mdTable.SetTitle(1, "") - for i, dps := range modu.Properties { mdTable.SetContent(i, 0, dps.FuncName) mdTable.SetContent(i, 1, strings.Join(dps.Doc, " ")) @@ -570,7 +573,7 @@ provided by Hilbish. continue } f.WriteString(fmt.Sprintf("
\n
", dps.FuncName)) - htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(modname + "." + dps.FuncSig, "<", `\<`, -1), func(typ string) string { + htmlSig := typeTag.ReplaceAllStringFunc(strings.Replace(modname+"."+dps.FuncSig, "<", `\<`, -1), func(typ string) string { typName := typ[1:] typLookup := typeTable[strings.ToLower(typName)] ifaces := typLookup[0] + "." + typLookup[1] + "/" @@ -657,7 +660,7 @@ provided by Hilbish. typName := regexp.MustCompile(`\w+`).FindString(typ[1:]) typLookup := typeTable[strings.ToLower(typName)] fmt.Printf("%+q, \n", typLookup) - linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0] + "." + typLookup[1], strings.ToLower(typName)) + linkedTyp := fmt.Sprintf("/Hilbish/docs/api/%s/%s/#%s", typLookup[0], typLookup[0]+"."+typLookup[1], strings.ToLower(typName)) return fmt.Sprintf(`%s`, linkedTyp, typName) }) f.WriteString(fmt.Sprintf("#### %s\n", htmlSig)) diff --git a/docs/api/hilbish/hilbish.editor.md b/docs/api/hilbish/hilbish.editor.md deleted file mode 100644 index 6dac64b8..00000000 --- a/docs/api/hilbish/hilbish.editor.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Module hilbish.editor -description: interactions for Hilbish's line reader -layout: doc -menu: - docs: - parent: "API" ---- - -## Introduction -The hilbish.editor interface provides functions to -directly interact with the line editor in use. - -## Functions -||| -|----|----| -|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.| -|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.| - -
-
-

-hilbish.editor.deleteByAmount(amount) - - - -

- -Deletes characters in the line by the given amount. - -#### Parameters -`number` **`amount`** - - -
- -
-
-

-hilbish.editor.getLine() -> string - - - -

- -Returns the current input line. - -#### Parameters -This function has no parameters. -
- -
-
-

-hilbish.editor.getVimRegister(register) -> string - - - -

- -Returns the text that is at the register. - -#### Parameters -`string` **`register`** - - -
- -
-
-

-hilbish.editor.insert(text) - - - -

- -Inserts text into the Hilbish command line. - -#### Parameters -`string` **`text`** - - -
- -
-
-

-hilbish.editor.getChar() -> string - - - -

- -Reads a keystroke from the user. This is in a format of something like Ctrl-L. - -#### Parameters -This function has no parameters. -
- -
-
-

-hilbish.editor.setVimRegister(register, text) - - - -

- -Sets the vim register at `register` to hold the passed text. - -#### Parameters -`string` **`register`** - - -`string` **`text`** - - -
- diff --git a/docs/api/readline.md b/docs/api/readline.md new file mode 100644 index 00000000..dfa4568e --- /dev/null +++ b/docs/api/readline.md @@ -0,0 +1,66 @@ +--- +title: Module readline +description: line reader library +layout: doc +menu: + docs: + parent: "API" +--- + +## Introduction +The readline module is responsible for reading input from the user. +The readline module is what Hilbish uses to read input from the user, +including all the interactive features of Hilbish like history search, +syntax highlighting, everything. The global Hilbish readline instance +is usable at `hilbish.editor`. + +## Functions +||| +|----|----| +|new() -> @Readline|Creates a new readline instance.| + +
+
+

+readline.new() -> Readline + + + +

+ +Creates a new readline instance. + +#### Parameters +This function has no parameters. +
+ +## Types +
+ +## 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. + diff --git a/editor.go b/editor.go deleted file mode 100644 index 2c04f255..00000000 --- a/editor.go +++ /dev/null @@ -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 -} diff --git a/emmyLuaDocs/hilbish.lua b/emmyLuaDocs/hilbish.lua index a2935bba..1e9e0d46 100644 --- a/emmyLuaDocs/hilbish.lua +++ b/emmyLuaDocs/hilbish.lua @@ -7,24 +7,6 @@ local hilbish = {} --- @param cmd string function hilbish.aliases.add(alias, cmd) end ---- Deletes characters in the line by the given amount. -function hilbish.editor.deleteByAmount(amount) end - ---- Returns the current input line. -function hilbish.editor.getLine() end - ---- Returns the text that is at the register. -function hilbish.editor.getVimRegister(register) end - ---- Inserts text into the Hilbish command line. -function hilbish.editor.insert(text) end - ---- Reads a keystroke from the user. This is in a format of something like Ctrl-L. -function hilbish.editor.getChar() end - ---- Sets the vim register at `register` to hold the passed text. -function hilbish.editor.setVimRegister(register, text) end - --- Return binaries/executables based on the provided parameters. --- This function is meant to be used as a helper in a command completion handler. --- diff --git a/emmyLuaDocs/readline.lua b/emmyLuaDocs/readline.lua new file mode 100644 index 00000000..a7fc8059 --- /dev/null +++ b/emmyLuaDocs/readline.lua @@ -0,0 +1,32 @@ +--- @meta + +local readline = {} + +--- Deletes characters in the line by the given amount. +function readline:deleteByAmount(amount) end + +--- Returns the current input line. +function readline:getLine() end + +--- Returns the text that is at the register. +function readline:getVimRegister(register) end + +--- Inserts text into the Hilbish command line. +function readline:insert(text) end + +--- Prints a message *before* the prompt without it being interrupted by user input. +function readline:log(text) end + +--- Creates a new readline instance. +function readline.new() end + +--- Reads input from the user. +function readline:read() end + +--- Reads a keystroke from the user. This is in a format of something like Ctrl-L. +function readline:getChar() end + +--- Sets the vim register at `register` to hold the passed text. +function readline:setVimRegister(register, text) end + +return readline diff --git a/go.mod b/go.mod index cc88c8ef..23bd7f69 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20240815163633-562273e09b73 -replace github.com/maxlandon/readline => ./readline +replace github.com/maxlandon/readline => ./golibs/readline replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10 diff --git a/readline/CHANGES.md b/golibs/readline/CHANGES.md similarity index 100% rename from readline/CHANGES.md rename to golibs/readline/CHANGES.md diff --git a/readline/LICENSE b/golibs/readline/LICENSE similarity index 100% rename from readline/LICENSE rename to golibs/readline/LICENSE diff --git a/readline/README.md b/golibs/readline/README.md similarity index 100% rename from readline/README.md rename to golibs/readline/README.md diff --git a/readline/codes.go b/golibs/readline/codes.go similarity index 68% rename from readline/codes.go rename to golibs/readline/codes.go index 28a9e604..380b35d7 100644 --- a/readline/codes.go +++ b/golibs/readline/codes.go @@ -54,14 +54,14 @@ var ( seqCtrlDelete2 = string([]byte{27, 91, 77}) seqAltDelete = string([]byte{27, 91, 51, 59, 51, 126}) seqShiftTab = string([]byte{27, 91, 90}) - seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^[" + seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^[" seqAltB = string([]byte{27, 98}) seqAltD = string([]byte{27, 100}) seqAltF = string([]byte{27, 102}) seqAltR = string([]byte{27, 114}) // Used for alternative history seqAltBackspace = string([]byte{27, 127}) seqPageUp = string([]byte{27, 91, 53, 126}) - seqPageDown = string([]byte{27, 91, 54, 126}) + seqPageDown = string([]byte{27, 91, 54, 126}) ) const ( @@ -76,7 +76,7 @@ const ( seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left seqGetCursorPos = "\x1b6n" // response: "\x1b{Line};{Column}R" - seqHideCursor = "\x1b[?25l" + seqHideCursor = "\x1b[?25l" seqUnhideCursor = "\x1b[?25h" seqCtrlLeftArrow = "\x1b[1;5D" @@ -143,55 +143,94 @@ const ( // TODO: return whether its actually a sequence or not // remedies the edge case of someone literally typing Ctrl-A for example. -func (rl *Instance) ReadChar() string { +func (rl *Readline) ReadChar() string { b := make([]byte, 1024) i, _ := os.Stdin.Read(b) r := []rune(string(b)) s := string(r[:i]) switch b[0] { - case charCtrlA: return "Ctrl-A" - case charCtrlB: return "Ctrl-B" - case charCtrlC: return "Ctrl-C" - case charEOF: return "Ctrl-D" - case charCtrlE: return "Ctrl-E" - case charCtrlF: return "Ctrl-F" - case charCtrlG: return "Ctrl-G" - case charBackspace, charBackspace2: return "Backspace" - case charTab: return "Tab" - case charCtrlK: return "Ctrl-K" - case charCtrlL: return "Ctrl-L" - case charCtrlN: return "Ctrl-N" - case charCtrlO: return "Ctrl-O" - case charCtrlP: return "Ctrl-P" - case charCtrlQ: return "Ctrl-Q" - case charCtrlR: return "Ctrl-R" - case charCtrlS: return "Ctrl-S" - case charCtrlT: return "Ctrl-T" - case charCtrlU: return "Ctrl-U" - case charCtrlV: return "Ctrl-V" - case charCtrlW: return "Ctrl-W" - case charCtrlX: return "Ctrl-X" - case charCtrlY: return "Ctrl-Y" - case charCtrlZ: return "Ctrl-Z" - case '\r': fallthrough - case '\n': return "Enter" - case charEscape: - switch s { - case string(charEscape): return "Escape" - case seqUp: return "Up" - case seqDown: return "Down" - case seqBackwards: return "Left" - case seqForwards: return "Right" - case seqCtrlLeftArrow: return "Ctrl-Left" - case seqCtrlRightArrow: return "Ctrl-Right" - case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete" - case seqHome, seqHomeSc: return "Home" - case seqEnd, seqEndSc: return "End" - case seqDelete, seqDelete2: return "Delete" - case seqPageUp: return "Page-Up" - case seqPageDown: return "Page-Down" - } + case charCtrlA: + return "Ctrl-A" + case charCtrlB: + return "Ctrl-B" + case charCtrlC: + return "Ctrl-C" + case charEOF: + return "Ctrl-D" + case charCtrlE: + return "Ctrl-E" + case charCtrlF: + return "Ctrl-F" + case charCtrlG: + return "Ctrl-G" + case charBackspace, charBackspace2: + return "Backspace" + case charTab: + return "Tab" + case charCtrlK: + return "Ctrl-K" + case charCtrlL: + return "Ctrl-L" + case charCtrlN: + return "Ctrl-N" + case charCtrlO: + return "Ctrl-O" + case charCtrlP: + return "Ctrl-P" + case charCtrlQ: + return "Ctrl-Q" + case charCtrlR: + return "Ctrl-R" + case charCtrlS: + return "Ctrl-S" + case charCtrlT: + return "Ctrl-T" + case charCtrlU: + return "Ctrl-U" + case charCtrlV: + return "Ctrl-V" + case charCtrlW: + return "Ctrl-W" + case charCtrlX: + return "Ctrl-X" + case charCtrlY: + return "Ctrl-Y" + case charCtrlZ: + return "Ctrl-Z" + case '\r': + fallthrough + case '\n': + return "Enter" + case charEscape: + switch s { + case string(charEscape): + return "Escape" + case seqUp: + return "Up" + case seqDown: + return "Down" + case seqBackwards: + return "Left" + case seqForwards: + return "Right" + case seqCtrlLeftArrow: + return "Ctrl-Left" + case seqCtrlRightArrow: + return "Ctrl-Right" + case seqCtrlDelete, seqCtrlDelete2: + return "Ctrl-Delete" + case seqHome, seqHomeSc: + return "Home" + case seqEnd, seqEndSc: + return "End" + case seqDelete, seqDelete2: + return "Delete" + case seqPageUp: + return "Page-Up" + case seqPageDown: + return "Page-Down" + } } return s diff --git a/readline/comp-grid.go b/golibs/readline/comp-grid.go similarity index 92% rename from readline/comp-grid.go rename to golibs/readline/comp-grid.go index c198bdbd..0d523773 100644 --- a/readline/comp-grid.go +++ b/golibs/readline/comp-grid.go @@ -4,12 +4,13 @@ import ( "fmt" "strconv" "strings" + "github.com/rivo/uniseg" -) +) // initGrid - Grid display details. Called each time we want to be sure to have // a working completion group either immediately, or later on. Generally defered. -func (g *CompletionGroup) initGrid(rl *Instance) { +func (g *CompletionGroup) initGrid(rl *Readline) { // Compute size of each completion item box tcMaxLength := 1 @@ -44,7 +45,7 @@ func (g *CompletionGroup) initGrid(rl *Instance) { } // moveTabGridHighlight - Moves the highlighting for currently selected completion item (grid display) -func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done bool, next bool) { +func (g *CompletionGroup) moveTabGridHighlight(rl *Readline, x, y int) (done bool, next bool) { g.tcPosX += x g.tcPosY += y @@ -96,7 +97,7 @@ func (g *CompletionGroup) moveTabGridHighlight(rl *Instance, x, y int) (done boo } // writeGrid - A grid completion string -func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) { +func (g *CompletionGroup) writeGrid(rl *Readline) (comp string) { // If group title, print it and adjust offset. if g.Name != "" { @@ -127,9 +128,9 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) { sugg := g.Suggestions[i] if len(sugg) > GetTermWidth() { - sugg = sugg[:GetTermWidth() - 4] + "..." + sugg = sugg[:GetTermWidth()-4] + "..." } - formatStr := "%-"+cellWidth+"s%s " + formatStr := "%-" + cellWidth + "s%s " if g.tcMaxX == 1 { formatStr = "%s%s" } diff --git a/readline/comp-group.go b/golibs/readline/comp-group.go similarity index 95% rename from readline/comp-group.go rename to golibs/readline/comp-group.go index 74b528a3..f6019fd0 100644 --- a/readline/comp-group.go +++ b/golibs/readline/comp-group.go @@ -49,7 +49,7 @@ type CompletionGroup struct { } // init - The completion group computes and sets all its values, and is then ready to work. -func (g *CompletionGroup) init(rl *Instance) { +func (g *CompletionGroup) init(rl *Readline) { // Details common to all displays g.checkCycle(rl) // Based on the number of groups given to the shell, allows cycling or not @@ -70,7 +70,7 @@ func (g *CompletionGroup) init(rl *Instance) { // updateTabFind - When searching through all completion groups (whether it be command history or not), // we ask each of them to filter its own items and return the results to the shell for aggregating them. // The rx parameter is passed, as the shell already checked that the search pattern is valid. -func (g *CompletionGroup) updateTabFind(rl *Instance) { +func (g *CompletionGroup) updateTabFind(rl *Readline) { suggs := rl.Searcher(rl.search, g.Suggestions) // We perform filter right here, so we create a new completion group, and populate it with our results. @@ -97,7 +97,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) { } // checkCycle - Based on the number of groups given to the shell, allows cycling or not -func (g *CompletionGroup) checkCycle(rl *Instance) { +func (g *CompletionGroup) checkCycle(rl *Readline) { if len(rl.tcGroups) == 1 { g.allowCycle = true } @@ -108,7 +108,7 @@ func (g *CompletionGroup) checkCycle(rl *Instance) { } // checkMaxLength - Based on the number of groups given to the shell, check/set MaxLength defaults -func (g *CompletionGroup) checkMaxLength(rl *Instance) { +func (g *CompletionGroup) checkMaxLength(rl *Readline) { // This means the user forgot to set it if g.MaxLength == 0 { @@ -147,7 +147,7 @@ func checkNilItems(groups []*CompletionGroup) (checked []*CompletionGroup) { // writeCompletion - This function produces a formatted string containing all appropriate items // and according to display settings. This string is then appended to the main completion string. -func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) { +func (g *CompletionGroup) writeCompletion(rl *Readline) (comp string) { // Avoids empty groups in suggestions if len(g.Suggestions) == 0 { @@ -169,7 +169,7 @@ func (g *CompletionGroup) writeCompletion(rl *Instance) (comp string) { // getCurrentCell - The completion groups computes the current cell value, // depending on its display type and its different parameters -func (g *CompletionGroup) getCurrentCell(rl *Instance) string { +func (g *CompletionGroup) getCurrentCell(rl *Readline) string { switch g.DisplayType { case TabDisplayGrid: diff --git a/readline/comp-list.go b/golibs/readline/comp-list.go similarity index 96% rename from readline/comp-list.go rename to golibs/readline/comp-list.go index 403cf5d1..bb34b49b 100644 --- a/readline/comp-list.go +++ b/golibs/readline/comp-list.go @@ -8,7 +8,7 @@ import ( // initList - List display details. Because of the way alternative completions // are handled, MaxLength cannot be set when there are alternative completions. -func (g *CompletionGroup) initList(rl *Instance) { +func (g *CompletionGroup) initList(rl *Readline) { // We may only ever have two different // columns: (suggestions, and alternatives) @@ -53,7 +53,7 @@ func (g *CompletionGroup) initList(rl *Instance) { // moveTabListHighlight - Moves the highlighting for currently selected completion item (list display) // We don't care about the x, because only can have 2 columns of selectable choices (--long and -s) -func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done bool, next bool) { +func (g *CompletionGroup) moveTabListHighlight(rl *Readline, x, y int) (done bool, next bool) { // We dont' pass to x, because not managed by callers g.tcPosY += x @@ -153,7 +153,7 @@ func (g *CompletionGroup) moveTabListHighlight(rl *Instance, x, y int) (done boo } // writeList - A list completion string -func (g *CompletionGroup) writeList(rl *Instance) (comp string) { +func (g *CompletionGroup) writeList(rl *Readline) (comp string) { // Print group title and adjust offset if there is one. if g.Name != "" { @@ -249,7 +249,7 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) { return } -func (rl *Instance) getListPad() (pad int) { +func (rl *Readline) getListPad() (pad int) { for _, group := range rl.tcGroups { if group.DisplayType == TabDisplayList { for i := range group.Suggestions { diff --git a/readline/comp-map.go b/golibs/readline/comp-map.go similarity index 94% rename from readline/comp-map.go rename to golibs/readline/comp-map.go index ec985ff8..65cf2035 100644 --- a/readline/comp-map.go +++ b/golibs/readline/comp-map.go @@ -7,7 +7,7 @@ import ( // initMap - Map display details. Called each time we want to be sure to have // a working completion group either immediately, or later on. Generally defered. -func (g *CompletionGroup) initMap(rl *Instance) { +func (g *CompletionGroup) initMap(rl *Readline) { // We make the map anyway, especially if we need to use it later if g.Descriptions == nil { @@ -35,7 +35,7 @@ func (g *CompletionGroup) initMap(rl *Instance) { } // moveTabMapHighlight - Moves the highlighting for currently selected completion item (map display) -func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool, next bool) { +func (g *CompletionGroup) moveTabMapHighlight(rl *Readline, x, y int) (done bool, next bool) { g.tcPosY += x g.tcPosY += y @@ -72,7 +72,7 @@ func (g *CompletionGroup) moveTabMapHighlight(rl *Instance, x, y int) (done bool } // writeMap - A map or list completion string -func (g *CompletionGroup) writeMap(rl *Instance) (comp string) { +func (g *CompletionGroup) writeMap(rl *Readline) (comp string) { if g.Name != "" { // Print group title (changes with line returns depending on type) diff --git a/readline/cursor.go b/golibs/readline/cursor.go similarity index 94% rename from readline/cursor.go rename to golibs/readline/cursor.go index 9d68a5a2..8128d733 100644 --- a/readline/cursor.go +++ b/golibs/readline/cursor.go @@ -1,7 +1,7 @@ package readline import ( -// "fmt" + // "fmt" "os" "regexp" "strconv" @@ -28,7 +28,7 @@ func leftMost() []byte { var rxRcvCursorPos = regexp.MustCompile("^\x1b([0-9]+);([0-9]+)R$") -func (rl *Instance) getCursorPos() (x int, y int) { +func (rl *Readline) getCursorPos() (x int, y int) { if !rl.EnableGetCursorPos { return -1, -1 } @@ -143,7 +143,7 @@ func unhideCursor() { print(seqUnhideCursor) } -func (rl *Instance) backspace(forward bool) { +func (rl *Readline) backspace(forward bool) { if len(rl.line) == 0 || rl.pos == 0 { return } @@ -151,7 +151,7 @@ func (rl *Instance) backspace(forward bool) { rl.deleteBackspace(forward) } -func (rl *Instance) moveCursorByAdjust(adjust int) { +func (rl *Readline) moveCursorByAdjust(adjust int) { switch { case adjust > 0: rl.pos += adjust diff --git a/readline/editor.go b/golibs/readline/editor.go similarity index 96% rename from readline/editor.go rename to golibs/readline/editor.go index 41a24db3..186e066b 100644 --- a/readline/editor.go +++ b/golibs/readline/editor.go @@ -14,7 +14,7 @@ import ( ) // writeTempFile - This function optionally accepts a filename (generally specified with an extension). -func (rl *Instance) writeTempFile(content []byte, filename string) (string, error) { +func (rl *Readline) writeTempFile(content []byte, filename string) (string, error) { // The final path to the buffer on disk var path string diff --git a/readline/editor_plan9.go b/golibs/readline/editor_plan9.go similarity index 60% rename from readline/editor_plan9.go rename to golibs/readline/editor_plan9.go index e6789f15..c1ab1e9d 100644 --- a/readline/editor_plan9.go +++ b/golibs/readline/editor_plan9.go @@ -1,9 +1,10 @@ +//go:build plan9 // +build plan9 package readline import "errors" -func (rl *Instance) launchEditor(multiline []rune) ([]rune, error) { +func (rl *Readline) launchEditor(multiline []rune) ([]rune, error) { return rl.line, errors.New("Not currently supported on Plan 9") } diff --git a/readline/editor_unix.go b/golibs/readline/editor_unix.go similarity index 94% rename from readline/editor_unix.go rename to golibs/readline/editor_unix.go index c103b257..5a5b3e00 100644 --- a/readline/editor_unix.go +++ b/golibs/readline/editor_unix.go @@ -15,7 +15,7 @@ const defaultEditor = "vi" // depending on the actions taken by the user within it (eg: x or q! in Vim) // The filename parameter can be used to pass a specific filename.ext pattern, // which might be useful if the editor has builtin filetype plugin functionality. -func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) { +func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) { name, err := rl.writeTempFile([]byte(string(multiline)), filename) if err != nil { return multiline, err diff --git a/readline/editor_windows.go b/golibs/readline/editor_windows.go similarity index 77% rename from readline/editor_windows.go rename to golibs/readline/editor_windows.go index c1972dde..a5530e14 100644 --- a/readline/editor_windows.go +++ b/golibs/readline/editor_windows.go @@ -5,6 +5,6 @@ package readline import "errors" // StartEditorWithBuffer - Not implemented on Windows platforms. -func (rl *Instance) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) { +func (rl *Readline) StartEditorWithBuffer(multiline []rune, filename string) ([]rune, error) { return rl.line, errors.New("Not currently supported on Windows") } diff --git a/readline/errors.go b/golibs/readline/errors.go similarity index 100% rename from readline/errors.go rename to golibs/readline/errors.go diff --git a/readline/events.go b/golibs/readline/events.go similarity index 83% rename from readline/events.go rename to golibs/readline/events.go index 5d630769..6b687283 100644 --- a/readline/events.go +++ b/golibs/readline/events.go @@ -13,11 +13,11 @@ type EventReturn struct { } // AddEvent registers a new keypress handler -func (rl *Instance) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) { +func (rl *Readline) AddEvent(keyPress string, callback func(string, []rune, int) *EventReturn) { rl.evtKeyPress[keyPress] = callback } // DelEvent deregisters an existing keypress handler -func (rl *Instance) DelEvent(keyPress string) { +func (rl *Readline) DelEvent(keyPress string) { delete(rl.evtKeyPress, keyPress) } diff --git a/readline/go.mod b/golibs/readline/go.mod similarity index 100% rename from readline/go.mod rename to golibs/readline/go.mod diff --git a/readline/go.sum b/golibs/readline/go.sum similarity index 100% rename from readline/go.sum rename to golibs/readline/go.sum diff --git a/readline/hint.go b/golibs/readline/hint.go similarity index 86% rename from readline/hint.go rename to golibs/readline/hint.go index d0c54fe0..2dc7a29d 100644 --- a/readline/hint.go +++ b/golibs/readline/hint.go @@ -5,13 +5,13 @@ import "regexp" // SetHintText - a nasty function to force writing a new hint text. It does not update helpers, it just renders // them, so the hint will survive until the helpers (thus including the hint) will be updated/recomputed. /* -func (rl *Instance) SetHintText(s string) { +func (rl *Readline) SetHintText(s string) { rl.hintText = []rune(s) rl.renderHelpers() } */ -func (rl *Instance) getHintText() { +func (rl *Readline) getHintText() { if !rl.modeAutoFind && !rl.modeTabFind { // Return if no hints provided by the user/engine @@ -27,7 +27,7 @@ func (rl *Instance) getHintText() { } // writeHintText - only writes the hint text and computes its offsets. -func (rl *Instance) writeHintText() { +func (rl *Readline) writeHintText() { if len(rl.hintText) == 0 { //rl.hintY = 0 return @@ -43,7 +43,7 @@ func (rl *Instance) writeHintText() { wrapped, hintLen := WrapText(string(rl.hintText), width) offset += hintLen -// rl.hintY = offset + // rl.hintY = offset hintText := string(wrapped) @@ -52,12 +52,12 @@ func (rl *Instance) writeHintText() { } } -func (rl *Instance) resetHintText() { +func (rl *Readline) resetHintText() { //rl.hintY = 0 rl.hintText = []rune{} } -func (rl *Instance) insertHintText() { +func (rl *Readline) insertHintText() { if len(rl.hintText) != 0 { // fill in hint text rl.insert(rl.hintText) diff --git a/readline/history.go b/golibs/readline/history.go similarity index 93% rename from readline/history.go rename to golibs/readline/history.go index 0c87a623..9e862614 100644 --- a/readline/history.go +++ b/golibs/readline/history.go @@ -29,24 +29,24 @@ type History interface { } // SetHistoryCtrlR - Set the history source triggered with Ctrl-r combination -func (rl *Instance) SetHistoryCtrlR(name string, history History) { +func (rl *Readline) SetHistoryCtrlR(name string, history History) { rl.mainHistName = name rl.mainHistory = history } // GetHistoryCtrlR - Returns the history source triggered by Ctrl-r -func (rl *Instance) GetHistoryCtrlR() History { +func (rl *Readline) GetHistoryCtrlR() History { return rl.mainHistory } // SetHistoryAltR - Set the history source triggered with Alt-r combination -func (rl *Instance) SetHistoryAltR(name string, history History) { +func (rl *Readline) SetHistoryAltR(name string, history History) { rl.altHistName = name rl.altHistory = history } // GetHistoryAltR - Returns the history source triggered by Alt-r -func (rl *Instance) GetHistoryAltR() History { +func (rl *Readline) GetHistoryAltR() History { return rl.altHistory } @@ -101,7 +101,7 @@ func (h *NullHistory) Dump() interface{} { } // Browse historic lines: -func (rl *Instance) walkHistory(i int) { +func (rl *Readline) walkHistory(i int) { var ( old, new string dedup bool @@ -123,7 +123,7 @@ func (rl *Instance) walkHistory(i int) { // When we are exiting the current line buffer to move around // the history, we make buffer the current line - if rl.histOffset == 0 && rl.histOffset + i == 1 { + if rl.histOffset == 0 && rl.histOffset+i == 1 { rl.lineBuf = string(rl.line) } @@ -168,7 +168,7 @@ func (rl *Instance) walkHistory(i int) { // completeHistory - Populates a CompletionGroup with history and returns it the shell // we populate only one group, so as to pass it to the main completion engine. -func (rl *Instance) completeHistory() (hist []*CompletionGroup) { +func (rl *Readline) completeHistory() (hist []*CompletionGroup) { hist = make([]*CompletionGroup, 1) hist[0] = &CompletionGroup{ diff --git a/readline/info.go b/golibs/readline/info.go similarity index 89% rename from readline/info.go rename to golibs/readline/info.go index 269157d8..ac77297f 100644 --- a/readline/info.go +++ b/golibs/readline/info.go @@ -4,12 +4,12 @@ import "regexp" // SetInfoText - a nasty function to force writing a new info text. It does not update helpers, it just renders // them, so the info will survive until the helpers (thus including the info) will be updated/recomputed. -func (rl *Instance) SetInfoText(s string) { +func (rl *Readline) SetInfoText(s string) { rl.infoText = []rune(s) rl.renderHelpers() } -func (rl *Instance) getInfoText() { +func (rl *Readline) getInfoText() { if !rl.modeAutoFind && !rl.modeTabFind { // Return if no infos provided by the user/engine @@ -25,7 +25,7 @@ func (rl *Instance) getInfoText() { } // writeInfoText - only writes the info text and computes its offsets. -func (rl *Instance) writeInfoText() { +func (rl *Readline) writeInfoText() { if len(rl.infoText) == 0 { rl.infoY = 0 return @@ -50,7 +50,7 @@ func (rl *Instance) writeInfoText() { } } -func (rl *Instance) resetInfoText() { +func (rl *Readline) resetInfoText() { rl.infoY = 0 rl.infoText = []rune{} } diff --git a/readline/instance.go b/golibs/readline/instance.go similarity index 90% rename from readline/instance.go rename to golibs/readline/instance.go index 3f52bed5..ef12a3ff 100644 --- a/readline/instance.go +++ b/golibs/readline/instance.go @@ -5,12 +5,16 @@ import ( "os" "regexp" "sync" + + "github.com/arnodel/golua/lib/packagelib" ) // Instance is used to encapsulate the parameter group and run time of any given // readline instance so that you can reuse the readline API for multiple entry // captures without having to repeatedly unload configuration. -type Instance struct { + +// #type +type Readline struct { // // Input Modes ------------------------------------------------------------------------------- @@ -31,13 +35,13 @@ type Instance struct { Multiline bool // If set to true, the shell will have a two-line prompt. MultilinePrompt string // If multiline is true, this is the content of the 2nd line. - mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt - rightPrompt string - rightPromptLen int - realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line. - defaultPrompt []rune - promptLen int - stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs + mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt + rightPrompt string + rightPromptLen int + realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line. + defaultPrompt []rune + promptLen int + stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs // // Input Line --------------------------------------------------------------------------------- @@ -114,9 +118,9 @@ type Instance struct { searchMode FindMode // Used for varying hints, and underlying functions called regexSearch *regexp.Regexp // Holds the current search regex match search string - mainHist bool // Which history stdin do we want - histInfo []rune // We store a piece of hist info, for dual history sources - Searcher func(string, []string) []string + mainHist bool // Which history stdin do we want + histInfo []rune // We store a piece of hist info, for dual history sources + Searcher func(string, []string) []string // // History ----------------------------------------------------------------------------------- @@ -200,17 +204,19 @@ type Instance struct { // concurency mutex sync.Mutex - ViModeCallback func(ViMode) + ViModeCallback func(ViMode) ViActionCallback func(ViAction, []string) RawInputCallback func([]rune) // called on all input bufferedOut *bufio.Writer + + Loader packagelib.Loader } // NewInstance is used to create a readline instance and initialise it with sane defaults. -func NewInstance() *Instance { - rl := new(Instance) +func NewInstance() *Readline { + rl := new(Readline) // Prompt rl.Multiline = false @@ -245,7 +251,9 @@ func NewInstance() *Instance { } for _, hay := range haystack { - if rl.regexSearch == nil { continue } + if rl.regexSearch == nil { + continue + } if rl.regexSearch.MatchString(hay) { suggs = append(suggs, hay) } @@ -256,6 +264,11 @@ func NewInstance() *Instance { rl.bufferedOut = bufio.NewWriter(os.Stdout) + rl.Loader = packagelib.Loader{ + Name: "readline", + Load: rl.luaLoader, + } + // Registers rl.initRegisters() diff --git a/readline/line.go b/golibs/readline/line.go similarity index 90% rename from readline/line.go rename to golibs/readline/line.go index 3069ad86..3466e92f 100644 --- a/readline/line.go +++ b/golibs/readline/line.go @@ -6,7 +6,7 @@ import ( // When the DelayedSyntaxWorker gives us a new line, we need to check if there // is any processing to be made, that all lines match in terms of content. -func (rl *Instance) updateLine(line []rune) { +func (rl *Readline) updateLine(line []rune) { if len(rl.currentComp) > 0 { } else { @@ -18,7 +18,7 @@ func (rl *Instance) updateLine(line []rune) { // getLine - In many places we need the current line input. We either return the real line, // or the one that includes the current completion candidate, if there is any. -func (rl *Instance) GetLine() []rune { +func (rl *Readline) GetLine() []rune { if len(rl.currentComp) > 0 { return rl.lineComp } @@ -30,7 +30,7 @@ func (rl *Instance) GetLine() []rune { // function is only ever called once, and after having moved back to prompt position // and having printed the line: this is so that at any moment, everyone has the good // values for moving around, synchronized with the update input line. -func (rl *Instance) echo() { +func (rl *Readline) echo() { // Then we print the prompt, and the line, hideCursor() @@ -79,7 +79,7 @@ func (rl *Instance) echo() { unhideCursor() } -func (rl *Instance) insert(r []rune) { +func (rl *Readline) insert(r []rune) { for { // I don't really understand why `0` is creaping in at the end of the // array but it only happens with unicode characters. @@ -112,11 +112,11 @@ func (rl *Instance) insert(r []rune) { rl.updateHelpers() } -func (rl *Instance) Insert(t string) { +func (rl *Readline) Insert(t string) { rl.insert([]rune(t)) } -func (rl *Instance) deleteX() { +func (rl *Readline) deleteX() { switch { case len(rl.line) == 0: return @@ -134,7 +134,7 @@ func (rl *Instance) deleteX() { rl.updateHelpers() } -func (rl *Instance) deleteBackspace(forward bool) { +func (rl *Readline) deleteBackspace(forward bool) { switch { case len(rl.line) == 0: return @@ -153,7 +153,7 @@ func (rl *Instance) deleteBackspace(forward bool) { rl.updateHelpers() } -func (rl *Instance) clearLine() { +func (rl *Readline) clearLine() { if len(rl.line) == 0 { return } @@ -179,21 +179,21 @@ func (rl *Instance) clearLine() { rl.clearVirtualComp() } -func (rl *Instance) deleteToBeginning() { +func (rl *Readline) deleteToBeginning() { rl.resetVirtualComp(false) // Keep the line length up until the cursor rl.line = rl.line[rl.pos:] rl.pos = 0 } -func (rl *Instance) deleteToEnd() { +func (rl *Readline) deleteToEnd() { rl.resetVirtualComp(false) // Keep everything before the cursor rl.line = rl.line[:rl.pos] } // @TODO(Renzix): move to emacs sepecific file -func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) { +func (rl *Readline) emacsForwardWord(tokeniser tokeniser) (adjust int) { split, index, pos := tokeniser(rl.line, rl.pos) if len(split) == 0 { return @@ -214,7 +214,7 @@ func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) { return } -func (rl *Instance) emacsBackwardWord(tokeniser tokeniser) (adjust int) { +func (rl *Readline) emacsBackwardWord(tokeniser tokeniser) (adjust int) { split, index, pos := tokeniser(rl.line, rl.pos) if len(split) == 0 { return diff --git a/readline/line_test.go b/golibs/readline/line_test.go similarity index 100% rename from readline/line_test.go rename to golibs/readline/line_test.go diff --git a/golibs/readline/lua.go b/golibs/readline/lua.go new file mode 100644 index 00000000..9efbc48f --- /dev/null +++ b/golibs/readline/lua.go @@ -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()) +} diff --git a/readline/prompt.go b/golibs/readline/prompt.go similarity index 91% rename from readline/prompt.go rename to golibs/readline/prompt.go index d141cd63..09af1623 100644 --- a/readline/prompt.go +++ b/golibs/readline/prompt.go @@ -8,20 +8,20 @@ import ( // SetPrompt will define the readline prompt string. // It also calculates the runes in the string as well as any non-printable escape codes. -func (rl *Instance) SetPrompt(s string) { +func (rl *Readline) SetPrompt(s string) { rl.mainPrompt = s rl.computePrompt() } // SetRightPrompt sets the right prompt. -func (rl *Instance) SetRightPrompt(s string) { +func (rl *Readline) SetRightPrompt(s string) { rl.rightPrompt = s + " " rl.computePrompt() } // RefreshPromptLog - A simple function to print a string message (a log, or more broadly, // an asynchronous event) without bothering the user, and by "pushing" the prompt below the message. -func (rl *Instance) RefreshPromptLog(log string) (err error) { +func (rl *Readline) RefreshPromptLog(log string) (err error) { // We adjust cursor movement, depending on which mode we're currently in. if !rl.modeTabCompletion { @@ -73,7 +73,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) { } // RefreshPromptInPlace - Refreshes the prompt in the very same place he is. -func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) { +func (rl *Readline) RefreshPromptInPlace(prompt string) (err error) { // We adjust cursor movement, depending on which mode we're currently in. // Prompt data intependent if !rl.modeTabCompletion { @@ -117,7 +117,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) { // @prompt => If not nil (""), will use this prompt instead of the currently set prompt. // @offset => Used to set the number of lines to go upward, before reprinting. Set to 0 if not used. // @clearLine => If true, will clean the current input line on the next refresh. -func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) { +func (rl *Readline) RefreshPromptCustom(prompt string, offset int, clearLine bool) (err error) { // We adjust cursor movement, depending on which mode we're currently in. if !rl.modeTabCompletion { @@ -166,7 +166,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo // computePrompt - At any moment, returns an (1st or 2nd line) actualized prompt, // considering all input mode parameters and prompt string values. -func (rl *Instance) computePrompt() (prompt []rune) { +func (rl *Readline) computePrompt() (prompt []rune) { if rl.Multiline { if rl.MultilinePrompt != "" { rl.realPrompt = []rune(rl.MultilinePrompt) @@ -190,11 +190,11 @@ func (rl *Instance) computePrompt() (prompt []rune) { // Strip color escapes rl.promptLen = getRealLength(string(rl.realPrompt)) rl.rightPromptLen = getRealLength(string(rl.rightPrompt)) - + return } -func (rl *Instance) colorizeVimPrompt(p []rune) (cp []rune) { +func (rl *Readline) colorizeVimPrompt(p []rune) (cp []rune) { if rl.VimModeColorize { return []rune(fmt.Sprintf("%s%s%s", BOLD, string(p), RESET)) } @@ -211,8 +211,8 @@ func getRealLength(s string) (l int) { return getWidth([]rune(stripped)) } -func (rl *Instance) echoRightPrompt() { - if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 { +func (rl *Readline) echoRightPrompt() { + if rl.fullX < GetTermWidth()-rl.rightPromptLen-1 { moveCursorForwards(GetTermWidth()) moveCursorBackwards(rl.rightPromptLen) print(rl.rightPrompt) diff --git a/readline/raw_bsd.go b/golibs/readline/raw_bsd.go similarity index 100% rename from readline/raw_bsd.go rename to golibs/readline/raw_bsd.go diff --git a/readline/raw_linux.go b/golibs/readline/raw_linux.go similarity index 100% rename from readline/raw_linux.go rename to golibs/readline/raw_linux.go diff --git a/readline/raw_plan9.go b/golibs/readline/raw_plan9.go similarity index 81% rename from readline/raw_plan9.go rename to golibs/readline/raw_plan9.go index 2736629f..583d61d1 100644 --- a/readline/raw_plan9.go +++ b/golibs/readline/raw_plan9.go @@ -2,16 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package terminal provides support functions for dealing with terminals, as -// commonly found on UNIX systems. -// -// Putting a terminal into raw mode is the most common requirement: -// -// oldState, err := terminal.MakeRaw(0) -// if err != nil { -// panic(err) -// } -// defer terminal.Restore(0, oldState) package readline import ( diff --git a/readline/raw_solaris.go b/golibs/readline/raw_solaris.go similarity index 100% rename from readline/raw_solaris.go rename to golibs/readline/raw_solaris.go diff --git a/readline/raw_unix.go b/golibs/readline/raw_unix.go similarity index 100% rename from readline/raw_unix.go rename to golibs/readline/raw_unix.go diff --git a/readline/raw_windows.go b/golibs/readline/raw_windows.go similarity index 85% rename from readline/raw_windows.go rename to golibs/readline/raw_windows.go index 50d29f37..6e8c61cb 100644 --- a/readline/raw_windows.go +++ b/golibs/readline/raw_windows.go @@ -2,18 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows -// Package terminal provides support functions for dealing with terminals, as -// commonly found on UNIX systems. -// -// Putting a terminal into raw mode is the most common requirement: -// -// oldState, err := terminal.MakeRaw(0) -// if err != nil { -// panic(err) -// } -// defer terminal.Restore(0, oldState) package readline import ( @@ -70,4 +61,3 @@ func GetSize(fd int) (width, height int, err error) { } return int(info.Size.X), int(info.Size.Y), nil } - diff --git a/readline/readline.go b/golibs/readline/readline.go similarity index 98% rename from readline/readline.go rename to golibs/readline/readline.go index 7282071b..889a2f5f 100644 --- a/readline/readline.go +++ b/golibs/readline/readline.go @@ -13,7 +13,7 @@ var rxMultiline = regexp.MustCompile(`[\r\n]+`) // Readline displays the readline prompt. // It will return a string (user entered data) or an error. -func (rl *Instance) Readline() (string, error) { +func (rl *Readline) Readline() (string, error) { fd := int(os.Stdin.Fd()) state, err := MakeRaw(fd) if err != nil { @@ -183,7 +183,7 @@ func (rl *Instance) Readline() (string, error) { if rl.modeTabFind { rl.backspaceTabFind() } else { - if (rl.pos < len(rl.line)) { + if rl.pos < len(rl.line) { rl.deleteBackspace(true) } } @@ -545,7 +545,7 @@ func (rl *Instance) Readline() (string, error) { // editorInput is an unexported function used to determine what mode of text // entry readline is currently configured for and then update the line entries // accordingly. -func (rl *Instance) editorInput(r []rune) { +func (rl *Readline) editorInput(r []rune) { if len(r) == 0 { return } @@ -595,7 +595,7 @@ func (rl *Instance) editorInput(r []rune) { // viEscape - In case th user is using Vim input, and the escape sequence has not // been handled by other cases, we dispatch it to Vim and handle a few cases here. -func (rl *Instance) viEscape(r []rune) { +func (rl *Readline) viEscape(r []rune) { // Sometimes the escape sequence is interleaved with another one, // but key strokes might be in the wrong order, so we double check @@ -611,7 +611,7 @@ func (rl *Instance) viEscape(r []rune) { } } -func (rl *Instance) escapeSeq(r []rune) { +func (rl *Readline) escapeSeq(r []rune) { switch string(r) { // Vim escape sequences & dispatching -------------------------------------------------------- case string(charEscape): @@ -755,11 +755,11 @@ func (rl *Instance) escapeSeq(r []rune) { rl.updateHelpers() return - case seqDelete,seqDelete2: + case seqDelete, seqDelete2: if rl.modeTabFind { rl.backspaceTabFind() } else { - if (rl.pos < len(rl.line)) { + if rl.pos < len(rl.line) { rl.deleteBackspace(true) } } @@ -890,7 +890,7 @@ func (rl *Instance) escapeSeq(r []rune) { } } -func (rl *Instance) carridgeReturn() { +func (rl *Readline) carridgeReturn() { rl.moveCursorByAdjust(len(rl.line)) rl.updateHelpers() rl.clearHelpers() @@ -924,7 +924,7 @@ func isMultiline(r []rune) bool { return false } -func (rl *Instance) allowMultiline(data []byte) bool { +func (rl *Readline) allowMultiline(data []byte) bool { rl.clearHelpers() printf("\r\nWARNING: %d bytes of multiline data was dumped into the shell!", len(data)) for { diff --git a/readline/register.go b/golibs/readline/register.go similarity index 95% rename from readline/register.go rename to golibs/readline/register.go index 2edf1fab..a20fbcad 100644 --- a/readline/register.go +++ b/golibs/readline/register.go @@ -23,7 +23,7 @@ type registers struct { mutex *sync.Mutex } -func (rl *Instance) initRegisters() { +func (rl *Readline) initRegisters() { rl.registers = ®isters{ num: make(map[int][]rune, 10), alpha: make(map[string][]rune, 52), @@ -36,7 +36,7 @@ func (rl *Instance) initRegisters() { // the number of Vim iterations and we save the resulting string to the appropriate buffer. // It's the same as saveToRegisterTokenize, but without the need to generate tokenized & // cursor-pos-actualized versions of the input line. -func (rl *Instance) saveToRegister(adjust int) { +func (rl *Readline) saveToRegister(adjust int) { // Get the current cursor position and go the length specified. var begin = rl.pos @@ -66,7 +66,7 @@ func (rl *Instance) saveToRegister(adjust int) { // saveToRegisterTokenize - Passing a function that will move around the line in the desired way, we get // the number of Vim iterations and we save the resulting string to the appropriate buffer. Because we // need the cursor position to be really moved around between calls to the jumper, we also need the tokeniser. -func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) { +func (rl *Readline) saveToRegisterTokenize(tokeniser tokeniser, jumper func(tokeniser) int, vii int) { // The register is going to have to heavily manipulate the cursor position. // Remember the original one first, for the end. @@ -104,11 +104,11 @@ func (rl *Instance) saveToRegisterTokenize(tokeniser tokeniser, jumper func(toke // saveBufToRegister - Instead of computing the buffer ourselves based on an adjust, // let the caller pass directly this buffer, yet relying on the register system to // determine which register will store the buffer. -func (rl *Instance) saveBufToRegister(buffer []rune) { +func (rl *Readline) saveBufToRegister(buffer []rune) { rl.SetRegisterBuf(string(rl.registers.currentRegister), buffer) } -func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) { +func (rl *Readline) SetRegisterBuf(reg string, buffer []rune) { // We must make an immutable version of the buffer first. buf := string(buffer) @@ -141,7 +141,7 @@ func (rl *Instance) SetRegisterBuf(reg string, buffer []rune) { // The user asked to paste a buffer onto the line, so we check from which register // we are supposed to select the buffer, and return it to the caller for insertion. -func (rl *Instance) pasteFromRegister() (buffer []rune) { +func (rl *Readline) pasteFromRegister() (buffer []rune) { // When exiting this function the currently selected register is dropped, defer rl.registers.resetRegister() @@ -155,7 +155,7 @@ func (rl *Instance) pasteFromRegister() (buffer []rune) { return rl.GetFromRegister(activeRegister) } -func (rl *Instance) GetFromRegister(reg string) []rune { +func (rl *Readline) GetFromRegister(reg string) []rune { // Find the active register, and return its content. num, err := strconv.Atoi(reg) @@ -264,7 +264,7 @@ func (r *registers) resetRegister() { } // The user can show registers completions and insert, no matter the cursor position. -func (rl *Instance) completeRegisters() (groups []*CompletionGroup) { +func (rl *Readline) completeRegisters() (groups []*CompletionGroup) { // We set the info exceptionally info := BLUE + "-- registers --" + RESET diff --git a/readline/syntax.go b/golibs/readline/syntax.go similarity index 90% rename from readline/syntax.go rename to golibs/readline/syntax.go index a64becef..388d9da9 100644 --- a/readline/syntax.go +++ b/golibs/readline/syntax.go @@ -2,7 +2,7 @@ package readline // syntaxCompletion - applies syntax highlighting to the current input line. // nothing special to note here, nor any changes envisioned. -func (rl *Instance) syntaxCompletion() { +func (rl *Readline) syntaxCompletion() { if rl.SyntaxCompleter == nil { return } diff --git a/readline/tab-virtual.go b/golibs/readline/tab-virtual.go similarity index 95% rename from readline/tab-virtual.go rename to golibs/readline/tab-virtual.go index d1e1d764..e8d95138 100644 --- a/readline/tab-virtual.go +++ b/golibs/readline/tab-virtual.go @@ -2,13 +2,14 @@ package readline import ( "strings" + "github.com/rivo/uniseg" ) // insertCandidateVirtual - When a completion candidate is selected, we insert it virtually in the input line: // this will not trigger further firltering against the other candidates. Each time this function // is called, any previous candidate is dropped, after being used for moving the cursor around. -func (rl *Instance) insertCandidateVirtual(candidate []rune) { +func (rl *Readline) insertCandidateVirtual(candidate []rune) { for { // I don't really understand why `0` is creaping in at the end of the // array but it only happens with unicode characters. @@ -57,7 +58,7 @@ func (rl *Instance) insertCandidateVirtual(candidate []rune) { // Insert the current completion candidate into the input line. // This candidate might either be the currently selected one (white frame), // or the only candidate available, if the total number of candidates is 1. -func (rl *Instance) insertCandidate() { +func (rl *Readline) insertCandidate() { cur := rl.getCurrentGroup() @@ -83,7 +84,7 @@ func (rl *Instance) insertCandidate() { } // updateVirtualComp - Either insert the current completion candidate virtually, or on the real line. -func (rl *Instance) updateVirtualComp() { +func (rl *Readline) updateVirtualComp() { cur := rl.getCurrentGroup() if cur != nil { @@ -118,7 +119,7 @@ func (rl *Instance) updateVirtualComp() { // resetVirtualComp - This function is called before most of our readline key handlers, // and makes sure that the current completion (virtually inserted) is either inserted or dropped, // and that all related parameters are reinitialized. -func (rl *Instance) resetVirtualComp(drop bool) { +func (rl *Readline) resetVirtualComp(drop bool) { // If we don't have a current virtual completion, there's nothing to do. // IMPORTANT: this MUST be first, to avoid nil problems with empty comps. @@ -196,7 +197,7 @@ func trimTrailing(comp string) (trimmed string, hadSlash bool) { } // viDeleteByAdjustVirtual - Same as viDeleteByAdjust, but for our virtually completed input line. -func (rl *Instance) viDeleteByAdjustVirtual(adjust int) { +func (rl *Readline) viDeleteByAdjustVirtual(adjust int) { var ( newLine []rune backOne bool @@ -235,7 +236,7 @@ func (rl *Instance) viDeleteByAdjustVirtual(adjust int) { } // viJumpEVirtual - Same as viJumpE, but for our virtually completed input line. -func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) { +func (rl *Readline) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, int)) (adjust int) { split, index, pos := tokeniser(rl.lineComp, rl.pos) if len(split) == 0 { return @@ -258,7 +259,7 @@ func (rl *Instance) viJumpEVirtual(tokeniser func([]rune, int) ([]string, int, i return } -func (rl *Instance) deleteVirtual() { +func (rl *Readline) deleteVirtual() { switch { case len(rl.lineComp) == 0: return @@ -274,7 +275,7 @@ func (rl *Instance) deleteVirtual() { // We are done with the current virtual completion candidate. // Get ready for the next one -func (rl *Instance) clearVirtualComp() { +func (rl *Readline) clearVirtualComp() { rl.line = rl.lineComp rl.currentComp = []rune{} rl.compAddSpace = false diff --git a/readline/tab.go b/golibs/readline/tab.go similarity index 93% rename from readline/tab.go rename to golibs/readline/tab.go index f2cc1408..04b9e2bd 100644 --- a/readline/tab.go +++ b/golibs/readline/tab.go @@ -28,7 +28,7 @@ const ( // getTabCompletion - This root function sets up all completion items and engines, // dealing with all search and completion modes. But it does not perform printing. -func (rl *Instance) getTabCompletion() { +func (rl *Readline) getTabCompletion() { // Populate registers if requested. if rl.modeAutoFind && rl.searchMode == RegisterFind { @@ -53,7 +53,7 @@ func (rl *Instance) getTabCompletion() { } // getRegisterCompletion - Populates and sets up completion for Vim registers. -func (rl *Instance) getRegisterCompletion() { +func (rl *Readline) getRegisterCompletion() { rl.tcGroups = rl.completeRegisters() if len(rl.tcGroups) == 0 { @@ -84,7 +84,7 @@ func (rl *Instance) getRegisterCompletion() { } // getTabSearchCompletion - Populates and sets up completion for completion search. -func (rl *Instance) getTabSearchCompletion() { +func (rl *Readline) getTabSearchCompletion() { // Get completions from the engine, and make sure there is a current group. rl.getCompletions() @@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() { rl.getCurrentGroup() // Set the info for this completion mode - rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...) + rl.infoText = append([]rune("Completion search: "+UNDERLINE+BOLD), rl.tfLine...) for _, g := range rl.tcGroups { g.updateTabFind(rl) @@ -107,7 +107,7 @@ func (rl *Instance) getTabSearchCompletion() { } // getHistorySearchCompletion - Populates and sets up completion for command history search -func (rl *Instance) getHistorySearchCompletion() { +func (rl *Readline) getHistorySearchCompletion() { // Refresh full list each time rl.tcGroups = rl.completeHistory() @@ -142,7 +142,7 @@ func (rl *Instance) getHistorySearchCompletion() { // getNormalCompletion - Populates and sets up completion for normal comp mode. // Will automatically cancel the completion mode if there are no candidates. -func (rl *Instance) getNormalCompletion() { +func (rl *Readline) getNormalCompletion() { // Get completions groups, pass delayedTabContext and check nils rl.getCompletions() @@ -172,7 +172,7 @@ func (rl *Instance) getNormalCompletion() { // getCompletions - Calls the completion engine/function to yield a list of 0 or more completion groups, // sets up a delayed tab context and passes it on to the tab completion engine function, and ensure no // nil groups/items will pass through. This function is called by different comp search/nav modes. -func (rl *Instance) getCompletions() { +func (rl *Readline) getCompletions() { // If there is no wired tab completion engine, nothing we can do. if rl.TabCompleter == nil { @@ -214,7 +214,7 @@ func (rl *Instance) getCompletions() { // moveTabCompletionHighlight - This function is in charge of // computing the new position in the current completions liste. -func (rl *Instance) moveTabCompletionHighlight(x, y int) { +func (rl *Readline) moveTabCompletionHighlight(x, y int) { rl.completionOpen = true g := rl.getCurrentGroup() @@ -253,7 +253,7 @@ func (rl *Instance) moveTabCompletionHighlight(x, y int) { } // writeTabCompletion - Prints all completion groups and their items -func (rl *Instance) writeTabCompletion() { +func (rl *Readline) writeTabCompletion() { // The final completions string to print. var completions string @@ -289,7 +289,7 @@ func (rl *Instance) writeTabCompletion() { // cropCompletions - When the user cycles through a completion list longer // than the console MaxTabCompleterRows value, we crop the completions string // so that "global" cycling (across all groups) is printed correctly. -func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) { +func (rl *Readline) cropCompletions(comps string) (cropped string, usedY int) { // If we actually fit into the MaxTabCompleterRows, return the comps if rl.tcUsedY < rl.MaxTabCompleterRows { @@ -366,7 +366,7 @@ func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) { return } -func (rl *Instance) getAbsPos() int { +func (rl *Readline) getAbsPos() int { var prev int var foundCurrent bool for _, grp := range rl.tcGroups { @@ -390,7 +390,7 @@ func (rl *Instance) getAbsPos() int { // We pass a special subset of the current input line, so that // completions are available no matter where the cursor is. -func (rl *Instance) getCompletionLine() (line []rune, pos int) { +func (rl *Readline) getCompletionLine() (line []rune, pos int) { pos = rl.pos - len(rl.currentComp) if pos < 0 { @@ -409,7 +409,7 @@ func (rl *Instance) getCompletionLine() (line []rune, pos int) { return } -func (rl *Instance) getCurrentGroup() (group *CompletionGroup) { +func (rl *Readline) getCurrentGroup() (group *CompletionGroup) { for _, g := range rl.tcGroups { if g.isCurrent && len(g.Suggestions) > 0 { return g @@ -431,7 +431,7 @@ func (rl *Instance) getCurrentGroup() (group *CompletionGroup) { // cycleNextGroup - Finds either the first non-empty group, // or the next non-empty group after the current one. -func (rl *Instance) cycleNextGroup() { +func (rl *Readline) cycleNextGroup() { for i, g := range rl.tcGroups { if g.isCurrent { g.isCurrent = false @@ -452,7 +452,7 @@ func (rl *Instance) cycleNextGroup() { } // cyclePreviousGroup - Same as cycleNextGroup but reverse -func (rl *Instance) cyclePreviousGroup() { +func (rl *Readline) cyclePreviousGroup() { for i, g := range rl.tcGroups { if g.isCurrent { g.isCurrent = false @@ -471,7 +471,7 @@ func (rl *Instance) cyclePreviousGroup() { } // Check if we have a single completion candidate -func (rl *Instance) hasOneCandidate() bool { +func (rl *Readline) hasOneCandidate() bool { if len(rl.tcGroups) == 0 { return false } @@ -509,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool { // - The user-specified max completion length // - The terminal lengh // we use this function to prompt for confirmation before printing comps. -func (rl *Instance) promptCompletionConfirm(sentence string) { +func (rl *Readline) promptCompletionConfirm(sentence string) { rl.infoText = []rune(sentence) rl.compConfirmWait = true @@ -518,7 +518,7 @@ func (rl *Instance) promptCompletionConfirm(sentence string) { rl.renderHelpers() } -func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) { +func (rl *Readline) getCompletionCount() (comps int, lines int, adjusted int) { for _, group := range rl.tcGroups { comps += len(group.Suggestions) // if group.Name != "" { @@ -535,7 +535,7 @@ func (rl *Instance) getCompletionCount() (comps int, lines int, adjusted int) { return } -func (rl *Instance) resetTabCompletion() { +func (rl *Readline) resetTabCompletion() { rl.modeTabCompletion = false rl.tabCompletionSelect = false rl.compConfirmWait = false diff --git a/readline/tabfind.go b/golibs/readline/tabfind.go similarity index 90% rename from readline/tabfind.go rename to golibs/readline/tabfind.go index 3e463126..9d6aba91 100644 --- a/readline/tabfind.go +++ b/golibs/readline/tabfind.go @@ -12,7 +12,7 @@ const ( RegisterFind ) -func (rl *Instance) backspaceTabFind() { +func (rl *Readline) backspaceTabFind() { if len(rl.tfLine) > 0 { rl.tfLine = rl.tfLine[:len(rl.tfLine)-1] } @@ -21,7 +21,7 @@ func (rl *Instance) backspaceTabFind() { // Filter and refresh (print) a list of completions. The caller should have reset // the virtual completion system before, so that should not clash with this. -func (rl *Instance) updateTabFind(r []rune) { +func (rl *Readline) updateTabFind(r []rune) { rl.tfLine = append(rl.tfLine, r...) @@ -34,7 +34,7 @@ func (rl *Instance) updateTabFind(r []rune) { rl.renderHelpers() } -func (rl *Instance) resetTabFind() { +func (rl *Readline) resetTabFind() { rl.modeTabFind = false // rl.modeAutoFind = false // Added, because otherwise it gets stuck on search completions diff --git a/readline/term.go b/golibs/readline/term.go similarity index 100% rename from readline/term.go rename to golibs/readline/term.go diff --git a/readline/timer.go b/golibs/readline/timer.go similarity index 98% rename from readline/timer.go rename to golibs/readline/timer.go index 76eab23a..0e5f9f0f 100644 --- a/readline/timer.go +++ b/golibs/readline/timer.go @@ -7,12 +7,12 @@ import ( // DelayedTabContext is a custom context interface for async updates to the tab completions type DelayedTabContext struct { - rl *Instance + rl *Readline Context context.Context cancel context.CancelFunc } -func delayedSyntaxTimer(rl *Instance, i int64) { +func delayedSyntaxTimer(rl *Readline, i int64) { if rl.PasswordMask != 0 || rl.DelayedSyntaxWorker == nil { return } diff --git a/readline/tokenise.go b/golibs/readline/tokenise.go similarity index 100% rename from readline/tokenise.go rename to golibs/readline/tokenise.go diff --git a/readline/tui-effects.go b/golibs/readline/tui-effects.go similarity index 100% rename from readline/tui-effects.go rename to golibs/readline/tui-effects.go diff --git a/readline/undo.go b/golibs/readline/undo.go similarity index 89% rename from readline/undo.go rename to golibs/readline/undo.go index 9b5e55a1..6d8d5bd2 100644 --- a/readline/undo.go +++ b/golibs/readline/undo.go @@ -5,7 +5,7 @@ type undoItem struct { pos int } -func (rl *Instance) undoAppendHistory() { +func (rl *Readline) undoAppendHistory() { defer func() { rl.viUndoSkipAppend = false }() if rl.viUndoSkipAppend { @@ -18,7 +18,7 @@ func (rl *Instance) undoAppendHistory() { }) } -func (rl *Instance) undoLast() { +func (rl *Readline) undoLast() { var undo undoItem for { if len(rl.viUndoHistory) == 0 { diff --git a/readline/update.go b/golibs/readline/update.go similarity index 93% rename from readline/update.go rename to golibs/readline/update.go index 0538aadc..59f41a6f 100644 --- a/readline/update.go +++ b/golibs/readline/update.go @@ -10,7 +10,7 @@ import ( // updateHelpers is a key part of the whole refresh process: // it should coordinate reprinting the input line, any Infos and completions // and manage to get back to the current (computed) cursor coordinates -func (rl *Instance) updateHelpers() { +func (rl *Readline) updateHelpers() { print(seqHideCursor) // Load all Infos & completions before anything. // Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText() @@ -19,7 +19,9 @@ func (rl *Instance) updateHelpers() { if rl.modeTabCompletion && !rl.completionOpen { rl.getTabCompletion() } else { - if rl.completionOpen { rl.completionOpen = false } + if rl.completionOpen { + rl.completionOpen = false + } } // We clear everything @@ -49,7 +51,7 @@ func getWidth(x []rune) int { } // Update reference should be called only once in a "loop" (not Readline(), but key control loop) -func (rl *Instance) updateReferences() { +func (rl *Readline) updateReferences() { // We always need to work with clean data, // since we will have incrementers all around @@ -103,7 +105,7 @@ func (rl *Instance) updateReferences() { } } -func (rl *Instance) resetHelpers() { +func (rl *Readline) resetHelpers() { rl.modeAutoFind = false // Now reset all below-input helpers @@ -113,7 +115,7 @@ func (rl *Instance) resetHelpers() { // clearHelpers - Clears everything: prompt, input, Infos & comps, // and comes back at the prompt. -func (rl *Instance) clearHelpers() { +func (rl *Readline) clearHelpers() { // Now go down to the last line of input moveCursorDown(rl.fullY - rl.posY) @@ -132,7 +134,7 @@ func (rl *Instance) clearHelpers() { // renderHelpers - pritns all components (prompt, line, Infos & comps) // and replaces the cursor to its current position. This function never // computes or refreshes any value, except from inside the echo function. -func (rl *Instance) renderHelpers() { +func (rl *Readline) renderHelpers() { // when the instance is in this state we want it to be "below" the user's // input for it to be aligned properly @@ -197,14 +199,14 @@ func (rl *Instance) renderHelpers() { moveCursorForwards(rl.posX) } -func (rl *Instance) bufprintF(format string, a ...any) { +func (rl *Readline) bufprintF(format string, a ...any) { fmt.Fprintf(rl.bufferedOut, format, a...) } -func (rl *Instance) bufprint(text string) { +func (rl *Readline) bufprint(text string) { fmt.Fprint(rl.bufferedOut, text) } -func (rl *Instance) bufflush() { +func (rl *Readline) bufflush() { rl.bufferedOut.Flush() } diff --git a/readline/vim.go b/golibs/readline/vim.go similarity index 95% rename from readline/vim.go rename to golibs/readline/vim.go index d496705c..5e2477a5 100644 --- a/readline/vim.go +++ b/golibs/readline/vim.go @@ -34,6 +34,7 @@ var ( ) type ViAction int + const ( VimActionYank = iota VimActionPaste @@ -52,7 +53,7 @@ var ( // vi - Apply a key to a Vi action. Note that as in the rest of the code, all cursor movements // have been moved away, and only the rl.pos is adjusted: when echoing the input line, the shell // will compute the new cursor pos accordingly. -func (rl *Instance) vi(r rune) { +func (rl *Readline) vi(r rune) { activeRegister := string(rl.registers.currentRegister) // Check if we are in register mode. If yes, and for some characters, @@ -384,7 +385,7 @@ func (rl *Instance) vi(r rune) { } } -func (rl *Instance) getViIterations() int { +func (rl *Readline) getViIterations() int { i, _ := strconv.Atoi(rl.viIteration) if i < 1 { i = 1 @@ -393,7 +394,7 @@ func (rl *Instance) getViIterations() int { return i } -func (rl *Instance) refreshVimStatus() { +func (rl *Readline) refreshVimStatus() { rl.ViModeCallback(rl.modeViMode) rl.computePrompt() rl.updateHelpers() @@ -401,7 +402,7 @@ func (rl *Instance) refreshVimStatus() { // viInfoMessage - lmorg's way of showing Vim status is to overwrite the info. // Currently not used, as there is a possibility to show the current Vim mode in the prompt. -func (rl *Instance) viInfoMessage() { +func (rl *Readline) viInfoMessage() { switch rl.modeViMode { case VimKeys: rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)") @@ -421,7 +422,7 @@ func (rl *Instance) viInfoMessage() { rl.renderHelpers() } -func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) { +func (rl *Readline) viJumpB(tokeniser tokeniser) (adjust int) { split, index, pos := tokeniser(rl.line, rl.pos) switch { case len(split) == 0: @@ -436,7 +437,7 @@ func (rl *Instance) viJumpB(tokeniser tokeniser) (adjust int) { return adjust * -1 } -func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) { +func (rl *Readline) viJumpE(tokeniser tokeniser) (adjust int) { split, index, pos := tokeniser(rl.line, rl.pos) if len(split) == 0 { return @@ -459,7 +460,7 @@ func (rl *Instance) viJumpE(tokeniser tokeniser) (adjust int) { return } -func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) { +func (rl *Readline) viJumpW(tokeniser tokeniser) (adjust int) { split, index, pos := tokeniser(rl.line, rl.pos) switch { case len(split) == 0: @@ -472,7 +473,7 @@ func (rl *Instance) viJumpW(tokeniser tokeniser) (adjust int) { return } -func (rl *Instance) viJumpPreviousBrace() (adjust int) { +func (rl *Readline) viJumpPreviousBrace() (adjust int) { if rl.pos == 0 { return 0 } @@ -486,7 +487,7 @@ func (rl *Instance) viJumpPreviousBrace() (adjust int) { return 0 } -func (rl *Instance) viJumpNextBrace() (adjust int) { +func (rl *Readline) viJumpNextBrace() (adjust int) { if rl.pos >= len(rl.line)-1 { return 0 } @@ -500,7 +501,7 @@ func (rl *Instance) viJumpNextBrace() (adjust int) { return 0 } -func (rl *Instance) viJumpBracket() (adjust int) { +func (rl *Readline) viJumpBracket() (adjust int) { split, index, pos := tokeniseBrackets(rl.line, rl.pos) switch { case len(split) == 0: diff --git a/readline/vimdelete.go b/golibs/readline/vimdelete.go similarity index 95% rename from readline/vimdelete.go rename to golibs/readline/vimdelete.go index f5c1806c..54573d28 100644 --- a/readline/vimdelete.go +++ b/golibs/readline/vimdelete.go @@ -5,7 +5,7 @@ import ( ) // vimDelete - -func (rl *Instance) viDelete(r rune) { +func (rl *Readline) viDelete(r rune) { // We are allowed to type iterations after a delete ('d') command. // in which case we don't exit the delete mode. The next thing typed @@ -91,7 +91,7 @@ func (rl *Instance) viDelete(r rune) { } } -func (rl *Instance) viDeleteByAdjust(adjust int) { +func (rl *Readline) viDeleteByAdjust(adjust int) { var ( newLine []rune backOne bool @@ -142,11 +142,11 @@ func (rl *Instance) viDeleteByAdjust(adjust int) { rl.updateHelpers() } -func (rl *Instance) DeleteByAmount(adjust int) { +func (rl *Readline) DeleteByAmount(adjust int) { rl.viDeleteByAdjust(adjust) } -func (rl *Instance) vimDeleteToken(r rune) bool { +func (rl *Readline) vimDeleteToken(r rune) bool { tokens, _, _ := tokeniseSplitSpaces(rl.line, 0) pos := int(r) - 48 // convert ASCII to integer if pos > len(tokens) { diff --git a/readline/wrap.go b/golibs/readline/wrap.go similarity index 100% rename from readline/wrap.go rename to golibs/readline/wrap.go diff --git a/lua.go b/lua.go index 3a28f096..3a51eaaf 100644 --- a/lua.go +++ b/lua.go @@ -80,6 +80,7 @@ func loadLibs(r *rt.Runtime) { cmds = commander.New(r) lib.LoadLibs(r, cmds.Loader) + lib.LoadLibs(l, lr.rl.Loader) } func yarnloadLibs(r *rt.Runtime) { @@ -89,6 +90,13 @@ func yarnloadLibs(r *rt.Runtime) { 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) { diff --git a/nature/editor.lua b/nature/editor.lua new file mode 100644 index 00000000..002d3409 --- /dev/null +++ b/nature/editor.lua @@ -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) diff --git a/nature/init.lua b/nature/init.lua index 9b75a271..4f973f33 100644 --- a/nature/init.lua +++ b/nature/init.lua @@ -28,6 +28,7 @@ require 'nature.vim' require 'nature.runner' require 'nature.hummingbird' require 'nature.abbr' +require 'nature.editor' local shlvl = tonumber(os.getenv 'SHLVL') if shlvl ~= nil then diff --git a/readline/godoc.go b/readline/godoc.go deleted file mode 100644 index ecea2bd5..00000000 --- a/readline/godoc.go +++ /dev/null @@ -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 diff --git a/readline/raw/LICENSE b/readline/raw/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/readline/raw/LICENSE +++ /dev/null @@ -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. diff --git a/readline/raw/README.md b/readline/raw/README.md deleted file mode 100644 index 2ef9aadb..00000000 --- a/readline/raw/README.md +++ /dev/null @@ -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` \ No newline at end of file diff --git a/readline/raw/main.go b/readline/raw/main.go deleted file mode 100644 index eff84406..00000000 --- a/readline/raw/main.go +++ /dev/null @@ -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]) - } -} diff --git a/rl.go b/rl.go index b431ad7d..e316967e 100644 --- a/rl.go +++ b/rl.go @@ -13,9 +13,10 @@ import ( ) type lineReader struct { - rl *readline.Instance + rl *readline.Readline fileHist *fileHistory } + var hinter *rt.Closure var highlighter *rt.Closure @@ -54,58 +55,64 @@ func newLineReader(prompt string, noHist bool) *lineReader { rl.ViModeCallback = func(mode readline.ViMode) { modeStr := "" switch mode { - case readline.VimKeys: modeStr = "normal" - case readline.VimInsert: modeStr = "insert" - case readline.VimDelete: modeStr = "delete" - case readline.VimReplaceOnce, readline.VimReplaceMany: modeStr = "replace" + case readline.VimKeys: + modeStr = "normal" + case readline.VimInsert: + modeStr = "insert" + case readline.VimDelete: + modeStr = "delete" + case readline.VimReplaceOnce, readline.VimReplaceMany: + modeStr = "replace" } setVimMode(modeStr) } rl.ViActionCallback = func(action readline.ViAction, args []string) { actionStr := "" switch action { - case readline.VimActionPaste: actionStr = "paste" - case readline.VimActionYank: actionStr = "yank" + case readline.VimActionPaste: + actionStr = "paste" + case readline.VimActionYank: + actionStr = "yank" } hooks.Emit("hilbish.vimAction", actionStr, args) } rl.HintText = func(line []rune, pos int) []rune { hinter := hshMod.Get(rt.StringValue("hinter")) retVal, err := rt.Call1(l.MainThread(), hinter, - rt.StringValue(string(line)), rt.IntValue(int64(pos))) + rt.StringValue(string(line)), rt.IntValue(int64(pos))) if err != nil { fmt.Println(err) return []rune{} } - + hintText := "" if luaStr, ok := retVal.TryString(); ok { hintText = luaStr } - + return []rune(hintText) } rl.SyntaxHighlighter = func(line []rune) string { highlighter := hshMod.Get(rt.StringValue("highlighter")) retVal, err := rt.Call1(l.MainThread(), highlighter, - rt.StringValue(string(line))) + rt.StringValue(string(line))) if err != nil { fmt.Println(err) return string(line) } - + highlighted := "" if luaStr, ok := retVal.TryString(); ok { highlighted = luaStr } - + return highlighted } rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) { term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false) compHandle := hshMod.Get(rt.StringValue("completion")).AsTable().Get(rt.StringValue("handler")) err := rt.Call(l.MainThread(), compHandle, []rt.Value{rt.StringValue(string(line)), - rt.IntValue(int64(pos))}, term) + rt.IntValue(int64(pos))}, term) var compGroups []*readline.CompletionGroup if err != nil { @@ -175,11 +182,11 @@ func newLineReader(prompt string, noHist bool) *lineReader { itemAliases[itemName] = itemAlias } else if keytyp == rt.IntType { vlStr, ok := lval.TryString() - if !ok { - // TODO: error - return - } - items = append(items, vlStr) + if !ok { + // TODO: error + return + } + items = append(items, vlStr) } else { // TODO: error return @@ -188,20 +195,22 @@ func newLineReader(prompt string, noHist bool) *lineReader { var dispType readline.TabDisplayType switch luaCompType.AsString() { - case "grid": dispType = readline.TabDisplayGrid - case "list": dispType = readline.TabDisplayList + case "grid": + dispType = readline.TabDisplayGrid + case "list": + dispType = readline.TabDisplayList // need special cases, will implement later //case "map": dispType = readline.TabDisplayMap } compGroups = append(compGroups, &readline.CompletionGroup{ - DisplayType: dispType, - Aliases: itemAliases, + DisplayType: dispType, + Aliases: itemAliases, Descriptions: itemDescriptions, ItemDisplays: itemDisplays, - Suggestions: items, - TrimSlash: false, - NoSpace: true, + Suggestions: items, + TrimSlash: false, + NoSpace: true, }) }) @@ -227,8 +236,8 @@ func (lr *lineReader) SetPrompt(p string) { halfPrompt := strings.Split(p, "\n") if len(halfPrompt) > 1 { lr.rl.Multiline = true - lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt) - 1], "\n")) - lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt) - 1:][0] + lr.rl.SetPrompt(strings.Join(halfPrompt[:len(halfPrompt)-1], "\n")) + lr.rl.MultilinePrompt = halfPrompt[len(halfPrompt)-1:][0] } else { lr.rl.Multiline = false lr.rl.MultilinePrompt = "" @@ -260,16 +269,16 @@ func (lr *lineReader) Resize() { // #interface history // command history -// The history interface deals with command history. +// The history interface deals with command history. // This includes the ability to override functions to change the main // method of saving history. func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table { lrLua := map[string]util.LuaExport{ - "add": {lr.luaAddHistory, 1, false}, - "all": {lr.luaAllHistory, 0, false}, + "add": {lr.luaAddHistory, 1, false}, + "all": {lr.luaAllHistory, 0, false}, "clear": {lr.luaClearHistory, 0, false}, - "get": {lr.luaGetHistory, 1, false}, - "size": {lr.luaSize, 0, false}, + "get": {lr.luaGetHistory, 1, false}, + "size": {lr.luaSize, 0, false}, } mod := rt.NewTable()