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 != "" {
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy