|
2 | 2 | package repl
|
3 | 3 |
|
4 | 4 | import (
|
5 |
| - "bufio" |
6 | 5 | "fmt"
|
7 | 6 | "io"
|
8 |
| - "log" |
9 | 7 | "os"
|
| 8 | + "os/user" |
| 9 | + "path/filepath" |
| 10 | + "sort" |
| 11 | + "strings" |
10 | 12 |
|
11 | 13 | "github.com/ncw/gpython/compile"
|
12 | 14 | "github.com/ncw/gpython/py"
|
13 | 15 | "github.com/ncw/gpython/vm"
|
| 16 | + "github.com/peterh/liner" |
14 | 17 | )
|
15 | 18 |
|
| 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 | + |
16 | 127 | func Run() {
|
17 |
| - fmt.Printf("Gpython 3.4.0\n") |
18 |
| - bio := bufio.NewReader(os.Stdin) |
19 | 128 | 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") |
20 | 137 | prog := "<stdin>"
|
21 | 138 | module.Globals["__file__"] = py.String(prog)
|
22 | 139 | for {
|
23 |
| - fmt.Printf(">>> ") |
24 |
| - line, hasMoreInLine, err := bio.ReadLine() |
25 |
| - if err == io.EOF { |
26 |
| - break |
27 |
| - } |
| 140 | + line, err := rl.Prompt(">>> ") |
28 | 141 | 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 |
34 | 148 | }
|
| 149 | + rl.AppendHistory(line) |
35 | 150 | // FIXME need +"\n" because "single" is broken
|
36 | 151 | obj, err := compile.Compile(string(line)+"\n", prog, "single", 0, true)
|
37 | 152 | if err != nil {
|
|
0 commit comments