Skip to content

Commit 174fe58

Browse files
committed
Use alternative readline library
1 parent 5b1c526 commit 174fe58

File tree

5 files changed

+205
-36
lines changed

5 files changed

+205
-36
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ require (
4444
github.com/nakagami/firebirdsql v0.9.6
4545
github.com/ory/dockertest/v3 v3.10.0
4646
github.com/prestodb/presto-go-client v0.0.0-20230524183650-a1a0bac0f63e
47+
github.com/reeflective/readline v1.0.8
4748
github.com/sijms/go-ora/v2 v2.7.22
4849
github.com/sirupsen/logrus v1.9.3
4950
github.com/snowflakedb/gosnowflake v1.6.25
@@ -56,6 +57,7 @@ require (
5657
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
5758
github.com/yookoala/realpath v1.0.0
5859
github.com/ziutek/mymysql v1.5.4
60+
golang.org/x/term v0.14.0
5961
gorm.io/driver/bigquery v1.2.0
6062
modernc.org/ql v1.4.7
6163
modernc.org/sqlite v1.27.0
@@ -260,7 +262,6 @@ require (
260262
golang.org/x/oauth2 v0.14.0 // indirect
261263
golang.org/x/sync v0.5.0 // indirect
262264
golang.org/x/sys v0.14.0 // indirect
263-
golang.org/x/term v0.14.0 // indirect
264265
golang.org/x/text v0.14.0 // indirect
265266
golang.org/x/time v0.4.0 // indirect
266267
golang.org/x/tools v0.15.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,8 @@ github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGy
896896
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
897897
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
898898
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
899+
github.com/reeflective/readline v1.0.8 h1:VuDGI82lAwl1H5by+hpW4OQgM+9ikh6EuOySQUGP3sI=
900+
github.com/reeflective/readline v1.0.8/go.mod h1:5JgnHb/ZCvp/6RUA59HEansPBxWTkyBO4hJ5LL9Fp1Y=
899901
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
900902
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
901903
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=

rline/new_readline.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//go:build new_readline
2+
3+
package rline
4+
5+
import (
6+
"github.com/reeflective/readline/inputrc"
7+
"golang.org/x/term"
8+
"io"
9+
"os"
10+
"os/signal"
11+
"syscall"
12+
13+
"github.com/mattn/go-isatty"
14+
"github.com/reeflective/readline"
15+
)
16+
17+
var (
18+
// ErrInterrupt is the interrupt error.
19+
ErrInterrupt = readline.ErrInterrupt
20+
)
21+
22+
// baseRline should be embedded in a struct implementing the IO interface,
23+
// as it keeps implementation specific state.
24+
type baseRline struct {
25+
instance *readline.Shell
26+
prompt string
27+
}
28+
29+
// Prompt sets the prompt for the next interactive line read.
30+
func (l *rline) Prompt(s string) {
31+
l.prompt = s
32+
}
33+
34+
// Completer sets the auto-completer.
35+
func (l *rline) Completer(a Completer) {
36+
l.instance.Completer = func(line []rune, cursor int) readline.Completions {
37+
candidates, _ := a.Complete(line, cursor)
38+
values := make([]string, len(candidates))
39+
for candidate := range candidates {
40+
values = append(values, string(candidate))
41+
}
42+
return readline.CompleteValues(values...)
43+
}
44+
}
45+
46+
// SetOutput sets the output format func.
47+
func (l *rline) SetOutput(f func(string) string) {
48+
l.instance.SyntaxHighlighter = func(line []rune) string {
49+
return f(string(line))
50+
}
51+
}
52+
53+
// New readline input/output handler.
54+
func New(forceNonInteractive bool, out, histfile string) (IO, error) {
55+
// determine if interactive
56+
interactive, cygwin := false, false
57+
if !forceNonInteractive {
58+
interactive = isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
59+
cygwin = isatty.IsCygwinTerminal(os.Stdout.Fd()) && isatty.IsCygwinTerminal(os.Stdin.Fd())
60+
}
61+
var stdout io.WriteCloser
62+
var closers []func() error
63+
switch {
64+
case out != "":
65+
var err error
66+
stdout, err = os.OpenFile(out, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
67+
if err != nil {
68+
return nil, err
69+
}
70+
closers = append(closers, stdout.Close)
71+
interactive = false
72+
default:
73+
stdout = os.Stdout
74+
}
75+
// configure stderr
76+
var stderr io.Writer = os.Stderr
77+
// TODO handle interrupts?
78+
options := []inputrc.Option{inputrc.WithName("usql")}
79+
/*
80+
&readline.Config{
81+
HistoryFile: histfile,
82+
DisableAutoSaveHistory: true,
83+
InterruptPrompt: "^C",
84+
HistorySearchFold: true,
85+
Stdin: stdin,
86+
Stdout: stdout,
87+
Stderr: stderr,
88+
FuncIsTerminal: func() bool {
89+
return interactive || cygwin
90+
},
91+
FuncFilterInputRune: func(r rune) (rune, bool) {
92+
if r == readline.CharCtrlZ {
93+
return r, false
94+
}
95+
return r, true
96+
},
97+
}
98+
*/
99+
// create readline instance
100+
shell := readline.NewShell(options...)
101+
var history readline.History
102+
if histfile != "" {
103+
history, err := readline.NewHistoryFromFile(histfile)
104+
if err != nil {
105+
return nil, err
106+
}
107+
shell.History.Add("default", history)
108+
}
109+
110+
n := func() ([]rune, error) {
111+
line, err := shell.Readline()
112+
return []rune(line), err
113+
}
114+
pw := func(prompt string) (string, error) {
115+
_, err := shell.Printf(prompt)
116+
if err != nil {
117+
return "", err
118+
}
119+
return readPassword()
120+
}
121+
if forceNonInteractive {
122+
n, pw = nil, nil
123+
}
124+
result := &rline{
125+
baseRline: baseRline{instance: shell},
126+
nextLine: n,
127+
close: func() error {
128+
for _, f := range closers {
129+
_ = f()
130+
}
131+
return nil
132+
},
133+
stdout: stdout,
134+
stderr: stderr,
135+
isInteractive: interactive || cygwin,
136+
passwordPrompt: pw,
137+
}
138+
shell.Prompt.Primary(func() string {
139+
return result.prompt
140+
})
141+
if history != nil {
142+
result.saveHistory = func(input string) error {
143+
_, err := history.Write(input)
144+
return err
145+
}
146+
}
147+
return result, nil
148+
}
149+
150+
func readPassword() (string, error) {
151+
stdin := syscall.Stdin
152+
oldState, err := term.GetState(stdin)
153+
if err != nil {
154+
return "", err
155+
}
156+
defer term.Restore(stdin, oldState)
157+
158+
sigch := make(chan os.Signal, 1)
159+
signal.Notify(sigch, os.Interrupt)
160+
go func() {
161+
for _ = range sigch {
162+
term.Restore(stdin, oldState)
163+
os.Exit(1)
164+
}
165+
}()
166+
167+
password, err := term.ReadPassword(stdin)
168+
if err != nil {
169+
return "", err
170+
}
171+
return string(password), nil
172+
}

rline/readline.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,29 @@ var (
1818
// baseRline should be embedded in a struct implementing the IO interface,
1919
// as it keeps implementation specific state.
2020
type baseRline struct {
21-
instance *readline.Instance
21+
instance *readline.Instance
22+
prompt func(string)
23+
completer func(Completer)
24+
}
25+
26+
// Prompt sets the prompt for the next interactive line read.
27+
func (l *rline) Prompt(s string) {
28+
l.instance.SetPrompt(s)
29+
}
30+
31+
// Completer sets the auto-completer.
32+
func (l *rline) Completer(a Completer) {
33+
cfg := l.instance.Config.Clone()
34+
cfg.AutoComplete = readlineCompleter{c: a}
35+
l.instance.SetConfig(cfg)
36+
}
37+
38+
type readlineCompleter struct {
39+
c Completer
40+
}
41+
42+
func (r readlineCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
43+
return r.c.Complete(line, pos)
2244
}
2345

2446
// SetOutput sets the output format func.
@@ -102,32 +124,20 @@ func New(forceNonInteractive bool, out, histfile string) (IO, error) {
102124
n, pw = nil, nil
103125
}
104126
return &rline{
105-
instance: l,
127+
baseRline: baseRline{
128+
instance: l,
129+
},
106130
nextLine: n,
107131
close: func() error {
108132
for _, f := range closers {
109133
_ = f()
110134
}
111135
return nil
112136
},
113-
stdout: stdout,
114-
stderr: stderr,
115-
isInteractive: interactive || cygwin,
116-
prompt: l.SetPrompt,
117-
completer: func(a Completer) {
118-
cfg := l.Config.Clone()
119-
cfg.AutoComplete = readlineCompleter{c: a}
120-
l.SetConfig(cfg)
121-
},
137+
stdout: stdout,
138+
stderr: stderr,
139+
isInteractive: interactive || cygwin,
122140
saveHistory: l.SaveHistory,
123141
passwordPrompt: pw,
124142
}, nil
125143
}
126-
127-
type readlineCompleter struct {
128-
c Completer
129-
}
130-
131-
func (r readlineCompleter) Do(line []rune, pos int) (newLine [][]rune, length int) {
132-
return r.c.Complete(line, pos)
133-
}

rline/rline.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ type rline struct {
5151
stdout io.Writer
5252
stderr io.Writer
5353
isInteractive bool
54-
prompt func(string)
55-
completer func(Completer)
5654
saveHistory func(string) error
5755
passwordPrompt passwordPrompt
5856
}
@@ -90,20 +88,6 @@ func (l *rline) Interactive() bool {
9088
return l.isInteractive
9189
}
9290

93-
// Prompt sets the prompt for the next interactive line read.
94-
func (l *rline) Prompt(s string) {
95-
if l.prompt != nil {
96-
l.prompt(s)
97-
}
98-
}
99-
100-
// Completer sets the auto-completer.
101-
func (l *rline) Completer(a Completer) {
102-
if l.completer != nil {
103-
l.completer(a)
104-
}
105-
}
106-
10791
// Save saves a line of history.
10892
func (l *rline) Save(s string) error {
10993
if l.saveHistory != nil {

0 commit comments

Comments
 (0)