Commit 4a801e63 authored by Paolo Borelli's avatar Paolo Borelli

new External Tools plugin

parent 91a0d0b4
2006-01-16 Steve Frécinaux <nud@apinc.org>
plugins/externaltools/*: new plugin to execute shell commands.
2006-01-15 Paolo Maggi <paolo@gnome.org>
* gedit/gedit-document-loader.c (load_local_file_real): removed
......
============
gedit 2.13.3
============
New features and fixes
======================
- New External Tools plugin, to execute shell scripts (Steve Frécinaux)
- New 'Snippets' plugin to insert repetitive text fragments (Jesse Van Den Kieboom)
- misc UI improvements and bugfixes
New and updated translations
============================
- Adam Weinberger (en_CA)
- Hendrik Richter (de)
- Kostas Papadimas (el)
- Slobodan D. Sredojevic (sr, sr@Latn)
- Takeshi AIHANA (ja)
- Clytie Siddall (vi)
- Francisco Javier F. Serrador (es)
- Priit Laes (et)
- Theppitak Karoonboonyanan (th)
============
gedit 2.13.2
============
......
......@@ -298,6 +298,8 @@ help/sv/Makefile
pixmaps/Makefile
plugins/changecase/Makefile
plugins/docinfo/Makefile
plugins/externaltools/Makefile
plugins/externaltools/tools/Makefile
plugins/indent/Makefile
plugins/Makefile
plugins/modelines/Makefile
......
#DIST_SUBDIRS = changecase docinfo savecopy sample time spell indent taglist shell_output sort
DIST_SUBDIRS = changecase docinfo indent sample sort spell time taglist
#SUBDIRS = changecase docinfo savecopy sample time indent taglist shell_output sort $(SPELL_PLUGIN_DIR)
SUBDIRS = changecase docinfo indent sample sort time taglist $(SPELL_PLUGIN_DIR)
if ENABLE_PYTHON
DIST_SUBDIRS += pythonconsole modelines snippets
SUBDIRS += pythonconsole modelines snippets
DIST_SUBDIRS += externaltools modelines pythonconsole snippets
SUBDIRS += externaltools modelines pythonconsole snippets
endif
# Shell output plugin
# Python snippets plugin
SUBDIRS = tools
plugindir = $(libdir)/gedit-2/plugins
INCLUDES = \
-I$(top_srcdir) \
$(GEDIT_CFLAGS) \
$(WARN_CFLAGS) \
$(DISABLE_DEPRECATED_CFLAGS) \
-DGNOME_ICONDIR=\""$(datadir)/pixmaps"\" \
-DGEDIT_GLADEDIR=\""$(datadir)/gedit-2/glade/"\"
plugin_LTLIBRARIES = libshell_output.la
libshell_output_la_SOURCES = shell_output.c
libshell_output_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
gladedir = $(datadir)/gedit-2/glade
glade_DATA = shell_output.glade2
plugin_in_files = shell_output.gedit-plugin.desktop.in
plugin_in_files = externaltools.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 = $(glade_DATA) $(plugin_in_files)
EXTRA_DIST = $(plugin_in_files)
CLEANFILES = $(plugin_DATA)
DISTCLEANFILES = $(plugin_DATA)
[Gedit Plugin]
Loader=python
Module=externaltools
IAge=2
_Name=External Tools
_Description=Execute external commands and shell scripts.
Authors=Steve Frécinaux <nud@apinc.org>
Copyright=Copyright © 2005 Steve Frécinaux
Website=http://www.gedit.org
This diff is collapsed.
# -*- coding: UTF-8 -*-
# Gedit External Tools plugin
# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
__all__ = ('ToolsPlugin', 'Manager', 'OutputPanel', 'Capture', 'UniqueById')
import gedit
import gtk
from gettext import gettext as _
from manager import Manager
from outputpanel import OutputPanel
from capture import Capture
from functions import *
class ToolsPlugin(gedit.Plugin):
def activate(self, window):
manager = window.get_ui_manager()
window_data = dict()
window.set_data("ToolsPluginWindowData", window_data)
window_data['action_group'] = gtk.ActionGroup("GeditToolsPluginActions")
window_data['action_group'].set_translation_domain('gedit')
window_data['action_group'].add_actions([('ToolsManager',
None,
'_External Tools...',
None,
"Opens the External Tools Manager",
lambda action: self.open_dialog())])
window_data['ui_id'] = manager.new_merge_id()
manager.insert_action_group(window_data['action_group'], -1)
manager.add_ui(window_data['ui_id'],
'/MenuBar/ToolsMenu/ToolsOps_5',
'ToolsManager', 'ToolsManager',
gtk.UI_MANAGER_MENUITEM, False)
insert_tools_menu(window)
manager.ensure_update()
# Create output console
window_data["output_buffer"] = OutputPanel(window)
bottom = window.get_bottom_panel()
bottom.add_item(window_data["output_buffer"].panel, "Shell Output", gtk.STOCK_EXECUTE)
def deactivate(self, window):
window_data = window.get_data("ToolsPluginWindowData")
manager = window.get_ui_manager()
manager.remove_ui(window_data["ui_id"])
manager.remove_action_group(window_data["action_group"])
remove_tools_menu(window)
manager.ensure_update()
bottom = window.get_bottom_panel()
bottom.remove_item(window_data["output_buffer"].panel)
window.set_data("ToolsPluginWindowData", None)
print "deactivate on window %s" % window
def create_configure_dialog(self):
return self.open_dialog()
def open_dialog(self):
tm = Manager().dialog
window = gedit.gedit_app_get_default().get_active_window()
if window:
tm.set_transient_for(window)
return tm
# -*- coding: utf-8 -*-
# Gedit External Tools plugin
# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
__all__ = ('Capture', )
import os, sys, signal
import subprocess
import gobject
from threading import Thread
class Capture(gobject.GObject):
CAPTURE_STDOUT = 0x01
CAPTURE_STDERR = 0x02
CAPTURE_BOTH = 0x03
__gsignals__ = {
'stdout-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
'stderr-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
'begin-execute': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, tuple()),
'end-execute' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,))
}
def __init__(self, command, cwd = None, env = {}):
gobject.GObject.__init__(self)
self.pipe = None
self.env = env
self.cwd = cwd
self.flags = self.CAPTURE_BOTH
self.command = command
self.input_text = None
def set_env(self, name, value):
self.env[name] = value
def set_command(self, command):
self.command = command
def set_flags(self, flags):
self.flags = flags
def set_input(self, text):
self.input_text = text
def execute(self):
if self.command is None:
return
# Initialize pipe
popen_args = {
'cwd' : self.cwd,
'shell': True,
'env' : self.env
}
if self.input_text is not None:
popen_args['stdin'] = subprocess.PIPE
if self.flags & self.CAPTURE_STDOUT:
popen_args['stdout'] = subprocess.PIPE
if self.flags & self.CAPTURE_STDERR:
popen_args['stderr'] = subprocess.PIPE
self.pipe = subprocess.Popen(self.command, **popen_args)
# Signal
self.emit('begin-execute')
# IO
if self.input_text is not None:
self.pipe.stdin.write(self.input_text)
if self.flags & self.CAPTURE_STDOUT:
gobject.io_add_watch(self.pipe.stdout, gobject.IO_IN | gobject.IO_HUP, self.on_output)
if self.flags & self.CAPTURE_STDERR:
gobject.io_add_watch(self.pipe.stderr, gobject.IO_IN | gobject.IO_HUP, self.on_output)
# Wait for the process to complete
gobject.child_watch_add(self.pipe.pid, self.on_child_end)
def on_output(self, source, condition):
line = source.readline()
if len(line) > 0:
if source == self.pipe.stdout:
self.emit('stdout-line', line)
else:
self.emit('stderr-line', line)
return True
return False
def stop(self, error_code = -1):
if self.pipe is not None:
os.kill(self.pipe.pid, signal.SIGTERM)
def on_child_end(self, pid, returncode):
self.emit('end-execute', returncode)
# -*- coding: utf-8 -*-
# Gedit External Tools plugin
# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import ElementTree as et
import os
import gtk
import gnomevfs
import gedit
from gettext import gettext as _
from outputpanel import OutputPanel
from capture import *
# ==== Data storage related functions ====
class ToolsTree:
def __init__(self):
self.xml_location = os.path.join(os.environ['HOME'], '.gnome2/gedit')
if not os.path.isdir(self.xml_location):
os.mkdir(self.xml_location)
self.xml_location = os.path.join(self.xml_location, 'gedit-tools.xml')
if not os.path.isfile(self.xml_location):
self.tree = et.parse(os.path.join(os.path.dirname(__file__), 'stock-tools.xml'))
self.root = self.tree.getroot()
self.save()
else:
try:
self.tree = et.parse(self.xml_location)
self.root = self.tree.getroot()
except:
self.tree = None
self.root = None
def __iter__(self):
return iter(self.root)
def save(self):
self.tree.write(self.xml_location)
def get_tool_from_accelerator(self, keyval, mod, ignore = None):
if not self.root:
return None
for tool in self.root:
if tool != ignore:
skey, smod = gtk.accelerator_parse(tool.get('accelerator'))
if skey == keyval and smod & mod == smod:
return tool
return None
def default(val, d):
if val is not None:
return val
else:
return d
# ==== UI related functions ====
def insert_tools_menu(window, tools = None):
window_data = dict()
window.set_data("ToolsPluginCommandsData", window_data)
if tools is None:
tools = ToolsTree()
manager = window.get_ui_manager()
window_data['action_group'] = gtk.ActionGroup("GeditToolsPluginCommandsActions")
window_data['action_group'].set_translation_domain('gedit')
window_data['ui_id'] = manager.new_merge_id()
i = 0;
for tool in tools:
menu_id = "ToolCommand%06d" % i
window_data['action_group'].add_actions([(menu_id,
None,
tool.get('label'),
tool.get('accelerator'),
tool.get('description'),
capture_menu_action)],
(window, tool))
manager.add_ui(window_data['ui_id'],
'/MenuBar/ToolsMenu/ToolsOps_4',
menu_id,
menu_id,
gtk.UI_MANAGER_MENUITEM,
False)
i = i + 1
manager.insert_action_group(window_data['action_group'], -1)
def remove_tools_menu(window):
window_data = window.get_data("ToolsPluginCommandsData")
manager = window.get_ui_manager()
manager.remove_ui(window_data['ui_id'])
manager.remove_action_group(window_data['action_group'])
window.set_data("ToolsPluginCommandsData", None)
def update_tools_menu(tools = None):
for window in gedit.gedit_app_get_default().get_windows():
remove_tools_menu(window)
insert_tools_menu(window, tools)
window.get_ui_manager().ensure_update()
# ==== Capture related functions ====
def capture_menu_action(action, window, node):
# Get the panel
panel = window.get_data("ToolsPluginWindowData")["output_buffer"]
panel.show()
panel.clear()
document = window.get_active_document()
if document is None:
# :TODO: Allow command on no document
panel.write("No document\n", panel.command)
return
uri = document.get_uri()
if uri is None:
# :TODO: Allow command on unnamed documents
panel.write("Current document has no file name\n", panel.command)
return
# Configure capture environment
path = gnomevfs.get_local_path_from_uri(uri)
cwd = os.path.dirname(path)
capture = Capture(node.text, cwd)
capture.set_flags(capture.CAPTURE_BOTH)
capture.env = {
'GEDIT_CURRENT_DOCUMENT_URI' : uri,
'GEDIT_CURRENT_DOCUMENT_PATH': path,
'GEDIT_CURRENT_DOCUMENT_DIR' : cwd
}
# Assign the error output to the output panel
panel.process = capture
capture.connect('stderr-line', capture_stderr_line_panel, panel)
capture.connect('begin-execute', capture_begin_execute_panel, panel, node.get('label'))
capture.connect('end-execute', capture_end_execute_panel, panel)
# Get input text
input_type = node.get('input')
if input_type != 'nothing':
if input_type == 'document':
start, end = document.get_bounds()
elif input_type == 'selection':
try:
start, end = document.get_selection_bounds()
except ValueError:
start, end = document.get_bounds()
elif input_type == 'line':
start = document.get_insert()
end = insert.copy()
if not start.starts_line():
start.backward_line()
if not end.ends_line():
end.forward_to_line_end()
elif input_type == 'word':
start = document.get_insert()
end = insert.copy()
if not start.inside_word():
panel.write(_('You must be inside a word to run this command'),
panel.command_tag)
return
if not start.starts_word():
start.backward_word_start()
if not end.ends_word():
end.forward_word_end()
input_text = document.get_text(start, end)
capture.set_input(input_text)
# Assign the standard output to the chosen "file"
output_type = node.get('output')
if output_type == 'output-panel':
capture.connect('stdout-line', capture_stdout_line_panel, panel)
else:
if output_type == 'insert':
pos = document.get_iter_at_mark(document.get_mark('insert'))
elif output_type == 'replace-selection':
document.delete_selection()
pos = document.get_iter_at_mark(document.get_mark('insert'))
elif output_type == 'new-document':
document = window.create_tab(True).get_document()
pos = document.get_end_iter()
elif output_type == 'replace-document':
document.set_text('')
pos = document.get_end_iter()
else:
pos = document.get_end_iter()
capture.connect('stdout-line', capture_stdout_line_document, document, pos)
# Run the command
document.begin_user_action()
capture.execute()
document.end_user_action()
def capture_stderr_line_panel(capture, line, panel):
panel.write(line, panel.error_tag)
def capture_begin_execute_panel(capture, panel, label):
panel['stop'].set_sensitive(True)
panel.clear()
panel.write("Running %s...\n" % label, panel.command_tag)
def capture_end_execute_panel(capture, exit_code, panel):
panel.write("Exit code: %s\n" % exit_code, panel.command_tag)
panel['stop'].set_sensitive(False)
def capture_stdout_line_panel(capture, line, panel):
panel.write(line)
def capture_stdout_line_document(capture, line, document, pos):
document.insert(pos, line)
# -*- coding: utf-8 -*-
# Gedit External Tools plugin
# Copyright (C) 2005-2006 Steve Frécinaux <steve@istique.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
__all__ = ('Manager', )
import gtk
from gtk import glade
import os.path
from gettext import gettext as _
from functions import *
import ElementTree as et
GLADE_FILE = os.path.join(os.path.dirname(__file__), "tools.glade")
class Manager:
LABEL_COLUMN = 0 # For Combo and Tree
NODE_COLUMN = 1 # For Tree only
NAME_COLUMN = 1 # For Combo only
__shared_state = None
combobox_items = {
'input': (
('nothing' , _('Nothing')),
('document' , _('Current document')),
('selection', _('Current selection')),
('line' , _('Current line')),
('word' , _('Current word'))
),
'output': (
('output-panel' , _('Insert in output panel')),
('new-document' , _('Create new document')),
('append-document' , _('Append to current document')),
('replace-document' , _('Replace current document')),
('replace-selection', _('Replace current selection')),
('insert' , _('Insert at cursor position'))
),
'applicability': (
('all' , _('All documents')),
('titled' , _('All documents except untitled ones')),
('local' , _('Local files only')),
('remote' , _('Remote files only')),
('untitled', _('Untitled documents only'))
)
}
def __init__(self):
if Manager.__shared_state is not None:
self.__dict__ = Manager.__shared_state
if self.dialog: return
else:
Manager.__shared_state = self.__dict__
callbacks = {
'on_new_tool_button_clicked' : self.on_new_tool_button_clicked,
'on_remove_tool_button_clicked' : self.on_remove_tool_button_clicked,
'on_tool_manager_dialog_response': self.on_tool_manager_dialog_response,
'on_accelerator_key_press' : self.on_accelerator_key_press,
'on_accelerator_focus_in' : self.on_accelerator_focus_in,
'on_entry_accelerator_focus_out' : self.on_entry_accelerator_focus_out
}
# Load the "main-window" widget from the glade file.
self.ui = glade.XML(GLADE_FILE, 'tool-manager-dialog')
self.ui.signal_autoconnect(callbacks)
self.dialog = self.ui.get_widget('tool-manager-dialog')
self.view = self.ui.get_widget('view')
for name in ['input', 'output', 'applicability']:
self.__init_combobox(name)
self.__init_tools_model()
self.__init_tools_view()
self.dialog.show()
def __init_tools_model(self):
self.tools = ToolsTree()
self.current_node = None
self.model = gtk.ListStore(str, object)
self.model.set_sort_column_id(self.LABEL_COLUMN, gtk.SORT_ASCENDING)
self.view.set_model(self.model)
for item in self.tools:
self.model.append([item.get('label'), item])
def __init_tools_view(self):
# Tools column
column = gtk.TreeViewColumn('Tools')
renderer = gtk.CellRendererText()
column.pack_start(renderer, False)
column.set_attributes(renderer, text = self.LABEL_COLUMN)
renderer.set_property('editable', True)
self.view.append_column(column)
renderer.connect('edited', self.on_view_label_cell_edited)
self.view.get_selection().connect('changed', self.on_view_selection_changed, None)
def __init_combobox(self, name):
combo = self[name]
model = gtk.ListStore(str, str)
combo.set_model(model)
for name, label in Manager.combobox_items[name]:
model.append((label, name))
combo.set_active(0)
def __getitem__(self, key):
"""Convenience function to get a widget from its name"""
return self.ui.get_widget(key)
def set_active_by_name(self, combo_name, option_name):
combo = self[combo_name]
model = combo.get_model()
piter = model.get_iter_first()
while piter is not None:
if model.get_value(piter, self.NAME_COLUMN) == option_name:
combo.set_active_iter(piter)
return True
piter = model.iter_next(piter)
return False
def get_selected_tool(self):
model, piter = self.view.get_selection().get_selected()
if piter is not None:
return piter, model.get_value(piter, self.NODE_COLUMN);
else:
return None, None
def save_current_tool(self):
if self.current_node is None:
return
buf = self['commands'].get_buffer()
self.current_node.text = buf.get_text(buf.get_start_iter(),
buf.get_end_iter())
self.current_node.set('description',
self['description'].get_text())
for name in ('input', 'output', 'applicability'):
combo = self[name]
self.current_node.set(name,
combo.get_model().get_value(
combo.get_active_iter(),
self.NAME_COLUMN))
def fill_fields(self):
node = self.current_node
self['description'].set_text(default(node.get('description'), _('A Brand New Tool')))
self['accelerator'].set_text(default(node.get('accelerator'), ''))
self['commands'].get_buffer().set_text(default(node.text, ''))
self.set_active_by_name('input',
default(node.get('input'),
Manager.combobox_items['input'][0][0]))
self.set_active_by_name('output',
default(node.get('output'),
Manager.combobox_items['output'][0][0]))
self.set_active_by_name('applicability',
default(node.get('applicability'),
Manager.combobox_items['applicability'][0][0]))
self['title'].set_label(_('Edit tool <i>%s</i>:') % node.get('label'))