Pure Go line editor with history, inspired by linenoise

Liner

Liner is a command line editor with history. It was inspired by linenoise; everything Unix-like is a VT100 (or is trying very hard to be). If your terminal is not pretending to be a VT100, change it. Liner also support Windows.

Liner is released under the X11 license (which is similar to the new BSD license).

Line Editing

The following line editing commands are supported on platforms and terminals that Liner supports:

Keystroke Action
Ctrl-A, Home Move cursor to beginning of line
Ctrl-E, End Move cursor to end of line
Ctrl-B, Left Move cursor one character left
Ctrl-F, Right Move cursor one character right
Ctrl-Left, Alt-B Move cursor to previous word
Ctrl-Right, Alt-F Move cursor to next word
Ctrl-D, Del (if line is not empty) Delete character under cursor
Ctrl-D (if line is empty) End of File - usually quits application
Ctrl-C Reset input (create new empty prompt)
Ctrl-L Clear screen (line is unmodified)
Ctrl-T Transpose previous character with current character
Ctrl-H, BackSpace Delete character before cursor
Ctrl-W, Alt-BackSpace Delete word leading up to cursor
Alt-D Delete word following cursor
Ctrl-K Delete from cursor to end of line
Ctrl-U Delete from start of line to cursor
Ctrl-P, Up Previous match from history
Ctrl-N, Down Next match from history
Ctrl-R Reverse Search history (Ctrl-S forward, Ctrl-G cancel)
Ctrl-Y Paste from Yank buffer (Alt-Y to paste next yank instead)
Tab Next completion
Shift-Tab (after Tab) Previous completion

Getting started

package main

import (
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/peterh/liner"
)

var (
	history_fn = filepath.Join(os.TempDir(), ".liner_example_history")
	names      = []string{"john", "james", "mary", "nancy"}
)

func main() {
	line := liner.NewLiner()
	defer line.Close()

	line.SetCtrlCAborts(true)

	line.SetCompleter(func(line string) (c []string) {
		for _, n := range names {
			if strings.HasPrefix(n, strings.ToLower(line)) {
				c = append(c, n)
			}
		}
		return
	})

	if f, err := os.Open(history_fn); err == nil {
		line.ReadHistory(f)
		f.Close()
	}

	if name, err := line.Prompt("What is your name? "); err == nil {
		log.Print("Got: ", name)
		line.AppendHistory(name)
	} else if err == liner.ErrPromptAborted {
		log.Print("Aborted")
	} else {
		log.Print("Error reading line: ", err)
	}

	if f, err := os.Create(history_fn); err != nil {
		log.Print("Error writing history file: ", err)
	} else {
		line.WriteHistory(f)
		f.Close()
	}
}

For documentation, see http://godoc.org/github.com/peterh/liner

Owner
Comments
  • Added tab prompter capability

    Added tab prompter capability

    In an attempt to allow customization of the tab prompt (requested in #20) I've added a "tab prompter" callback to the tab completion. This isn't yet documented or unit tested. However, if this seems like a reasonable approach and would be accepted, then I'll add documentation and tests to the pull request. An example of bash-like tab prompting would be:

    func(items []string, readNext liner.NextReader) (string, error) {
      if len(items) == 0 {
        return "", nil
      } else if len(items) == 1 {
        return items[0], nil
      } else if len(items) > completionQueryItems {
        fmt.Printf("\nDisplay all %d possibilities? (y or n) ", len(items))
        for {
          next, err := readNext()
          if err != nil {
            return "", err
          }
    
          if key, ok := next.(rune); ok {
            if unicode.ToLower(key) == 'n' {
              return "", nil
            } else if unicode.ToLower(key) == 'y' {
              break
            }
          }
        }
      }
    
      fmt.Printf("\n%v\n", items)
      return "", nil
    }
    

    The above example would present a very basic display of the possibilities when a single tab is pressed. While not exactly like bash (no tab-tab support) it certainly allows for customizing the prompt. Please let me know what you think.

    Regards, Andrew

  • Return an error when the terminal reports zero columns and is refreshed

    Return an error when the terminal reports zero columns and is refreshed

    When used within a call to docker exec -it where the original container does not have a TTY allocated to it (such as in docker/docker#8755), the number of columns read from the ioctl() call will be zero, but it will not return an error. If you call Prompt() or PasswordPrompt() after this, the prompt will panic when it tries to divide a number by the number of columns (which is zero).

    This change detects when the number of columns returned is zero and returns ErrPromptUnsupported from the PromptXXX methods to avoid a panic and so the application is able to deal with the problem more easily.

  • Exception generated intermittently by using Prompt

    Exception generated intermittently by using Prompt

    Hi we are making use of your liner library in a command line tool we are working. We have noticed that we are getting intermittent crashes with the following stack trace. I was hoping you might be able to shed some light on the problem. Thanks.

    pureweb >Exception 0xc0000005 0x1 0xc082063988 0x7720c4d0 PC=0x7720c4d0 signal arrived during cgo execution

    syscall.(_Proc).Call(0xc08208bf20, 0xc0820479c0, 0x4, 0x4, 0x1, 0x0, 0x0, 0x0) c:/go/src/syscall/dll_windows.go:136 +0x5c2 syscall.(_LazyProc).Call(0xc082005ef0, 0xc0820479c0, 0x4, 0x4, 0x1, 0x0, 0x0, 0x 0) c:/go/src/syscall/dll_windows.go:279 +0x74 github.com/peterh/liner.(_State).readNext(0xc0820680a0, 0x0, 0x0, 0x0, 0x0) E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/github.com/peterh/liner/inp ut_windows.go:160 +0x182 github.com/peterh/liner.(_State).Prompt(0xc0820680a0, 0x7787b0, 0x9, 0x0, 0x0, 0 x0, 0x0) E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/github.com/peterh/liner/lin e.go:492 +0x358 github.com/gobs/cmd.(*Cmd).CmdLoop(0xc082068000) E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/github.com/gobs/cmd/cmd.go: 564 +0xb1 main.main() E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/calgaryscientific.com/purew eb-cli/main.go:70 +0xaaa

    goroutine 5 [syscall, 1110 minutes]: os/signal.loop() c:/go/src/os/signal/signal_unix.go:21 +0x26 created by os/signal.init-+1 c:/go/src/os/signal/signal_unix.go:27 +0x3c

    goroutine 13 [chan receive, 1110 minutes]: main.func-+001() E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/calgaryscientific.com/purew eb-cli/main.go:57 +0x74 created by main.main E:/bamboobuilds/PWEB-PMGN-WIN/src/gocode/src/calgaryscientific.com/purew eb-cli/main.go:62 +0xa30

    goroutine 56 [IO wait, 127 minutes]: net.(_pollDesc).Wait(0xc08207a170, 0x72, 0x0, 0x0) c:/go/src/net/fd_poll_runtime.go:84 +0x4e net.(_ioSrv).ExecIO(0xc082040148, 0xc08207a060, 0x75a5b0, 0x7, 0x7f8090, 0x0, 0x 0, 0x0) c:/go/src/net/fd_windows.go:188 +0x305 net.(_netFD).Read(0xc08207a000, 0xc082011000, 0x1000, 0x1000, 0x0, 0x0, 0x0) c:/go/src/net/fd_windows.go:470 +0x180 net.(_conn).Read(0xc082040168, 0xc082011000, 0x1000, 0x1000, 0x0, 0x0, 0x0) c:/go/src/net/net.go:121 +0xe3 net/http.noteEOFReader.Read(0x283998, 0xc082040168, 0xc082028058, 0xc082011000, 0x1000, 0x1000, 0x6bb9a0, 0x0, 0x0) c:/go/src/net/http/transport.go:1270 +0x75 net/http.(_noteEOFReader).Read(0xc082007480, 0xc082011000, 0x1000, 0x1000, 0xc08 2012000, 0x0, 0x0) :125 +0xdb bufio.(_Reader).fill(0xc082042de0) c:/go/src/bufio/bufio.go:97 +0x1d5 bufio.(_Reader).Peek(0xc082042de0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0) c:/go/src/bufio/bufio.go:132 +0xf7 net/http.(_persistConn).readLoop(0xc082028000) c:/go/src/net/http/transport.go:842 +0xab created by net/http.(*Transport).dialConn c:/go/src/net/http/transport.go:660 +0xca6

    goroutine 57 [select, 127 minutes]: net/http.(_persistConn).writeLoop(0xc082028000) c:/go/src/net/http/transport.go:945 +0x424 created by net/http.(_Transport).dialConn c:/go/src/net/http/transport.go:661 +0xcc3 rax 0x1 rbx 0x1 rcx 0x7746ddda rdx 0x0 rdi 0x0 rsi 0x0 rbp 0x8f8820 rsp 0x6fcd0 r8 0x6fc98 r9 0x8f8820 r10 0x0 r11 0x246 r12 0xc082063988 r13 0xc0820639ac r14 0x20 r15 0x10 rip 0x7720c4d0 rflags 0x10246 cs 0x33 fs 0x53 gs 0x2b

  • Inject output while in prompt

    Inject output while in prompt

    Is there a way to output something to the user while in prompt (from a separate go routine) - so that the output will be printed on a separate line and the prompt will be refreshed on a line below?

    I made an ugly hack to make it work by creating a public function InjectString that saves the string to be injected on the State and pushed a marker rune on State.next. Then, I handle it in State.Prompt by doing:

    case inject:
      s.cursorPos(0)
      s.eraseLine()
      fmt.Println(s.injected)
      s.refresh(p, line, pos)
    

    But, it is really an ugly hack. I needed to change State.next to a regular channel so that I can write to it and not only read from it... There must be a better way but I could not find it in a few minutes of looking in the code.

  • Completion at cursor position

    Completion at cursor position

    Hello, Currently, the completion seems to work only at the end of the line. Would it be possible to make it work at current cursor position ? For an example: sql> create table test (id int, name text); sql> select t. from test t; When the cursor is after the dot, the table columns can be completed. Regards.

  • catch os.Interrupt (ctrl-C) so that the tty state can be restored

    catch os.Interrupt (ctrl-C) so that the tty state can be restored

    catch os.Interrupt (ctrl-C) so that the tty state can be restored before terminating.

    On both OSX and Linux a Ctrl-C kills the application and leaves the tty/terminal window with no echo. This patch catches os.Interrupt so that the application closes cleanly.

    Tested on OSX (Yosemite) and Linux (Centos 5.11)

  • Ctrl-Z (SIGTSTP) Should Be Handled

    Ctrl-Z (SIGTSTP) Should Be Handled

    • Operating System: Ubuntu Linux
    • Terminal Emulator: GNOME Terminal running bash
    • Bug behaviour: Joker, built with (forked version of) liner, ignores Ctrl-Z (SIGTSTP) from terminal
    • Expected behaviour: Handles (respects) Ctrl-Z as SIGTSTP

    Note that kill -TSTP <pid>, from another terminal, works, as does a subsequent fg from the original terminal (which is then sitting at a bash prompt).

    If it's not intended for this to work (why not, since it should for non-shells that take interactive input?), please document that clearly in the README.md. Ditto if there's some configurable that needs to be set (by Joker, in my case). Currently README.md doesn't mention Ctrl-Z that I can see.

  • Renamed termios type to TerminalMode. Added GetTerminalMode, GetOriginal...

    Renamed termios type to TerminalMode. Added GetTerminalMode, GetOriginal...

    Hi Peter,

    I exposed the liner.termios type and renamed it TerminalMode. I added a TerminalSupported function and GetTerminalMode/SetTerminalMode methods. I also added a GetOriginalMode method.

    GetTerminalMode returns a boolean status in addition to the TerminalMode. This is because it was being checked in input_windows.go. To maintain the current behavior the status is currently ignored in input.go. For symmetry, SetTerminalMode also returns a boolean status (currently it always returns true in input_windows.go). Again to maintain the current behavior the status is ignored in both input.go and input_windows.go.

    Let me know what you think of these changes.

    Thanks,

    Michael.

  • Hardcoded

    Hardcoded "^C" Println when pressing ctrl+c

    It could be nice to have the hardcoded "^C" print removed when pressing ctrl+c. I would like the decision whether to print out "^C" handed out to the user.

  • Allow for better usability in word-based cursor movement and deletion

    Allow for better usability in word-based cursor movement and deletion

    Hi again. More word stuff.

    The standard unicode.IsSpace based word boundaries seem fine for most usages, but when for example using liner as a language REPL, it'd be nice to be able to tweak what constitutes a word for cursor movement and deletion purposes.

    This patch adds the method:

    // SetWordBreakers sets runes that will be considered stops for word-based
    // cursor movement and deletion without requiring surrounding whitespace.
    func (s *State) SetWordBreakers(breakers ...rune) 
    

    And updates wordLeft, wordRight and ctrlW accordingly.

    It's best to just try those 3 actions in some examples with and without the patch to get a feel for the difference. e.g.

    ed.SetWordBreakers('(', ')')
    // and editing the line: 
    // (def zip (proc (xs ys) (zip-with cons xs ys)))
    

    and

    ed.SetWordBreakers('.', '/')
    // and editing the line: 
    // https://github.com/peterh/liner
    

    What do you think? This gives the means for a good boost in usability when liner is used for language REPLs, without complicating the implementation much.

  • influx CLI requires stty col > 0

    influx CLI requires stty col > 0

    Running influx in a docker container, I came across this issue. The terminal configured was extremely basic and apparently had no screen size defined in stty.

    • Operating System (eg. Windows, Linux, Mac) linux
    • Terminal Emulator (eg. xterm, gnome-terminal, konsole, ConEmu, Terminal.app, Command Prompt) ssh
    • Bug behaviour

    `InfluxDB shell version: 0.13.0

    panic: runtime error: integer divide by zero [signal 0x8 code=0x1 addr=0x61f870 pc=0x61f870] goroutine 1 [running]: panic(0x786fa0, 0xc82000a060) /usr/local/go/src/runtime/panic.go:481 +0x3e6 github.com/peterh/liner.(*State).refreshMultiLine(0xc8200823c0, 0xc8200ed410, 0x2, 0x2, 0xc82000b910, 0x1, 0x2, 0x1, 0x0, 0x0) /root/go/src/github.com/peterh/liner/line.go:159 +0xf0`

    • Complete sample that reproduces the bug ensure stty has col = 0; enter CLI and press a key

    influxdb@deis-monitor-influxdb-4qttw:~$ stty -a speed 38400 baud; rows 0; columns 0; line = 0;

  • Unexpected behaviour when sending SIGINT to long prompts

    Unexpected behaviour when sending SIGINT to long prompts

    I'm running Debian (sid) with Alacritty as my terminal emulator. (I can also reproduce the issue below with Gnome Terminal.)

    If I call the Prompt method with a long string and then send a SIGINT, the program will exit (with a status code of 1) instead of cancelling the prompt. This example reproduces it:

    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strings"
    
    	"github.com/peterh/liner"
    )
    
    func main() {
    	l := liner.NewLiner()
    	cols := int(reflect.ValueOf(l).Elem().FieldByName("columns").Int())
    	var err error
    
    	// Ctrl-C resets the prompt, as expected.
    	_, err = l.Prompt(genString(cols - 10))
    	fmt.Printf("err: %v\n", err)
    
    	// This kills the program when hitting Ctrl-C, exiting with a status code of 1.
    	_, err = l.Prompt(genString(cols - 9))
    	fmt.Printf("err: %v\n", err)
    }
    
    func genString(n int) string {
    	var s strings.Builder
    	for i := 0; i < n; i++ {
    		s.WriteString("a")
    	}
    	return s.String()
    }
    

    The issue is the call here to the ReadLine method from bufio. (This is reached from this region.)

    I'm not (immediately) sure what the right fix would be. You've no doubt thought a lot more about these kinds of questions than I have—what do you think?

  • "Ghost cursor" artifact left behind after user types a line on Windows 10

    I'm evaluating line input modules to use for my Golang version of sqlcmd (github.com/microsoft/go-sqlcmd) So far liner looks like a good candidate except for this display glitch. I can't tell if it's a Windows problem or an issue with the module. When my process exits the ghost cursor images disappear.

    • Operating System (eg. Windows, Linux, Mac) WINDOWS 10
    • Terminal Emulator (eg. xterm, gnome-terminal, konsole, ConEmu, Terminal.app, Command Prompt) Command Prompt
    • Bug behaviour Liner leaves behind a static cursor image on the first character of input
    • Expected behaviour No such distracting artifacts
    • Complete sample that reproduces the bug
    package main
    
    import (
    	"fmt"
    
    	"github.com/peterh/liner"
    )
    
    func main() {
    	line := liner.NewLiner()
    	defer line.Close()
    	text := ""
    	var err error
    	for text != "q" {
    		text, err = line.Prompt("Prompt:")
    		if err == nil {
    			fmt.Println("You typed:" + text)
    		}
    	}
    }
    
    

    image

    image

  • Weird behaviour with multi-line and history

    Weird behaviour with multi-line and history

    When I run the following minimal example on Manjaro Linux / xfce4-terminal (0.8.10) or on Mac OS X 10.14.6 / Terminal

    package main
    
    import "github.com/peterh/liner"
    
    func main() {
    
    	line := liner.NewLiner()
    	defer line.Close()
    
    	line.SetMultiLineMode(true)
    
    	for {
    		input, _ := line.Prompt("> ")
    		line.AppendHistory(input)
    		if input == "exit" {
    			break
    		}
    	}
    
    }
    
    

    and enter a short line, a long line which extends to a second line and then recall the first line from history with the up key and press enter, there is an additional blank line (the one before the exit) which shouldn't be there.

    > s=1
    > s=fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    fff
    > s=1
    
    > exit
    

    The prompt seems to jump one line up while going over the two line history entry which somehow creates the extra newline. If the multi line entry spans several lines you get even more blank lines.

  • Add support for displaying complete options immediately

    Add support for displaying complete options immediately

    Sometimes, for example when liner is used to allow the user to choose from a few options, it would be useful to immediately print the available options, instead of waiting the user to press the Tab key first.

    I think a function like SetTabCompletionImmediate (bool) would enable users to control this.

    If this change would be accepted, I could try to provide the implementation.

Related tags
Pure Go command line prompt with history, kill-ring, and tab completion

Prompt Prompt is a command line prompt editor with history, kill-ring, and tab completion. It was inspired by linenoise and derivatives which eschew u

Nov 20, 2021
Integrated console application library, using Go structs as commands, with menus, completions, hints, history, Vim mode, $EDITOR usage, and more ...
Integrated console application library, using Go structs as commands, with menus, completions, hints, history, Vim mode, $EDITOR usage, and more ...

Gonsole - Integrated Console Application library This package rests on a readline console library, (giving advanced completion, hint, input and histor

Nov 20, 2022
Source code editor in pure Go.
Source code editor in pure Go.

Editor Source code editor in pure Go. About This is a simple but advanced source code editor As the editor is being developed, the rules of how the UI

Dec 25, 2022
A modern UNIX ed (line editor) clone written in Go

ed (the awesome UNIX line editor) ed is a clone of the UNIX command-line tool by the same name ed a line editor that was nortorious for being and most

May 29, 2021
Command line editor for Discourse

Edit Discourse topics locally The discedit tool allows you to edit Discourse topics in your favourite local text editor. It works by pulling a topic f

Nov 15, 2021
go-editor is the clean go module that refractors from Kubernetes to help you edit resources in a command-line way.

go-editor The source code of go-editor comes from Kubernetes and refractor as the clean Go module. You can embed go-editor in your command-line tool l

Dec 5, 2021
Pi-hole data right from your terminal. Live updating view, query history extraction and more!
Pi-hole data right from your terminal. Live updating view, query history extraction and more!

Pi-CLI Pi-CLI is a command line program used to view data from a Pi-Hole instance directly in your terminal.

Dec 12, 2022
A sample FaaS function that gets a stock quote and 30 day history by symbol and returns a HTML page with a generates SVG sparkline.

faas stonks This uses serverless technology to get a stock quote and 30 day sparkline from Yahoo Finance. Deployment Nimbella account Namespace with o

Sep 23, 2021
Create new commands from your shell history or terminal.

overdub Create new commands from your shell history or terminal. TODO list for initial release Filter out unlikely commands (e.g. package managers) fr

Aug 9, 2022
🏗️ Fetch a specific commit without any history (shallow depth w/o cloning)

shallow-fetch-sha ??️ For a given git repository and commit, fetch and checkout just that commit without any history. This can be extremely useful in

Nov 27, 2021
A simple CLI tool that outputs the history of connections to Amazon EC2 instances using AWS Session Manager.

ssmh This is a simple CLI tool that outputs the history of connections to Amazon EC2 instances using AWS Session Manager. Installation brew install mi

Dec 10, 2021
Alfred workfow for search safari history.
Alfred workfow for search safari history.

Alfred Safari Toolkit This is an Alfred workflow for Safari. I have been using tupton/alfred-safari-history for years, but it runs with Python2 which

Dec 17, 2022
One-line-at-a-time was a hobby project inspired by the character Dwight K. Schrute of 'The Office'
One-line-at-a-time was a hobby project inspired by the character Dwight K. Schrute of 'The Office'

One Line at a Time Introduction One-line-at-a-time was a hobby project inspired by the character Dwight K. Schrute of 'The Office'. His efficient usag

Dec 13, 2021
A tiny command-line orientated PKM tool inspired by Taskwarrior and daily logging.

eden eden is a command line tool for creating and manipulating daily log notes. It started life as a series of different bash script that did various

Jan 20, 2022
A command line http test tool. Maintain the case via git and pure text
A command line http test tool. Maintain the case via git and pure text

httptest A command line http test tool Maintain the api test cases via git and pure text We want to test the APIs via http requests and assert the res

Dec 16, 2022
A modern and intuitive terminal-based text editor
A modern and intuitive terminal-based text editor

micro is a terminal-based text editor that aims to be easy to use and intuitive, while also taking advantage of the capabilities of modern terminals

Jan 7, 2023
📝 Easily format yaml files on terminal or your editor

YAMLFMT A simple and extensible yaml formatter. Installation go install github.com/UltiRequiem/yamlfmt@latest Make sure your $PATH includes the $GOPAT

Oct 31, 2022
Are you programming and suddenly your stomach is rumbling? No problem, order your Ifood without leaving your favorite text editor ❤️

vim-ifood Você ta programando e de repente bateu aquela fome? Sem problemas, peça seu Ifood sem sair do seu editor de texto favorito ❤️ Are you progra

Jun 2, 2022
TIled map editor CSV export conversion to C array

tiled2c This tool is very simplistic and is a variation of the one written to convert between Tiled map editor CSV exports and a format for the sega s

Nov 28, 2021