8000 repl: line editing with history and completion · go-python/gpython@5775015 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5775015

Browse files
committed
repl: line editing with history and completion
1 parent 5148b8e commit 5775015

File tree

2 files changed

+172
-14
lines changed

2 files changed

+172
-14
lines changed

notes.txt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,49 @@ IDEA replace all .(cast) with CheckExactString or whatever python calls it...
88

99
Then can use CheckString later...
1010

11+
Instead of using TypeCall etc, just implement all the __methods__ for
12+
Type. Then there is one and only one way of calling the __methods__.
13+
14+
How subclass a builtin type? Methods etc should work fine, but want
15+
CheckString to return the "string" out of the superclass.
16+
17+
Things to do before release
18+
===========================
19+
20+
* Line numbers
21+
* compile single
22+
* interactive interpreter
23+
* Subclass builtins
24+
* pygen
25+
26+
FIXME recursive types for __repr__ in list, dict, tuple
27+
>>> L=[0]
28+
>>> L[0]=L
29+
>>> L
30+
[[...]]
31+
32+
interactive interpreter
33+
calls compile(..."single) until doesn't get "SyntaxError: unexpected EOF while parsing" ?
34+
see pythonrun.c
35+
PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
36+
...
37+
mod = PyParser_ASTFromFileObject(fp, filename, enc,
38+
Py_single_input, ps1, ps2,
39+
flags, &errcode, arena);
40+
Py_XDECREF(v);
41+
Py_XDECREF(w);
42+
Py_XDECREF(oenc);
43+
if (mod == NULL) {
44+
PyArena_Free(arena);
45+
if (errcode == E_EOF) {
46+
PyErr_Clear();
47+
return E_EOF;
48+
}
49+
PyErr_Print();
50+
return -1;
51+
}
52+
53+
1154
Limitations & Missing parts
1255
===========================
1356
* string keys only in dictionaries

repl/repl.go

Lines changed: 129 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,151 @@
22
package repl
33

44
import (
5-
"bufio"
65
"fmt"
76
"io"
8-
"log"
97
"os"
8+
"os/user"
9+
"path/filepath"
10+
"sort"
11+
"strings"
1012

1113
"github.com/ncw/gpython/compile"
1214
"github.com/ncw/gpython/py"
1315
"github.com/ncw/gpython/vm"
16+
"github.com/peterh/liner"
1417
)
1518

19+
const HistoryFileName = ".gpyhistory"
20+
21+
// homeDirectory finds the home directory or returns ""
22+
func homeDirectory() string {
23+
usr, err := user.Current()
24+
if err == nil {
25+
return usr.HomeDir
26+
}
27+
// Fall back to reading $HOME - work around user.Current() not
28+
// working for cross compiled binaries on OSX.
29+
// https://github.com/golang/go/issues/6376
30+
return os.Getenv("HOME")
31+
}
32+
33+
// Holds state for readline services
34+
type readline struct {
35+
*liner.State
36+
historyFile string
37+
module *py.Module
38+
}
39+
40+
// newReadline creates a new instance of readline
41+
func newReadline(module *py.Module) *readline {
42+
rl := &readline{
43+
State: liner.NewLiner(),
44+
module: module,
45+
}
46+
home := homeDirectory()
47+
if home != "" {
F438 48+
rl.historyFile = filepath.Join(home, HistoryFileName)
49+
}
50+
rl.SetTabCompletionStyle(liner.TabPrints)
51+
rl.SetWordCompleter(rl.Completer)
52+
return rl
53+
}
54+
55+
// readHistory reads the history into the term
56+
func (rl *readline) ReadHistory() error {
57+
f, err := os.Open(rl.historyFile)
58+
if err != nil {
59+
return err
60+
}
61+
defer f.Close()
62+
_, err = rl.State.ReadHistory(f)
63+
if err != nil {
64+
return err
65+
}
66+
return nil
67+
}
68+
69+
// writeHistory writes the history from the term
70+
func (rl *readline) WriteHistory() error {
71+
f, err := os.OpenFile(rl.historyFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
72+
if err != nil {
73+
return err
74+
}
75+
defer f.Close()
76+
_, err = rl.State.WriteHistory(f)
77+
if err != nil {
78+
return err
79+
}
80+
return nil
81+
}
82+
83+
// Close the readline and write history
84+
func (rl *readline) Close() error {
85+
err := rl.State.Close()
86+
if err != nil {
87+
return err
88+
}
89+
if rl.historyFile != "" {
90+
err := rl.WriteHistory()
91+
if err != nil {
92+
return err
93+
}
94+
}
95+
return nil
96+
}
97+
98+
// WordCompleter takes the currently edited line with the cursor
99+
// position and returns the completion candidates for the partial word
100+
// to be completed. If the line is "Hello, wo!!!" and the cursor is
101+
// before the first '!', ("Hello, wo!!!", 9) is passed to the
102+
// completer which may returns ("Hello, ", {"world", "Word"}, "!!!")
103+
// to have "Hello, world!!!".
104+
func (rl *readline) Completer(line string, pos int) (head string, completions []string, tail string) {
105+
head = line[:pos]
106+
tail = line[pos:]
107+
lastSpace := strings.LastIndex(head, " ")
108+
head, partial := line[:lastSpace+1], line[lastSpace+1:]
109+
// log.Printf("head = %q, partial = %q, tail = %q", head, partial, tail)
110+
found := make(map[string]struct{})
111+
match := func(d py.StringDict) {
112+
for k := range d {
113+
if strings.HasPrefix(k, partial) {
114+
if _, ok := found[k]; !ok {
115+
completions = append(completions, k)
116+
found[k] = struct{}{}
117+
}
118+
}
119+
}
120+
}
121+
match(rl.module.Globals)
122+
match(py.Builtins.Globals)
123+
sort.Strings(completions)
124+
return head, completions, tail
125+
}
126+
16127
func Run() {
17-
fmt.Printf("Gpython 3.4.0\n")
18-
bio := bufio.NewReader(os.Stdin)
19128
module := py.NewModule("__main__", "", nil, nil)
129+
rl := newReadline(module)
130+
defer rl.Close()
131+
err := rl.ReadHistory()
132+
if err != nil {
133+
fmt.Printf("Failed to open history: %v\n", err)
134+
}
135+
136+
fmt.Printf("Gpython 3.4.0\n")
20137
prog := "<stdin>"
21138
module.Globals["__file__"] = py.String(prog)
22139
for {
23-
fmt.Printf(">>> ")
24-
line, hasMoreInLine, err := bio.ReadLine()
25-
if err == io.EOF {
26-
break
27-
}
140+
line, err := rl.Prompt(">>> ")
28141
if err != nil {
29-
log.Printf("Error: %v", err)
30-
break
31-
}
32-
if hasMoreInLine {
33-
log.Printf("Line truncated")
142+
if err == io.EOF {
143+
fmt.Printf("\n")
144+
break
145+
}
146+
fmt.Printf("Problem reading line: %v\n", err)
147+
continue
34148
}
149+
rl.AppendHistory(line)
35150
// FIXME need +"\n" because "single" is broken
36151
obj, err := compile.Compile(string(line)+"\n", prog, "single", 0, true)
37152
if err != nil {

0 commit comments

Comments
 (0)
0