multiline

package module
v0.23.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2026 License: MIT Imports: 12 Imported by: 11

README

go-multiline-ny

Go Reference

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.

[^Y]: The submit condition can be customized.

Example

image

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

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

Acknowledgements

Author

Documentation

Index

Constants

This section is empty.

Variables

View Source
var NewLineMarkForIncrementalSearch = "\u21B2 "

NewLineMarkForIncrementalSearch is the string used instead of "\n". This variable is not guaranteed to remain valid in the future.

Functions

This section is empty.

Types

type Editor added in v0.4.0

type Editor struct {
	LineEditor readline.Editor
	Dirty      bool

	StatusLineHeight int
	Highlight        []readline.Highlight
	ResetColor       string
	DefaultColor     string
	OnAfterRender    func(w io.Writer, availWidth int) // experimenal
	// contains filtered or unexported fields
}

func (*Editor) BindKey added in v0.8.0

func (m *Editor) BindKey(key keys.Code, f readline.Command) error

func (*Editor) CmdBackwardChar added in v0.11.2

func (m *Editor) CmdBackwardChar(ctx context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdBackwardDeleteChar added in v0.11.2

func (m *Editor) CmdBackwardDeleteChar(ctx context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdDeleteChar added in v0.11.2

func (m *Editor) CmdDeleteChar(ctx context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdForwardChar added in v0.11.2

func (m *Editor) CmdForwardChar(ctx context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdNextHistory added in v0.11.2

func (m *Editor) CmdNextHistory(_ context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdNextLine added in v0.11.2

func (m *Editor) CmdNextLine(ctx context.Context, rl *readline.Buffer) readline.Result

func (*Editor) CmdPreviousHistory added in v0.11.2

func (m *Editor) CmdPreviousHistory(_ context.Context, b *readline.Buffer) readline.Result

func (*Editor) CmdPreviousLine added in v0.11.2

func (m *Editor) CmdPreviousLine(ctx context.Context, rl *readline.Buffer) readline.Result

func (*Editor) CmdYank added in v0.11.2

func (m *Editor) CmdYank(_ context.Context, b *readline.Buffer) readline.Result

func (*Editor) CursorLine added in v0.11.1

func (m *Editor) CursorLine() int

func (*Editor) GotoEndLine added in v0.11.1

func (m *Editor) GotoEndLine() func()

func (*Editor) Headline added in v0.21.0

func (m *Editor) Headline() int

func (*Editor) Lines added in v0.11.1

func (m *Editor) Lines() []string

func (*Editor) NewLine added in v0.8.0

func (m *Editor) NewLine(_ context.Context, b *readline.Buffer) readline.Result

func (*Editor) NewPrefixCommand added in v0.18.1

func (m *Editor) NewPrefixCommand(prompt string) *PrefixCommand

func (*Editor) Out added in v0.22.2

func (m *Editor) Out() *bufio.Writer

func (*Editor) PrintFromLine added in v0.21.0

func (m *Editor) PrintFromLine(i int) int

PrintFromLine prints lines[i:]. `headline` must be corrected. It does not fix view.

func (*Editor) Read added in v0.4.0

func (m *Editor) Read(ctx context.Context) ([]string, error)

func (*Editor) SetColoring deprecated added in v0.6.6

func (m *Editor) SetColoring(c interface{})

Deprecated: set LineEditor.Highlight instead

func (*Editor) SetDefault added in v0.9.0

func (m *Editor) SetDefault(d []string)

func (*Editor) SetHistory added in v0.6.6

func (m *Editor) SetHistory(h readline.IHistory)

func (*Editor) SetHistoryCycling added in v0.6.7

func (m *Editor) SetHistoryCycling(value bool)

func (*Editor) SetMoveEnd added in v0.9.0

func (m *Editor) SetMoveEnd(value bool)

func (*Editor) SetNextEditHook added in v0.21.0

func (m *Editor) SetNextEditHook(f func(string) bool)

func (*Editor) SetPredictColor added in v0.16.0

func (m *Editor) SetPredictColor(colors [2]string)

SetPredictColor enables the prediction of go-readline-ny v1.5.0 and specify the colors (e.g.) `m.SetPredictColor([...]string{"\x1B[3;22;34m", "\x1B[23;39m"})`

func (*Editor) SetPrompt added in v0.6.6

func (m *Editor) SetPrompt(f func(io.Writer, int) (int, error))

func (*Editor) SetTty added in v0.22.2

func (m *Editor) SetTty(tty ttyadapter.Tty)

func (*Editor) SetWriter added in v0.6.6

func (m *Editor) SetWriter(w io.Writer)

func (*Editor) Submit added in v0.8.0

func (m *Editor) Submit(_ context.Context, B *readline.Buffer) readline.Result

func (*Editor) SubmitOnEnterWhen added in v0.12.0

func (m *Editor) SubmitOnEnterWhen(f func([]string, int) bool)

SubmitOnEnterWhen defines the condition to submit when Enter-key is pressed.

func (*Editor) SwapEnter deprecated added in v0.6.7

func (m *Editor) SwapEnter() error

Deprecated:

func (*Editor) Sync added in v0.11.2

func (m *Editor) Sync(line string)

func (*Editor) Writer added in v0.22.2

func (m *Editor) Writer() io.Writer

type PrefixCommand added in v0.11.1

type PrefixCommand struct {
	readline.KeyMap
	// contains filtered or unexported fields
}

func (*PrefixCommand) Call added in v0.11.1

func (*PrefixCommand) String added in v0.11.1

func (*PrefixCommand) String() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL