initial revision of pycol
[pycol.git] / pycol.py
1 #!/usr/bin/python3
2
3 import readline
4 import clicmd
5 import shlex
6 import sys
7 import traceback
8
9 class Cmdline(object):
10     def __init__(self):
11         self.contexts = {}
12         self.prompt = "> "
13         self.context = None
14         self.completions = []
15
16         # Call the completer when tab is hit
17         readline.set_completer(self.complete)
18         readline.parse_and_bind('tab: complete')
19
20         readline.set_completion_display_matches_hook(self.display_matches)
21
22         # remove some word breaks
23         delims = ' \t\n'
24         readline.set_completer_delims(delims)
25
26     def display_matches(self, sustitution, matches, longest_match_length):
27         print()
28         for m in matches:
29             print("   %s"%m)
30         print("%s%s"%(self.prompt, readline.get_line_buffer()),
31               end = "", flush = True)
32         readline.forced_update_display()
33
34     def add_context(self, name, ctx):
35         assert(not name in self.contexts), "duplicate context name %s"%(name)
36         self.contexts[name] = ctx
37
38     def set_context(self, name):
39         self.context = self.contexts[name]
40
41     def set_prompt(self, prompt):
42         self.prompt = prompt
43
44     def complete(self, text, state):
45         response = None
46
47         # state is not 0, the list of completions is already stored in
48         # self.completions[]: just return the next one
49         if state != 0:
50             if state >= len(self.completions):
51                 return None
52             return self.completions[state]
53
54         # else, try to build the completion list
55         try:
56             line = readline.get_line_buffer() # full line
57             end = readline.get_endidx() # cursor
58
59             #print("<%s>"%origline[:end])
60             in_buf = line[:end]
61
62             tokens = shlex.split(in_buf, comments = False) # XXX check all calls to shlex
63             # whitespace after the first token, it means we want to complete
64             # the second token
65             if len(tokens) == 0 or not in_buf.endswith(tokens[-1]):
66                 tokens.append("")
67             completions = self.context.complete(tokens)
68
69             # Build the completion list in the readline format (a list of
70             # string). It may include the help if tab is pressed twice.
71             self.completions = []
72             completion_type = readline.get_completion_type()
73             # completion_key = readline.get_completion_invoking_key() # XXX
74             completion_key = 0
75             if chr(completion_type) == "?":
76                 for c in completions:
77                     help_str = c.cmd.get_help()
78                     if c.token == "":
79                         self.completions.append(c.cmd.to_expr() + ": " + help_str)
80                     elif help_str == "":
81                         self.completions.append(c.token)
82                     else:
83                         self.completions.append(c.token + ": " + help_str)
84                 # check if it matches the command, in this case display [return]
85                 # XXX does it work well?
86                 result = None
87                 for cmd in self.context:
88                     result = cmd.match(tokens)
89                     if result != None:
90                         self.completions.append("[return]")
91                         break
92             else:
93                 for c in completions:
94                     if c.token == "":
95                         continue
96                     if c.terminal == True:
97                         self.completions.append(c.token + " ")
98                     else:
99                         self.completions.append(c.token)
100
101             if len(self.completions) == 0:
102                 return None
103
104             return self.completions[0]
105
106         except:
107             traceback.print_exc()
108
109         return None
110
111     def input_loop(self):
112         line = ''
113         while line != 'stop':
114             try:
115                 line = input(self.prompt)
116             except KeyboardInterrupt:
117                 print()
118                 continue
119             except EOFError:
120                 print()
121                 return
122             tokens = shlex.split(line, comments = True) # XXX
123             if len(tokens) == 0:
124                 continue
125             assert(self.context != None), "context not set, use set_context()"
126             result = None
127             for cmd in self.context:
128                 result = cmd.match(tokens)
129                 if result != None:
130                     break
131             if result == None:
132                 print("Invalid command or syntax error")
133             else:
134                 assert(cmd.cb != None), \
135                     "no callback function for %s"%(cmd)
136                 cmd.cb(self, result)
137
138