[gedit-plugins] Added new plugin, commander
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gedit-plugins] Added new plugin, commander
- Date: Sat, 16 Jan 2010 19:19:41 +0000 (UTC)
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>>>></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>>>></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 <filename>"""
+
+ 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 <newname>"""
+
+ 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 <command>"""
+ 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 <command>"""
+
+ 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 <something>\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 <text>
+
+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 <text>
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <regex>
+
+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 <regex>
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <find> [<replace>]
+
+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 <text>
+
+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 <text>
+
+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 <text>
+
+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 <text>
+
+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 <text>
+
+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 <text>
+
+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 <command>
+
+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 <num>
+
+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 <num>
+
+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 <num>
+
+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 <num>
+
+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 <num>
+
+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 <module>
+
+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 <option> <value>
+
+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 <language>
+
+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 <width>
+
+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 <yes/no>
+
+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 <none/all/tabs/newlines/nbsp/spaces>
+
+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: ! <command>
+
+You can use <b><!</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: !! <command>
+
+You can use <b><!</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: !& <command>
+
+You can use <b><!</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]