[gedit-plugins] Added new plugin, commander



commit 8f357dfc249d3ce59e51f39573c39160b2e2209f
Author: Jesse van den Kieboom <jesse icecrew nl>
Date:   Sat Jan 16 20:16:05 2010 +0100

    Added new plugin, commander

 configure.ac                                       |   12 +-
 plugins/commander/Makefile.am                      |   20 +
 .../commander/commander.gedit-plugin.desktop.in.in |   10 +
 plugins/commander/commander/Makefile.am            |   18 +
 plugins/commander/commander/__init__.py            |   42 ++
 plugins/commander/commander/commands/Makefile.am   |   14 +
 plugins/commander/commander/commands/__init__.py   |  372 +++++++++++++
 plugins/commander/commander/commands/completion.py |  202 +++++++
 plugins/commander/commander/commands/exceptions.py |    6 +
 plugins/commander/commander/commands/method.py     |   95 ++++
 plugins/commander/commander/commands/module.py     |  129 +++++
 plugins/commander/commander/commands/result.py     |   41 ++
 .../commander/commands/rollbackimporter.py         |   36 ++
 plugins/commander/commander/drawing.py             |   79 +++
 plugins/commander/commander/entry.py               |  567 ++++++++++++++++++++
 plugins/commander/commander/history.py             |   71 +++
 plugins/commander/commander/info.py                |  330 ++++++++++++
 plugins/commander/commander/transparentwindow.py   |   70 +++
 plugins/commander/commander/utils.py               |   43 ++
 plugins/commander/commander/windowhelper.py        |   38 ++
 plugins/commander/modules/Makefile.am              |   19 +
 plugins/commander/modules/bookmark.py              |   62 +++
 plugins/commander/modules/doc.py                   |  216 ++++++++
 plugins/commander/modules/edit.py                  |  198 +++++++
 plugins/commander/modules/find/Makefile.am         |   11 +
 plugins/commander/modules/find/__init__.py         |   76 +++
 plugins/commander/modules/find/finder.py           |  273 ++++++++++
 plugins/commander/modules/find/regex.py            |  124 +++++
 plugins/commander/modules/find/test.py             |   76 +++
 plugins/commander/modules/format.py                |  100 ++++
 plugins/commander/modules/goto.py                  |   34 ++
 plugins/commander/modules/help.py                  |   53 ++
 plugins/commander/modules/move.py                  |   93 ++++
 plugins/commander/modules/reload.py                |   29 +
 plugins/commander/modules/set.py                   |  135 +++++
 plugins/commander/modules/shell.py                 |  175 ++++++
 36 files changed, 3866 insertions(+), 3 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 242b336..9bdbf18 100644
--- a/configure.ac
+++ b/configure.ac
@@ -89,9 +89,9 @@ ALL_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 USEFUL_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 DEFAULT_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 
-PYTHON_ALL_PLUGINS="bracketcompletion codecomment colorpicker joinlines multiedit sessionsaver smartspaces terminal"
-PYTHON_USEFUL_PLUGINS="bracketcompletion codecomment colorpicker joinlines multiedit sessionsaver smartspaces terminal"
-PYTHON_DEFAULT_PLUGINS="bracketcompletion codecomment colorpicker joinlines multiedit sessionsaver smartspaces terminal"
+PYTHON_ALL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
+PYTHON_USEFUL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
+PYTHON_DEFAULT_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
 
 DIST_PLUGINS="$ALL_PLUGINS $PYTHON_ALL_PLUGINS"
 
@@ -423,6 +423,12 @@ plugins/codecomment/Makefile
 plugins/codecomment/codecomment.gedit-plugin.desktop.in
 plugins/colorpicker/Makefile
 plugins/colorpicker/colorpicker.gedit-plugin.desktop.in
+plugins/commander/Makefile
+plugins/commander/commander.gedit-plugin.desktop.in
+plugins/commander/commander/Makefile
+plugins/commander/commander/commands/Makefile
+plugins/commander/modules/Makefile
+plugins/commander/modules/find/Makefile
 plugins/drawspaces/Makefile
 plugins/drawspaces/drawspaces.gedit-plugin.desktop.in
 plugins/joinlines/Makefile
diff --git a/plugins/commander/Makefile.am b/plugins/commander/Makefile.am
new file mode 100644
index 0000000..30ce6da
--- /dev/null
+++ b/plugins/commander/Makefile.am
@@ -0,0 +1,20 @@
+# Commander
+
+SUBDIRS = commander modules
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = commander.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po)
+	$(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/commander/commander.gedit-plugin.desktop.in.in b/plugins/commander/commander.gedit-plugin.desktop.in.in
new file mode 100644
index 0000000..1e3b0f6
--- /dev/null
+++ b/plugins/commander/commander.gedit-plugin.desktop.in.in
@@ -0,0 +1,10 @@
+[Gedit Plugin]
+Loader=python
+Module=commander
+IAge=2
+_Name=Commander
+_Description=Command line interface for advanced editing
+Authors=Jesse van den Kieboom <jessevdk gnome org>
+Copyright=Copyright © 2009 Jesse van den Kieboom
+Website=http://www.gedit.org
+Version= VERSION@
diff --git a/plugins/commander/commander/Makefile.am b/plugins/commander/commander/Makefile.am
new file mode 100644
index 0000000..a1532c4
--- /dev/null
+++ b/plugins/commander/commander/Makefile.am
@@ -0,0 +1,18 @@
+# Commander
+
+SUBDIRS = commands
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/commander
+
+plugin_PYTHON =	\
+	drawing.py \
+	entry.py \
+	history.py \
+	info.py \
+	__init__.py \
+	transparentwindow.py \
+	utils.py \
+	windowhelper.py
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/commander/commander/__init__.py b/plugins/commander/commander/__init__.py
new file mode 100644
index 0000000..f02693b
--- /dev/null
+++ b/plugins/commander/commander/__init__.py
@@ -0,0 +1,42 @@
+import os
+import sys
+
+path = os.path.dirname(__file__)
+
+if not path in sys.path:
+	sys.path.insert(0, path)
+
+import gedit
+from windowhelper import WindowHelper
+import commands
+
+class Commander(gedit.Plugin):
+	def __init__(self):
+		gedit.Plugin.__init__(self)
+		
+		self._instances = {}
+		self._path = os.path.dirname(__file__)
+		
+		if not self._path in sys.path:
+			sys.path.insert(0, self._path)
+
+		commands.Commands().set_dirs([
+			os.path.expanduser('~/.gnome2/gedit/commander/modules'),
+			os.path.join(self.get_data_dir(), 'modules')
+		])
+		
+	def activate(self, window):
+		self._instances[window] = WindowHelper(self, window)
+
+	def deactivate(self, window):
+		self._instances[window].deactivate()
+		del self._instances[window]
+		
+		if len(self._instances) == 0:
+			commands.Commands().stop()
+			
+			if self._path in sys.path:
+				sys.path.remove(self._path)
+
+	def update_ui(self, window):
+		self._instances[window].update_ui()
diff --git a/plugins/commander/commander/commands/Makefile.am b/plugins/commander/commander/commands/Makefile.am
new file mode 100644
index 0000000..00018e2
--- /dev/null
+++ b/plugins/commander/commander/commands/Makefile.am
@@ -0,0 +1,14 @@
+# Commander
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/commander/commands
+
+plugin_PYTHON =	\
+	completion.py \
+	exceptions.py \
+	__init__.py \
+	method.py \
+	module.py \
+	result.py \
+	rollbackimporter.py
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/commander/commander/commands/__init__.py b/plugins/commander/commander/commands/__init__.py
new file mode 100644
index 0000000..d7fac9d
--- /dev/null
+++ b/plugins/commander/commander/commands/__init__.py
@@ -0,0 +1,372 @@
+import os
+import gio
+import sys
+import bisect
+import types
+import shlex
+import glib
+import re
+import os
+
+import module
+import method
+import result
+import exceptions
+
+__all__ = ['is_commander_module', 'Commands']
+
+def attrs(**kwargs):
+	def generator(f):
+		for k in kwargs:
+			setattr(f, k, kwargs[k])
+		
+		return f
+	
+	return generator
+
+def autocomplete(d={}, **kwargs):
+	ret = {}
+	
+	for dic in (d, kwargs):
+		for k in dic:
+			if type(dic[k]) == types.FunctionType:
+				ret[k] = dic[k]
+
+	return attrs(autocomplete=ret)
+
+def is_commander_module(mod):
+	if type(mod) == types.ModuleType:
+		return mod and ('__commander_module__' in mod.__dict__)
+	else:
+		mod = str(mod)
+		return mod.endswith('.py') or (os.path.isdir(mod) and os.path.isfile(os.path.join(mod, '__init__.py')))
+
+class Singleton(object):
+	_instance = None
+
+	def __new__(cls, *args, **kwargs):
+		if not cls._instance:
+			cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
+			cls._instance.__init_once__()
+
+		return cls._instance
+
+class Commands(Singleton):
+	class Continuated:
+		def __init__(self, generator):
+			self.generator = generator
+			self.retval = None
+	
+		def autocomplete_func(self):
+			if retval == result.Result.PROMPT:
+				return retval.autocomplete
+			else:
+				return {}
+
+	class State:
+		def __init__(self):
+			self.clear()
+		
+		def clear(self):
+			self.stack = []
+
+		def top(self):
+			return self.stack[0]
+		
+		def run(self, ret):
+			ct = self.top()
+			
+			if ret:
+				ct.retval = ct.generator.send(ret)
+			else:
+				ct.retval = ct.generator.next()
+			
+			return ct.retval
+
+		def push(self, gen):
+			self.stack.insert(0, Commands.Continuated(gen))
+
+		def pop(self):
+			if not self.stack:
+				return
+
+			try:
+				self.stack[0].generator.close()
+			except GeneratorExit:
+				pass
+			
+			del self.stack[0]
+
+		def __len__(self):
+			return len(self.stack)
+
+		def __nonzero__(self):
+			return len(self) != 0
+
+	def __init_once__(self):
+		self._modules = None
+		self._dirs = []
+		self._monitors = []
+		
+		self._timeouts = {}
+		
+		self._stack = []
+	
+	def set_dirs(self, dirs):
+		self._dirs = dirs
+	
+	def stop(self):
+		for mon in self._monitors:
+			mon.cancel()
+		
+		self._monitors = []
+		self._modules = None
+		
+		for k in self._timeouts:
+			glib.source_remove(self._timeouts[k])
+		
+		self._timeouts = {}
+	
+	def modules(self):
+		self.ensure()
+		return list(self._modules)
+	
+	def add_monitor(self, d):
+		gfile = gio.File(d)
+		monitor = None
+		
+		try:
+			monitor = gfile.monitor_directory(gio.FILE_MONITOR_NONE, None)
+		except gio.Error, e:
+			# Could not create monitor, this happens on systems where file monitoring is
+			# not supported, but we don't really care
+			pass
+
+		if monitor:
+			monitor.connect('changed', self.on_monitor_changed)
+			self._monitors.append(monitor)
+	
+	def scan(self, d):
+		files = []
+		
+		try:
+			files = os.listdir(d)
+		except OSError:
+			pass
+		
+		for f in files:
+			full = os.path.join(d, f)
+
+			# Test for python files or modules
+			if is_commander_module(full):
+				if self.add_module(full) and os.path.isdir(full):
+					# Add monitor on the module directory if module was 
+					# successfully added. TODO: recursively add monitors
+					self.add_monitor(full)
+		
+		# Add a monitor on the scanned directory itself
+		self.add_monitor(d)
+		
+	def module_name(self, filename):
+		# Module name is the basename without the .py
+		return os.path.basename(os.path.splitext(filename)[0])
+		
+	def add_module(self, filename):
+		base = self.module_name(filename)
+		
+		# Check if module already exists
+		if base in self._modules:
+			return
+		
+		# Create new 'empty' module
+		mod = module.Module(base, os.path.dirname(filename))
+		bisect.insort_right(self._modules, mod)
+		
+		# Reload the module
+		self.reload_module(mod)
+		return True
+		
+	def ensure(self):
+		# Ensure that modules have been scanned
+		if self._modules != None:
+			return
+
+		self._modules = []
+		
+		for d in self._dirs:
+			self.scan(d)
+
+	def _run_generator(self, state, ret=None):
+		try:
+			# Determine first use
+			retval = state.run(ret)
+
+			if not retval or (isinstance(retval, result.Result) and (retval == result.DONE or retval == result.HIDE)):
+				state.pop()
+				 
+				if state:
+					return self._run_generator(state)
+
+			return self.run(state, retval)
+			
+		except StopIteration:
+			state.pop()
+
+			if state:
+				return self.run(state)
+		except Exception, e:
+			# Something error like, we throw on the parent generator
+			state.pop()
+			
+			if state:
+				state.top().generator.throw(type(e), e)
+			else:
+				# Re raise it for the top most to show the error
+				raise e
+
+		return None	
+	
+	def run(self, state, ret):
+		if type(ret) == types.GeneratorType:
+			# Ok, this is cool stuff, generators can ask and susped execution
+			# of commands, for instance to prompt for some more information
+			state.push(ret)
+			
+			return self._run_generator(state)
+		elif not isinstance(ret, result.Result) and len(state) > 1:
+			# Basicly, send it to the previous?
+			state.pop()
+
+			return self._run_generator(state, ret)
+		else:
+			return ret
+	
+	def execute(self, state, argstr, words, wordsstr, entry, modifier):
+		self.ensure()
+		
+		if state:			
+			return self._run_generator(state, [argstr, words, modifier])
+
+		cmd = completion.single_command(wordsstr, 0)
+		
+		if not cmd:
+			raise exceptions.Execute('Could not find command: ' + wordsstr[0])
+
+		if len(words) > 1:
+			argstr = argstr[words[1].start(0):]
+		else:
+			argstr = ''
+		
+		# Execute command
+		return self.run(state, cmd.execute(argstr, wordsstr[1:], entry, modifier))
+	
+	def invoke(self, entry, modifier, command, args, argstr=None):
+		self.ensure()
+		
+		cmd = completion.single_command([command], 0)
+		
+		if not cmd:
+			raise exceptions.Execute('Could not find command: ' + command)
+		
+		if argstr == None:
+			argstr = ' '.join(args)
+
+		ret = cmd.execute(argstr, args, entry, modifier)
+		
+		if type(ret) == types.GeneratorType:
+			raise exceptions.Execute('Cannot invoke commands that yield (yet)')
+		else:
+			return ret
+	
+	def resolve_module(self, path, load=True):
+		if not self._modules or not is_commander_module(path):
+			return None
+
+		# Strip off __init__.py for module kind of modules
+		if path.endswith('__init__.py'):
+			path = os.path.dirname(path)
+
+		base = self.module_name(path)
+
+		# Find module
+		idx = bisect.bisect_left(self._modules, base)
+		mod = None
+		
+		if idx < len(self._modules):
+			mod = self._modules[idx]
+
+		if not mod or mod.name != base:
+			if load:
+				self.add_module(path)
+
+			return None
+		
+		return mod
+
+	def remove_module_root(self, mod):
+		for r in mod.roots():
+			if r in self._modules:
+				self._modules.remove(r)
+	
+	def reload_module(self, mod):
+		if isinstance(mod, basestring):
+			mod = self.resolve_module(mod)
+		
+		if not mod or not self._modules:
+			return
+
+		# Remove roots
+		self.remove_module_root(mod)
+
+		# Now, try to reload the module
+		try:
+			mod.reload()
+		except Exception, e:
+			# Reload failed, we remove the module
+			print 'Failed to reload module:', e
+
+			self._modules.remove(mod)
+			return
+
+		# Insert roots
+		for r in mod.roots():
+			bisect.insort(self._modules, r)
+
+	def on_timeout_delete(self, path, mod):
+		if not path in self._timeouts:
+			return False
+		
+		# Remove the module
+		mod.unload()
+		self.remove_module_root(mod)
+		self._modules.remove(mod)
+
+		return False
+
+	def on_monitor_changed(self, monitor, gfile1, gfile2, evnt):
+		if evnt == gio.FILE_MONITOR_EVENT_CHANGED:
+			# Reload the module
+			self.reload_module(gfile1.get_path())
+		elif evnt == gio.FILE_MONITOR_EVENT_DELETED:
+			path = gfile1.get_path()
+			mod = self.resolve_module(path, False)
+			
+			if not mod:
+				return
+
+			if path in self._timeouts:
+				glib.source_remove(self._timeouts[path])
+			
+			# We add a timeout because a common save strategy causes a
+			# DELETE/CREATE event chain
+			self._timeouts[path] = glib.timeout_add(500, self.on_timeout_delete, path, mod)
+		elif evnt == gio.FILE_MONITOR_EVENT_CREATED:
+			path = gfile1.get_path()
+			
+			# Check if this CREATE followed a previous DELETE
+			if path in self._timeouts:
+				glib.source_remove(self._timeouts[path])
+				del self._timeouts[path]
+			
+			# Reload the module
+			self.reload_module(path)
diff --git a/plugins/commander/commander/commands/completion.py b/plugins/commander/commander/commands/completion.py
new file mode 100644
index 0000000..5d1a778
--- /dev/null
+++ b/plugins/commander/commander/commands/completion.py
@@ -0,0 +1,202 @@
+import commander.commands as commands
+import bisect
+import sys
+import os
+import re
+import gio
+
+from xml.sax import saxutils
+
+__all__ = ['command', 'filename']
+
+def _common_prefix_part(first, second):
+	length = min(len(first), len(second))
+	
+	for i in range(0, length):
+		if first[i] != second[i]:
+			return first[:i]
+
+	return first[:length]
+	
+def common_prefix(args, sep=None):
+	# A common prefix can be something like
+	# first: some-thing
+	# second: sho-tar
+	# res: s-t
+	args = list(args)
+
+	if not args:
+		return ''
+
+	if len(args) == 1:
+		return str(args[0])
+	
+	first = str(args[0])
+	second = str(args[1])
+
+	if not sep:
+		ret = _common_prefix_part(first, second)
+	else:
+		first = first.split(sep)
+		second = second.split(sep)
+		ret = []
+
+		for i in range(0, min(len(first), len(second))):
+			ret.append(_common_prefix_part(first[i], second[i]))
+		
+		ret = sep.join(ret)
+			
+	del args[0]
+	args[0] = ret
+
+	return common_prefix(args, sep)
+
+def _expand_commands(cmds):
+	if not cmds:
+		cmds.extend(commands.Commands().modules())
+		return
+
+	old = list(cmds)
+	del cmds[:]
+
+	# Expand 'commands' to all the respective subcommands
+	
+	for cmd in old:
+		for c in cmd.commands():
+			bisect.insort(cmds, c)
+
+def _filter_command(cmd, subs):
+	parts = cmd.name.split('-')
+	
+	if len(subs) > len(parts):
+		return False
+	
+	for i in xrange(0, len(subs)):
+		if not parts[i].startswith(subs[i]):
+			return False
+	
+	return True
+
+def _filter_commands(cmds, subs):
+	# See what parts of cmds still match the parts in subs
+	idx = bisect.bisect_left(cmds, subs[0])
+	ret = []
+	
+	while idx < len(cmds):
+		if not cmds[idx].name.startswith(subs[0]):
+			break
+		
+		if _filter_command(cmds[idx], subs):
+			ret.append(cmds[idx])
+		
+		idx += 1
+	
+	return ret
+
+def single_command(words, idx):
+	ret = command(words, idx)
+
+	if not ret:
+		return None
+
+	ret[0] = filter(lambda x: x.method, ret[0])
+	
+	if not ret[0]:
+		return None
+	
+	return ret[0][0]
+
+def command(words, idx):
+	s = words[idx].strip()
+	
+	if not s:
+		return None
+	
+	parts = s.split('.')
+	cmds = []
+
+	for i in parts:
+		# Expand all the parents to their child commands
+		_expand_commands(cmds)
+
+		if not cmds:
+			return None
+		
+		subs = i.split('-')	
+		cmds = _filter_commands(cmds, subs)
+
+		if not cmds:
+			return None
+	
+	if not cmds:
+		return None
+	
+	if len(parts) == 1:
+		completed = common_prefix(cmds)
+	else:
+		completed = '.'.join(parts[0:-1]) + '.' + common_prefix(cmds, '-')
+
+	return [cmds, completed]
+
+def _file_color(path):
+	if os.path.isdir(path):
+		format = '<span color="#799ec6">%s</span>'
+	else:
+		format = '%s'
+	
+	return format % (saxutils.escape(os.path.basename(path)),)
+
+def _sort_nicely(l): 
+	convert = lambda text: int(text) if text.isdigit() else text
+	alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
+
+	l.sort(key=alphanum_key)
+
+def filename(words, idx, view):
+	prefix = os.path.dirname(words[idx])
+	partial = os.path.expanduser(words[idx])
+	
+	doc = view.get_buffer()
+	
+	if not doc.is_untitled():
+		root = os.path.dirname(gio.File(doc.get_uri()).get_path())
+	else:
+		root = os.path.expanduser('~/')
+		
+	if not os.path.isabs(partial):
+		partial = os.path.join(root, partial)
+	
+	dirname = os.path.dirname(partial)
+	
+	try:
+		files = os.listdir(dirname)
+	except OSError:
+		return None
+	
+	base = os.path.basename(partial)
+	ret = []
+	real = []
+
+	for f in files:
+		if f.startswith(base) and (base or not f.startswith('.')):
+			real.append(os.path.join(dirname, f))
+			ret.append(os.path.join(prefix, f))
+	
+	_sort_nicely(real)
+	
+	if len(ret) == 1:
+		if os.path.isdir(real[0]):
+			after = '/'
+		else:
+			after = ' '
+
+		return ret, ret[0], after
+	else:
+		return map(lambda x: _file_color(x), real), common_prefix(ret)
+
+def words(ret):
+	def decorator(words, idx):
+		rr = filter(lambda x: x.startswith(words[idx]), ret)
+		return rr, common_prefix(rr)
+	
+	return decorator
diff --git a/plugins/commander/commander/commands/exceptions.py b/plugins/commander/commander/commands/exceptions.py
new file mode 100644
index 0000000..a1f2ea2
--- /dev/null
+++ b/plugins/commander/commander/commands/exceptions.py
@@ -0,0 +1,6 @@
+class Execute(Exception):
+	def __init__(self, msg):
+		self.msg = msg
+	
+	def __str__(self):
+		return self.msg
diff --git a/plugins/commander/commander/commands/method.py b/plugins/commander/commander/commands/method.py
new file mode 100644
index 0000000..cc8ef58
--- /dev/null
+++ b/plugins/commander/commander/commands/method.py
@@ -0,0 +1,95 @@
+import exceptions
+import types
+import inspect
+import sys
+import commander.utils as utils
+
+class Method:
+	def __init__(self, method, name, parent):
+		self.method = method
+		self.name = name.replace('_', '-')
+		self.parent = parent
+		self._func_props = None
+	
+	def __str__(self):
+		return self.name
+
+	def autocomplete_func(self):
+		if hasattr(self.method, 'autocomplete'):
+			return getattr(self.method, 'autocomplete')
+
+		return None
+	
+	def args(self):
+		fp = self.func_props()
+		
+		return fp.args, fp.varargs
+		
+	def func_props(self):
+		if not self._func_props:
+			# Introspect the function arguments
+			self._func_props = utils.getargspec(self.method)
+		
+		return self._func_props
+	
+	def commands(self):
+		return []
+	
+	def cancel(self, view):
+		if self.parent:
+			self.parent.cancel(view, self)
+	
+	def cancel_continuation(self, view):
+		if self.parent:
+			self.parent.continuation(view, self)
+
+	def doc(self):
+		if self.method.__doc__:
+			return self.method.__doc__
+		else:
+			return ''
+
+	def oneline_doc(self):
+		return self.doc().split("\n")[0]
+	
+	def execute(self, argstr, words, entry, modifier):
+		fp = self.func_props()
+		
+		kwargs = {'argstr': argstr, 'args': words, 'entry': entry, 'view': entry.view(), 'modifier': modifier, 'window': entry.view().get_toplevel()}
+		oargs = list(fp.args)
+		args = []
+		idx = 0
+		
+		if fp.defaults:
+			numdef = len(fp.defaults)
+		else:
+			numdef = 0
+		
+		for k in fp.args:
+			if k in kwargs:
+				args.append(kwargs[k])
+				oargs.remove(k)
+				del kwargs[k]
+			elif idx >= len(words):
+				if numdef < len(oargs):
+					raise exceptions.Execute('Invalid number of arguments (need %s)' % (oargs[0],))
+			else:
+				args.append(words[idx])
+				oargs.remove(k)
+				idx += 1
+
+		# Append the rest if it can handle varargs
+		if fp.varargs and idx < len(words):
+			args.extend(words[idx:])
+
+		if not fp.keywords:
+			kwargs = {}
+
+		return self.method(*args, **kwargs)
+
+	def __cmp__(self, other):
+		if isinstance(other, Method):
+			return cmp(self.name, other.name)
+		else:
+			return cmp(self.name, other)
+
diff --git a/plugins/commander/commander/commands/module.py b/plugins/commander/commander/commands/module.py
new file mode 100644
index 0000000..f7bcb3f
--- /dev/null
+++ b/plugins/commander/commander/commands/module.py
@@ -0,0 +1,129 @@
+import sys
+import os
+import types
+import bisect
+
+import utils
+import exceptions
+import method
+import rollbackimporter
+
+class Module(method.Method):
+	def __init__(self, base, mod, parent=None):
+		method.Method.__init__(self, None, base, parent)
+
+		self._commands = None
+		self._dirname = None
+		self._roots = None
+
+		if type(mod) == types.ModuleType:
+			self.mod = mod
+			
+			if '__default__' in mod.__dict__:
+				self.method = mod.__dict__['__default__']
+			else:
+				self.method = None
+		else:
+			self.mod = None
+			self._dirname = mod
+			self._rollback = rollbackimporter.RollbackImporter()
+	
+	def commands(self):
+		if self._commands == None:
+			self.scan_commands()
+
+		return self._commands
+
+	def clear(self):
+		self._commands = None
+	
+	def roots(self):
+		if self._roots == None:
+			if not self.mod:
+				return []
+		
+			dic = self.mod.__dict__
+		
+			if '__root__' in dic:
+				root = dic['__root__']
+			else:
+				root = []
+		
+			root = filter(lambda x: x in dic and type(dic[x]) == types.FunctionType, root)
+			self._roots = map(lambda x: method.Method(dic[x], x, self.mod), root)
+		
+		return self._roots
+	
+	def scan_commands(self):
+		self._commands = []
+		
+		if self.mod == None:
+			return
+
+		dic = self.mod.__dict__
+
+		if '__root__' in dic:
+			root = dic['__root__']
+		else:
+			root = []
+
+		for k in dic:
+			if k.startswith('_') or k in root:
+				continue
+			
+			item = dic[k]
+			
+			if type(item) == types.FunctionType:
+				bisect.insort(self._commands, method.Method(item, k, self))
+			elif type(item) == types.ModuleType and utils.is_commander_module(item):
+				mod = Module(k, item, self)
+				bisect.insort(self._commands, mod)
+				
+				# Insert root functions into this module
+				for r in mod.roots():
+					bisect.insert(self._commands, r)
+	
+	def unload(self):
+		self._commands = None
+
+		if not self._dirname:
+			return False
+		
+		self._rollback.uninstall()
+		self.mod = None
+
+		return True
+	
+	def reload(self):
+		if not self.unload():
+			return
+
+		if self.name in sys.modules:
+			raise Exception('Module already exists...')
+
+		oldpath = list(sys.path)
+
+		try:
+			sys.path.insert(0, self._dirname)
+			
+			self._rollback.monitor()
+			self.mod = __import__(self.name, globals(), locals(), [], 0)
+			self._rollback.cancel()
+			
+			if not utils.is_commander_module(self.mod):
+				raise Exception('Module is not a commander module...')
+			
+			if '__default__' in self.mod.__dict__:
+				self.method = self.mod.__dict__['__default__']
+			else:
+				self.method = None
+		except:
+			sys.path = oldpath
+			self._rollback.uninstall()
+			
+			if self.name in sys.modules:
+				del sys.modules[self.name]
+			raise
+		
+		sys.path = oldpath
+
diff --git a/plugins/commander/commander/commands/result.py b/plugins/commander/commander/commands/result.py
new file mode 100644
index 0000000..b4e4b77
--- /dev/null
+++ b/plugins/commander/commander/commands/result.py
@@ -0,0 +1,41 @@
+class Result(object):
+	HIDE = 1
+	DONE = 2
+	PROMPT = 3
+	SUSPEND = 4
+
+	def __init__(self, value):
+		self._value = value
+	
+	def __int__(self):
+		return self._value
+	
+	def __cmp__(self, other):
+		if isinstance(other, int) or isinstance(other, Result):
+			return cmp(int(self), int(other))
+		else:
+			return 1
+
+# Easy shortcuts
+HIDE = Result(Result.HIDE)
+DONE = Result(Result.DONE)
+
+class Prompt(Result):
+	def __init__(self, prompt, autocomplete={}):
+		Result.__init__(self, Result.PROMPT)
+
+		self.prompt = prompt
+		self.autocomplete = autocomplete
+
+class Suspend(Result):
+	def __init__(self):
+		Result.__init__(self, Result.SUSPEND)
+		self._callbacks = []
+	
+	def register(self, cb, *args):
+		self._callbacks.append([cb, args])
+	
+	def resume(self):
+		for cb in self._callbacks:
+			args = cb[1]
+			cb[0](*args)
diff --git a/plugins/commander/commander/commands/rollbackimporter.py b/plugins/commander/commander/commands/rollbackimporter.py
new file mode 100644
index 0000000..e1e4e2c
--- /dev/null
+++ b/plugins/commander/commander/commands/rollbackimporter.py
@@ -0,0 +1,36 @@
+import sys
+import utils
+
+class RollbackImporter:
+	def __init__(self):
+		"Creates an instance and installs as the global importer"
+		self._new_modules = []
+		self._original_import = __builtins__['__import__']
+	
+	def monitor(self):
+		__builtins__['__import__'] = self._import
+
+	def cancel(self):
+		__builtins__['__import__'] = self._original_import
+
+	def _import(self, name, globals=None, locals=None, fromlist=[], level=-1):
+		maybe = not name in sys.modules
+
+		mod = apply(self._original_import, (name, globals, locals, fromlist, level))
+		
+		if maybe and utils.is_commander_module(mod):
+			self._new_modules.append(name)
+		
+		return mod
+
+	def uninstall(self):
+		self.cancel()
+		
+		for modname in self._new_modules:
+			if modname in sys.modules:
+				del sys.modules[modname]
+		
+		self._new_modules = []
+			
+		
+
diff --git a/plugins/commander/commander/drawing.py b/plugins/commander/commander/drawing.py
new file mode 100644
index 0000000..ef4a557
--- /dev/null
+++ b/plugins/commander/commander/drawing.py
@@ -0,0 +1,79 @@
+import math
+import cairo
+import gtk
+
+def _draw_transparent_background(ct, region):
+	ct.set_operator(cairo.OPERATOR_CLEAR)
+	ct.region(region)
+	ct.fill()
+
+def _on_widget_expose(widget, evnt):
+	ct = evnt.window.cairo_create()
+	ct.save()
+	
+	# Basicly just clear the background
+	_draw_transparent_background(ct, evnt.region)
+
+	ct.restore()
+	return False
+	
+def _on_parent_expose(parent, evnt, widget):
+	#if evnt.window != widget.window.get_parent():
+	#	return False
+	
+	# Composite child window back onto parent
+	ct = evnt.window.cairo_create()
+	ct.set_source_pixmap(widget.window, widget.allocation.x, widget.allocation.y)
+	region = gtk.gdk.region_rectangle(widget.allocation)
+	
+	region.intersect(evnt.region)
+	ct.region(region)
+	ct.clip()
+	
+	# Composite it now
+	ct.set_operator(cairo.OPERATOR_OVER)
+	ct.paint_with_alpha(0.5)
+	
+	return True
+
+def _on_widget_realize(widget):
+	widget.window.set_back_pixmap(None, False)
+
+	if widget.is_composited():
+		widget.window.set_composited(True)
+		widget.get_parent().connect_after('expose-event', _on_parent_expose, widget)
+
+def transparent_background(widget):
+	#widget.set_name('transparent-background')
+	widget.connect_after('realize', _on_widget_realize)
+	widget.set_app_paintable(True)
+
+	cmap = widget.get_screen().get_rgba_colormap()
+	
+	if cmap:
+		widget.set_colormap(cmap)
+
+	widget.connect('expose-event', _on_widget_expose)
+
+def set_rounded_rectangle_path(ct, x, y, width, height, radius):
+	ct.move_to(x + radius, y)
+
+	ct.arc(x + width - radius, y + radius, radius, math.pi * 1.5, math.pi * 2)
+	ct.arc(x + width - radius, y + height - radius, radius, 0, math.pi * 0.5)
+	ct.arc(x + radius, y + height - radius, radius, math.pi * 0.5, math.pi)
+	ct.arc(x + radius, y + radius, radius, math.pi, math.pi * 1.5)
+
+gtk.rc_parse_string("""
+style "OverrideBackground" {
+	engine "pixmap" {
+		image {
+			function = FLAT_BOX
+		}
+		image {
+			function = BOX
+		}
+	}
+}
+
+widget "*.transparent-background" style "OverrideBackground"
+""")
diff --git a/plugins/commander/commander/entry.py b/plugins/commander/commander/entry.py
new file mode 100644
index 0000000..928d38e
--- /dev/null
+++ b/plugins/commander/commander/entry.py
@@ -0,0 +1,567 @@
+import gtk
+import cairo
+import glib
+import os
+import drawing
+import re
+import inspect
+
+import commander.commands as commands
+import commands.completion
+import commands.module
+import commands.method
+import commands.exceptions
+
+import commander.utils as utils
+
+from history import History
+from info import Info
+from xml.sax import saxutils
+import traceback
+
+class Entry(gtk.EventBox):
+	def __init__(self, view):
+		gtk.EventBox.__init__(self)
+		self._view = view
+		
+		hbox = gtk.HBox(False, 3)
+		hbox.show()
+		hbox.set_border_width(3)
+		
+		self._entry = gtk.Entry()
+		self._entry.modify_font(self._view.style.font_desc)
+		self._entry.set_has_frame(False)
+		self._entry.set_name('command-bar')
+		self._entry.modify_text(gtk.STATE_NORMAL, self._view.style.text[gtk.STATE_NORMAL])
+		self._entry.set_app_paintable(True)
+		
+		self._entry.connect('realize', self.on_realize)
+		self._entry.connect('expose-event', self.on_entry_expose)
+
+		self._entry.show()
+
+		self._prompt_label = gtk.Label('<b>&gt;&gt;&gt;</b>')
+		self._prompt_label.set_use_markup(True)
+		self._prompt_label.modify_font(self._view.style.font_desc)
+		self._prompt_label.show()
+		self._prompt_label.modify_fg(gtk.STATE_NORMAL, self._view.style.text[gtk.STATE_NORMAL])
+		
+		self.modify_bg(gtk.STATE_NORMAL, self.background_gdk())
+		self._entry.modify_base(gtk.STATE_NORMAL, self.background_gdk())
+		
+		self._entry.connect('focus-out-event', self.on_entry_focus_out)
+		self._entry.connect('key-press-event', self.on_entry_key_press)
+		
+		self.connect_after('size-allocate', self.on_size_allocate)
+		self.connect_after('expose-event', self.on_expose)
+		self.connect_after('realize', self.on_realize)
+		
+		self._history = History(os.path.expanduser('~/.gnome2/gedit/commander/history'))
+		self._prompt = None
+		
+		hbox.pack_start(self._prompt_label, False, False, 0)
+		hbox.pack_start(self._entry, True, True, 0)
+		
+		self.add(hbox)
+		self.attach()
+
+		self._entry.grab_focus()
+		self._wait_timeout = 0
+		self._info_window = None
+		
+		self.connect('destroy', self.on_destroy)
+		
+		self._history_prefix = None		
+		self._suspended = None
+		self._handlers = [
+			[0, gtk.keysyms.Up, self.on_history_move, -1],
+			[0, gtk.keysyms.Down, self.on_history_move, 1],
+			[None, gtk.keysyms.Return, self.on_execute, None],
+			[None, gtk.keysyms.KP_Enter, self.on_execute, None],
+			[0, gtk.keysyms.Tab, self.on_complete, None],
+			[0, gtk.keysyms.ISO_Left_Tab, self.on_complete, None]
+		]
+		
+		self._re_complete = re.compile('("((?:\\\\"|[^"])*)"?|\'((?:\\\\\'|[^\'])*)\'?|[^\s]+)')
+		self._command_state = commands.Commands.State()
+	
+	def view(self):
+		return self._view
+
+	def on_realize(self, widget):
+		widget.window.set_back_pixmap(None, False)
+	
+	def on_entry_expose(self, widget, evnt):
+		ct = evnt.window.cairo_create()
+		ct.rectangle(evnt.area.x, evnt.area.y, evnt.area.width, evnt.area.height)
+		
+		bg = self.background_color()
+		ct.set_source_rgb(bg[0], bg[1], bg[1])
+		ct.fill()
+		return False
+
+	def on_expose(self, widget, evnt):
+		ct = evnt.window.cairo_create()
+		color = self.background_color()
+		
+		ct.rectangle(evnt.area.x, evnt.area.y, evnt.area.width, evnt.area.height)
+		ct.clip()
+		
+		# Draw separator line
+		ct.move_to(0, 0)
+		ct.set_line_width(1)
+		ct.line_to(self.allocation.width, 0)
+
+		ct.set_source_rgb(1 - color[0], 1 - color[1], 1 - color[2])
+		ct.stroke()
+		return False
+		
+	def on_size_allocate(self, widget, alloc):
+		vwwnd = self._view.get_window(gtk.TEXT_WINDOW_BOTTOM).get_parent()
+		size = vwwnd.get_size()
+		position = vwwnd.get_position()
+
+		self._view.set_border_window_size(gtk.TEXT_WINDOW_BOTTOM, alloc.height)
+	
+	def attach(self):
+		# Attach ourselves in the text view, and position just above the 
+		# text window
+		self._view.set_border_window_size(gtk.TEXT_WINDOW_BOTTOM, 1)
+		alloc = self._view.allocation
+		
+		self.show()
+		self._view.add_child_in_window(self, gtk.TEXT_WINDOW_BOTTOM, 0, 0)
+		self.set_size_request(alloc.width, -1)
+
+	def background_gdk(self):
+		bg = self.background_color()
+		
+		bg = map(lambda x: int(x * 65535), bg)
+		return gtk.gdk.Color(bg[0], bg[1], bg[2])
+		
+	def background_color(self):
+		bg = self._view.get_style().base[self._view.state]
+		
+		return [bg.red / 65535.0 * 1.1, bg.green / 65535.0 * 1.1, bg.blue / 65535.0 * 0.9, 0.8]
+
+	def on_entry_focus_out(self, widget, evnt):
+		if self._entry.flags() & gtk.SENSITIVE:
+			self.destroy()
+	
+	def on_entry_key_press(self, widget, evnt):
+		state = evnt.state & gtk.accelerator_get_default_mod_mask()
+		text = self._entry.get_text()
+		
+		if evnt.keyval == gtk.keysyms.Escape and self._info_window:
+			if self._suspended:
+				self._suspended.resume()
+
+			if self._info_window:
+				self._info_window.destroy()
+
+			self._entry.set_sensitive(True)
+			return True
+
+		if evnt.keyval == gtk.keysyms.Escape:
+			if text:
+				self._entry.set_text('')
+			elif self._command_state:
+				self._command_state.clear()
+				self.prompt()
+			else:
+				self._view.grab_focus()
+				self.destroy()
+
+			return True
+
+		for handler in self._handlers:
+			if (handler[0] == None or handler[0] == state) and evnt.keyval == handler[1] and handler[2](handler[3], state):
+				return True
+
+		if self._info_window and self._info_window.empty():
+			self._info_window.destroy()
+		
+		self._history_prefix = None
+		return False
+	
+	def on_history_move(self, direction, modifier):
+		pos = self._entry.get_position()
+		
+		self._history.update(self._entry.get_text())
+		
+		if self._history_prefix == None:
+			if len(self._entry.get_text()) == pos:
+				self._history_prefix = self._entry.get_chars(0, pos)
+			else:
+				self._history_prefix = ''
+		
+		if self._history_prefix == None:
+			hist = ''
+		else:
+			hist = self._history_prefix
+			
+		next = self._history.move(direction, hist)
+		
+		if next != None:
+			self._entry.set_text(next)
+			self._entry.set_position(-1)
+		
+		return True
+	
+	def prompt(self, pr=''):
+		self._prompt = pr
+
+		if not pr:
+			pr = ''
+		else:
+			pr = ' ' + pr
+		
+		self._prompt_label.set_markup('<b>&gt;&gt;&gt;</b>%s' % pr)
+	
+	def make_info(self):
+		if self._info_window == None:
+			self._info_window = Info(self)
+			self._info_window.show()
+			
+			self._info_window.connect('destroy', self.on_info_window_destroy)
+	
+	def on_info_window_destroy(self, widget):
+		self._info_window = None
+	
+	def info_show(self, text='', use_markup=False):
+		self.make_info()
+		self._info_window.add_lines(text, use_markup)
+	
+	def info_status(self, text):
+		self.make_info()
+		self._info_window.status(text)
+	
+	def info_add_action(self, stock, callback, data=None):
+		self.make_info()
+		return self._info_window.add_action(stock, callback, data)
+
+	def command_history_done(self):
+		self._history.update(self._entry.get_text())
+		self._history.add()
+		self._history_prefix = None
+		self._entry.set_text('')
+	
+	def on_wait_cancel(self):
+		if self._suspended:
+			self._suspended.resume()
+		
+		if self._cancel_button:
+			self._cancel_button.destroy()
+
+		if self._info_window and self._info_window.empty():
+			self._info_window.destroy()
+			self._entry.grab_focus()
+			self._entry.set_sensitive(True)			
+	
+	def _show_wait_cancel(self):
+		self._cancel_button = self.info_add_action(gtk.STOCK_STOP, self.on_wait_cancel)
+		self.info_status('<i>Waiting to finish...</i>')
+		
+		self._wait_timeout = 0
+		return False
+
+	def _complete_word_match(self, match):
+		for i in (3, 2, 0):
+			if match.group(i) != None:
+				return [match.group(i), match.start(i), match.end(i)]
+
+	def on_suspend_resume(self):
+		if self._wait_timeout:
+			glib.source_remove(self._wait_timeout)
+			self._wait_timeout = 0
+		else:
+			self._cancel_button.destroy()
+			self._cancel_button = None
+			self.info_status(None)
+		
+		self._entry.set_sensitive(True)
+		self.command_history_done()
+
+		if self._entry.props.has_focus or (self._info_window and not self._info_window.empty()):
+			self._entry.grab_focus()
+
+		self.on_execute(None, 0)
+
+	def ellipsize(self, s, size):
+		if len(s) <= size:
+			return s
+		
+		mid = (size - 4) / 2
+		return s[:mid] + '...' + s[-mid:]
+
+	def destroy(self):
+		self.hide()
+		gtk.EventBox.destroy(self)
+
+	def on_execute(self, dummy, modifier):
+		if self._info_window and not self._suspended:
+			self._info_window.destroy()
+
+		text = self._entry.get_text().strip()
+		words = list(self._re_complete.finditer(text))
+		wordsstr = []
+		
+		for word in words:
+			spec = self._complete_word_match(word)
+			wordsstr.append(spec[0])
+
+		if not wordsstr and not self._command_state:
+			self._entry.set_text('')
+			return
+		
+		self._suspended = None
+
+		try:
+			ret = commands.Commands().execute(self._command_state, text, words, wordsstr, self, modifier)
+		except Exception, e:
+			self.command_history_done()
+			self._command_state.clear()
+			
+			self.prompt()
+			
+			# Show error in info
+			self.info_show('<b><span color="#f66">Error:</span></b> ' + saxutils.escape(str(e)), True)
+
+			if not isinstance(e, commands.exceptions.Execute):
+				self.info_show(traceback.format_exc(), False)
+
+			return True
+
+		if ret == commands.result.Result.SUSPEND:
+			# Wait for it...
+			self._suspended = ret
+			ret.register(self.on_suspend_resume)
+
+			self._wait_timeout = glib.timeout_add(500, self._show_wait_cancel)
+			self._entry.set_sensitive(False)
+		else:
+			self.command_history_done()
+			self.prompt('')
+			
+			if ret == commands.result.Result.PROMPT:
+				self.prompt(ret.prompt)
+			elif (ret == None or ret == commands.result.HIDE) and not self._prompt and (not self._info_window or self._info_window.empty()):
+				self._command_state.clear()
+				self._view.grab_focus()
+				self.destroy()
+			else:
+				self._entry.grab_focus()
+
+		return True
+	
+	def on_complete(self, dummy, modifier):
+		# First split all the text in words
+		text = self._entry.get_text()
+		pos = self._entry.get_position()
+
+		words = list(self._re_complete.finditer(text))
+		wordsstr = []
+		
+		for word in words:
+			spec = self._complete_word_match(word)
+			wordsstr.append(spec[0])
+		
+		# Find out at which word the cursor actually is
+		# Examples:
+		#  * hello world|
+		#  * hello| world
+		#  * |hello world
+		#  * hello wor|ld
+		#  * hello  |  world
+		#  * "hello world|"
+		posidx = None
+		
+		for idx in xrange(0, len(words)):
+			spec = self._complete_word_match(words[idx])
+
+			if words[idx].start(0) > pos:
+				# Empty space, new completion
+				wordsstr.insert(idx, '')
+				words.insert(idx, None)
+				posidx = idx
+				break
+			elif spec[2] == pos:
+				# At end of word, resume completion
+				posidx = idx
+				break
+			elif spec[1] <= pos and spec[2] > pos:
+				# In middle of word, do not complete
+				return True
+
+		if posidx == None:
+			wordsstr.append('')
+			words.append(None)
+			posidx = len(wordsstr) - 1
+		
+		# First word completes a command, if not in any special 'mode'
+		# otherwise, relay completion to the command, or complete by advice
+		# from the 'mode' (prompt)
+		cmds = commands.Commands()
+		
+		if not self._command_state and posidx == 0:
+			# Complete the first command
+			ret = commands.completion.command(words=wordsstr, idx=posidx)
+		else:
+			complete = None
+
+			if not self._command_state:
+				# Get the command first
+				cmd = commands.completion.single_command(wordsstr, 0)
+			else:
+				cmd = self._command_state.top()
+
+			if cmd:
+				complete = cmd.autocomplete_func()
+			
+			if not complete:
+				return True
+			
+			# 'complete' contains a dict with arg -> func to do the completion
+			# of the named argument the command (or stack item) expects
+			args, varargs = cmd.args()
+			
+			# Remove system arguments
+			s = ['argstr', 'args', 'entry', 'view']
+			args = filter(lambda x: not x in s, args)
+			
+			if posidx - 1 < len(args):
+				arg = args[posidx - 1]
+			elif varargs:
+				arg = '*'
+			else:
+				return True
+
+			if not arg in complete:
+				return True
+			
+			func = complete[arg]
+
+			try:
+				spec = utils.getargspec(func)
+				
+				kwargs = {
+					'words': wordsstr[1:],
+					'idx': posidx - 1,
+					'view': self._view
+				}
+				
+				if not spec.keywords:
+					for k in kwargs.keys():
+						if not k in spec.args:
+							del kwargs[k]
+				
+				ret = func(**kwargs)
+			except Exception, e:
+				# Can be number of arguments, or return values or simply buggy
+				# modules
+				print e
+				traceback.print_exc()
+				return True
+		
+		if not ret or not ret[0]:
+			return True
+		
+		res = ret[0]
+		completed = ret[1]
+		
+		if len(ret) > 2:
+			after = ret[2]
+		else:
+			after = ' '
+		
+		# Replace the word
+		if words[posidx] == None:
+			# At end of everything, just append
+			spec = None
+
+			self._entry.insert_text(completed, self._entry.get_text_length())
+			self._entry.set_position(-1)
+		else:
+			spec = self._complete_word_match(words[posidx])
+
+			self._entry.delete_text(spec[1], spec[2])
+			self._entry.insert_text(completed, spec[1])
+			self._entry.set_position(spec[1] + len(completed))
+
+		if len(res) == 1:
+			# Full completion
+			lastpos = self._entry.get_position()
+			
+			if not isinstance(res[0], commands.module.Module) or not res[0].commands():
+				if words[posidx] and after == ' ' and (words[posidx].group(2) != None or words[posidx].group(3) != None):
+					lastpos = lastpos + 1
+			
+				self._entry.insert_text(after, lastpos)
+				self._entry.set_position(lastpos + 1)
+			elif completed == wordsstr[posidx] or not res[0].method:
+				self._entry.insert_text('.', lastpos)
+				self._entry.set_position(lastpos + 1)
+
+			if self._info_window:
+				self._info_window.destroy()
+		else:
+			# Show popup with completed items
+			if self._info_window:
+				self._info_window.clear()
+			
+			ret = []
+
+			for x in res:
+				if isinstance(x, commands.method.Method):
+					ret.append('<b>' + x.name + '</b> (<i>' + x.oneline_doc() + '</i>)')
+				else:
+					ret.append(str(x))
+
+			self.info_show("\n".join(ret), True)
+
+		return True
+	
+	def on_destroy(self, widget):
+		self._view.set_border_window_size(gtk.TEXT_WINDOW_BOTTOM, 0)
+
+		if self._info_window:
+			self._info_window.destroy()
+
+		self._history.save()
+
+gtk.rc_parse_string("""
+binding "TerminalLike" {
+	unbind "<Control>A"
+
+	bind "<Control>W" {
+		"delete-from-cursor" (word-ends, -1)
+	}
+	bind "<Control>A" {
+		"move-cursor" (buffer-ends, -1, 0)
+	}
+	bind "<Control>U" {
+		"delete-from-cursor" (display-line-ends, -1)
+	}
+	bind "<Control>K" {
+		"delete-from-cursor" (display-line-ends, 1)
+	}
+	bind "<Control>E" {
+		"move-cursor" (buffer-ends, 1, 0)
+	}
+	bind "Escape" {
+		"delete-from-cursor" (display-lines, 1)
+	}
+}
+
+style "NoBackground" {
+	engine "pixmap" {
+		image {
+			function = FLAT_BOX
+			detail = "entry_bg"
+		}
+	}
+}
+
+widget "*.command-bar" binding "TerminalLike"
+widget "*.command-bar" style "NoBackground"
+""")
diff --git a/plugins/commander/commander/history.py b/plugins/commander/commander/history.py
new file mode 100644
index 0000000..5196686
--- /dev/null
+++ b/plugins/commander/commander/history.py
@@ -0,0 +1,71 @@
+import os
+
+class History:
+	def __init__(self, filename):
+		self._filename = filename
+		self._history = ['']
+		self._ptr = 0
+
+		self.load()
+	
+	def find(self, direction, prefix):
+		ptr = self._ptr + direction
+		
+		while ptr >= 0 and ptr < len(self._history):
+			if self._history[ptr].startswith(prefix):
+				return ptr
+
+			ptr += direction
+		
+		return -1
+	
+	def move(self, direction, prefix):
+		next = self.find(direction, prefix)
+		
+		if next != -1:
+			self._ptr = next
+			return self._history[self._ptr]
+		else:
+			return None
+	
+	def up(self, prefix=''):
+		return self.move(-1, prefix)
+
+	def down(self, prefix=''):
+		return self.move(1, prefix)
+
+	def add(self):
+		if self._history[-1] != '':
+			self._history.append('')
+
+		self._ptr = len(self._history) - 1
+	
+	def update(self, line):
+		self._history[self._ptr] = line
+	
+	def load(self):
+		try:
+			self._history = map(lambda x: x.strip("\n"), file(self._filename, 'r').readlines())
+			self._history.append('')
+			self._ptr = len(self._history) - 1
+		except IOError:
+			pass
+	
+	def save(self):
+		try:
+			os.makedirs(os.path.dirname(self._filename))
+		except OSError:
+			pass
+		
+		try:
+			f = file(self._filename, 'w')
+			
+			if self._history[-1] == '':
+				hist = self._history[:-1]
+			else:
+				hist = self._history
+
+			f.writelines(map(lambda x: x + "\n", hist))
+			f.close()
+		except IOError:
+			pass
diff --git a/plugins/commander/commander/info.py b/plugins/commander/commander/info.py
new file mode 100644
index 0000000..1caa491
--- /dev/null
+++ b/plugins/commander/commander/info.py
@@ -0,0 +1,330 @@
+from transparentwindow import TransparentWindow
+import gtk
+import math
+import pango
+
+class Info(TransparentWindow):
+	def __init__(self, entry):
+		TransparentWindow.__init__(self, gtk.WINDOW_POPUP)
+		
+		self._entry = entry
+		self._vbox = gtk.VBox(False, 3)
+		
+		self.set_transient_for(entry.get_toplevel())
+		
+		self._vw = gtk.ScrolledWindow()
+		self._vw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
+		self._vw.show()
+
+		self._text = gtk.TextView()
+		self._text.modify_font(entry._view.style.font_desc)
+		self._text.modify_text(gtk.STATE_NORMAL, entry._entry.style.text[gtk.STATE_NORMAL])
+		self._text.connect('expose-event', self.on_text_expose)
+		self._text.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+		
+		buf = self._text.get_buffer()
+		
+		buf.connect_after('insert-text', self.on_text_insert_text)
+		buf.connect_after('delete-range', self.on_text_delete_range)
+		
+		self._text.set_editable(False)
+		
+		self._vw.add(self._text)
+		self._vbox.pack_end(self._vw, expand=False, fill=False)
+		self._vbox.show()
+		self._button_bar = None
+
+		self.add(self._vbox)
+		self._text.show()
+		self._status_label = None
+		
+		self.props.can_focus = False
+		self.set_border_width(8)
+		
+		self._text.connect('realize', self.on_text_realize)
+		
+		self.attach()
+		self.show()
+		
+		self.connect_after('size-allocate', self.on_size_allocate)
+		self._vw.connect_after('size-allocate', self.on_text_size_allocate)
+
+		self.max_lines = 10
+		
+		self._attr_map = {
+			pango.ATTR_STYLE: 'style',
+			pango.ATTR_WEIGHT: 'weight',
+			pango.ATTR_VARIANT: 'variant',
+			pango.ATTR_STRETCH: 'stretch',
+			pango.ATTR_SIZE: 'size',
+			pango.ATTR_FOREGROUND: 'foreground',
+			pango.ATTR_BACKGROUND: 'background',
+			pango.ATTR_UNDERLINE: 'underline',
+			pango.ATTR_STRIKETHROUGH: 'strikethrough',
+			pango.ATTR_RISE: 'rise',
+			pango.ATTR_SCALE: 'scale'
+		}
+
+	def empty(self):
+		buf = self._text.get_buffer()
+		return buf.get_start_iter().equal(buf.get_end_iter())
+
+	def status(self, text=None):
+		if self._status_label == None and text != None:
+			self._status_label = gtk.Label('')
+			self._status_label.modify_font(self._text.style.font_desc)
+			self._status_label.modify_fg(gtk.STATE_NORMAL, self._text.style.text[gtk.STATE_NORMAL])
+			self._status_label.show()
+			self._status_label.set_alignment(0, 0.5)
+			self._status_label.set_padding(10, 0)
+			self._status_label.set_use_markup(True)
+			
+			self.ensure_button_bar()
+			self._button_bar.pack_start(self._status_label, True, True, 0)
+		
+		if text != None:
+			self._status_label.set_markup(text)
+		elif self._status_label:
+			self._status_label.destroy()
+			
+			if not self._button_bar and self.empty():
+				self.destroy()
+	
+	def attrs_to_tags(self, attrs):
+		buf = self._text.get_buffer()
+		table = buf.get_tag_table()
+		ret = []
+	
+		for attr in attrs:
+			if not attr.type in self._attr_map:
+				continue
+			
+			if attr.type == pango.ATTR_FOREGROUND or attr.type == pango.ATTR_BACKGROUND:
+				value = attr.color
+			else:
+				value = attr.value
+				
+			tagname = str(attr.type) + ':' + str(value)
+
+			tag = table.lookup(tagname)
+			
+			if not tag:
+				tag = buf.create_tag(tagname)
+				tag.set_property(self._attr_map[attr.type], value)
+			
+			ret.append(tag)
+		
+		return ret
+	
+	def add_lines(self, line, use_markup=False):
+		buf = self._text.get_buffer()
+		
+		if not buf.get_start_iter().equal(buf.get_end_iter()):
+			line = "\n" + line
+		
+		if not use_markup:
+			buf.insert(buf.get_end_iter(), line)
+			return
+			
+		try:
+			ret = pango.parse_markup(line)
+		except Exception, e:
+			print 'Could not parse markup:', e
+			buf.insert(buf.get_end_iter(), line)
+			return
+		
+		piter = ret[0].get_iterator()
+		text = ret[1]
+		
+		while piter:
+			attrs = piter.get_attrs()
+			begin, end = piter.range()
+			
+			tags = self.attrs_to_tags(attrs)
+			buf.insert_with_tags(buf.get_end_iter(), text[begin:end], *tags)
+			
+			if not piter.next():
+				break
+	
+	def toomany_lines(self):
+		buf = self._text.get_buffer()
+		piter = buf.get_start_iter()
+		num = 0
+		
+		while self._text.forward_display_line(piter):
+			num += 1
+			
+			if num > self.max_lines:
+				return True
+		
+		return False		
+	
+	def contents_changed(self):
+		buf = self._text.get_buffer()
+		
+		if self.toomany_lines() and (self._vw.get_policy()[1] != gtk.POLICY_ALWAYS):
+			self._vw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+			
+			layout = self._text.create_pango_layout('Some text to measure')
+			extents = layout.get_pixel_extents()
+			
+			self._text.set_size_request(-1, extents[1][3] * self.max_lines)
+		elif not self.toomany_lines() and (self._vw.get_policy()[1] == gtk.POLICY_ALWAYS):
+			self._vw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
+			self._text.set_size_request(-1, -1)
+		
+		if not self.toomany_lines():
+			size = self.get_size()
+			self.resize(size[0], 1)
+
+	def ensure_button_bar(self):
+		if not self._button_bar:
+			self._button_bar = gtk.HBox(False, 3)
+			self._button_bar.show()
+			self._vbox.pack_start(self._button_bar, False, False, 0)
+		
+	def add_action(self, stock, callback, data=None):
+		image = gtk.image_new_from_stock(stock, gtk.ICON_SIZE_MENU)
+		image.show()
+		
+		image.set_data('COMMANDER_ACTION_STOCK_ITEM', [stock, gtk.ICON_SIZE_MENU])
+		
+		self.ensure_button_bar()
+
+		ev = gtk.EventBox()
+		ev.set_visible_window(False)
+		ev.add(image)
+		ev.show()
+		
+		self._button_bar.pack_end(ev, False, False, 0)
+		
+		ev.connect('button-press-event', self.on_action_activate, callback, data)
+		ev.connect('enter-notify-event', self.on_action_enter_notify)
+		ev.connect('leave-notify-event', self.on_action_leave_notify)
+		
+		ev.connect_after('destroy', self.on_action_destroy)
+		return ev
+	
+	def on_action_destroy(self, widget):
+		if self._button_bar and len(self._button_bar.get_children()) == 0:
+			self._button_bar.destroy()
+			self._button_bar = None
+	
+	def on_action_enter_notify(self, widget, evnt):
+		img = widget.get_child()
+		img.set_state(gtk.STATE_PRELIGHT)
+		widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+		
+		stock = img.get_data('COMMANDER_ACTION_STOCK_ITEM')
+		pix = img.render_icon(stock[0], stock[1])
+		img.set_from_pixbuf(pix)		
+		
+	def on_action_leave_notify(self, widget, evnt):
+		img = widget.get_child()
+		img.set_state(gtk.STATE_NORMAL)
+		widget.window.set_cursor(None)
+		
+		stock = img.get_data('COMMANDER_ACTION_STOCK_ITEM')
+		pix = img.render_icon(stock[0], stock[1])
+		img.set_from_pixbuf(pix)
+
+	def on_action_activate(self, widget, evnt, callback, data):
+		if data:
+			callback(data)
+		else:
+			callback()
+
+	def clear(self):
+		self._text.get_buffer().set_text('')
+
+	def on_text_expose(self, widget, evnt):
+		if evnt.window != widget.get_window(gtk.TEXT_WINDOW_TEXT):
+			return False
+		
+		ct = evnt.window.cairo_create()
+		ct.save()
+		
+		area = evnt.area
+		ct.rectangle(area.x, area.y, area.width, area.height)
+		ct.clip()
+		
+		self.draw_background(ct, self._text, False)
+		
+		ct.restore()
+		return False
+		
+	def on_text_realize(self, widget):
+		self._text.get_window(gtk.TEXT_WINDOW_TEXT).set_back_pixmap(None, False)
+	
+	def attach(self):
+		vwwnd = self._entry._view.get_window(gtk.TEXT_WINDOW_TEXT)
+		origin = vwwnd.get_origin()
+		geom = vwwnd.get_geometry()
+
+		margin = 5
+		
+		self.realize()
+		
+		self.move(origin[0], origin[1] + geom[3] - self.allocation.height)
+		self.resize(geom[2] - margin * 2, self.allocation.height)
+	
+	def on_text_insert_text(self, buf, piter, text, length):
+		self.contents_changed()
+	
+	def on_text_delete_range(self, buf, start, end):
+		self.contents_changed()
+		
+	def on_size_allocate(self, widget, allocation):
+		vwwnd = self._entry._view.get_window(gtk.TEXT_WINDOW_TEXT)
+		origin = vwwnd.get_origin()
+		geom = vwwnd.get_geometry()
+
+		self.move(origin[0] + (geom[2] - self.allocation.width) / 2, origin[1] + geom[3] - self.allocation.height)
+	
+	def on_expose(self, widget, evnt):
+		ret = TransparentWindow.on_expose(self, widget, evnt)
+		
+		if ret:
+			return True
+		
+		ct = evnt.window.cairo_create()
+		ct.save()
+		
+		area = evnt.area
+		ct.rectangle(area.x, area.y, area.width, area.height)
+		ct.clip()			
+			
+		color = self.background_color()
+		
+		self.background_shape(ct)
+
+		ct.set_source_rgba(1 - color[0], 1 - color[1], 1 - color[2], 0.3)
+		ct.stroke()
+		
+		ct.restore()
+		return False
+	
+	def background_shape(self, ct):
+		w = self.allocation.width
+		h = self.allocation.height
+		
+		ct.set_line_width(1)
+		radius = 10
+	
+		ct.move_to(0.5, h)
+
+		if self.is_composited():
+			ct.arc(radius + 0.5, radius, radius, math.pi, math.pi * 1.5)
+			ct.arc(w - radius - 0.5, radius, radius, math.pi * 1.5, math.pi * 2)
+		else:
+			ct.line_to(0.5, 0)
+			ct.line_to(w - 0.5, 0)
+
+		ct.line_to(w - 0.5, h)
+		
+	def background_color(self):
+		return self._entry.background_color()
+	
+	def on_text_size_allocate(self, widget, alloc):
+		pass
+
diff --git a/plugins/commander/commander/transparentwindow.py b/plugins/commander/commander/transparentwindow.py
new file mode 100644
index 0000000..eb3146e
--- /dev/null
+++ b/plugins/commander/commander/transparentwindow.py
@@ -0,0 +1,70 @@
+import gtk
+import cairo
+
+class TransparentWindow(gtk.Window):
+	def __init__(self, lvl=gtk.WINDOW_TOPLEVEL):
+		gtk.Window.__init__(self, lvl)
+
+		self.set_decorated(False)
+		self.set_app_paintable(True)
+		self.set_skip_pager_hint(True)
+		self.set_skip_taskbar_hint(True)
+		self.set_events(gtk.gdk.ALL_EVENTS_MASK)
+
+		self.set_rgba()
+	
+	def set_rgba(self):
+		cmap = self.get_screen().get_rgba_colormap()
+		
+		if not cmap:
+			return
+			
+		self.set_colormap(cmap)
+		self.connect('realize', self.on_realize)
+		self.connect('expose-event', self.on_expose)
+
+	def on_realize(self, widget):
+		self.window.set_back_pixmap(None, False)
+
+	def background_color(self):
+		return [0, 0, 0, 0.8]
+
+	def background_shape(self, ct):
+		ct.rectangle(0, 0, self.allocation.width, self.allocation.height)
+
+	def draw_background(self, ct, widget=None, shape=True):
+		if widget == None:
+			widget = self
+
+		ct.set_operator(cairo.OPERATOR_SOURCE)
+		ct.rectangle(0, 0, widget.allocation.width, widget.allocation.height)
+		ct.set_source_rgba(0, 0, 0, 0)
+		
+		if not shape:
+			ct.fill_preserve()
+		else:
+			ct.fill()
+		
+		color = self.background_color()
+
+		if shape:
+			self.background_shape(ct)
+
+		ct.set_source_rgba(color[0], color[1], color[2], color[3])
+		ct.fill()
+
+	def on_expose(self, widget, evnt):
+		if not self.window:
+			return
+		
+		ct = evnt.window.cairo_create()
+		ct.save()
+		
+		area = evnt.area
+		ct.rectangle(area.x, area.y, area.width, area.height)
+		ct.clip()
+		
+		self.draw_background(ct)
+		
+		ct.restore()
+		return False
diff --git a/plugins/commander/commander/utils.py b/plugins/commander/commander/utils.py
new file mode 100644
index 0000000..a41a24e
--- /dev/null
+++ b/plugins/commander/commander/utils.py
@@ -0,0 +1,43 @@
+import os
+import types
+import inspect
+import sys
+
+class Struct(dict):
+	def __getattr__(self, name):
+		if not name in self:
+			val = super(Struct, self).__getattr__(self, name)
+		else:
+			val = self[name]
+		
+		return val
+	
+	def __setattr__(self, name, value):
+		if not name in self:
+			super(Struct, self).__setattr__(self, name, value)
+		else:
+			self[name] = value
+	
+	def __delattr__(self, name):
+		del self[name]
+
+def is_commander_module(mod):
+	if type(mod) == types.ModuleType:
+		return mod and ('__commander_module__' in mod.__dict__)
+	else:
+		mod = str(mod)
+		return mod.endswith('.py') or (os.path.isdir(mod) and os.path.isfile(os.path.join(mod, '__init__.py')))
+
+def getargspec(func):
+	ret = inspect.getargspec(func)
+
+	# Before 2.6 this was just a normal tuple, we don't want that
+	if sys.version_info < (2, 6):
+		ret = Struct({
+			'args': ret[0],
+			'varargs': ret[1],
+			'keywords': ret[2],
+			'defaults': ret[3]
+		})
+		
+	return ret
\ No newline at end of file
diff --git a/plugins/commander/commander/windowhelper.py b/plugins/commander/commander/windowhelper.py
new file mode 100644
index 0000000..6cdcf83
--- /dev/null
+++ b/plugins/commander/commander/windowhelper.py
@@ -0,0 +1,38 @@
+import gedit
+import gtk
+from entry import Entry
+from info import Info
+
+class WindowHelper:
+	def __init__(self, plugin, window):
+		self._window = window
+		self._plugin = plugin
+		self._entry = None
+		
+		self._accel = gtk.AccelGroup()
+		self._accel.connect_group(gtk.keysyms.C, gtk.gdk.SUPER_MASK, 0, self._do_command)
+		self._window.add_accel_group(self._accel)
+	
+	def deactivate(self):
+		self._window.remove_accel_group(self._accel)
+		self._window = None
+		self._plugin = None
+
+	def update_ui(self):
+		pass
+	
+	def _do_command(self, group, obj, keyval, mod):
+		view = self._window.get_active_view()
+		
+		if not view:
+			return False
+
+		if not self._entry:
+			self._entry = Entry(self._window.get_active_view())
+			self._entry.connect('destroy', self.on_entry_destroy)
+
+		self._entry.grab_focus()
+		return True
+	
+	def on_entry_destroy(self, widget):
+		self._entry = None
diff --git a/plugins/commander/modules/Makefile.am b/plugins/commander/modules/Makefile.am
new file mode 100644
index 0000000..fd847ce
--- /dev/null
+++ b/plugins/commander/modules/Makefile.am
@@ -0,0 +1,19 @@
+# Commander
+
+SUBDIRS = find
+
+plugindir = $(GEDIT_PLUGINS_DATA_DIR)/commander/modules
+
+plugin_PYTHON =	\
+	bookmark.py \
+	doc.py \
+	edit.py \
+	format.py \
+	goto.py \
+	help.py \
+	move.py \
+	reload.py \
+	set.py \
+	shell.py
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/commander/modules/bookmark.py b/plugins/commander/modules/bookmark.py
new file mode 100644
index 0000000..45f5709
--- /dev/null
+++ b/plugins/commander/modules/bookmark.py
@@ -0,0 +1,62 @@
+import commander.commands
+import commander.commands.exceptions
+
+__commander_module__ = True
+
+def check_bookmark_plugin(window):
+	if not window.get_message_bus().is_registered('/plugins/bookmarks', 'toggle'):
+		raise commander.commands.exceptions.Execute("The bookmarks plugin is not installed, not active or too old")
+
+def __default__(view, window):
+	"""Commander interface to the bookmarks plugin: bookmark
+
+This module provides an interface to the bookmarks plugin from gedit-plugins.
+If installed and active, you can add/remove/toggle bookmarks using the
+commander."""
+
+	check_bookmark_plugin(window)
+	window.get_message_bus().send('/plugins/bookmarks', 'toggle', view=view)
+
+def add(view, window):
+	"""Add bookmark: bookmark.add
+
+Add bookmark on the current line. If there already is a bookmark on the current
+line, nothing happens."""
+
+	check_bookmark_plugin(window)
+	window.get_message_bus().send('/plugins/bookmarks', 'add', view=view)
+
+def remove(view, window):
+	"""Remove bookmark: bookmark.remove
+
+Remove bookmark from the current line. If there is no bookmark on the current
+line, nothing happens."""
+
+	check_bookmark_plugin(window)
+	window.get_message_bus().send('/plugins/bookmarks', 'remove', view=view)
+
+def toggle(view, window):
+	"""Toggle bookmark: bookmark.toggle
+
+Toggle bookmark on the current line."""
+
+	check_bookmark_plugin(window)
+	window.get_message_bus().send('/plugins/bookmarks', 'toggle', view=view)
+
+def next(view, window):
+	"""Goto next bookmark: bookmark.next
+
+Jump to the next bookmark location"""
+
+	check_bookmark_plugin(window)
+
+	window.get_message_bus().send('/plugins/bookmarks', 'goto_next', view=view)
+
+def previous(view, window):
+	"""Goto previous bookmark: bookmark.previous
+
+Jump to the previous bookmark location"""
+
+	check_bookmark_plugin(window)
+
+	window.get_message_bus().send('/plugins/bookmarks', 'goto_previous', view=view)
diff --git a/plugins/commander/modules/doc.py b/plugins/commander/modules/doc.py
new file mode 100644
index 0000000..73ee13a
--- /dev/null
+++ b/plugins/commander/modules/doc.py
@@ -0,0 +1,216 @@
+import commander.commands as commands
+import commander.commands.completion
+import commander.commands.result
+import commander.commands.exceptions
+import re
+
+__commander_module__ = True
+
+class Argument:
+	def __init__(self, argtype, typename, name):
+		self.type = argtype.strip()
+		self.type_name = typename.strip()
+		self.name = name.strip()
+		
+class Function:
+	def __init__(self, text):
+		self._parse(text)
+
+	def _parse(self, text):
+		self.valid = False
+		
+		parser = re.compile('^\\s*(?:(?:\\b(?:static|inline)\\b)\\s+)?(([a-z_:][a-z0-9_:<>]*)(?:\\s*(?:\\b(?:const)\\b)\\s*)?\\s*[*&]*\\s+)?([a-z_][a-z0-9_:~]*)\\s*\\(([^)]*)\\)(\\s*const)?', re.I)
+	
+		m = parser.match(text)
+		
+		if not m:
+			return
+		
+		self.valid = True
+		
+		self.return_type = m.group(1) and m.group(1).strip() != 'void' and m.group(1).strip()
+		self.return_type_name = self.return_type and m.group(2).strip()
+		
+		parts = m.group(3).split('::')
+		self.name = parts[-1]
+		
+		if len(parts) > 1:
+			self.classname = '::'.join(parts[0:-1])
+		else:
+			self.classname = None
+		
+		self.constructor = self.name == self.classname
+		self.destructor = self.name == '~%s' % (self.classname,)
+
+		self.const = m.group(5) != None
+		self.args = []
+
+		argre = re.compile('(([a-z_:][a-z0-9_:<>]*)(?:\\s*(?:\\s*\\bconst\\b\\s*|[*&])\s*)*)\\s*([a-z_][a-z_0-9]*)$', re.I)
+		
+		for arg in m.group(4).split(','):
+			arg = arg.strip()
+			
+			if arg == 'void' or arg == '':
+				continue
+			else:
+				m2 = argre.match(arg.strip())
+				
+				if not m2:
+					self.valid = False
+					return
+				
+				arg = Argument(m2.group(1), m2.group(2), m2.group(3))
+			
+			self.args.append(arg)
+
+class Documenter:
+	def __init__(self, window, view, iter):
+		self.window = window
+		self.view = view
+		self.iter = iter
+		
+		bus = self.window.get_message_bus()
+		
+		self.canplaceholder = bus.lookup('/plugins/snippets', 'parse-and-activate') != None
+		self.placeholder = 1
+		self.text = ''
+	
+	def append(self, *args):
+		for text in args:
+			self.text += str(text)
+		
+		return self
+	
+	def append_placeholder(self, *args):
+		if not self.canplaceholder:
+			return self.append(*args)
+		
+		text = " ".join(map(lambda x: str(x), args))
+		self.text += "${%d:%s}" % (self.placeholder, text)
+		self.placeholder += 1
+		
+		return self
+	
+	def insert(self):
+		if self.canplaceholder:
+			bus = self.window.get_message_bus()
+			bus.send('/plugins/snippets', 'parse-and-activate', snippet=self.text, iter=self.iter, view=self.view)
+
+def _make_documenter(window, view):
+	buf = view.get_buffer()
+	
+	bus = window.get_message_bus()
+	canplaceholder = bus.lookup('/plugins/snippets', 'parse-and-activate') != None
+
+	insert = buf.get_iter_at_mark(buf.get_insert())
+	insert.set_line_offset(0)
+
+	offset = insert.get_offset()
+	
+	end = insert.copy()
+	
+	# This is just something random
+	if not end.forward_chars(500):
+		end = buf.get_end_iter()
+
+	text = insert.get_text(end)	
+	func = Function(text)
+
+	if not func.valid:
+		raise commander.commands.exceptions.Execute('Could not find function specification')
+	
+	doc = Documenter(window, view, insert)
+	return doc, func
+
+def gtk(window, view):
+	"""Generate gtk-doc documentation: doc.gtk
+
+Generate a documentation template for the C or C++ function defined at the
+cursor. The cursor needs to be on the first line of the function declaration
+for it to work."""
+	
+	buf = view.get_buffer()
+	lang = buf.get_language()
+
+	if not lang or not lang.get_id() in ('c', 'chdr', 'cpp'):
+		raise commander.commands.exceptions.Execute('Don\'t know about this language')
+
+	doc, func = _make_documenter(window, view)
+
+	# Generate docstring for this function
+	doc.append("/**\n * ", func.name, ":\n")
+	structp = re.compile('([A-Z]+[a-zA-Z]*)|struct\s+_([A-Z]+[a-zA-Z]*)')
+	
+	for arg in func.args:
+		sm = structp.match(arg.type_name)
+		doc.append(" * @", arg.name, ": ")
+		
+		if sm:
+			doc.append_placeholder("A #%s" % (sm.group(1) or sm.group(2),))
+		else:
+			doc.append_placeholder("Description")
+		
+		doc.append("\n")
+	
+	doc.append(" * \n * ").append_placeholder("Description").append(".\n")
+	
+	if func.return_type:
+		sm = structp.match(func.return_type_name)
+		doc.append(" *\n * Returns: ")
+		
+		if sm:
+			doc.append_placeholder("A #%s" % (sm.group(1) or sm.group(2),))
+		else:
+			doc.append_placeholder("Description")
+
+		doc.append("\n")
+
+	doc.append(" *\n **/\n")
+	doc.insert()
+
+def doxygen(window, view):
+	"""Generate doxygen documentation: doc.doxygen
+
+Generate a documentation template for the function defined at the
+cursor. The cursor needs to be on the first line of the function declaration
+for it to work."""
+
+	buf = view.get_buffer()
+	
+	if not buf.get_language().get_id() in ('c', 'chdr', 'cpp'):
+		raise commander.commands.exceptions.Execute('Don\'t know about this language')
+
+	doc, func = _make_documenter(window, view)
+
+	# Generate docstring for this function
+	doc.append("/** \\brief ").append_placeholder("Short description")
+	
+	if func.const:
+		doc.append(" (const)")
+	
+	doc.append(".\n")
+
+	for arg in func.args:
+		doc.append(" * @param ", arg.name, " ").append_placeholder("Description").append("\n")
+	
+	doc.append(" *\n * ")
+	
+	if func.constructor:
+		doc.append("Constructor.\n *\n * ")
+	elif func.destructor:
+		doc.append("Destructor.\n *\n * ")
+
+	doc.append_placeholder("Detailed description").append(".\n")
+	
+	if func.return_type:
+		doc.append(" *\n * @return: ")
+		
+		if func.return_type == 'bool':
+			doc.append("true if ").append_placeholder("Description").append(", false otherwise")
+		else:
+			doc.append_placeholder("Description")
+		
+		doc.append("\n")
+
+	doc.append(" *\n */\n")
+	doc.insert()
diff --git a/plugins/commander/modules/edit.py b/plugins/commander/modules/edit.py
new file mode 100644
index 0000000..801e236
--- /dev/null
+++ b/plugins/commander/modules/edit.py
@@ -0,0 +1,198 @@
+"""Edit files or commands"""
+import os
+import gio
+import gedit
+import glob
+import sys
+import types
+import inspect
+import gio
+
+import commander.commands as commands
+import commander.commands.completion
+import commander.commands.result
+import commander.commands.exceptions
+
+__commander_module__ = True
+
+ commands autocomplete(filename=commander.commands.completion.filename)
+def __default__(filename, view):
+	"""Edit file: edit &lt;filename&gt;"""
+	
+	doc = view.get_buffer()
+	cwd = os.getcwd()
+	
+	if not doc.is_untitled():
+		cwd = os.path.dirname(doc.get_uri())
+	else:
+		cwd = os.path.expanduser('~/')
+	
+	if not os.path.isabs(filename):
+		filename = os.path.join(cwd, filename)
+	
+	matches = glob.glob(filename)
+	files = []
+	
+	if matches:
+		for match in matches:
+			files.append(gio.File(match).get_uri())
+	else:
+		files.append(gio.File(filename).get_uri())
+	
+	if files:
+		window = view.get_toplevel()
+		gedit.commands.load_uris(window, files)
+		
+	return commander.commands.result.HIDE
+
+def _dummy_cb(num, total):
+	pass
+
+def rename(view, newfile):
+	"""Rename current file: edit.rename &lt;newname&gt;"""
+	
+	doc = view.get_buffer()
+	
+	if not hasattr(doc, 'set_uri'):
+		raise commander.commands.exceptions.Execute('Your version of gedit does not support this action')
+	
+	if doc.is_untitled():
+		raise commander.commands.exceptions.Execute('Document is unsaved and thus cannot be renamed')
+	
+	if doc.get_modified():
+		raise commander.commands.exceptions.Execute('You have unsaved changes in your document')
+	
+	if not doc.is_local():
+		raise commander.commands.exceptions.Execute('You can only rename local files')
+	
+	f = gio.File(doc.get_uri())
+	
+	if not f.query_exists():
+		raise commander.commands.exceptions.Execute('Current document file does not exist')
+	
+	if os.path.isabs(newfile):
+		dest = gio.File(newfile)
+	else:
+		dest = f.get_parent().resolve_relative_path(newfile)
+	
+	if f.equal(dest):
+		yield commander.commands.result.HIDE
+	
+	if not dest.get_parent().query_exists():
+		# Check to create parent directory
+		fstr, words, modifierret = (yield commands.result.Prompt('Directory does not exist, create? [Y/n] '))
+		
+		if fstr.strip().lower() in ['y', 'ye', 'yes', '']:
+			# Create parent directories
+			try:
+				os.makedirs(dest.get_parent().get_path())
+			except OSError, e:
+				raise commander.commands.exceptions.Execute('Could not create directory')
+		else:
+			yield commander.commands.result.HIDE
+	
+	if dest.query_exists():
+		fstr, words, modifierret = (yield commands.result.Prompt('Destination already exists, overwrite? [Y/n]'))
+		
+		if not fstr.strip().lower() in ['y', 'ye', 'yes', '']:
+			yield commander.commands.result.HIDE
+	
+	try:
+		f.move(dest, _dummy_cb, flags=gio.FILE_COPY_OVERWRITE)
+		
+		doc.set_uri(dest.get_uri())
+		yield commander.commands.result.HIDE
+	except Exception, e:
+		raise commander.commands.exceptions.Execute('Could not move file: %s' % (e,))
+
+def _mod_has_func(mod, func):
+	return func in mod.__dict__ and type(mod.__dict__[func]) == types.FunctionType
+
+def _mod_has_alias(mod, alias):
+	return '__root__' in mod.__dict__ and alias in mod.__dict__['__root__']
+
+def _edit_command(view, mod, func=None):
+	try:
+		uri = gio.File(inspect.getsourcefile(mod)).get_uri()
+	except:
+		return False
+
+	if not func:
+		gedit.commands.load_uri(view.get_toplevel(), uri)
+	else:
+		try:
+			lines = inspect.getsourcelines(func)
+			line = lines[-1]
+		except:
+			line = 0
+
+		gedit.commands.load_uri(view.get_toplevel(), uri, None, line)
+	
+	return True
+
+def _resume_command(view, mod, parts):
+	if not parts:
+		return _edit_command(view, mod)
+	
+	func = parts[0].replace('-', '_')
+
+	if len(parts) == 1 and _mod_has_func(mod, func):
+		return _edit_command(view, mod, mod.__dict__[func])
+	elif len(parts) == 1 and _mod_has_alias(mod, parts[0]):
+		return _edit_command(view, mod)
+	
+	if not func in mod.__dict__:
+		return False
+	
+	if not commands.is_commander_module(mod.__dict__[func]):
+		return False
+	
+	return _resume_command(view, mod.__dict__[func], parts[1:])
+
+ commands autocomplete(name=commander.commands.completion.command)
+def command(view, name):
+	"""Edit commander command: edit.command &lt;command&gt;"""
+	parts = name.split('.')
+	
+	for mod in sys.modules:
+		if commands.is_commander_module(sys.modules[mod]) and (mod == parts[0] or _mod_has_alias(sys.modules[mod], parts[0])):
+			if mod == parts[0]:
+				ret = _resume_command(view, sys.modules[mod], parts[1:])
+			else:
+				ret = _resume_command(view, sys.modules[mod], parts)
+			
+			if not ret:
+				raise commander.commands.exceptions.Execute('Could not find command: ' + name)
+			else:
+				return commander.commands.result.HIDE
+	
+	raise commander.commands.exceptions.Execute('Could not find command: ' + name)
+
+def new_command(view, entry, name):
+	"""Create a new commander command module: edit.new-command &lt;command&gt;"""
+	
+	filename = os.path.expanduser('~/.gnome2/gedit/commander/modules/' + name + '.py')
+	
+	if os.path.isfile(filename):
+		raise commander.commands.exceptions.Execute('Commander module `' + name + '\' already exists')
+	
+	f = open(filename, 'w')
+	f.write("import commander.commands\n\n__commander_module__ = True\n\ndef __default__(view, entry):\n\t\"\"\"Some kind of cool new feature: cool &lt;something&gt;\n\nUse this to apply the cool new feature\"\"\"\n\tpass\n")
+	f.close()
+	
+	return __default__(filename, view)
+
+def save(view):
+	window = view.get_toplevel()
+	gedit.commands.save_document(window, view.get_buffer())
+
+	return commander.commands.result.HIDE
+
+def save_all(view):
+	window = view.get_toplevel()
+	gedit.commands.save_all_documents(window)
+
+	return commander.commands.result.HIDE
+
+locals()['file'] = __default__
+move = rename
diff --git a/plugins/commander/modules/find/Makefile.am b/plugins/commander/modules/find/Makefile.am
new file mode 100644
index 0000000..0acdf81
--- /dev/null
+++ b/plugins/commander/modules/find/Makefile.am
@@ -0,0 +1,11 @@
+# Commander
+
+plugindir = $(GEDIT_PLUGINS_DATA_DIR)/commander/modules/find
+
+plugin_PYTHON =	\
+	finder.py \
+	__init__.py \
+	regex.py \
+	test.py
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/commander/modules/find/__init__.py b/plugins/commander/modules/find/__init__.py
new file mode 100644
index 0000000..e5bf53b
--- /dev/null
+++ b/plugins/commander/modules/find/__init__.py
@@ -0,0 +1,76 @@
+import commander.commands as commands
+import gedit
+import re
+import regex
+from xml.sax import saxutils
+import finder
+
+__commander_module__ = True
+__root__ = ['/', 'find_i', '//', 'r/', 'r//']
+
+class TextFinder(finder.Finder):
+	def __init__(self, entry, flags):
+		finder.Finder.__init__(self, entry)
+		
+		self.flags = flags
+
+	def do_find(self, bounds):
+		buf = self.view.get_buffer()
+		
+		if self.findstr:
+			buf.set_search_text(self.findstr, self.flags)
+		
+		ret = map(lambda x: x.copy(), bounds)
+		
+		if buf.search_forward(bounds[0], bounds[1], ret[0], ret[1]):
+			return ret
+		else:
+			return False
+
+def __default__(entry, argstr):
+	"""Find in document: find &lt;text&gt;
+
+Quickly find phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.find(argstr)
+
+def _find_insensitive(entry, argstr):
+	"""Find in document (case insensitive): find-i &lt;text&gt;
+
+Quickly find phrases in the document (case insensitive)"""
+	fd = TextFinder(entry, 0)
+	yield fd.find(argstr)
+
+def replace(entry, findstr, replstr=None):
+	"""Find/replace in document: find.replace &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.replace(findstr, False, replstr)
+
+def replace_i(entry, findstr, replstr=None):
+	"""Find/replace all in document (case insensitive): find.replace-i &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace phrases in the document (case insensitive)"""
+	fd = TextFinder(entry, 0)
+	yield fd.replace(findstr, True, replstr)
+
+def replace_all(entry, findstr, replstr=None):
+	"""Find/replace all in document: find.replace-all &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace all phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.replace(findstr, True, replstr)
+
+def replace_all_i(entry, findstr, replstr=None):
+	"""Find/replace all in document (case insensitive): find.replace-all-i &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace all phrases in the document (case insensitive)"""
+	fd = TextFinder(entry,0)
+	yield fd.replace(findstr, True, replstr)
+
+locals()['/'] = __default__
+locals()['find_i'] = _find_insensitive
+locals()['//'] = replace
+locals()['r/'] = regex.__default__
+locals()['r//'] = regex.replace
diff --git a/plugins/commander/modules/find/finder.py b/plugins/commander/modules/find/finder.py
new file mode 100644
index 0000000..eb732b9
--- /dev/null
+++ b/plugins/commander/modules/find/finder.py
@@ -0,0 +1,273 @@
+from xml.sax import saxutils
+import commander.commands as commands
+import commander.utils as utils
+
+class Finder:
+	FIND_STARTMARK = 'gedit-commander-find-startmark'
+	FIND_ENDMARK = 'gedit-commander-find-endmark'
+	
+	FIND_RESULT_STARTMARK = 'gedit-commander-find-result-startmark'
+	FIND_RESULT_ENDMARK = 'gedit-commander-find-result-endmark'
+	
+	def __init__(self, entry):
+		self.entry = entry
+		self.view = entry.view()
+		
+		self.findstr = None
+		self.replacestr = None
+
+		self.search_boundaries = utils.Struct({'start': None, 'end': None})
+		self.find_result = utils.Struct({'start': None, 'end': None})
+		
+		self.unescapes = [
+			['\\n', '\n'],
+			['\\r', '\r'],
+			['\\t', '\t']
+		]
+		
+		self.from_start = False
+		self.search_start_mark = None
+	
+	def unescape(self, s):
+		for esc in self.unescapes:
+			s = s.replace(esc[0], esc[1])
+		
+		return s
+	
+	def do_find(self, bounds):
+		return None
+	
+	def get_replace(self, text):
+		return self.replacestr
+	
+	def set_replace(self, replacestr):
+		self.replacestr = self.unescape(replacestr)
+	
+	def set_find(self, findstr):
+		self.findstr = self.unescape(findstr)
+	
+	def select_last_result(self):
+		buf = self.view.get_buffer()
+		
+		startiter = buf.get_iter_at_mark(self.find_result.start)
+		enditer = buf.get_iter_at_mark(self.find_result.end)
+		
+		buf.select_range(startiter, enditer)
+
+		visible = self.view.get_visible_rect()
+		loc = self.view.get_iter_location(startiter)
+
+		# Scroll there if needed
+		if loc.y + loc.height < visible.y or loc.y > visible.y + visible.height:
+			self.view.scroll_to_iter(startiter, 0.2, True, 0, 0.5)
+	
+	def find_next(self, select=False):
+		buf = self.view.get_buffer()
+		
+		# Search from the end of the last result to the end of the search boundary
+		bounds = [buf.get_iter_at_mark(self.find_result.end),
+			      buf.get_iter_at_mark(self.search_boundaries.end)]
+
+		ret = self.do_find(bounds)
+		
+		# Check if we need to wrap around if nothing is found
+		startiter = buf.get_iter_at_mark(self.search_start_mark)
+		startbound = buf.get_iter_at_mark(self.search_boundaries.start)
+	
+		if not ret and not self.from_start and not startiter.equal(startbound):
+			self.from_start = True
+
+			# Try from beginning
+			bounds[0] = buf.get_start_iter()
+			bounds[1] = buf.get_iter_at_mark(self.search_start_mark)
+			
+			# Make sure to just stop at the start of the previous
+			self.search_boundaries.end = self.search_start_mark
+			
+			ret = self.do_find(bounds)
+	
+		if not ret:
+			return False
+		else:
+			# Mark find result
+			buf.move_mark(self.find_result.start, ret[0])
+			buf.move_mark(self.find_result.end, ret[1])
+			
+			if select:
+				self.select_last_result()
+			
+			return True
+
+	def _create_or_move(self, markname, piter, left_gravity):
+		buf = self.view.get_buffer()
+		mark = buf.get_mark(markname)
+		
+		if not mark:
+			mark = buf.create_mark(markname, piter, left_gravity)
+		else:
+			buf.move_mark(mark, piter)
+		
+		return mark
+
+	def find_first(self, doend=True, select=False):
+		words = []
+		buf = self.view.get_buffer()
+	
+		while not self.findstr:
+			fstr, words, modifier = (yield commands.result.Prompt('Find:'))
+			
+			if fstr:
+				self.set_find(fstr)
+		
+		# Determine search area
+		bounds = list(buf.get_selection_bounds())
+		
+		if self.search_start_mark:
+			buf.delete_mark(self.search_start_mark)
+			self.search_start_mark = None
+		
+		if not bounds:
+			# Search in the whole buffer, from the current cursor position on to the
+			# end, and then continue to start from the beginning of the buffer if needed
+			bounds = list(buf.get_bounds())
+			self.search_start_mark = buf.create_mark(None, buf.get_iter_at_mark(buf.get_insert()), True)
+		
+		bounds[0].order(bounds[1])
+
+		# Set marks at the boundaries
+		self.search_boundaries.start = self._create_or_move(Finder.FIND_STARTMARK, bounds[0], True)
+		self.search_boundaries.end = self._create_or_move(Finder.FIND_ENDMARK, bounds[1], False)
+		
+		# Set the result marks so the next find will start at the correct location
+		piter = buf.get_iter_at_mark(buf.get_insert())
+		
+		self.find_result.start = self._create_or_move(Finder.FIND_RESULT_STARTMARK, piter, True)
+		self.find_result.end = self._create_or_move(Finder.FIND_RESULT_ENDMARK, piter, False)
+
+		if not self.find_next(select=select):
+			if doend:
+				self.entry.info_show('<i>Search hit end of the document</i>', True)
+
+			yield commands.result.DONE
+		else:
+			yield True
+	
+	def cancel(self):
+		buf = self.view.get_buffer()
+
+		buf.set_search_text('', 0)
+		buf.move_mark(buf.get_selection_bound(), buf.get_iter_at_mark(buf.get_insert()))
+		
+		if self.search_start_mark:
+			buf.delete_mark(self.search_start_mark)
+	
+	def find(self, findstr):
+		if findstr:
+			self.set_find(findstr)
+
+		buf = self.view.get_buffer()
+
+		try:
+			if (yield self.find_first(select=True)):
+				while True:
+					argstr, words, modifier = (yield commands.result.Prompt('Search next [<i>%s</i>]:' % (saxutils.escape(self.findstr),)))
+	
+					if argstr:
+						self.set_find(argstr)
+
+					if not self.find_next(select=True):
+						break
+
+				self.entry.info_show('<i>Search hit end of the document</i>', True)
+		except GeneratorExit, e:
+			self.cancel()
+			raise e
+
+		self.cancel()
+		yield commands.result.DONE
+	
+	def _restore_cursor(self, mark):
+		buf = mark.get_buffer()
+		
+		buf.place_cursor(buf.get_iter_at_mark(mark))
+		buf.delete_mark(mark)
+		
+		self.view.scroll_to_mark(buf.get_insert(), 0.2, True, 0, 0.5)
+	
+	def replace(self, findstr, replaceall=False, replacestr=None):
+		if findstr:
+			self.set_find(findstr)
+
+		if replacestr != None:
+			self.set_replace(replacestr)
+
+		# First find something
+		buf = self.view.get_buffer()
+		
+		if replaceall:
+			startmark = buf.create_mark(None, buf.get_iter_at_mark(buf.get_insert()), False)
+		
+		ret = (yield self.find_first(select=not replaceall))
+		
+		if not ret:
+			yield commands.result.DONE
+		
+		# Then ask for the replacement string
+		if not self.replacestr:
+			try:
+				replacestr, words, modifier = (yield commands.result.Prompt('Replace with:'))
+				self.set_replace(replacestr)
+			except GeneratorExit, e:
+				if replaceall:
+					self._restore_cursor(startmark)
+
+				self.cancel()
+				raise e
+
+		# On replace all, wrap it in begin/end user action
+		if replaceall:
+			buf.begin_user_action()
+
+		try:
+			while True:
+				if not replaceall:
+					rep, words, modifier = (yield commands.result.Prompt('Replace next [%s]:' % (saxutils.escape(self.replacestr),)))
+			
+					if rep:
+						self.set_replace(rep)
+
+				bounds = utils.Struct({'start': buf.get_iter_at_mark(self.find_result.start),
+				                       'end': buf.get_iter_at_mark(self.find_result.end)})
+
+				# If there is a selection, replace it with the replacement string
+				if not bounds.start.equal(bounds.end):
+					text = bounds.start.get_text(bounds.end)
+					repl = self.get_replace(text)
+
+					buf.begin_user_action()
+					buf.delete(bounds.start, bounds.end)
+					buf.insert(bounds.start, repl)
+					buf.end_user_action()
+
+				# Find next
+				if not self.find_next(select=not replaceall):
+					if not replaceall:
+						self.entry.info_show('<i>Search hit end of the document</i>', True)
+
+					break
+	
+		except GeneratorExit, e:
+			if replaceall:
+				self._restore_cursor(startmark)
+				buf.end_user_action()
+
+			self.cancel()
+			raise e				
+
+		if replaceall:
+			self._restore_cursor(startmark)
+
+			buf.end_user_action()
+
+		self.cancel()
+		yield commands.result.DONE
\ No newline at end of file
diff --git a/plugins/commander/modules/find/regex.py b/plugins/commander/modules/find/regex.py
new file mode 100644
index 0000000..4bddf80
--- /dev/null
+++ b/plugins/commander/modules/find/regex.py
@@ -0,0 +1,124 @@
+import commander.commands as commands
+import finder
+
+import gedit
+import re
+
+__commander_module__ = True
+__root__ = ['regex_i']
+
+class RegexFinder(finder.Finder):
+	def __init__(self, entry, flags = 0):
+		finder.Finder.__init__(self, entry)
+		
+		self.flags = re.UNICODE | re.MULTILINE | re.DOTALL | flags
+		self.groupre = re.compile('(\\\\)?\\$([0-9]+|{(([0-9]+):([^}]+))})')
+	
+	def set_find(self, findstr):
+		finder.Finder.set_find(self, findstr)
+		
+		try:
+			self.findre = re.compile(findstr, self.flags)
+		except Exception, e:
+			raise commands.exceptions.Execute('Invalid regular expression: ' + str(e))
+
+	def do_find(self, bounds):
+		buf = self.view.get_buffer()
+		
+		text = bounds[0].get_text(bounds[1])
+		ret = self.findre.search(text)
+		
+		if ret:
+			start = bounds[0].copy()
+			start.forward_chars(ret.start())
+
+			end = bounds[0].copy()
+			end.forward_chars(ret.end())
+			
+			return [start, end]
+		else:
+			return False
+	
+	def _transform(self, text, trans):
+		if not trans:
+			return text
+		
+		transforms = {
+			'u': lambda x: "%s%s" % (x[0].upper(), x[1:]),
+			'U': lambda x: x.upper(),
+			'l': lambda x: "%s%s" % (x[0].lower(), x[1:]),
+			'L': lambda x: x.lower(),
+			't': lambda x: x.title()
+		}
+		
+		for i in trans.split(','):
+			if i in transforms:
+				text = transforms[i](text)
+		
+		return text
+	
+	def _do_re_replace_group(self, matchit, group):
+		if group.group(3):
+			num = int(group.group(4))
+		else:
+			num = int(group.group(2))
+		
+		if group.group(1):
+			return group.group(2)
+		elif num < len(matchit.groups()) + 1:
+			return self._transform(matchit.group(num), group.group(5))
+		else:
+			return group.group(0)
+	
+	def _do_re_replace(self, matchit):
+		return self.groupre.sub(lambda x: self._do_re_replace_group(matchit, x), self.replacestr)
+	
+	def get_replace(self, text):
+		try:
+			return self.findre.sub(self._do_re_replace, text)
+		except Exception, e:
+			raise commands.exceptions.Execute('Invalid replacement: ' + str(e))
+
+def __default__(entry, argstr):
+	"""Find regex in document: find.regex &lt;regex&gt;
+
+Find text in the document that matches a given regular expression. The regular
+expression syntax is that of python regular expressions."""
+	fd = RegexFinder(entry)
+	yield fd.find(argstr)
+
+def _find_insensitive(entry, argstr):
+	"""Find regex in document (case insensitive): find.regex-i &lt;regex&gt;
+
+Find text in the document that matches a given regular expression. The regular
+expression syntax is that of python regular expressions. Matching dicards case."""
+	fd = RegexFinder(entry, re.IGNORECASE)
+	yield fd.find(argstr)
+
+def replace(entry, findre, replstr=None):
+	"""Find/replace regex in document: find.replace &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace phrases in the document using regular expressions"""
+	fd = RegexFinder(entry)
+	yield fd.replace(findre, False, replstr)
+
+def replace_i(entry, findre, replstr=None):
+	"""Find/replace regex in document (case insensitive): find.replace-i &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace phrases in the document using regular expressions"""
+	fd = RegexFinder(entry, re.IGNORECASE)
+	yield fd.replace(findre, False, replstr)
+
+def replace_all(entry, findre, replstr=None):
+	"""Find/replace all regex in document: find.replace-all &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace all phrases in the document using regular expressions"""
+	fd = RegexFinder(entry, 0)
+	yield fd.replace(findre, True, replstr)
+
+def replace_all_i(entry, findre, replstr=None):
+	"""Find/replace all regex in document: find.replace-all-i &lt;find&gt; [&lt;replace&gt;]
+
+Quickly find and replace all phrases in the document using regular expressions"""
+	fd = RegexFinder(entry, re.IGNORECASE)
+	yield fd.replace(findre, True, replstr)
diff --git a/plugins/commander/modules/find/test.py b/plugins/commander/modules/find/test.py
new file mode 100644
index 0000000..02d051e
--- /dev/null
+++ b/plugins/commander/modules/find/test.py
@@ -0,0 +1,76 @@
+import commander.commands as commands
+import gedit
+import re
+import regex
+from xml.sax import saxutils
+import finder
+
+__commander_module__ = True
+__root__ = ['/', 'find_i', '//', 'r/', 'r//']
+
+class TextFinder(finder.Finder):
+	def __init__(self, entry, flags):
+		finder.Finder.__init__(self, entry)
+		
+		self.flags = flags
+
+	def do_find(self, bounds):
+		buf = self.view.get_buffer()
+		
+		if self.findstr:
+			buf.set_search_text(self.findstr, self.flags)
+		
+		ret = map(lambda x: x.copy(), bounds)
+		
+		if buf.search_forward(bounds[0], bounds[1], ret[0], ret[1]):
+			return ret
+		else:
+			return False
+
+def __default__(entry, argstr):
+	"""Find in document: find &lt;text&gt;
+
+Quickly find phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.find(argstr)
+
+def _find_insensitive(entry, argstr):
+	"""Find in document (case insensitive): find-i &lt;text&gt;
+
+Quickly find phrases in the document (case insensitive)"""
+	fd = TextFinder(entry, 0)
+	yield fd.find(argstr)
+
+def replace(entry, argstr):
+	"""Find/replace in document: find.replace &lt;text&gt;
+
+Quickly find and replace phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.replace(argstr, False)
+
+def replace_i(entry, argstr):
+	"""Find/replace all in document (case insensitive): find.replace-i &lt;text&gt;
+
+Quickly find and replace phrases in the document (case insensitive)"""
+	fd = TextFinder(entry, 0)
+	yield fd.replace(argstr, True)
+
+def replace_all(entry, argstr):
+	"""Find/replace all in document: find.replace-all &lt;text&gt;
+
+Quickly find and replace all phrases in the document"""
+	fd = TextFinder(entry, gedit.SEARCH_CASE_SENSITIVE)
+	yield fd.replace(argstr, True)
+
+def replace_all_i(entry, argstr):
+	"""Find/replace all in document (case insensitive): find.replace-all-i &lt;text&gt;
+
+Quickly find and replace all phrases in the document (case insensitive)"""
+	fd = TextFinder(entry,0)
+	yield fd.replace(argstr, True)
+
+locals()['/'] = __default__	
+locals()['find_i'] = _find_insensitive			  
+locals()['//'] = replace	 	 	
+locals()['r/'] = regex.__default__	 	 
+locals()['r//'] = regex.replace	 	 
diff --git a/plugins/commander/modules/format.py b/plugins/commander/modules/format.py
new file mode 100644
index 0000000..8a500fa
--- /dev/null
+++ b/plugins/commander/modules/format.py
@@ -0,0 +1,100 @@
+import commander.commands as commands
+
+__commander_module__ = True
+
+def remove_trailing_spaces(view, removeall=False):
+	"""Remove trailing spaces: format.remove-trailing-spaces
+
+Remove trailing spaces in the selection. If there is no selection, trailing
+spaces are removed from the whole document."""
+ 
+	if removeall:
+		buffers = view.get_toplevel().get_documents()
+	else:
+		buffers = [view.get_buffer()]
+
+	for buf in buffers:
+		bounds = buf.get_selection_bounds()
+
+		if not bounds:
+			bounds = buf.get_bounds()
+
+		buf.begin_user_action()
+
+		try:
+			# For each line, remove trailing spaces
+			if not bounds[1].ends_line():
+				bounds[1].forward_to_line_end()
+
+			until = buf.create_mark(None, bounds[1], False)
+			start = bounds[0]
+			start.set_line_offset(0)
+
+			while start.compare(buf.get_iter_at_mark(until)) < 0:
+				end = start.copy()
+				end.forward_to_line_end()
+				last = end.copy()
+
+				if end.equal(buf.get_end_iter()):
+					end.backward_char()
+
+				while end.get_char().isspace() and end.compare(start) > 0:
+					end.backward_char()
+
+				if not end.ends_line():
+					if not end.get_char().isspace():
+						end.forward_char()
+
+					buf.delete(end, last)
+
+				start = end.copy()
+				start.forward_line()
+
+		except Exception, e:
+			print e
+
+		buf.delete_mark(until)
+		buf.end_user_action()
+
+	return commands.result.HIDE
+
+def _transform(view, how):
+	buf = view.get_buffer()
+	bounds = buf.get_selection_bounds()
+
+	if not bounds:
+		start = buf.get_iter_at_mark(buf.get_insert())
+		end = start.copy()
+
+		if not end.ends_line():
+			end.forward_to_line_end()
+
+		bounds = [start, end]
+
+	if not bounds[0].equal(bounds[1]):
+		text = how(bounds[0].get_text(bounds[1]))
+
+		buf.begin_user_action()
+		buf.delete(bounds[0], bounds[1])
+		buf.insert(bounds[0], text)
+		buf.end_user_action()
+
+	return commands.result.HIDE
+
+def upper(view):
+	"""Make upper case: format.upper
+
+Transform text in selection to upper case."""
+	return _transform(view, lambda x: x.upper())
+
+def lower(view):
+	"""Make lower case: format.lower
+
+Transform text in selection to lower case."""
+	return _transform(view, lambda x: x.lower())
+
+def title(view):
+	"""Make title case: format.title
+
+Transform text in selection to title case."""
+	return _transform(view, lambda x: x.title()).replace('_', '')
diff --git a/plugins/commander/modules/goto.py b/plugins/commander/modules/goto.py
new file mode 100644
index 0000000..e150162
--- /dev/null
+++ b/plugins/commander/modules/goto.py
@@ -0,0 +1,34 @@
+"""Goto specific places in the document"""
+import os
+import commander.commands as commands
+
+__commander_module__ = True
+
+def __default__(view, line, column=0):
+	"""Goto line number"""
+	
+	buf = view.get_buffer()
+	ins = buf.get_insert()
+	citer = buf.get_iter_at_mark(ins)
+
+	try:
+		if line.startswith('+'):
+			linnum = citer.get_line() + int(line[1:])
+		elif line.startswith('-'):
+			linnum = citer.get_line() - int(line[1:])
+		else:
+			linnum = int(line) - 1
+		
+		column = int(column) - 1
+	except ValueError:
+		raise commands.exceptions.Execute('Please specify a valid line number')
+
+	linnum = max(0, linnum)
+	column = max(0, column)
+
+	citer = buf.get_iter_at_line(linnum)
+	citer.forward_chars(column)
+	
+	buf.move_mark(ins, citer)
+	buf.move_mark(buf.get_selection_bound(), buf.get_iter_at_mark(ins))
+	return False
diff --git a/plugins/commander/modules/help.py b/plugins/commander/modules/help.py
new file mode 100644
index 0000000..155abb6
--- /dev/null
+++ b/plugins/commander/modules/help.py
@@ -0,0 +1,53 @@
+import sys
+import os
+import types
+
+import commander.commands as commands
+import commander.commands.completion
+
+from xml.sax import saxutils
+
+__commander_module__ = True
+
+def _name_match(first, second):
+	first = first.split('-')
+	second = second.split('-')
+
+	if len(first) > len(second):
+		return False
+	
+	for i in xrange(0, len(first)):
+		if not second[i].startswith(first[i]):
+			return False
+	
+	return True
+
+def _doc_text(command, func):
+	if not _name_match(command.split('.')[-1], func.name):
+		prefix = '<i>(Alias):</i> '
+	else:
+		prefix = ''
+
+	doc = func.doc()
+
+	if not doc:
+		doc = "<b>%s</b>\n\n<i>No documentation available</i>" % (func.name,)
+	else:
+		parts = doc.split("\n")
+		parts[0] = prefix + '<b>' + parts[0] + '</b>'
+		doc = "\n".join(parts)
+
+	return doc
+
+ commands autocomplete(command=commander.commands.completion.command)
+def __default__(entry, command='help'):
+	"""Show help on commands: help &lt;command&gt;
+
+Show detailed information on how to use a certain command (if available)"""
+	res = commander.commands.completion.command([command], 0)
+	
+	if not res:
+		raise commander.commands.exceptions.Execute('Could not find command: ' + command)
+
+	entry.info_show(_doc_text(command, res[0][0]), True)
+	return commands.result.DONE		
diff --git a/plugins/commander/modules/move.py b/plugins/commander/modules/move.py
new file mode 100644
index 0000000..4a5f753
--- /dev/null
+++ b/plugins/commander/modules/move.py
@@ -0,0 +1,93 @@
+import commander.commands as commands
+import gtk
+import re
+
+__commander_module__ = True
+
+def _move(view, what, num, modifier):
+	try:
+		num = int(num)
+	except:
+		raise commands.exceptions.Execute('Invalid number: ' + str(num))
+		
+	view.emit('move-cursor', what, num, modifier & gtk.gdk.CONTROL_MASK)
+	return commands.result.HIDE
+
+def word(view, modifier, num=1):
+	"""Move cursor per word: move.word &lt;num&gt;
+
+Move the cursor per word (use negative num to move backwards)"""
+	return _move(view, gtk.MOVEMENT_WORDS, num, modifier)
+
+def line(view, modifier, num=1):
+	"""Move cursor per line: move.line &lt;num&gt;
+
+Move the cursor per line (use negative num to move backwards)"""
+	return _move(view, gtk.MOVEMENT_DISPLAY_LINES, num, modifier)
+
+def char(view, modifier, num=1):
+	"""Move cursor per char: move.char &lt;num&gt;
+
+Move the cursor per char (use negative num to move backwards)"""
+	return _move(view, gtk.MOVEMENT_VISUAL_POSITIONS, num, modifier)
+
+def paragraph(view, modifier, num=1):
+	"""Move cursor per paragraph: move.paragraph &lt;num&gt;
+
+Move the cursor per paragraph (use negative num to move backwards)"""
+	return _move(view, gtk.MOVEMENT_PARAGRAPHS, num, modifier)
+
+def regex(view, modifier, regex, num=1):
+	"""Move cursor per regex: move.regex &lt;num&gt;
+
+Move the cursor per regex (use negative num to move backwards)"""
+	try:
+		r = re.compile(regex, re.DOTALL | re.MULTILINE | re.UNICODE)
+	except Exception, e:
+		raise commands.exceptions.Execute('Invalid regular expression: ' + str(e))
+	
+	try:
+		num = int(num)
+	except Exception, e:
+		raise commands.exceptions.Execute('Invalid number: ' + str(e))
+	
+	buf = view.get_buffer()
+	start = buf.get_iter_at_mark(buf.get_insert())
+	
+	if num > 0:
+		end = buf.get_end_iter()
+	else:
+		end = start.copy()
+		start = buf.get_start_iter()
+	
+	text = start.get_text(end)
+	ret = list(r.finditer(text))
+	
+	if num < 0:
+		ret.reverse()
+	
+	idx = min(abs(num), len(ret))
+	
+	if idx > 0:
+		found = ret[idx - 1]
+		start = buf.get_iter_at_mark(buf.get_insert())
+		
+		if num < 0:
+			start.backward_char(len(text) - found.start(0))
+		else:
+			start.forward_chars(found.start(0))
+
+		if modifier & gtk.gdk.CONTROL_MASK:
+			buf.move_mark(buf.get_selection_bound(), start)
+		else:
+			buf.move_mark(buf.get_insert(), start)
+			buf.move_mark(buf.get_selection_bound(), start)
+		
+		visible = view.get_visible_rect()
+		loc = view.get_iter_location(start)
+
+		# Scroll there if needed
+		if loc.y + loc.height < visible.y or loc.y > visible.y + visible.height:
+			view.scroll_to_mark(buf.get_insert(), 0.2, True, 0, 0.5)
+	
+	return commands.result.HIDE
diff --git a/plugins/commander/modules/reload.py b/plugins/commander/modules/reload.py
new file mode 100644
index 0000000..899155b
--- /dev/null
+++ b/plugins/commander/modules/reload.py
@@ -0,0 +1,29 @@
+import commander.commands
+import commander.commands.completion
+import commander.commands.exceptions
+import commander.commands.result
+import commander.utils as utils
+import commander.commands.module
+
+__commander_module__ = True
+
+ commander commands autocomplete(command=commander.commands.completion.command)
+def __default__(command):
+	"""Force reload of a module: reload &lt;module&gt;
+
+Force a reload of a module. This is mostly useful on systems where file monitoring
+does not work correctly."""
+
+	# Get the command
+	res = commander.commands.completion.command([command], 0)
+	
+	if not res:
+		raise commander.commands.exceptions.Execute('Could not find command: ' + command)
+
+	mod = res[0][0]
+	
+	while not isinstance(mod, commander.commands.module.Module):
+		mod = mod.parent
+	
+	commander.commands.Commands().reload_module(mod)
+	return commander.commands.result.DONE
\ No newline at end of file
diff --git a/plugins/commander/modules/set.py b/plugins/commander/modules/set.py
new file mode 100644
index 0000000..eb03cbe
--- /dev/null
+++ b/plugins/commander/modules/set.py
@@ -0,0 +1,135 @@
+import commander.commands as commands
+import commander.commands.exceptions
+
+import types
+import gtksourceview2 as gsv
+
+__commander_module__ = True
+
+def _complete_options(words, idx):
+	ret = []
+	
+	gb = globals()
+
+	for k in gb:
+		if type(gb[k]) == types.FunctionType and not k.startswith('_'):
+			ret.append(k.replace('_', '-'))
+
+	ret.sort()
+	return commands.completion.words(ret)(words, idx)
+
+def _complete_language(words, idx):
+	manager = gsv.language_manager_get_default()
+	ids = manager.get_language_ids()
+	ids.append('none')
+	ids.sort()
+	
+	return commands.completion.words(ids)(words, idx)
+
+def _complete_use_spaces(words, idx):
+	return commands.completion.words(['yes', 'no'])(words, idx)
+
+def _complete_draw_spaces(words, idx):
+	ret = ['none', 'all', 'tabs', 'newlines', 'nbsp', 'spaces']
+	return commands.completion.words(ret)(words, idx)
+
+def _complete_value(words, idx):
+	# Depends a bit on the option
+	ret, completion = _complete_options(words, idx - 1)
+	
+	if not ret:
+		return None
+	
+	completer = '_complete_' + ret[0].replace('-', '_')
+	gb = globals()
+
+	if completer in gb:
+		return gb[completer](words[1:], idx - 1)
+	else:
+		return None
+
+ commands autocomplete(option=_complete_options, value=_complete_value)
+def __default__(view, option, value):
+	"""Set gedit option: set &lt;option&gt; &lt;value&gt;
+
+Sets a gedit option, such as document language, or indenting"""
+
+	option = option.replace('-', '_')
+	gb = globals()
+
+	if option in gb and type(gb[option]) == types.FunctionType:
+		return gb[option](view, value)
+	else:
+		raise commander.commands.exceptions.Execute('Invalid setting: ' + option)
+
+ commands autocomplete(language=_complete_language)
+def language(view, language=None):
+	"""Set document language: set.language &lt;language&gt;
+
+Set the document language to the language with the specified id"""
+	if not language or language == 'none':
+		view.get_buffer().set_language(None)
+		return False
+
+	manager = gsv.language_manager_get_default()
+	lang = manager.get_language(language)
+	
+	if lang:
+		view.get_buffer().set_language(lang)
+		return False
+	else:
+		raise commander.commands.exceptions.Execute('Invalid language: ' + language)
+
+def tab_width(view, width):
+	"""Set document tab width: set.tab-width &lt;width&gt;
+
+Set the document tab width"""
+
+	try:
+		width = int(width)
+	except:
+		raise commander.commands.exceptions.Execute("Invalid tab width: " + str(width))
+	
+	if width <= 0:
+		raise commander.commands.exceptions.Execute("Invalid tab width: " + str(width))
+	
+	view.set_tab_width(width)
+	return False
+
+tab_size = tab_width
+
+ commands autocomplete(value=_complete_use_spaces)
+def use_spaces(view, value):
+	"""Use spaces instead of tabs: set.use-spaces &lt;yes/no&gt;
+
+Set to true/yes to use spaces instead of tabs"""
+	
+	setting = value in ('yes', 'true', '1')
+	view.set_insert_spaces_instead_of_tabs(setting)
+	
+	return False
+
+ commands autocomplete({'*': _complete_draw_spaces})
+def draw_spaces(view, *args):
+	"""Draw spaces: set.draw-spaces &lt;none/all/tabs/newlines/nbsp/spaces&gt;
+
+Set what kind of spaces should be drawn. Multiple options can be defined, e.g.
+for drawing spaces and tabs: <i>set.draw-spaces space tab</i>"""
+	m = {
+		'none': 0,
+		'all': gsv.DRAW_SPACES_ALL,
+		'tabs': gsv.DRAW_SPACES_TAB,
+		'newlines': gsv.DRAW_SPACES_NEWLINE,
+		'nbsp': gsv.DRAW_SPACES_NBSP,
+		'spaces': gsv.DRAW_SPACES_SPACE
+	}
+	
+	flags = 0
+	
+	for arg in args:
+		for a in m:
+			if a.startswith(arg):
+				flags = flags | m[a]
+		
+	view.set_draw_spaces(flags)
+	return False
diff --git a/plugins/commander/modules/shell.py b/plugins/commander/modules/shell.py
new file mode 100644
index 0000000..0c42304
--- /dev/null
+++ b/plugins/commander/modules/shell.py
@@ -0,0 +1,175 @@
+import subprocess
+import glib
+import fcntl
+import os
+import tempfile
+import signal
+import gio
+
+import commander.commands as commands
+import commander.commands.exceptions
+import commander.commands.result
+
+__commander_module__ = True
+__root__ = ['!', '!!', '!&']
+
+class Process:
+	def __init__(self, entry, pipe, replace, background, tmpin, stdout, suspend):
+		self.pipe = pipe
+		self.replace = replace
+		self.tmpin = tmpin
+		self.entry = entry
+		self.suspend = suspend
+		
+		if replace:
+			self.entry.view().set_editable(False)
+
+		if not background:
+			fcntl.fcntl(stdout, fcntl.F_SETFL, os.O_NONBLOCK)	
+			conditions = glib.IO_IN | glib.IO_PRI | glib.IO_ERR | glib.IO_HUP
+		
+			self.watch = glib.io_add_watch(stdout, conditions, self.collect_output)
+			self._buffer = ''
+		else:
+			stdout.close()
+
+	def update(self):
+		parts = self._buffer.split("\n")
+		
+		for p in parts[:-1]:
+			self.entry.info_show(p)
+		
+		self._buffer = parts[-1]
+
+	def collect_output(self, fd, condition):
+		if condition & (glib.IO_IN | glib.IO_PRI):
+			try:
+				ret = fd.read()
+				
+				# This seems to happen on OS X...
+				if ret == '':
+					condition = condition | glib.IO_HUP
+				else:
+					self._buffer += ret
+				
+					if not self.replace:
+						self.update()
+			except:
+				self.entry.info_show(self._buffer.strip("\n"))
+				self.stop()
+				return False
+
+		if condition & (glib.IO_ERR | glib.IO_HUP):
+			if self.replace:
+				buf = self.entry.view().get_buffer()
+				buf.begin_user_action()
+				
+				bounds = buf.get_selection_bounds()
+				
+				if bounds:
+					buf.delete(bounds[0], bounds[1])
+
+				buf.insert_at_cursor(self._buffer)
+				buf.end_user_action()
+			else:
+				self.entry.info_show(self._buffer.strip("\n"))
+			
+			self.stop()
+			return False
+		
+		return True
+
+	def stop(self):
+		if not self.suspend:
+			return
+
+		if hasattr(self.pipe, 'kill'):
+			self.pipe.kill()
+
+		glib.source_remove(self.watch)
+		
+		if self.replace:
+			self.entry.view().set_editable(True)
+		
+		if self.tmpin:
+			self.tmpin.close()
+		
+		sus = self.suspend
+		self.suspend = None
+		sus.resume()
+
+def _run_command(entry, replace, background, argstr):
+	tmpin = None
+	
+	cwd = None
+	doc = entry.view().get_buffer()
+	
+	if not doc.is_untitled() and doc.is_local():
+		gfile = gio.File(doc.get_uri())
+		cwd = os.path.dirname(gfile.get_path())
+	
+	if '<!' in argstr:
+		bounds = entry.view().get_buffer().get_selection_bounds()
+		
+		if not bounds:
+			bounds = entry.view().get_buffer().get_bounds()
+
+		inp = bounds[0].get_text(bounds[1])
+		
+		# Write to temporary file
+		tmpin = tempfile.NamedTemporaryFile(delete=False)
+		tmpin.write(inp)
+		tmpin.flush()
+
+		# Replace with temporary file
+		argstr = argstr.replace('<!', '< "' + tmpin.name + '"')
+
+	try:
+		p = subprocess.Popen(argstr, shell=True, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+		stdout = p.stdout
+
+	except Exception, e:
+		raise commander.commands.exceptions.Execute('Failed to execute: ' + e)
+	
+	suspend = None
+	
+	if not background:
+		suspend = commander.commands.result.Suspend()
+	
+	proc = Process(entry, p, replace, background, tmpin, stdout, suspend)
+
+	if not background:
+		yield suspend
+	
+		# Cancelled or simply done
+		proc.stop()
+
+		yield commander.commands.result.DONE
+	else:
+		yield commander.commands.result.HIDE
+
+def __default__(entry, argstr):
+	"""Run shell command: ! &lt;command&gt;
+
+You can use <b>&lt;!</b> as a special input meaning the current selection or current
+document."""
+	return _run_command(entry, False, False, argstr)
+
+def _run_replace(entry, argstr):
+	"""Run shell command and place output in document: !! &lt;command&gt;
+
+You can use <b>&lt;!</b> as a special input meaning the current selection or current
+document."""
+	return _run_command(entry, True, False, argstr)
+
+def _run_background(entry, argstr):
+	"""Run shell command in the background: !&amp; &lt;command&gt;
+
+You can use <b>&lt;!</b> as a special input meaning the current selection or current
+document."""
+	return _run_command(entry, False, True, argstr)
+
+locals()['!'] = __default__
+locals()['!!'] = _run_replace
+locals()['!&'] = _run_background
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]