##// END OF EJS Templates
merge
Ville M. Vainio -
r1085:5e58c777 merge
parent child Browse files
Show More
@@ -1,334 +1,343 b''
1 """ Extension for bzr command tab completer. Supports comlpeting commands and options
2
3 Unlike the core IPython, you should note that this extension is under GPL, not BSD.
4
5 Based on "shell" bzr plugin by Aaron Bentley, license is below. The IPython additions
6 are at the bottom of the file, the rest is left untouched.
7
8 Must be loaded with ip.load('ipy_bzr')
9
10 """
11 a
1 # Copyright (C) 2004, 2005 Aaron Bentley
12 # Copyright (C) 2004, 2005 Aaron Bentley
2 # <aaron@aaronbentley.com>
13 # <aaron@aaronbentley.com>
3 #
14 #
4 # This program is free software; you can redistribute it and/or modify
15 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
17 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
18 # (at your option) any later version.
8 #
19 #
9 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
23 # GNU General Public License for more details.
13 #
24 #
14 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
26 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
28
18 import cmd
29 import cmd
19 from itertools import chain
30 from itertools import chain
20 import os
31 import os
21 import shlex
32 import shlex
22 import stat
33 import stat
23 import string
34 import string
24 import sys
35 import sys
25
36
26 from bzrlib import osutils
37 from bzrlib import osutils
27 from bzrlib.branch import Branch
38 from bzrlib.branch import Branch
28 from bzrlib.config import config_dir, ensure_config_dir_exists
39 from bzrlib.config import config_dir, ensure_config_dir_exists
29 from bzrlib.commands import get_cmd_object, get_all_cmds, get_alias
40 from bzrlib.commands import get_cmd_object, get_all_cmds, get_alias
30 from bzrlib.errors import BzrError
41 from bzrlib.errors import BzrError
31 from bzrlib.workingtree import WorkingTree
42 from bzrlib.workingtree import WorkingTree
32
43 import bzrlib.plugin
33
44
34
45
35 SHELL_BLACKLIST = set(['rm', 'ls'])
46 SHELL_BLACKLIST = set(['rm', 'ls'])
36 COMPLETION_BLACKLIST = set(['shell'])
47 COMPLETION_BLACKLIST = set(['shell'])
37
48
38
49
39 class BlackListedCommand(BzrError):
50 class BlackListedCommand(BzrError):
40 def __init__(self, command):
51 def __init__(self, command):
41 BzrError.__init__(self, "The command %s is blacklisted for shell use" %
52 BzrError.__init__(self, "The command %s is blacklisted for shell use" %
42 command)
53 command)
43
54
44
55
45 class CompletionContext(object):
56 class CompletionContext(object):
46 def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
57 def __init__(self, text, command=None, prev_opt=None, arg_pos=None):
47 self.text = text
58 self.text = text
48 self.command = command
59 self.command = command
49 self.prev_opt = prev_opt
60 self.prev_opt = prev_opt
50 self.arg_pos = None
61 self.arg_pos = None
51
62
52 def get_completions(self):
63 def get_completions(self):
53 try:
64 try:
54 return self.get_completions_or_raise()
65 return self.get_completions_or_raise()
55 except Exception, e:
66 except Exception, e:
56 print e, type(e)
67 print e, type(e)
57 return []
68 return []
58
69
59 def get_option_completions(self):
70 def get_option_completions(self):
60 try:
71 try:
61 command_obj = get_cmd_object(self.command)
72 command_obj = get_cmd_object(self.command)
62 except BzrError:
73 except BzrError:
63 return []
74 return []
64 opts = [o+" " for o in iter_opt_completions(command_obj)]
75 opts = [o+" " for o in iter_opt_completions(command_obj)]
65 return list(filter_completions(opts, self.text))
76 return list(filter_completions(opts, self.text))
66
77
67 def get_completions_or_raise(self):
78 def get_completions_or_raise(self):
68 if self.command is None:
79 if self.command is None:
69 if '/' in self.text:
80 if '/' in self.text:
70 iter = iter_executables(self.text)
81 iter = iter_executables(self.text)
71 else:
82 else:
72 iter = (c+" " for c in iter_command_names() if
83 iter = (c+" " for c in iter_command_names() if
73 c not in COMPLETION_BLACKLIST)
84 c not in COMPLETION_BLACKLIST)
74 return list(filter_completions(iter, self.text))
85 return list(filter_completions(iter, self.text))
75 if self.prev_opt is None:
86 if self.prev_opt is None:
76 completions = self.get_option_completions()
87 completions = self.get_option_completions()
77 if self.command == "cd":
88 if self.command == "cd":
78 iter = iter_dir_completions(self.text)
89 iter = iter_dir_completions(self.text)
79 completions.extend(list(filter_completions(iter, self.text)))
90 completions.extend(list(filter_completions(iter, self.text)))
80 else:
91 else:
81 iter = iter_file_completions(self.text)
92 iter = iter_file_completions(self.text)
82 completions.extend(filter_completions(iter, self.text))
93 completions.extend(filter_completions(iter, self.text))
83 return completions
94 return completions
84
95
85
96
86 class PromptCmd(cmd.Cmd):
97 class PromptCmd(cmd.Cmd):
87
98
88 def __init__(self):
99 def __init__(self):
89 cmd.Cmd.__init__(self)
100 cmd.Cmd.__init__(self)
90 self.prompt = "bzr> "
101 self.prompt = "bzr> "
91 try:
102 try:
92 self.tree = WorkingTree.open_containing('.')[0]
103 self.tree = WorkingTree.open_containing('.')[0]
93 except:
104 except:
94 self.tree = None
105 self.tree = None
95 self.set_title()
106 self.set_title()
96 self.set_prompt()
107 self.set_prompt()
97 self.identchars += '-'
108 self.identchars += '-'
98 ensure_config_dir_exists()
109 ensure_config_dir_exists()
99 self.history_file = osutils.pathjoin(config_dir(), 'shell-history')
110 self.history_file = osutils.pathjoin(config_dir(), 'shell-history')
100 readline.set_completer_delims(string.whitespace)
111 readline.set_completer_delims(string.whitespace)
101 if os.access(self.history_file, os.R_OK) and \
112 if os.access(self.history_file, os.R_OK) and \
102 os.path.isfile(self.history_file):
113 os.path.isfile(self.history_file):
103 readline.read_history_file(self.history_file)
114 readline.read_history_file(self.history_file)
104 self.cwd = os.getcwd()
115 self.cwd = os.getcwd()
105
116
106 def write_history(self):
117 def write_history(self):
107 readline.write_history_file(self.history_file)
118 readline.write_history_file(self.history_file)
108
119
109 def do_quit(self, args):
120 def do_quit(self, args):
110 self.write_history()
121 self.write_history()
111 raise StopIteration
122 raise StopIteration
112
123
113 def do_exit(self, args):
124 def do_exit(self, args):
114 self.do_quit(args)
125 self.do_quit(args)
115
126
116 def do_EOF(self, args):
127 def do_EOF(self, args):
117 print
128 print
118 self.do_quit(args)
129 self.do_quit(args)
119
130
120 def postcmd(self, line, bar):
131 def postcmd(self, line, bar):
121 self.set_title()
132 self.set_title()
122 self.set_prompt()
133 self.set_prompt()
123
134
124 def set_prompt(self):
135 def set_prompt(self):
125 if self.tree is not None:
136 if self.tree is not None:
126 try:
137 try:
127 prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
138 prompt_data = (self.tree.branch.nick, self.tree.branch.revno(),
128 self.tree.relpath('.'))
139 self.tree.relpath('.'))
129 prompt = " %s:%d/%s" % prompt_data
140 prompt = " %s:%d/%s" % prompt_data
130 except:
141 except:
131 prompt = ""
142 prompt = ""
132 else:
143 else:
133 prompt = ""
144 prompt = ""
134 self.prompt = "bzr%s> " % prompt
145 self.prompt = "bzr%s> " % prompt
135
146
136 def set_title(self, command=None):
147 def set_title(self, command=None):
137 try:
148 try:
138 b = Branch.open_containing('.')[0]
149 b = Branch.open_containing('.')[0]
139 version = "%s:%d" % (b.nick, b.revno())
150 version = "%s:%d" % (b.nick, b.revno())
140 except:
151 except:
141 version = "[no version]"
152 version = "[no version]"
142 if command is None:
153 if command is None:
143 command = ""
154 command = ""
144 sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
155 sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version)))
145
156
146 def do_cd(self, line):
157 def do_cd(self, line):
147 if line == "":
158 if line == "":
148 line = "~"
159 line = "~"
149 line = os.path.expanduser(line)
160 line = os.path.expanduser(line)
150 if os.path.isabs(line):
161 if os.path.isabs(line):
151 newcwd = line
162 newcwd = line
152 else:
163 else:
153 newcwd = self.cwd+'/'+line
164 newcwd = self.cwd+'/'+line
154 newcwd = os.path.normpath(newcwd)
165 newcwd = os.path.normpath(newcwd)
155 try:
166 try:
156 os.chdir(newcwd)
167 os.chdir(newcwd)
157 self.cwd = newcwd
168 self.cwd = newcwd
158 except Exception, e:
169 except Exception, e:
159 print e
170 print e
160 try:
171 try:
161 self.tree = WorkingTree.open_containing(".")[0]
172 self.tree = WorkingTree.open_containing(".")[0]
162 except:
173 except:
163 self.tree = None
174 self.tree = None
164
175
165 def do_help(self, line):
176 def do_help(self, line):
166 self.default("help "+line)
177 self.default("help "+line)
167
178
168 def default(self, line):
179 def default(self, line):
169 args = shlex.split(line)
180 args = shlex.split(line)
170 alias_args = get_alias(args[0])
181 alias_args = get_alias(args[0])
171 if alias_args is not None:
182 if alias_args is not None:
172 args[0] = alias_args.pop(0)
183 args[0] = alias_args.pop(0)
173
184
174 commandname = args.pop(0)
185 commandname = args.pop(0)
175 for char in ('|', '<', '>'):
186 for char in ('|', '<', '>'):
176 commandname = commandname.split(char)[0]
187 commandname = commandname.split(char)[0]
177 if commandname[-1] in ('|', '<', '>'):
188 if commandname[-1] in ('|', '<', '>'):
178 commandname = commandname[:-1]
189 commandname = commandname[:-1]
179 try:
190 try:
180 if commandname in SHELL_BLACKLIST:
191 if commandname in SHELL_BLACKLIST:
181 raise BlackListedCommand(commandname)
192 raise BlackListedCommand(commandname)
182 cmd_obj = get_cmd_object(commandname)
193 cmd_obj = get_cmd_object(commandname)
183 except (BlackListedCommand, BzrError):
194 except (BlackListedCommand, BzrError):
184 return os.system(line)
195 return os.system(line)
185
196
186 try:
197 try:
187 if too_complicated(line):
198 if too_complicated(line):
188 return os.system("bzr "+line)
199 return os.system("bzr "+line)
189 else:
200 else:
190 return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
201 return (cmd_obj.run_argv_aliases(args, alias_args) or 0)
191 except BzrError, e:
202 except BzrError, e:
192 print e
203 print e
193 except KeyboardInterrupt, e:
204 except KeyboardInterrupt, e:
194 print "Interrupted"
205 print "Interrupted"
195 except Exception, e:
206 except Exception, e:
196 # print "Unhandled error:\n%s" % errors.exception_str(e)
207 # print "Unhandled error:\n%s" % errors.exception_str(e)
197 print "Unhandled error:\n%s" % (e)
208 print "Unhandled error:\n%s" % (e)
198
209
199
210
200 def completenames(self, text, line, begidx, endidx):
211 def completenames(self, text, line, begidx, endidx):
201 return CompletionContext(text).get_completions()
212 return CompletionContext(text).get_completions()
202
213
203 def completedefault(self, text, line, begidx, endidx):
214 def completedefault(self, text, line, begidx, endidx):
204 """Perform completion for native commands.
215 """Perform completion for native commands.
205
216
206 :param text: The text to complete
217 :param text: The text to complete
207 :type text: str
218 :type text: str
208 :param line: The entire line to complete
219 :param line: The entire line to complete
209 :type line: str
220 :type line: str
210 :param begidx: The start of the text in the line
221 :param begidx: The start of the text in the line
211 :type begidx: int
222 :type begidx: int
212 :param endidx: The end of the text in the line
223 :param endidx: The end of the text in the line
213 :type endidx: int
224 :type endidx: int
214 """
225 """
215 (cmd, args, foo) = self.parseline(line)
226 (cmd, args, foo) = self.parseline(line)
216 if cmd == "bzr":
227 if cmd == "bzr":
217 cmd = None
228 cmd = None
218 return CompletionContext(text, command=cmd).get_completions()
229 return CompletionContext(text, command=cmd).get_completions()
219
230
220
231
221 def run_shell():
232 def run_shell():
222 try:
233 try:
223 prompt = PromptCmd()
234 prompt = PromptCmd()
224 try:
235 try:
225 prompt.cmdloop()
236 prompt.cmdloop()
226 finally:
237 finally:
227 prompt.write_history()
238 prompt.write_history()
228 except StopIteration:
239 except StopIteration:
229 pass
240 pass
230
241
231
242
232 def iter_opt_completions(command_obj):
243 def iter_opt_completions(command_obj):
233 for option_name, option in command_obj.options().items():
244 for option_name, option in command_obj.options().items():
234 yield "--" + option_name
245 yield "--" + option_name
235 short_name = option.short_name()
246 short_name = option.short_name()
236 if short_name:
247 if short_name:
237 yield "-" + short_name
248 yield "-" + short_name
238
249
239
250
240 def iter_file_completions(arg, only_dirs = False):
251 def iter_file_completions(arg, only_dirs = False):
241 """Generate an iterator that iterates through filename completions.
252 """Generate an iterator that iterates through filename completions.
242
253
243 :param arg: The filename fragment to match
254 :param arg: The filename fragment to match
244 :type arg: str
255 :type arg: str
245 :param only_dirs: If true, match only directories
256 :param only_dirs: If true, match only directories
246 :type only_dirs: bool
257 :type only_dirs: bool
247 """
258 """
248 cwd = os.getcwd()
259 cwd = os.getcwd()
249 if cwd != "/":
260 if cwd != "/":
250 extras = [".", ".."]
261 extras = [".", ".."]
251 else:
262 else:
252 extras = []
263 extras = []
253 (dir, file) = os.path.split(arg)
264 (dir, file) = os.path.split(arg)
254 if dir != "":
265 if dir != "":
255 listingdir = os.path.expanduser(dir)
266 listingdir = os.path.expanduser(dir)
256 else:
267 else:
257 listingdir = cwd
268 listingdir = cwd
258 for file in chain(os.listdir(listingdir), extras):
269 for file in chain(os.listdir(listingdir), extras):
259 if dir != "":
270 if dir != "":
260 userfile = dir+'/'+file
271 userfile = dir+'/'+file
261 else:
272 else:
262 userfile = file
273 userfile = file
263 if userfile.startswith(arg):
274 if userfile.startswith(arg):
264 if os.path.isdir(listingdir+'/'+file):
275 if os.path.isdir(listingdir+'/'+file):
265 userfile+='/'
276 userfile+='/'
266 yield userfile
277 yield userfile
267 elif not only_dirs:
278 elif not only_dirs:
268 yield userfile + ' '
279 yield userfile + ' '
269
280
270
281
271 def iter_dir_completions(arg):
282 def iter_dir_completions(arg):
272 """Generate an iterator that iterates through directory name completions.
283 """Generate an iterator that iterates through directory name completions.
273
284
274 :param arg: The directory name fragment to match
285 :param arg: The directory name fragment to match
275 :type arg: str
286 :type arg: str
276 """
287 """
277 return iter_file_completions(arg, True)
288 return iter_file_completions(arg, True)
278
289
279
290
280 def iter_command_names(hidden=False):
291 def iter_command_names(hidden=False):
281 for real_cmd_name, cmd_class in get_all_cmds():
292 for real_cmd_name, cmd_class in get_all_cmds():
282 if not hidden and cmd_class.hidden:
293 if not hidden and cmd_class.hidden:
283 continue
294 continue
284 for name in [real_cmd_name] + cmd_class.aliases:
295 for name in [real_cmd_name] + cmd_class.aliases:
285 # Don't complete on aliases that are prefixes of the canonical name
296 # Don't complete on aliases that are prefixes of the canonical name
286 if name == real_cmd_name or not real_cmd_name.startswith(name):
297 if name == real_cmd_name or not real_cmd_name.startswith(name):
287 yield name
298 yield name
288
299
289
300
290 def iter_executables(path):
301 def iter_executables(path):
291 dirname, partial = os.path.split(path)
302 dirname, partial = os.path.split(path)
292 for filename in os.listdir(dirname):
303 for filename in os.listdir(dirname):
293 if not filename.startswith(partial):
304 if not filename.startswith(partial):
294 continue
305 continue
295 fullpath = os.path.join(dirname, filename)
306 fullpath = os.path.join(dirname, filename)
296 mode=os.lstat(fullpath)[stat.ST_MODE]
307 mode=os.lstat(fullpath)[stat.ST_MODE]
297 if stat.S_ISREG(mode) and 0111 & mode:
308 if stat.S_ISREG(mode) and 0111 & mode:
298 yield fullpath + ' '
309 yield fullpath + ' '
299
310
300
311
301 def filter_completions(iter, arg):
312 def filter_completions(iter, arg):
302 return (c for c in iter if c.startswith(arg))
313 return (c for c in iter if c.startswith(arg))
303
314
304
315
305 def iter_munged_completions(iter, arg, text):
316 def iter_munged_completions(iter, arg, text):
306 for completion in iter:
317 for completion in iter:
307 completion = str(completion)
318 completion = str(completion)
308 if completion.startswith(arg):
319 if completion.startswith(arg):
309 yield completion[len(arg)-len(text):]+" "
320 yield completion[len(arg)-len(text):]+" "
310
321
311
322
312 def too_complicated(line):
323 def too_complicated(line):
313 for char in '|<>*?':
324 for char in '|<>*?':
314 if char in line:
325 if char in line:
315 return True
326 return True
316 return False
327 return False
317
328
318
329
319 ### IPython mods start
330 ### IPython mods start
320
331
321 def init_ipython(ip):
332 def init_ipython(ip):
322 def bzr_completer(self,ev):
333 def bzr_completer(self,ev):
323 #print "bzr complete"
334 #print "bzr complete"
324 tup = ev.line.split(None,2)
335 tup = ev.line.split(None,2)
325 if len(tup) > 2:
336 if len(tup) > 2:
326 cmd = tup[1]
337 cmd = tup[1]
327 else:
338 else:
328 cmd = None
339 cmd = None
329
340
330 return CompletionContext(ev.symbol, command = cmd).get_completions()
341 return CompletionContext(ev.symbol, command = cmd).get_completions()
342 bzrlib.plugin.load_plugins()
331 ip.set_hook('complete_command', bzr_completer, str_key = 'bzr')
343 ip.set_hook('complete_command', bzr_completer, str_key = 'bzr')
332
333
334
General Comments 0
You need to be logged in to leave comments. Login now