An open API service indexing awesome lists of open source software.

https://github.com/hymkor/go-multiline-ny

A REPL-oriented multi-line Readline, without full-screen UI.
https://github.com/hymkor/go-multiline-ny

go golang readline

Last synced: 2 months ago
JSON representation

A REPL-oriented multi-line Readline, without full-screen UI.

Awesome Lists containing this project

README

          

go-multiline-ny
===============

[![Go Reference](https://pkg.go.dev/badge/github.com/hymkor/go-multiline-ny.svg)](https://pkg.go.dev/github.com/hymkor/go-multiline-ny)

**go-multiline-ny** is a **REPL-oriented multi-line editor** built on top of [go-readline-ny], a pure Go Readline-compatible implementation.
It allows vertical cursor movement across lines, updates only the affected input lines, and does **not** take over the full screen — making it ideal for SQL-like multi-line inputs.
It is implemented in pure Go, and works on Windows and most UNIX-like systems
(confirmed on Linux and FreeBSD; macOS is expected to work as well).

| Key | Feature
|-----|---------
| `Ctrl`+`M` or `Enter` | Insert a new line[^Y]
| `Ctrl`+`J`, `Ctrl`+`Enter` or `Meta`+`Enter` | Submit all lines
| `Ctrl`+`P` or `Up` | Move cursor to previous line or last line of previous set of input lines in history
| `Ctrl`+`N` or `Down` | Move cursor to next line or first line of next set of input lines in history
| `Meta`+`P` or `Ctrl`+`Up` | Fetch previous set of input lines in history
| `Meta`+`N` or `Ctrl`+`Down` | Fetch next set of input lines in history
| `Ctrl`+`Y` | Paste the string in the clipboard
| `Ctrl`+`R` | Incremental search

`Meta` means either `Alt`+`key` or `Esc` followed by key.

[go-readline-ny]: https://github.com/nyaosorg/go-readline-ny
[^Y]: The submit condition can be customized.

[Example](./examples/example.go)
---------

![image](./demo.gif)

```examples/example.go
package main

import (
"context"
"fmt"
"io"
"os"
"regexp"
"strings"

"github.com/mattn/go-colorable"

"github.com/nyaosorg/go-readline-ny"
"github.com/nyaosorg/go-readline-ny/keys"
"github.com/nyaosorg/go-readline-ny/simplehistory"

"github.com/hymkor/go-multiline-ny"
"github.com/hymkor/go-multiline-ny/completion"
)

var (
commands = []string{"select", "insert", "delete", "update"}
tables = []string{"dept", "emp", "bonus", "salgrade"}
columns = []string{"deptno", "dname", "loc", "empno", "ename", "job", "mgr", "hiredate", "sal", "comm", "grade", "losal", "hisal"}
)

func getCompletionCandidates(fields []string) (forCompletion []string, forListing []string) {
candidates := commands
for _, word := range fields {
if strings.EqualFold(word, "from") {
candidates = append([]string{"where"}, tables...)
} else if strings.EqualFold(word, "set") {
candidates = append([]string{"where"}, columns...)
} else if strings.EqualFold(word, "update") {
candidates = append([]string{"set"}, tables...)
} else if strings.EqualFold(word, "delete") {
candidates = []string{"from"}
} else if strings.EqualFold(word, "select") {
candidates = append([]string{"from"}, columns...)
} else if strings.EqualFold(word, "where") {
candidates = append([]string{"and", "or"}, columns...)
}
}
return candidates, candidates
}

func main() {
ctx := context.Background()
fmt.Println("C-m or Enter : Insert a linefeed")
fmt.Println("C-p or UP : Move to the previous line.")
fmt.Println("C-n or DOWN : Move to the next line")
fmt.Println("C-j or Meta+Enter : Submit")
fmt.Println("C-c : Abort.")
fmt.Println("C-D with no chars : Quit.")
fmt.Println("C-UP or Meta-P : Move to the previous history entry")
fmt.Println("C-DOWN or Meta-N : Move to the next history entry")

var ed multiline.Editor
ed.SetPrompt(func(w io.Writer, lnum int) (int, error) {
return fmt.Fprintf(w, "[%d] ", lnum+1)
})
ed.SetPredictColor(readline.PredictColorBlueItalic)

ed.Highlight = []readline.Highlight{
// Words -> dark green
{Pattern: regexp.MustCompile(`(?i)(SELECT|INSERT|FROM|WHERE|AS)`), Sequence: "\x1B[33;49;22m"},
// Double quotation -> light magenta
{Pattern: regexp.MustCompile(`(?m)"([^"\n]*\\")*[^"\n]*$|"([^"\n]*\\")*[^"\n]*"`), Sequence: "\x1B[32;49;1m"},
// Single quotation -> light red
{Pattern: regexp.MustCompile(`(?m)'([^'\n]*\\')*[^'\n]*$|'([^'\n]*\\')*[^'\n]*'`), Sequence: "\x1B[31;49;1m"},
// Number literal -> light blue
{Pattern: regexp.MustCompile(`[0-9]+`), Sequence: "\x1B[34;49;1m"},
// Comment -> dark gray
{Pattern: regexp.MustCompile(`(?s)/\*.*?\*/`), Sequence: "\x1B[30;49;1m"},
// Multiline string literal -> dark red
{Pattern: regexp.MustCompile("(?s)```.*?```"), Sequence: "\x1B[31;49;22m"},
}
ed.ResetColor = "\x1B[0m"
ed.DefaultColor = "\x1B[37;49;1m"

// To enable escape sequence on Windows.
// (On other operating systems, it can be omitted)
ed.SetWriter(colorable.NewColorableStdout())

// enable history (optional)
history := simplehistory.New()
ed.SetHistory(history)
ed.SetHistoryCycling(true)

// enable completion (optional)
ed.BindKey(keys.CtrlI, &completion.CmdCompletionOrList{
// Characters listed here are excluded from completion.
Delimiter: "&|><;",
// Enclose candidates with these characters when they contain spaces
Enclosure: `"'`,
// String to append when only one candidate remains
Postfix: " ",
// Function for listing candidates
Candidates: getCompletionCandidates,
})

// Show newline mark (experimental)
ed.OnAfterRender = func(w io.Writer, availWidth int) {
if availWidth >= 2 {
io.WriteString(w, "\x1B[33;22m↓\x1B[39m")
}
}

for {
lines, err := ed.Read(ctx)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
L := strings.Join(lines, "\n")
fmt.Println("-----")
fmt.Println(L)
fmt.Println("-----")
history.Add(L)
}
}
```

[Terminate input only if you type Enter when it ends with a semicolon](./examples/example-semi.go)
---------

```examples/example-semi.go
package main

import (
"context"
"fmt"
"io"
"os"
"strings"

"github.com/atotto/clipboard"
"github.com/mattn/go-colorable"

"github.com/nyaosorg/go-readline-ny/simplehistory"

"github.com/hymkor/go-multiline-ny"
)

type OSClipboard struct{}

func (OSClipboard) Read() (string, error) {
return clipboard.ReadAll()
}

func (OSClipboard) Write(s string) error {
return clipboard.WriteAll(s)
}

func main() {
ctx := context.Background()
fmt.Println("C-m or Enter : Submit when lines end with `;`")
fmt.Println(" Otherwise insert a linefeed.")
fmt.Println("C-j : Submit always")
fmt.Println("C-c : Abort.")
fmt.Println("C-D with no chars : Quit.")

var ed multiline.Editor
ed.SetPrompt(func(w io.Writer, lnum int) (int, error) {
return fmt.Fprintf(w, "[%d] ", lnum+1)
})

ed.SubmitOnEnterWhen(func(lines []string, _ int) bool {
return strings.HasSuffix(strings.TrimSpace(lines[len(lines)-1]), ";")
})

// To enable escape sequence on Windows.
// (On other operating systems, it can be omitted)
ed.SetWriter(colorable.NewColorableStdout())

// Use the clipboard of the operating system.
ed.LineEditor.Clipboard = OSClipboard{}

history := simplehistory.New()
ed.SetHistory(history)
ed.SetHistoryCycling(true)

for {
lines, err := ed.Read(ctx)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return
}
L := strings.Join(lines, "\n")
fmt.Println("-----")
fmt.Println(L)
fmt.Println("-----")
history.Add(L)
}
}
```

Changelog
---------

- [Changelog (English)](./CHANGELOG.md)
- [Changelog (Japanese)](./CHANGELOG_ja.md)

Acknowledgements
----------------

- [spiegel-im-spiegel (Spiegel)](https://github.com/spiegel-im-spiegel)
- [apstndb](https://github.com/apstndb)

Author
------

- [hymkor (HAYAMA Kaoru)](https://github.com/hymkor)