diff --git a/pluma.spec b/pluma.spec index 915d5da..08f456d 100644 --- a/pluma.spec +++ b/pluma.spec @@ -16,7 +16,7 @@ Summary: Text editor for the MATE desktop Name: pluma Version: %{branch}.1 %if 0%{?rel_build} -Release: 1%{?dist} +Release: 2%{?dist} %else Release: 0.7%{?git_rel}%{?dist} %endif @@ -29,10 +29,18 @@ URL: http://mate-desktop.org # Source for snapshot-builds. %{!?rel_build:Source0: http://git.mate-desktop.org/%{name}/snapshot/%{name}-%{commit}.tar.xz#/%{git_tar}} -%if 0%{?fedora} && 0%{?fedora} >= 29 -# disable python plugins -Patch0: pluma_0001-disable-python-plugins-again.patch -%endif +# https://github.com/mate-desktop/pluma/commit/e88a2ea +Patch1: pluma_0001-Reindent-all-Python-sources-to-ts-4.-Strip-trailing-1.22.patch +# https://github.com/mate-desktop/pluma/pull/433 +Patch2: pluma_0001-quickopen-plugin-change-code-for-Python-2-3-compatib.patch +# https://github.com/mate-desktop/pluma/pull/434 +Patch3: pluma_0001-pythonconsole-plugin-change-source-code-for-Python-2.patch +# https://github.com/mate-desktop/pluma/pull/435 +Patch4: pluma_0001-externaltools-plugin-change-code-for-Python-2-3-comp.patch +# https://github.com/mate-desktop/pluma/pull/436 +Patch5: pluma_0001-snippets-plugin-change-code-for-Python-2-3-compatibi.patch +# https://github.com/mate-desktop/pluma/pull/437 +Patch6: pluma_0001-Switch-to-Python-3.patch BuildRequires: desktop-file-utils BuildRequires: enchant-devel @@ -43,9 +51,7 @@ BuildRequires: iso-codes-devel BuildRequires: libSM-devel BuildRequires: mate-common BuildRequires: pygobject3-devel -%if 0%{?fedora} && 0%{?fedora} <= 28 -BuildRequires: python2-devel -%endif +BuildRequires: python3-devel Requires: %{name}-data = %{version}-%{release} # needed to get a gsettings schema, #959607 @@ -55,8 +61,8 @@ Requires: caja-schemas # the run-command plugin uses zenity Requires: zenity # libpeas isn't splited in rhel7 -%if 0%{?fedora} && 0%{?fedora} <= 28 -Requires: libpeas-loader-python +%if 0%{?fedora} && 0%{?fedora} <= 29 +Requires: libpeas-loader-python3 %endif %description @@ -103,10 +109,11 @@ Development files for pluma NOCONFIGURE=1 ./autogen.sh %endif -%if 0%{?fedora} && 0%{?fedora} >= 29 -# disable python plugins +# with python3 +sed -i 's|#!\/usr\/bin/\python|#!\/usr\/bin\/python3|g' plugins/externaltools/data/switch-c.tool.in + +# Patch 5 NOCONFIGURE=1 ./autogen.sh -%endif # Fix debug permissions with messy hack find ./*/* -type f -exec chmod 644 {} \; @@ -145,6 +152,7 @@ find %{buildroot} -name '*.a' -exec rm -f {} ';' %{_datadir}/metainfo/pluma.appdata.xml %{_datadir}/glib-2.0/schemas/org.mate.pluma.gschema.xml %{_datadir}/glib-2.0/schemas/org.mate.pluma.plugins.filebrowser.gschema.xml +%{_datadir}/glib-2.0/schemas/org.mate.pluma.plugins.pythonconsole.gschema.xml %{_datadir}/glib-2.0/schemas/org.mate.pluma.plugins.time.gschema.xml %{_datadir}/glib-2.0/schemas/org.mate.pluma.plugins.spell.gschema.xml @@ -161,8 +169,9 @@ find %{buildroot} -name '*.a' -exec rm -f {} ';' %changelog -* Fri Apr 26 2019 Wolfgang Ulbrich - 1.22.1-1 -- update to 1.22.1 +* Thu Jun 06 2019 Wolfgang Ulbrich - 1.22.1-2 +- enable python moduls again +- use python3 * Mon Mar 04 2019 Wolfgang Ulbrich - 1.22.0-1 - update to 1.22.0 diff --git a/pluma_0001-Reindent-all-Python-sources-to-ts-4.-Strip-trailing-1.22.patch b/pluma_0001-Reindent-all-Python-sources-to-ts-4.-Strip-trailing-1.22.patch new file mode 100644 index 0000000..d33f1a8 --- /dev/null +++ b/pluma_0001-Reindent-all-Python-sources-to-ts-4.-Strip-trailing-1.22.patch @@ -0,0 +1,13183 @@ +From ec7ec9934d6f80f99474ebeca16bce7b52fe78a1 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Fri, 10 May 2019 16:26:39 +0200 +Subject: [PATCH 1/6] Reindent all Python sources to ts=4. Strip trailing + spaces. + +--- + plugins/externaltools/tools/__init__.py | 22 +- + plugins/externaltools/tools/capture.py | 18 +- + plugins/externaltools/tools/functions.py | 44 +- + plugins/externaltools/tools/library.py | 18 +- + plugins/externaltools/tools/linkparsing.py | 2 +- + plugins/externaltools/tools/manager.py | 272 +-- + plugins/externaltools/tools/outputpanel.py | 6 +- + .../pythonconsole/pythonconsole/__init__.py | 2 +- + plugins/quickopen/quickopen/__init__.py | 36 +- + plugins/quickopen/quickopen/popup.py | 877 ++++--- + plugins/quickopen/quickopen/virtualdirs.py | 84 +- + plugins/quickopen/quickopen/windowhelper.py | 218 +- + plugins/snippets/snippets/Completion.py | 304 +-- + plugins/snippets/snippets/Document.py | 1994 ++++++++------- + plugins/snippets/snippets/Exporter.py | 176 +- + plugins/snippets/snippets/Helper.py | 244 +- + plugins/snippets/snippets/Importer.py | 184 +- + plugins/snippets/snippets/LanguageManager.py | 25 +- + plugins/snippets/snippets/Library.py | 1892 +++++++------- + plugins/snippets/snippets/Manager.py | 2176 ++++++++--------- + plugins/snippets/snippets/Parser.py | 466 ++-- + plugins/snippets/snippets/Placeholder.py | 1278 +++++----- + plugins/snippets/snippets/Snippet.py | 657 ++--- + .../snippets/snippets/SubstitutionParser.py | 356 +-- + plugins/snippets/snippets/WindowHelper.py | 248 +- + plugins/snippets/snippets/__init__.py | 94 +- + 28 files changed, 5871 insertions(+), 5856 deletions(-) + mode change 100755 => 100644 plugins/quickopen/quickopen/__init__.py + mode change 100755 => 100644 plugins/quickopen/quickopen/popup.py + mode change 100755 => 100644 plugins/quickopen/quickopen/virtualdirs.py + mode change 100755 => 100644 plugins/quickopen/quickopen/windowhelper.py + mode change 100755 => 100644 plugins/snippets/snippets/Completion.py + mode change 100755 => 100644 plugins/snippets/snippets/Document.py + mode change 100755 => 100644 plugins/snippets/snippets/Exporter.py + mode change 100755 => 100644 plugins/snippets/snippets/Helper.py + mode change 100755 => 100644 plugins/snippets/snippets/Importer.py + mode change 100755 => 100644 plugins/snippets/snippets/LanguageManager.py + mode change 100755 => 100644 plugins/snippets/snippets/Library.py + mode change 100755 => 100644 plugins/snippets/snippets/Manager.py + mode change 100755 => 100644 plugins/snippets/snippets/Parser.py + mode change 100755 => 100644 plugins/snippets/snippets/Placeholder.py + mode change 100755 => 100644 plugins/snippets/snippets/Snippet.py + mode change 100755 => 100644 plugins/snippets/snippets/SubstitutionParser.py + mode change 100755 => 100644 plugins/snippets/snippets/WindowHelper.py + mode change 100755 => 100644 plugins/snippets/snippets/__init__.py + +diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py +index 463c8f5..153d6c6 100755 +--- a/plugins/externaltools/tools/__init__.py ++++ b/plugins/externaltools/tools/__init__.py +@@ -57,12 +57,12 @@ class ToolMenu(object): + action._tool_handler = None + + self._action_group.remove_action(action) +- ++ + accelmap = Gtk.AccelMap.get() + + for s in self._signals: + accelmap.disconnect(s) +- ++ + self._signals = [] + + def _insert_directory(self, directory, path): +@@ -76,7 +76,7 @@ class ToolMenu(object): + manager.add_ui(self._merge_id, path, + action_name, action_name, + Gtk.UIManagerItemType.MENU, False) +- ++ + self._insert_directory(item, path + '/' + action_name) + + for item in directory.tools: +@@ -87,16 +87,16 @@ class ToolMenu(object): + # Attach the item and the handler to the action object + action._tool_item = item + action._tool_handler = handler +- ++ + # Make sure to replace accel + accelpath = '/ExternalToolsPluginToolActions/%s' % (action_name, ) +- ++ + if item.shortcut: + key, mod = Gtk.accelerator_parse(item.shortcut) + Gtk.AccelMap.change_entry(accelpath, key, mod, True) +- ++ + self._signals.append(Gtk.AccelMap.get().connect('changed::%s' % (accelpath,), self.on_accelmap_changed, item)) +- ++ + self._action_group.add_action_with_accel(action, item.shortcut) + + manager.add_ui(self._merge_id, path, +@@ -106,7 +106,7 @@ class ToolMenu(object): + def on_accelmap_changed(self, accelmap, path, key, mod, tool): + tool.shortcut = Gtk.accelerator_name(key, mod) + tool.save() +- ++ + self._plugin.update_manager(tool) + + def update(self): +@@ -119,10 +119,10 @@ class ToolMenu(object): + def filter_language(self, language, item): + if not item.languages: + return True +- ++ + if not language and 'plain' in item.languages: + return True +- ++ + if language and (language.get_id() in item.languages): + return True + else: +@@ -142,7 +142,7 @@ class ToolMenu(object): + 'titled': titled, + 'untitled': not titled, + } +- ++ + language = document.get_language() + + for action in self._action_group.list_actions(): +diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py +index 487c8db..73ce270 100755 +--- a/plugins/externaltools/tools/capture.py ++++ b/plugins/externaltools/tools/capture.py +@@ -29,7 +29,7 @@ class Capture(GObject.Object): + CAPTURE_STDERR = 0x02 + CAPTURE_BOTH = 0x03 + CAPTURE_NEEDS_SHELL = 0x04 +- ++ + WRITE_BUFFER_SIZE = 0x4000 + + __gsignals__ = { +@@ -73,7 +73,7 @@ class Capture(GObject.Object): + 'shell': self.flags & self.CAPTURE_NEEDS_SHELL, + 'env' : self.env + } +- ++ + if self.input_text is not None: + popen_args['stdin'] = subprocess.PIPE + if self.flags & self.CAPTURE_STDOUT: +@@ -84,17 +84,17 @@ class Capture(GObject.Object): + self.tried_killing = False + self.idle_write_id = 0 + self.read_buffer = '' +- ++ + try: + self.pipe = subprocess.Popen(self.command, **popen_args) + except OSError, e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return +- ++ + # Signal + self.emit('begin-execute') +- ++ + if self.flags & self.CAPTURE_STDOUT: + # Set non blocking + flags = fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK +@@ -132,13 +132,13 @@ class Capture(GObject.Object): + try: + l = len(self.write_buffer) + m = min(l, self.WRITE_BUFFER_SIZE) +- ++ + self.pipe.stdin.write(self.write_buffer[:m]) +- ++ + if m == l: + self.write_buffer = '' + self.pipe.stdin.close() +- ++ + self.idle_write_id = 0 + + return False +@@ -165,7 +165,7 @@ class Capture(GObject.Object): + + self.read_buffer += line + lines = self.read_buffer.splitlines(True) +- ++ + if not lines[-1].endswith("\n"): + self.read_buffer = lines[-1] + lines = lines[0:-1] +diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py +index e126844..dd4f82b 100755 +--- a/plugins/externaltools/tools/functions.py ++++ b/plugins/externaltools/tools/functions.py +@@ -30,13 +30,13 @@ def default(val, d): + def current_word(document): + piter = document.get_iter_at_mark(document.get_insert()) + start = piter.copy() +- ++ + if not piter.starts_word() and (piter.inside_word() or piter.ends_word()): + start.backward_word_start() +- ++ + if not piter.ends_word() and piter.inside_word(): + piter.forward_word_end() +- ++ + return (start, piter) + + # ==== Capture related functions ==== +@@ -56,32 +56,32 @@ def run_external_tool(window, panel, node): + # Environment vars relative to current document + document = view.get_buffer() + uri = document.get_uri() +- ++ + # Current line number + piter = document.get_iter_at_mark(document.get_insert()) + capture.set_env(PLUMA_CURRENT_LINE_NUMBER=str(piter.get_line() + 1)) +- ++ + # Current line text + piter.set_line_offset(0) + end = piter.copy() +- ++ + if not end.ends_line(): + end.forward_to_line_end() +- ++ + capture.set_env(PLUMA_CURRENT_LINE=piter.get_text(end)) +- ++ + # Selected text (only if input is not selection) + if node.input != 'selection' and node.input != 'selection-document': + bounds = document.get_selection_bounds() +- ++ + if bounds: + capture.set_env(PLUMA_SELECTED_TEXT=bounds[0].get_text(bounds[1])) +- ++ + bounds = current_word(document) + capture.set_env(PLUMA_CURRENT_WORD=bounds[0].get_text(bounds[1])) +- ++ + capture.set_env(PLUMA_CURRENT_DOCUMENT_TYPE=document.get_mime_type()) +- ++ + if uri is not None: + gfile = Gio.file_new_for_uri(uri) + scheme = gfile.get_uri_scheme() +@@ -106,7 +106,7 @@ def run_external_tool(window, panel, node): + PLUMA_DOCUMENTS_PATH = ' '.join(documents_path)) + + flags = capture.CAPTURE_BOTH +- ++ + if not node.has_hash_bang(): + flags |= capture.CAPTURE_NEEDS_SHELL + +@@ -131,7 +131,7 @@ def run_external_tool(window, panel, node): + elif input_type == 'selection' or input_type == 'selection-document': + try: + start, end = document.get_selection_bounds() +- ++ + print start, end + except ValueError: + if input_type == 'selection-document': +@@ -142,7 +142,7 @@ def run_external_tool(window, panel, node): + else: + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() +- ++ + elif input_type == 'line': + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() +@@ -196,12 +196,12 @@ def run_external_tool(window, panel, node): + document.begin_user_action() + + capture.connect('stderr-line', capture_stderr_line_panel, panel) +- capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name) ++ capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name) + capture.connect('end-execute', capture_end_execute_panel, panel, view, output_type) + + # Run the command + capture.execute() +- ++ + if output_type != 'nothing': + document.end_user_action() + +@@ -222,7 +222,7 @@ class MultipleDocumentsSaver: + signals[doc] = doc.connect('saving', self.on_document_saving) + Pluma.commands_save_document(window, doc) + doc.disconnect(signals[doc]) +- ++ + def on_document_saving(self, doc, size, total_size): + self._counter += 1 + self._signal_ids[doc] = doc.connect('saved', self.on_document_saved) +@@ -230,12 +230,12 @@ class MultipleDocumentsSaver: + def on_document_saved(self, doc, error): + if error: + self._error = True +- ++ + doc.disconnect(self._signal_ids[doc]) + del self._signal_ids[doc] +- ++ + self._counter -= 1 +- ++ + if self._counter == 0 and not self._error: + run_external_tool(self._window, self._panel, self._node) + +@@ -275,7 +275,7 @@ def capture_end_execute_panel(capture, exit_code, panel, view, output_type): + mtype, uncertain = Gio.content_type_guess(None, doc.get_text(start, end, False).encode('utf-8')) + lmanager = GtkSource.LanguageManager.get_default() + language = lmanager.guess_language(doc.get_uri(), mtype) +- ++ + if language is not None: + doc.set_language(language) + +diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py +index b4e6924..186c33f 100755 +--- a/plugins/externaltools/tools/library.py ++++ b/plugins/externaltools/tools/library.py +@@ -286,64 +286,80 @@ class Tool(object): + applicability = self._properties.get('Applicability') + if applicability: return applicability + return 'all' ++ + def set_applicability(self, value): + self._set_property_if_changed('Applicability', value) ++ + applicability = property(get_applicability, set_applicability) + + def get_name(self): + name = self._properties.get('Name') + if name: return name + return os.path.basename(self.filename) ++ + def set_name(self, value): + self._set_property_if_changed('Name', value) ++ + name = property(get_name, set_name) + + def get_shortcut(self): + shortcut = self._properties.get('Shortcut') + if shortcut: return shortcut + return None ++ + def set_shortcut(self, value): + self._set_property_if_changed('Shortcut', value) ++ + shortcut = property(get_shortcut, set_shortcut) + + def get_comment(self): + comment = self._properties.get('Comment') + if comment: return comment + return self.filename ++ + def set_comment(self, value): + self._set_property_if_changed('Comment', value) ++ + comment = property(get_comment, set_comment) + + def get_input(self): + input = self._properties.get('Input') + if input: return input + return 'nothing' ++ + def set_input(self, value): + self._set_property_if_changed('Input', value) ++ + input = property(get_input, set_input) + + def get_output(self): + output = self._properties.get('Output') + if output: return output + return 'output-panel' ++ + def set_output(self, value): + self._set_property_if_changed('Output', value) ++ + output = property(get_output, set_output) + + def get_save_files(self): + save_files = self._properties.get('Save-files') + if save_files: return save_files + return 'nothing' ++ + def set_save_files(self, value): + self._set_property_if_changed('Save-files', value) ++ + save_files = property(get_save_files, set_save_files) + + def get_languages(self): + languages = self._properties.get('Languages') + if languages: return languages + return [] ++ + def set_languages(self, value): + self._set_property_if_changed('Languages', value) ++ + languages = property(get_languages, set_languages) + + def has_hash_bang(self): +@@ -358,7 +374,6 @@ class Tool(object): + for line in fp: + if line.strip() == '': + continue +- + return line.startswith('#!') + + # There is no property for this one because this function is quite +@@ -404,7 +419,6 @@ class Tool(object): + + def save_with_script(self, script): + filename = self.library.get_full_path(self.filename, 'w') +- + fp = open(filename, 'w', 1) + + # Make sure to first print header (shebang, modeline), then +diff --git a/plugins/externaltools/tools/linkparsing.py b/plugins/externaltools/tools/linkparsing.py +index 27b9ba8..33ed614 100755 +--- a/plugins/externaltools/tools/linkparsing.py ++++ b/plugins/externaltools/tools/linkparsing.py +@@ -193,7 +193,7 @@ REGEXP_VALAC = r""" + + #ruby + #test.rb:5: ... +-# from test.rb:3:in `each' ++# from test.rb:3:in `each' + # fist line parsed by REGEXP_STANDARD + REGEXP_RUBY = r""" + ^\s+from\s +diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py +index 24d7d71..4da0deb 100755 +--- a/plugins/externaltools/tools/manager.py ++++ b/plugins/externaltools/tools/manager.py +@@ -34,7 +34,7 @@ class LanguagesPopup(Gtk.Window): + + def __init__(self, languages): + Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) +- ++ + self.set_default_size(200, 200) + self.props.can_focus = True + +@@ -42,9 +42,9 @@ class LanguagesPopup(Gtk.Window): + self.init_languages(languages) + + self.show() +- ++ + self.grab_add() +- ++ + self.keyboard = None + device_manager = Gdk.Display.get_device_manager(self.get_window().get_display()) + for device in device_manager.list_devices(Gdk.DeviceType.MASTER): +@@ -76,40 +76,40 @@ class LanguagesPopup(Gtk.Window): + + def build(self): + self.model = Gtk.ListStore(str, str, bool) +- ++ + self.sw = Gtk.ScrolledWindow() + self.sw.show() +- ++ + self.sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) +- ++ + self.view = Gtk.TreeView(self.model) + self.view.show() +- ++ + self.view.set_headers_visible(False) +- ++ + column = Gtk.TreeViewColumn() +- ++ + renderer = Gtk.CellRendererToggle() + column.pack_start(renderer, False) + column.set_attributes(renderer, active=self.COLUMN_ENABLED) +- ++ + renderer.connect('toggled', self.on_language_toggled) +- ++ + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.set_attributes(renderer, text=self.COLUMN_NAME) +- ++ + self.view.append_column(column) + self.view.set_row_separator_func(self.on_separator) +- ++ + self.sw.add(self.view) +- ++ + self.add(self.sw) +- ++ + def enabled_languages(self, model, path, piter, ret): + enabled = model.get_value(piter, self.COLUMN_ENABLED) +- ++ + if path.get_indices()[0] == 0 and enabled: + return True + +@@ -117,42 +117,42 @@ class LanguagesPopup(Gtk.Window): + ret.append(model.get_value(piter, self.COLUMN_ID)) + + return False +- ++ + def languages(self): + ret = [] +- ++ + self.model.foreach(self.enabled_languages, ret) + return ret +- ++ + def on_separator(self, model, piter): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' +- ++ + def init_languages(self, languages): + manager = GtkSource.LanguageManager() + langs = [manager.get_language(x) for x in manager.get_language_ids()] + langs.sort(key=lambda x: x.get_name()) +- ++ + self.model.append([_('All languages'), None, not languages]) + self.model.append(['-', None, False]) + self.model.append([_('Plain Text'), 'plain', 'plain' in languages]) + self.model.append(['-', None, False]) +- ++ + for lang in langs: + self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages]) + + def correct_all(self, model, path, piter, enabled): + if path == (0,): + return False +- ++ + model.set_value(piter, self.COLUMN_ENABLED, enabled) + + def on_language_toggled(self, renderer, path): + piter = self.model.get_iter(path) +- ++ + enabled = self.model.get_value(piter, self.COLUMN_ENABLED) + self.model.set_value(piter, self.COLUMN_ENABLED, not enabled) +- ++ + if path == '0': + self.model.foreach(self.correct_all, False) + else: +@@ -165,55 +165,55 @@ class LanguagesPopup(Gtk.Window): + else: + event.window = self.view.get_bin_window() + return self.view.event(event) +- ++ + def do_key_release_event(self, event): + event.window = self.view.get_bin_window() + return self.view.event(event) +- ++ + def in_window(self, event, window=None): + if not window: + window = self.get_window() + + geometry = window.get_geometry() + origin = window.get_origin() +- ++ + return event.x_root >= origin[1] and \ + event.x_root <= origin[1] + geometry[2] and \ + event.y_root >= origin[2] and \ + event.y_root <= origin[2] + geometry[3] +- ++ + def do_destroy(self): + if self.keyboard: + self.keyboard.ungrab(Gdk.CURRENT_TIME) + self.pointer.ungrab(Gdk.CURRENT_TIME) +- ++ + return Gtk.Window.do_destroy(self) +- ++ + def setup_event(self, event, window): + fr = event.window.get_origin() + to = window.get_origin() +- ++ + event.window = window + event.x += fr[1] - to[1] + event.y += fr[2] - to[2] +- ++ + def resolve_widgets(self, root): + res = [root] +- ++ + if isinstance(root, Gtk.Container): + root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None) +- ++ + return res +- ++ + def resolve_windows(self, window): + if not window: + return [] + + res = [window] + res.extend(window.get_children()) +- ++ + return res +- ++ + def propagate_mouse_event(self, event, reverse=True): + allwidgets = self.resolve_widgets(self.get_child()) + +@@ -223,19 +223,19 @@ class LanguagesPopup(Gtk.Window): + for widget in allwidgets: + windows = self.resolve_windows(widget.get_window()) + windows.reverse() +- ++ + for window in windows: + if not (window.get_events() & event.type): + continue + +- if self.in_window(event, window): ++ if self.in_window(event, window): + self.setup_event(event, window) + + if widget.event(event): + return True +- ++ + return False +- ++ + def do_button_press_event(self, event): + if not self.in_window(event): + self.destroy() +@@ -250,19 +250,19 @@ class LanguagesPopup(Gtk.Window): + + def do_scroll_event(self, event): + return self.propagate_mouse_event(event, False) +- ++ + def do_motion_notify_event(self, event): + return self.propagate_mouse_event(event) +- ++ + def do_enter_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_leave_notify_event(self, event): + return self.propagate_mouse_event(event) +- ++ + def do_proximity_in_event(self, event): + return self.propagate_mouse_event(event) +- ++ + def do_proximity_out_event(self, event): + return self.propagate_mouse_event(event) + +@@ -281,12 +281,12 @@ class Manager(GObject.Object): + self._size = (0, 0) + self._languages = {} + self._tool_rows = {} +- ++ + self.build() + + def get_final_size(self): + return self._size +- ++ + def build(self): + callbacks = { + 'on_new_tool_button_clicked' : self.on_new_tool_button_clicked, +@@ -305,42 +305,42 @@ class Manager(GObject.Object): + self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui')) + self.ui.connect_signals(callbacks) + self.dialog = self.ui.get_object('tool-manager-dialog') +- ++ + self.view = self.ui.get_object('view') +- ++ + self.__init_tools_model() + self.__init_tools_view() + + for name in ['input', 'output', 'applicability', 'save-files']: + self.__init_combobox(name) +- ++ + self.do_update() + + def expand_from_doc(self, doc): + row = None +- ++ + if doc: + if doc.get_language(): + lid = doc.get_language().get_id() +- ++ + if lid in self._languages: + row = self._languages[lid] + elif 'plain' in self._languages: + row = self._languages['plain'] +- ++ + if not row and None in self._languages: + row = self._languages[None] +- ++ + if not row: + return +- ++ + self.view.expand_row(row.get_path(), False) + self.view.get_selection().select_path(row.get_path()) +- ++ + def run(self, window): + if self.dialog == None: + self.build() +- ++ + # Open up language + self.expand_from_doc(window.get_active_document()) + +@@ -351,7 +351,7 @@ class Manager(GObject.Object): + def add_accelerator(self, item): + if not item.shortcut: + return +- ++ + if item.shortcut in self.accelerators: + if not item in self.accelerators[item.shortcut]: + self.accelerators[item.shortcut].append(item) +@@ -375,42 +375,42 @@ class Manager(GObject.Object): + lid = language.get_id() + else: + lid = language +- ++ + if not lid in self._languages: + piter = self.model.append(None, [language]) +- ++ + parent = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter)) + self._languages[lid] = parent + else: + parent = self._languages[lid] +- ++ + piter = self.model.get_iter(parent.get_path()) + child = self.model.append(piter, [tool]) +- ++ + if not tool in self._tool_rows: + self._tool_rows[tool] = [] +- ++ + self._tool_rows[tool].append(Gtk.TreeRowReference.new(self.model, self.model.get_path(child))) + return child + + def add_tool(self, tool): + manager = GtkSource.LanguageManager() + ret = None +- ++ + for lang in tool.languages: + l = manager.get_language(lang) +- ++ + if l: + ret = self.add_tool_to_language(tool, l) + elif lang == 'plain': + ret = self.add_tool_to_language(tool, 'plain') +- ++ + if not ret: + ret = self.add_tool_to_language(tool, None) + + self.add_accelerator(tool) + return ret +- ++ + def __init_tools_model(self): + self.tools = ToolLibrary() + self.current_node = None +@@ -430,26 +430,26 @@ class Manager(GObject.Object): + # For languages, sort All before everything else, otherwise alphabetical + t1 = model.get_value(iter1, self.TOOL_COLUMN) + t2 = model.get_value(iter2, self.TOOL_COLUMN) +- ++ + if model.iter_parent(iter1) == None: + if t1 == None: + return -1 +- ++ + if t2 == None: + return 1 +- ++ + def lang_name(lang): + if isinstance(lang, GtkSource.Language): + return lang.get_name() + else: + return _('Plain Text') +- ++ + n1 = lang_name(t1) + n2 = lang_name(t2) + else: + n1 = t1.name + n2 = t2.name +- ++ + return cmp(n1.lower(), n2.lower()) + + def __init_tools_view(self): +@@ -459,12 +459,12 @@ class Manager(GObject.Object): + column.pack_start(renderer, False) + renderer.set_property('editable', True) + self.view.append_column(column) +- ++ + column.set_cell_data_func(renderer, self.get_cell_data_cb, None) + + renderer.connect('edited', self.on_view_label_cell_edited) + renderer.connect('editing-started', self.on_view_label_cell_editing_started) +- ++ + self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None) + + def __init_combobox(self, name): +@@ -491,10 +491,10 @@ class Manager(GObject.Object): + + if piter is not None: + tool = model.get_value(piter, self.TOOL_COLUMN) +- ++ + if not isinstance(tool, Tool): + tool = None +- ++ + return piter, tool + else: + return None, None +@@ -541,27 +541,27 @@ class Manager(GObject.Object): + + for nm in ('input', 'output', 'applicability', 'save-files'): + self[nm].set_active(0) +- ++ + self['languages_label'].set_text(_('All Languages')) +- ++ + def fill_languages_button(self): + if not self.current_node or not self.current_node.languages: + self['languages_label'].set_text(_('All Languages')) + else: + manager = GtkSource.LanguageManager() + langs = [] +- ++ + for lang in self.current_node.languages: + if lang == 'plain': + langs.append(_('Plain Text')) + else: + l = manager.get_language(lang) +- ++ + if l: + langs.append(l.get_name()) +- ++ + self['languages_label'].set_text(', '.join(langs)) +- ++ + def fill_fields(self): + node = self.current_node + self['accelerator'].set_text(default(node.shortcut, '')) +@@ -587,7 +587,7 @@ class Manager(GObject.Object): + for nm in ('input', 'output', 'applicability', 'save-files'): + model = self[nm].get_model() + piter = model.get_iter_first() +- ++ + self.set_active_by_name(nm, + default(node.__getattribute__(nm.replace('-', '_')), + model.get_value(piter, self.NAME_COLUMN))) +@@ -620,34 +620,34 @@ class Manager(GObject.Object): + self['tool-table'].set_sensitive(True) + else: + self.clear_fields() +- self['tool-table'].set_sensitive(False) ++ self['tool-table'].set_sensitive(False) + + def language_id_from_iter(self, piter): + if not piter: + return None + + tool = self.model.get_value(piter, self.TOOL_COLUMN) +- ++ + if isinstance(tool, Tool): + piter = self.model.iter_parent(piter) + tool = self.model.get_value(piter, self.TOOL_COLUMN) +- ++ + if isinstance(tool, GtkSource.Language): + return tool.get_id() + elif tool: + return 'plain' +- ++ + return None + + def selected_language_id(self): + # Find current language if there is any + model, piter = self.view.get_selection().get_selected() +- ++ + return self.language_id_from_iter(piter) + + def on_new_tool_button_clicked(self, button): + self.save_current_tool() +- ++ + # block handlers while inserting a new item + self.view.get_selection().handler_block(self.selection_changed_id) + +@@ -656,10 +656,10 @@ class Manager(GObject.Object): + self.tools.tree.tools.append(self.current_node) + + lang = self.selected_language_id() +- ++ + if lang: + self.current_node.languages = [lang] +- ++ + piter = self.add_tool(self.current_node) + + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), True) +@@ -671,7 +671,7 @@ class Manager(GObject.Object): + def tool_changed(self, tool, refresh=False): + for row in self._tool_rows[tool]: + self.model.row_changed(row.get_path(), self.model.get_iter(row.get_path())) +- ++ + if refresh and tool == self.current_node: + self.fill_fields() + +@@ -685,29 +685,29 @@ class Manager(GObject.Object): + + if node.is_global(): + shortcut = node.shortcut +- ++ + if node.parent.revert_tool(node): + self.remove_accelerator(node, shortcut) + self.add_accelerator(node) + + self['revert-tool-button'].set_sensitive(False) + self.fill_fields() +- ++ + self.tool_changed(node) + else: + parent = self.model.iter_parent(piter) + language = self.language_id_from_iter(parent) +- ++ + self.model.remove(piter) +- ++ + if language in node.languages: + node.languages.remove(language) + + self._tool_rows[node] = filter(lambda x: x.valid(), self._tool_rows[node]) +- ++ + if not self._tool_rows[node]: + del self._tool_rows[node] +- ++ + if node.parent.delete_tool(node): + self.remove_accelerator(node) + self.current_node = None +@@ -717,10 +717,10 @@ class Manager(GObject.Object): + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), False) + + self.view.grab_focus() +- ++ + path = self._languages[language].get_path() + parent = self.model.get_iter(path) +- ++ + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] +@@ -729,9 +729,9 @@ class Manager(GObject.Object): + if new_text != '': + piter = self.model.get_iter(path) + tool = self.model.get_value(piter, self.TOOL_COLUMN) +- ++ + tool.name = new_text +- ++ + self.save_current_tool() + self.tool_changed(tool) + +@@ -742,7 +742,7 @@ class Manager(GObject.Object): + if isinstance(editable, Gtk.Entry): + editable.set_text(tool.name) + editable.grab_focus() +- ++ + def on_view_selection_changed(self, selection, userdata): + self.save_current_tool() + self.do_update() +@@ -750,19 +750,19 @@ class Manager(GObject.Object): + def accelerator_collision(self, name, node): + if not name in self.accelerators: + return [] +- ++ + ret = [] +- ++ + for other in self.accelerators[name]: + if not other.languages or not node.languages: + ret.append(other) + continue +- ++ + for lang in other.languages: + if lang in node.languages: + ret.append(other) + continue +- ++ + return ret + + def set_accelerator(self, keyval, mod): +@@ -775,9 +775,9 @@ class Manager(GObject.Object): + self.current_node.shorcut = None + self.save_current_tool() + return True +- ++ + col = self.accelerator_collision(name, self.current_node) +- ++ + if col: + dialog = Gtk.MessageDialog(self.dialog, + Gtk.DialogFlags.MODAL, +@@ -787,7 +787,7 @@ class Manager(GObject.Object): + + dialog.run() + dialog.destroy() +- ++ + self.add_accelerator(self.current_node) + return False + +@@ -816,7 +816,7 @@ class Manager(GObject.Object): + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() +- ++ + # Capture all `normal characters` + return True + elif Gdk.keyval_to_unicode(event.keyval): +@@ -849,7 +849,7 @@ class Manager(GObject.Object): + return + + self.on_tool_manager_dialog_focus_out(dialog, None) +- ++ + self.dialog.destroy() + self.dialog = None + self.tools = None +@@ -873,7 +873,7 @@ class Manager(GObject.Object): + label = _('Plain Text') + else: + label = tool.get_name() +- ++ + markup = saxutils.escape(label) + editable = False + else: +@@ -885,7 +885,7 @@ class Manager(GObject.Object): + markup = escaped + + editable = True +- ++ + cell.set_properties(markup=markup, editable=editable) + + def tool_in_language(self, tool, lang): +@@ -894,84 +894,84 @@ class Manager(GObject.Object): + + ref = self._languages[lang] + parent = ref.get_path() +- ++ + for row in self._tool_rows[tool]: + path = row.get_path() +- ++ + if path.get_indices()[0] == parent.get_indices()[0]: + return True +- ++ + return False + + def update_languages(self, popup): + self.current_node.languages = popup.languages() + self.fill_languages_button() +- ++ + piter, node = self.get_selected_tool() + ret = None +- ++ + if node: + ref = Gtk.TreeRowReference(self.model, self.model.get_path(piter)) +- ++ + # Update languages, make sure to inhibit selection change stuff + self.view.get_selection().handler_block(self.selection_changed_id) +- ++ + # Remove all rows that are no longer + for row in list(self._tool_rows[self.current_node]): + piter = self.model.get_iter(row.get_path()) + language = self.language_id_from_iter(piter) +- ++ + if (not language and not self.current_node.languages) or \ + (language in self.current_node.languages): + continue +- ++ + # Remove from language + self.model.remove(piter) + self._tool_rows[self.current_node].remove(row) +- ++ + # If language is empty, remove it + parent = self.model.get_iter(self._languages[language].get_path()) +- ++ + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] +- ++ + # Now, add for any that are new + manager = GtkSource.LanguageManager() +- ++ + for lang in self.current_node.languages: + if not self.tool_in_language(self.current_node, lang): + l = manager.get_language(lang) +- ++ + if not l: + l = 'plain' +- ++ + self.add_tool_to_language(self.current_node, l) +- ++ + if not self.current_node.languages and not self.tool_in_language(self.current_node, None): + self.add_tool_to_language(self.current_node, None) +- ++ + # Check if we can still keep the current + if not ref or not ref.valid(): + # Change selection to first language + path = self._tool_rows[self.current_node][0].get_path() + piter = self.model.get_iter(path) + parent = self.model.iter_parent(piter) +- ++ + # Expand parent, select child and scroll to it + self.view.expand_row(self.model.get_path(parent), False) + self.view.get_selection().select_path(path) + self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False) +- ++ + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def on_languages_button_clicked(self, button): + popup = LanguagesPopup(self.current_node.languages) + popup.set_transient_for(self.dialog) +- ++ + origin = button.get_window().get_origin() + popup.move(origin[1], origin[2] - popup.get_allocation().height) +- ++ + popup.connect('destroy', self.update_languages) + + # ex:et:ts=4: +diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py +index 9613d78..e063eb2 100755 +--- a/plugins/externaltools/tools/outputpanel.py ++++ b/plugins/externaltools/tools/outputpanel.py +@@ -129,11 +129,11 @@ class OutputPanel(UniqueById): + # find all links and apply the appropriate tag for them + links = self.link_parser.parse(text) + for lnk in links: +- ++ + insert_iter = buffer.get_iter_at_mark(insert) + lnk.start = insert_iter.get_offset() + lnk.start + lnk.end = insert_iter.get_offset() + lnk.end +- ++ + start_iter = buffer.get_iter_at_offset(lnk.start) + end_iter = buffer.get_iter_at_offset(lnk.end) + +@@ -156,7 +156,7 @@ class OutputPanel(UniqueById): + panel.show() + panel.activate_item(self.panel) + +- def update_cursor_style(self, view, x, y): ++ def update_cursor_style(self, view, x, y): + if self.get_link_at_location(view, x, y) is not None: + cursor = self.link_cursor + else: +diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py +index 59ac413..07d13c7 100755 +--- a/plugins/pythonconsole/pythonconsole/__init__.py ++++ b/plugins/pythonconsole/pythonconsole/__init__.py +@@ -8,7 +8,7 @@ + # it under the terms of the GNU General Public License as published by + # the Free Software Foundation; either version 2, 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 +diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py +old mode 100755 +new mode 100644 +index 54e9598..3ae72a4 +--- a/plugins/quickopen/quickopen/__init__.py ++++ b/plugins/quickopen/quickopen/__init__.py +@@ -21,30 +21,30 @@ from gi.repository import GObject, Peas + from windowhelper import WindowHelper + + class QuickOpenPlugin(GObject.Object, Peas.Activatable): +- __gtype_name__ = "QuickOpenPlugin" ++ __gtype_name__ = "QuickOpenPlugin" + +- object = GObject.Property(type=GObject.Object) ++ object = GObject.Property(type=GObject.Object) + +- def __init__(self): +- GObject.Object.__init__(self) ++ def __init__(self): ++ GObject.Object.__init__(self) + +- self._popup_size = (450, 300) ++ self._popup_size = (450, 300) + +- def do_activate(self): +- window = self.object +- self._helper = WindowHelper(window, self) ++ def do_activate(self): ++ window = self.object ++ self._helper = WindowHelper(window, self) + +- def do_deactivate(self): +- self._helper.deactivate() +- self._helper = None ++ def do_deactivate(self): ++ self._helper.deactivate() ++ self._helper = None + +- def do_update_state(self): +- self._helper.update_ui() ++ def do_update_state(self): ++ self._helper.update_ui() + +- def get_popup_size(self): +- return self._popup_size ++ def get_popup_size(self): ++ return self._popup_size + +- def set_popup_size(self, size): +- self._popup_size = size ++ def set_popup_size(self, size): ++ self._popup_size = size + +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py +old mode 100755 +new mode 100644 +index b571b68..c6cc801 +--- a/plugins/quickopen/quickopen/popup.py ++++ b/plugins/quickopen/quickopen/popup.py +@@ -24,533 +24,532 @@ from gi.repository import GObject, Gio, GLib, Gdk, Gtk, Pango, Pluma + from virtualdirs import VirtualDirectory + + class Popup(Gtk.Dialog): +- __gtype_name__ = "QuickOpenPopup" ++ __gtype_name__ = "QuickOpenPopup" + +- def __init__(self, window, paths, handler): +- Gtk.Dialog.__init__(self, +- title=_('Quick Open'), +- parent=window, +- flags=Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) ++ def __init__(self, window, paths, handler): ++ Gtk.Dialog.__init__(self, ++ title=_('Quick Open'), ++ parent=window, ++ flags=Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, ++ buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) + +- self._open_button = self.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT) ++ self._open_button = self.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT) + +- self._handler = handler +- self._build_ui() ++ self._handler = handler ++ self._build_ui() + +- self._size = (0, 0) +- self._dirs = [] +- self._cache = {} +- self._theme = None +- self._cursor = None +- self._shift_start = None ++ self._size = (0, 0) ++ self._dirs = [] ++ self._cache = {} ++ self._theme = None ++ self._cursor = None ++ self._shift_start = None + +- self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) ++ self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + +- accel_group = Gtk.AccelGroup() +- accel_group.connect(Gdk.KEY_l, Gdk.ModifierType.CONTROL_MASK, 0, self.on_focus_entry) ++ accel_group = Gtk.AccelGroup() ++ accel_group.connect(Gdk.KEY_l, Gdk.ModifierType.CONTROL_MASK, 0, self.on_focus_entry) + +- self.add_accel_group(accel_group) ++ self.add_accel_group(accel_group) + +- unique = [] ++ unique = [] + +- for path in paths: +- if not path.get_uri() in unique: +- self._dirs.append(path) +- unique.append(path.get_uri()) ++ for path in paths: ++ if not path.get_uri() in unique: ++ self._dirs.append(path) ++ unique.append(path.get_uri()) + +- def get_final_size(self): +- return self._size ++ def get_final_size(self): ++ return self._size + +- def _build_ui(self): +- vbox = self.get_content_area() +- vbox.set_spacing(3) ++ def _build_ui(self): ++ vbox = self.get_content_area() ++ vbox.set_spacing(3) + +- self._entry = Gtk.Entry() ++ self._entry = Gtk.Entry() + +- self._entry.connect('changed', self.on_changed) +- self._entry.connect('key-press-event', self.on_key_press_event) ++ self._entry.connect('changed', self.on_changed) ++ self._entry.connect('key-press-event', self.on_key_press_event) + +- sw = Gtk.ScrolledWindow() +- sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) +- sw.set_shadow_type(Gtk.ShadowType.OUT) ++ sw = Gtk.ScrolledWindow() ++ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) ++ sw.set_shadow_type(Gtk.ShadowType.OUT) + +- tv = Gtk.TreeView() +- tv.set_headers_visible(False) ++ tv = Gtk.TreeView() ++ tv.set_headers_visible(False) + +- self._store = Gtk.ListStore(Gio.Icon, str, GObject.Object, Gio.FileType) +- tv.set_model(self._store) ++ self._store = Gtk.ListStore(Gio.Icon, str, GObject.Object, Gio.FileType) ++ tv.set_model(self._store) + +- self._treeview = tv +- tv.connect('row-activated', self.on_row_activated) ++ self._treeview = tv ++ tv.connect('row-activated', self.on_row_activated) + +- renderer = Gtk.CellRendererPixbuf() +- column = Gtk.TreeViewColumn() +- column.pack_start(renderer, False) +- column.add_attribute(renderer, "gicon", 0) ++ renderer = Gtk.CellRendererPixbuf() ++ column = Gtk.TreeViewColumn() ++ column.pack_start(renderer, False) ++ column.add_attribute(renderer, "gicon", 0) + +- renderer = Gtk.CellRendererText() +- column.pack_start(renderer, True) +- column.add_attribute(renderer, "markup", 1) ++ renderer = Gtk.CellRendererText() ++ column.pack_start(renderer, True) ++ column.add_attribute(renderer, "markup", 1) + +- column.set_cell_data_func(renderer, self.on_cell_data_cb, None) ++ column.set_cell_data_func(renderer, self.on_cell_data_cb, None) + +- tv.append_column(column) +- sw.add(tv) +- +- selection = tv.get_selection() +- selection.connect('changed', self.on_selection_changed) +- selection.set_mode(Gtk.SelectionMode.MULTIPLE) ++ tv.append_column(column) ++ sw.add(tv) + +- vbox.pack_start(self._entry, False, False, 0) +- vbox.pack_start(sw, True, True, 0) ++ selection = tv.get_selection() ++ selection.connect('changed', self.on_selection_changed) ++ selection.set_mode(Gtk.SelectionMode.MULTIPLE) + +- lbl = Gtk.Label() +- lbl.set_alignment(0, 0.5) +- lbl.set_ellipsize(Pango.EllipsizeMode.MIDDLE) +- self._info_label = lbl ++ vbox.pack_start(self._entry, False, False, 0) ++ vbox.pack_start(sw, True, True, 0) + +- vbox.pack_start(lbl, False, False, 0) ++ lbl = Gtk.Label() ++ lbl.set_alignment(0, 0.5) ++ lbl.set_ellipsize(Pango.EllipsizeMode.MIDDLE) ++ self._info_label = lbl + +- # Initial selection +- self.on_selection_changed(tv.get_selection()) +- vbox.show_all() ++ vbox.pack_start(lbl, False, False, 0) + +- def on_cell_data_cb(self, column, cell, model, piter, user_data): +- path = model.get_path(piter) +- +- if self._cursor and path == self._cursor.get_path(): +- style = self._treeview.get_style() +- bg = style.bg[Gtk.StateType.PRELIGHT] +- +- cell.set_property('cell-background-gdk', bg) +- cell.set_property('style', Pango.Style.ITALIC) +- else: +- cell.set_property('cell-background-set', False) +- cell.set_property('style-set', False) ++ # Initial selection ++ self.on_selection_changed(tv.get_selection()) ++ vbox.show_all() + +- def _icon_from_stock(self, stock): +- theme = Gtk.icon_theme_get_default() +- size = Gtk.icon_size_lookup(Gtk.IconSize.MENU) +- pixbuf = theme.load_icon(stock, size[0], Gtk.IconLookupFlags.USE_BUILTIN) ++ def on_cell_data_cb(self, column, cell, model, piter, user_data): ++ path = model.get_path(piter) + +- return pixbuf ++ if self._cursor and path == self._cursor.get_path(): ++ style = self._treeview.get_style() ++ bg = style.bg[Gtk.StateType.PRELIGHT] + +- def _list_dir(self, gfile): +- entries = [] ++ cell.set_property('cell-background-gdk', bg) ++ cell.set_property('style', Pango.Style.ITALIC) ++ else: ++ cell.set_property('cell-background-set', False) ++ cell.set_property('style-set', False) + +- try: +- ret = gfile.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, None) +- except GLib.GError: +- pass ++ def _icon_from_stock(self, stock): ++ theme = Gtk.icon_theme_get_default() ++ size = Gtk.icon_size_lookup(Gtk.IconSize.MENU) ++ pixbuf = theme.load_icon(stock, size[0], Gtk.IconLookupFlags.USE_BUILTIN) + +- if isinstance(ret, Gio.FileEnumerator): +- while True: +- entry = ret.next_file(None) ++ return pixbuf + +- if not entry: +- break ++ def _list_dir(self, gfile): ++ entries = [] + +- entries.append((gfile.get_child(entry.get_name()), entry)) +- else: +- entries = ret ++ try: ++ ret = gfile.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, None) ++ except GLib.GError: ++ pass + +- children = [] ++ if isinstance(ret, Gio.FileEnumerator): ++ while True: ++ entry = ret.next_file(None) + +- for entry in entries: +- children.append((entry[0], entry[1].get_name(), entry[1].get_file_type(), entry[1].get_icon())) ++ if not entry: ++ break + +- return children ++ entries.append((gfile.get_child(entry.get_name()), entry)) ++ else: ++ entries = ret + +- def _compare_entries(self, a, b, lpart): +- if lpart in a: +- if lpart in b: +- return cmp(a.index(lpart), b.index(lpart)) +- else: +- return -1 +- elif lpart in b: +- return 1 +- else: +- return 0 ++ children = [] + +- def _match_glob(self, s, glob): +- if glob: +- glob += '*' ++ for entry in entries: ++ children.append((entry[0], entry[1].get_name(), entry[1].get_file_type(), entry[1].get_icon())) + +- return fnmatch.fnmatch(s, glob) ++ return children + +- def do_search_dir(self, parts, d): +- if not parts or not d: +- return [] ++ def _compare_entries(self, a, b, lpart): ++ if lpart in a: ++ if lpart in b: ++ return cmp(a.index(lpart), b.index(lpart)) ++ else: ++ return -1 ++ elif lpart in b: ++ return 1 ++ else: ++ return 0 + +- if not d in self._cache: +- entries = self._list_dir(d) +- entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) ++ def _match_glob(self, s, glob): ++ if glob: ++ glob += '*' + +- self._cache[d] = entries +- else: +- entries = self._cache[d] ++ return fnmatch.fnmatch(s, glob) + +- found = [] +- newdirs = [] ++ def do_search_dir(self, parts, d): ++ if not parts or not d: ++ return [] + +- lpart = parts[0].lower() ++ if not d in self._cache: ++ entries = self._list_dir(d) ++ entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) + +- for entry in entries: +- if not entry: +- continue ++ self._cache[d] = entries ++ else: ++ entries = self._cache[d] + +- lentry = entry[1].lower() ++ found = [] ++ newdirs = [] + +- if not lpart or lpart in lentry or self._match_glob(lentry, lpart): +- if entry[2] == Gio.FileType.DIRECTORY: +- if len(parts) > 1: +- newdirs.append(entry[0]) +- else: +- found.append(entry) +- elif entry[2] == Gio.FileType.REGULAR and \ +- (not lpart or len(parts) == 1): +- found.append(entry) ++ lpart = parts[0].lower() + +- found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)) ++ for entry in entries: ++ if not entry: ++ continue + +- if lpart == '..': +- newdirs.append(d.get_parent()) ++ lentry = entry[1].lower() + +- for dd in newdirs: +- found.extend(self.do_search_dir(parts[1:], dd)) ++ if not lpart or lpart in lentry or self._match_glob(lentry, lpart): ++ if entry[2] == Gio.FileType.DIRECTORY: ++ if len(parts) > 1: ++ newdirs.append(entry[0]) ++ else: ++ found.append(entry) ++ elif entry[2] == Gio.FileType.REGULAR and \ ++ (not lpart or len(parts) == 1): ++ found.append(entry) + +- return found ++ found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)) + +- def _replace_insensitive(self, s, find, rep): +- out = '' +- l = s.lower() +- find = find.lower() +- last = 0 ++ if lpart == '..': ++ newdirs.append(d.get_parent()) + +- if len(find) == 0: +- return xml.sax.saxutils.escape(s) ++ for dd in newdirs: ++ found.extend(self.do_search_dir(parts[1:], dd)) + +- while True: +- m = l.find(find, last) ++ return found + +- if m == -1: +- break +- else: +- out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),) +- last = m + len(find) ++ def _replace_insensitive(self, s, find, rep): ++ out = '' ++ l = s.lower() ++ find = find.lower() ++ last = 0 + +- return out + xml.sax.saxutils.escape(s[last:]) ++ if len(find) == 0: ++ return xml.sax.saxutils.escape(s) + ++ while True: ++ m = l.find(find, last) + +- def make_markup(self, parts, path): +- out = [] ++ if m == -1: ++ break ++ else: ++ out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),) ++ last = m + len(find) + +- for i in range(0, len(parts)): +- out.append(self._replace_insensitive(path[i], parts[i], "%s")) ++ return out + xml.sax.saxutils.escape(s[last:]) + +- return os.sep.join(out) ++ def make_markup(self, parts, path): ++ out = [] + +- def _get_icon(self, f): +- query = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileQueryInfoFlags.NONE, None) ++ for i in range(0, len(parts)): ++ out.append(self._replace_insensitive(path[i], parts[i], "%s")) + +- if not query: +- return None +- else: +- return query.get_icon() ++ return os.sep.join(out) + +- def _make_parts(self, parent, child, pp): +- parts = [] ++ def _get_icon(self, f): ++ query = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileQueryInfoFlags.NONE, None) + +- # We went from parent, to child, using pp +- idx = len(pp) - 1 ++ if not query: ++ return None ++ else: ++ return query.get_icon() + +- while idx >= 0: +- if pp[idx] == '..': +- parts.insert(0, '..') +- else: +- parts.insert(0, child.get_basename()) +- child = child.get_parent() ++ def _make_parts(self, parent, child, pp): ++ parts = [] + +- idx -= 1 ++ # We went from parent, to child, using pp ++ idx = len(pp) - 1 + +- return parts ++ while idx >= 0: ++ if pp[idx] == '..': ++ parts.insert(0, '..') ++ else: ++ parts.insert(0, child.get_basename()) ++ child = child.get_parent() + +- def normalize_relative(self, parts): +- if not parts: +- return [] ++ idx -= 1 + +- out = self.normalize_relative(parts[:-1]) ++ return parts + +- if parts[-1] == '..': +- if not out or (out[-1] == '..') or len(out) == 1: +- out.append('..') +- else: +- del out[-1] +- else: +- out.append(parts[-1]) ++ def normalize_relative(self, parts): ++ if not parts: ++ return [] + +- return out ++ out = self.normalize_relative(parts[:-1]) + +- def _append_to_store(self, item): +- if not item in self._stored_items: +- self._store.append(item) +- self._stored_items[item] = True ++ if parts[-1] == '..': ++ if not out or (out[-1] == '..') or len(out) == 1: ++ out.append('..') ++ else: ++ del out[-1] ++ else: ++ out.append(parts[-1]) + +- def _clear_store(self): +- self._store.clear() +- self._stored_items = {} ++ return out + +- def _show_virtuals(self): +- for d in self._dirs: +- if isinstance(d, VirtualDirectory): +- for entry in d.enumerate_children("standard::*", 0, None): +- self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type())) ++ def _append_to_store(self, item): ++ if not item in self._stored_items: ++ self._store.append(item) ++ self._stored_items[item] = True + +- def _set_busy(self, busy): +- if busy: +- self.get_window().set_cursor(self._busy_cursor) +- else: +- self.get_window().set_cursor(None) ++ def _clear_store(self): ++ self._store.clear() ++ self._stored_items = {} + +- Gdk.flush() ++ def _show_virtuals(self): ++ for d in self._dirs: ++ if isinstance(d, VirtualDirectory): ++ for entry in d.enumerate_children("standard::*", 0, None): ++ self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type())) + +- def _remove_cursor(self): +- if self._cursor: +- path = self._cursor.get_path() +- self._cursor = None ++ def _set_busy(self, busy): ++ if busy: ++ self.get_window().set_cursor(self._busy_cursor) ++ else: ++ self.get_window().set_cursor(None) + +- self._store.row_changed(path, self._store.get_iter(path)) ++ Gdk.flush() + +- def do_search(self): +- self._set_busy(True) +- self._remove_cursor() ++ def _remove_cursor(self): ++ if self._cursor: ++ path = self._cursor.get_path() ++ self._cursor = None + +- text = self._entry.get_text().strip() +- self._clear_store() ++ self._store.row_changed(path, self._store.get_iter(path)) + +- if text == '': +- self._show_virtuals() +- else: +- parts = self.normalize_relative(text.split(os.sep)) +- files = [] ++ def do_search(self): ++ self._set_busy(True) ++ self._remove_cursor() + +- for d in self._dirs: +- for entry in self.do_search_dir(parts, d): +- pathparts = self._make_parts(d, entry[0], parts) +- self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2])) ++ text = self._entry.get_text().strip() ++ self._clear_store() + +- piter = self._store.get_iter_first() ++ if text == '': ++ self._show_virtuals() ++ else: ++ parts = self.normalize_relative(text.split(os.sep)) ++ files = [] + +- if piter: +- self._treeview.get_selection().select_path(self._store.get_path(piter)) ++ for d in self._dirs: ++ for entry in self.do_search_dir(parts, d): ++ pathparts = self._make_parts(d, entry[0], parts) ++ self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2])) + +- self.get_window().set_cursor(None) +- self._set_busy(False) ++ piter = self._store.get_iter_first() + +- def do_show(self): +- Gtk.Window.do_show(self) ++ if piter: ++ self._treeview.get_selection().select_path(self._store.get_path(piter)) + +- self._entry.grab_focus() +- self._entry.set_text("") +- +- self.do_search() +- +- def on_changed(self, editable): +- self.do_search() +- self.on_selection_changed(self._treeview.get_selection()) +- +- def _shift_extend(self, towhere): +- selection = self._treeview.get_selection() +- +- if not self._shift_start: +- model, rows = selection.get_selected_rows() +- start = rows[0] +- +- self._shift_start = Gtk.TreeRowReference(self._store, start) +- else: +- start = self._shift_start.get_path() +- +- selection.unselect_all() +- selection.select_range(start, towhere) +- +- def _select_index(self, idx, hasctrl, hasshift): +- path = (idx,) +- +- if not (hasctrl or hasshift): +- self._treeview.get_selection().unselect_all() +- +- if hasshift: +- self._shift_extend(path) +- else: +- self._shift_start = None +- +- if not hasctrl: +- self._treeview.get_selection().select_path(path) +- +- self._treeview.scroll_to_cell(path, None, True, 0.5, 0) +- self._remove_cursor() +- +- if hasctrl or hasshift: +- self._cursor = Gtk.TreeRowReference(self._store, path) +- +- piter = self._store.get_iter(path) +- self._store.row_changed(path, piter) +- +- def _move_selection(self, howmany, hasctrl, hasshift): +- num = self._store.iter_n_children(None) +- +- if num == 0: +- return True +- +- # Test for cursor +- path = None +- +- if self._cursor: +- path = self._cursor.get_path() +- else: +- model, rows = self._treeview.get_selection().get_selected_rows() +- +- if len(rows) == 1: +- path = rows[0] +- +- if not path: +- if howmany > 0: +- self._select_index(0, hasctrl, hasshift) +- else: +- self._select_index(num - 1, hasctrl, hasshift) +- else: +- idx = path[0] +- +- if idx + howmany < 0: +- self._select_index(0, hasctrl, hasshift) +- elif idx + howmany >= num: +- self._select_index(num - 1, hasctrl, hasshift) +- else: +- self._select_index(idx + howmany, hasctrl, hasshift) ++ self.get_window().set_cursor(None) ++ self._set_busy(False) + +- return True ++ def do_show(self): ++ Gtk.Window.do_show(self) ++ ++ self._entry.grab_focus() ++ self._entry.set_text("") ++ ++ self.do_search() ++ ++ def on_changed(self, editable): ++ self.do_search() ++ self.on_selection_changed(self._treeview.get_selection()) ++ ++ def _shift_extend(self, towhere): ++ selection = self._treeview.get_selection() ++ ++ if not self._shift_start: ++ model, rows = selection.get_selected_rows() ++ start = rows[0] ++ ++ self._shift_start = Gtk.TreeRowReference(self._store, start) ++ else: ++ start = self._shift_start.get_path() ++ ++ selection.unselect_all() ++ selection.select_range(start, towhere) ++ ++ def _select_index(self, idx, hasctrl, hasshift): ++ path = (idx,) ++ ++ if not (hasctrl or hasshift): ++ self._treeview.get_selection().unselect_all() ++ ++ if hasshift: ++ self._shift_extend(path) ++ else: ++ self._shift_start = None ++ ++ if not hasctrl: ++ self._treeview.get_selection().select_path(path) ++ ++ self._treeview.scroll_to_cell(path, None, True, 0.5, 0) ++ self._remove_cursor() ++ ++ if hasctrl or hasshift: ++ self._cursor = Gtk.TreeRowReference(self._store, path) ++ ++ piter = self._store.get_iter(path) ++ self._store.row_changed(path, piter) ++ ++ def _move_selection(self, howmany, hasctrl, hasshift): ++ num = self._store.iter_n_children(None) + +- def _direct_file(self): +- uri = self._entry.get_text() +- gfile = None +- +- if Pluma.utils_is_valid_uri(uri): +- gfile = Gio.file_new_for_uri(uri) +- elif os.path.isabs(uri): +- f = Gio.file_new_for_uri(uri) +- +- if f.query_exists(): +- gfile = f +- +- return gfile +- +- def _activate(self): +- model, rows = self._treeview.get_selection().get_selected_rows() +- ret = True +- +- for row in rows: +- s = model.get_iter(row) +- info = model.get(s, 2, 3) +- +- if info[1] != Gio.FileType.DIRECTORY: +- ret = ret and self._handler(info[0]) +- else: +- text = self._entry.get_text() +- +- for i in range(len(text) - 1, -1, -1): +- if text[i] == os.sep: +- break +- +- self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep) +- self._entry.set_position(-1) +- self._entry.grab_focus() +- return True +- +- if rows and ret: +- self.destroy() +- +- if not rows: +- gfile = self._direct_file() +- +- if gfile and self._handler(gfile): +- self.destroy() +- else: +- ret = False +- else: +- ret = False +- +- return ret +- +- def toggle_cursor(self): +- if not self._cursor: +- return +- +- path = self._cursor.get_path() +- selection = self._treeview.get_selection() +- +- if selection.path_is_selected(path): +- selection.unselect_path(path) +- else: +- selection.select_path(path) +- +- def on_key_press_event(self, widget, event): +- move_mapping = { +- Gdk.KEY_Down: 1, +- Gdk.KEY_Up: -1, +- Gdk.KEY_Page_Down: 5, +- Gdk.KEY_Page_Up: -5 +- } +- +- if event.keyval == Gdk.KEY_Escape: +- self.destroy() +- return True +- elif event.keyval in move_mapping: +- return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK) +- elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: +- return self._activate() +- elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.CONTROL_MASK: +- self.toggle_cursor() +- +- return False +- +- def on_row_activated(self, view, path, column): +- self._activate() +- +- def do_response(self, response): +- if response != Gtk.ResponseType.ACCEPT or not self._activate(): +- self.destroy() +- +- def do_configure_event(self, event): +- if self.get_realized(): +- alloc = self.get_allocation() +- self._size = (alloc.width, alloc.height) +- +- return Gtk.Dialog.do_configure_event(self, event) +- +- def on_selection_changed(self, selection): +- model, rows = selection.get_selected_rows() +- +- gfile = None +- fname = None +- +- if not rows: +- gfile = self._direct_file() +- elif len(rows) == 1: +- gfile = model.get(model.get_iter(rows[0]), 2)[0] +- else: +- fname = '' +- +- if gfile: +- if gfile.is_native(): +- fname = xml.sax.saxutils.escape(gfile.get_path()) +- else: +- fname = xml.sax.saxutils.escape(gfile.get_uri()) +- +- self._open_button.set_sensitive(fname != None) +- self._info_label.set_markup(fname or '') +- +- def on_focus_entry(self, group, accel, keyval, modifier): ++ if num == 0: ++ return True ++ ++ # Test for cursor ++ path = None ++ ++ if self._cursor: ++ path = self._cursor.get_path() ++ else: ++ model, rows = self._treeview.get_selection().get_selected_rows() ++ ++ if len(rows) == 1: ++ path = rows[0] ++ ++ if not path: ++ if howmany > 0: ++ self._select_index(0, hasctrl, hasshift) ++ else: ++ self._select_index(num - 1, hasctrl, hasshift) ++ else: ++ idx = path[0] ++ ++ if idx + howmany < 0: ++ self._select_index(0, hasctrl, hasshift) ++ elif idx + howmany >= num: ++ self._select_index(num - 1, hasctrl, hasshift) ++ else: ++ self._select_index(idx + howmany, hasctrl, hasshift) ++ ++ return True ++ ++ def _direct_file(self): ++ uri = self._entry.get_text() ++ gfile = None ++ ++ if Pluma.utils_is_valid_uri(uri): ++ gfile = Gio.file_new_for_uri(uri) ++ elif os.path.isabs(uri): ++ f = Gio.file_new_for_uri(uri) ++ ++ if f.query_exists(): ++ gfile = f ++ ++ return gfile ++ ++ def _activate(self): ++ model, rows = self._treeview.get_selection().get_selected_rows() ++ ret = True ++ ++ for row in rows: ++ s = model.get_iter(row) ++ info = model.get(s, 2, 3) ++ ++ if info[1] != Gio.FileType.DIRECTORY: ++ ret = ret and self._handler(info[0]) ++ else: ++ text = self._entry.get_text() ++ ++ for i in range(len(text) - 1, -1, -1): ++ if text[i] == os.sep: ++ break ++ ++ self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep) ++ self._entry.set_position(-1) + self._entry.grab_focus() ++ return True + +-# ex:ts=8:et: ++ if rows and ret: ++ self.destroy() ++ ++ if not rows: ++ gfile = self._direct_file() ++ ++ if gfile and self._handler(gfile): ++ self.destroy() ++ else: ++ ret = False ++ else: ++ ret = False ++ ++ return ret ++ ++ def toggle_cursor(self): ++ if not self._cursor: ++ return ++ ++ path = self._cursor.get_path() ++ selection = self._treeview.get_selection() ++ ++ if selection.path_is_selected(path): ++ selection.unselect_path(path) ++ else: ++ selection.select_path(path) ++ ++ def on_key_press_event(self, widget, event): ++ move_mapping = { ++ Gdk.KEY_Down: 1, ++ Gdk.KEY_Up: -1, ++ Gdk.KEY_Page_Down: 5, ++ Gdk.KEY_Page_Up: -5 ++ } ++ ++ if event.keyval == Gdk.KEY_Escape: ++ self.destroy() ++ return True ++ elif event.keyval in move_mapping: ++ return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK) ++ elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: ++ return self._activate() ++ elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.CONTROL_MASK: ++ self.toggle_cursor() ++ ++ return False ++ ++ def on_row_activated(self, view, path, column): ++ self._activate() ++ ++ def do_response(self, response): ++ if response != Gtk.ResponseType.ACCEPT or not self._activate(): ++ self.destroy() ++ ++ def do_configure_event(self, event): ++ if self.get_realized(): ++ alloc = self.get_allocation() ++ self._size = (alloc.width, alloc.height) ++ ++ return Gtk.Dialog.do_configure_event(self, event) ++ ++ def on_selection_changed(self, selection): ++ model, rows = selection.get_selected_rows() ++ ++ gfile = None ++ fname = None ++ ++ if not rows: ++ gfile = self._direct_file() ++ elif len(rows) == 1: ++ gfile = model.get(model.get_iter(rows[0]), 2)[0] ++ else: ++ fname = '' ++ ++ if gfile: ++ if gfile.is_native(): ++ fname = xml.sax.saxutils.escape(gfile.get_path()) ++ else: ++ fname = xml.sax.saxutils.escape(gfile.get_uri()) ++ ++ self._open_button.set_sensitive(fname != None) ++ self._info_label.set_markup(fname or '') ++ ++ def on_focus_entry(self, group, accel, keyval, modifier): ++ self._entry.grab_focus() ++ ++# ex:ts=4:et: +diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py +old mode 100755 +new mode 100644 +index a2d6985..53d716a +--- a/plugins/quickopen/quickopen/virtualdirs.py ++++ b/plugins/quickopen/quickopen/virtualdirs.py +@@ -20,64 +20,64 @@ + from gi.repository import Gio, Gtk + + class VirtualDirectory(object): +- def __init__(self, name): +- self._name = name +- self._children = [] ++ def __init__(self, name): ++ self._name = name ++ self._children = [] + +- def get_uri(self): +- return 'virtual://' + self._name ++ def get_uri(self): ++ return 'virtual://' + self._name + +- def get_parent(self): +- return None ++ def get_parent(self): ++ return None + +- def enumerate_children(self, attr, flags, callback): +- return self._children ++ def enumerate_children(self, attr, flags, callback): ++ return self._children + +- def append(self, child): +- if not child.is_native(): +- return ++ def append(self, child): ++ if not child.is_native(): ++ return + +- try: +- info = child.query_info("standard::*", Gio.FileQueryInfoFlags.NONE, None) ++ try: ++ info = child.query_info("standard::*", Gio.FileQueryInfoFlags.NONE, None) + +- if info: +- self._children.append((child, info)) +- except: +- pass ++ if info: ++ self._children.append((child, info)) ++ except: ++ pass + + class RecentDocumentsDirectory(VirtualDirectory): +- def __init__(self, maxitems=10): +- VirtualDirectory.__init__(self, 'recent') ++ def __init__(self, maxitems=10): ++ VirtualDirectory.__init__(self, 'recent') + +- self._maxitems = maxitems +- self.fill() ++ self._maxitems = maxitems ++ self.fill() + +- def fill(self): +- manager = Gtk.RecentManager.get_default() ++ def fill(self): ++ manager = Gtk.RecentManager.get_default() + +- items = manager.get_items() +- items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited())) ++ items = manager.get_items() ++ items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited())) + +- added = 0 ++ added = 0 + +- for item in items: +- if item.has_group('pluma'): +- self.append(Gio.file_new_for_uri(item.get_uri())) +- added += 1 ++ for item in items: ++ if item.has_group('pluma'): ++ self.append(Gio.file_new_for_uri(item.get_uri())) ++ added += 1 + +- if added >= self._maxitems: +- break ++ if added >= self._maxitems: ++ break + + class CurrentDocumentsDirectory(VirtualDirectory): +- def __init__(self, window): +- VirtualDirectory.__init__(self, 'documents') ++ def __init__(self, window): ++ VirtualDirectory.__init__(self, 'documents') + +- self.fill(window) ++ self.fill(window) + +- def fill(self, window): +- for doc in window.get_documents(): +- location = doc.get_location() +- if location: +- self.append(location) ++ def fill(self, window): ++ for doc in window.get_documents(): ++ location = doc.get_location() ++ if location: ++ self.append(location) + +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/quickopen/quickopen/windowhelper.py b/plugins/quickopen/quickopen/windowhelper.py +old mode 100755 +new mode 100644 +index c23629c..19e44cb +--- a/plugins/quickopen/quickopen/windowhelper.py ++++ b/plugins/quickopen/quickopen/windowhelper.py +@@ -27,7 +27,7 @@ ui_str = """ + + + +- ++ + + + +@@ -35,157 +35,157 @@ ui_str = """ + """ + + class WindowHelper: +- def __init__(self, window, plugin): +- self._window = window +- self._plugin = plugin ++ def __init__(self, window, plugin): ++ self._window = window ++ self._plugin = plugin + +- self._popup = None +- self._install_menu() ++ self._popup = None ++ self._install_menu() + +- def deactivate(self): +- self._uninstall_menu() +- self._window = None +- self._plugin = None ++ def deactivate(self): ++ self._uninstall_menu() ++ self._window = None ++ self._plugin = None + +- def update_ui(self): +- pass ++ def update_ui(self): ++ pass + +- def _uninstall_menu(self): +- manager = self._window.get_ui_manager() ++ def _uninstall_menu(self): ++ manager = self._window.get_ui_manager() + +- manager.remove_ui(self._ui_id) +- manager.remove_action_group(self._action_group) ++ manager.remove_ui(self._ui_id) ++ manager.remove_action_group(self._action_group) + +- manager.ensure_update() ++ manager.ensure_update() + +- def _install_menu(self): +- manager = self._window.get_ui_manager() +- self._action_group = Gtk.ActionGroup("PlumaQuickOpenPluginActions") +- self._action_group.add_actions([ +- ("QuickOpen", Gtk.STOCK_OPEN, _("Quick open"), +- 'O', _("Quickly open documents"), +- self.on_quick_open_activate) +- ]) ++ def _install_menu(self): ++ manager = self._window.get_ui_manager() ++ self._action_group = Gtk.ActionGroup("PlumaQuickOpenPluginActions") ++ self._action_group.add_actions([ ++ ("QuickOpen", Gtk.STOCK_OPEN, _("Quick open"), ++ 'O', _("Quickly open documents"), ++ self.on_quick_open_activate) ++ ]) + +- manager.insert_action_group(self._action_group, -1) +- self._ui_id = manager.add_ui_from_string(ui_str) ++ manager.insert_action_group(self._action_group, -1) ++ self._ui_id = manager.add_ui_from_string(ui_str) + +- def _create_popup(self): +- paths = [] ++ def _create_popup(self): ++ paths = [] + +- # Open documents +- paths.append(CurrentDocumentsDirectory(self._window)) ++ # Open documents ++ paths.append(CurrentDocumentsDirectory(self._window)) + +- doc = self._window.get_active_document() ++ doc = self._window.get_active_document() + +- # Current document directory +- if doc and doc.is_local(): +- gfile = doc.get_location() +- paths.append(gfile.get_parent()) ++ # Current document directory ++ if doc and doc.is_local(): ++ gfile = doc.get_location() ++ paths.append(gfile.get_parent()) + +- # File browser root directory +- bus = self._window.get_message_bus() ++ # File browser root directory ++ bus = self._window.get_message_bus() + +- try: +- msg = bus.send_sync('/plugins/filebrowser', 'get_root') ++ try: ++ msg = bus.send_sync('/plugins/filebrowser', 'get_root') + +- if msg: +- uri = msg.get_value('uri') ++ if msg: ++ uri = msg.get_value('uri') + +- if uri: +- gfile = Gio.file_new_for_uri(uri) ++ if uri: ++ gfile = Gio.file_new_for_uri(uri) + +- if gfile.is_native(): +- paths.append(gfile) ++ if gfile.is_native(): ++ paths.append(gfile) + +- except StandardError: +- pass ++ except StandardError: ++ pass + +- # Recent documents +- paths.append(RecentDocumentsDirectory()) ++ # Recent documents ++ paths.append(RecentDocumentsDirectory()) + +- # Local bookmarks +- for path in self._local_bookmarks(): +- paths.append(path) ++ # Local bookmarks ++ for path in self._local_bookmarks(): ++ paths.append(path) + +- # Desktop directory +- desktopdir = self._desktop_dir() ++ # Desktop directory ++ desktopdir = self._desktop_dir() + +- if desktopdir: +- paths.append(Gio.file_new_for_path(desktopdir)) ++ if desktopdir: ++ paths.append(Gio.file_new_for_path(desktopdir)) + +- # Home directory +- paths.append(Gio.file_new_for_path(os.path.expanduser('~'))) ++ # Home directory ++ paths.append(Gio.file_new_for_path(os.path.expanduser('~'))) + +- self._popup = Popup(self._window, paths, self.on_activated) ++ self._popup = Popup(self._window, paths, self.on_activated) + +- self._popup.set_default_size(*self._plugin.get_popup_size()) +- self._popup.set_transient_for(self._window) +- self._popup.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) ++ self._popup.set_default_size(*self._plugin.get_popup_size()) ++ self._popup.set_transient_for(self._window) ++ self._popup.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) + +- self._window.get_group().add_window(self._popup) ++ self._window.get_group().add_window(self._popup) + +- self._popup.connect('destroy', self.on_popup_destroy) ++ self._popup.connect('destroy', self.on_popup_destroy) + +- def _local_bookmarks(self): +- filename = os.path.expanduser('~/.gtk-bookmarks') ++ def _local_bookmarks(self): ++ filename = os.path.expanduser('~/.gtk-bookmarks') + +- if not os.path.isfile(filename): +- return [] ++ if not os.path.isfile(filename): ++ return [] + +- paths = [] ++ paths = [] + +- for line in file(filename, 'r').xreadlines(): +- uri = line.strip().split(" ")[0] +- f = Gio.file_new_for_uri(uri) ++ for line in file(filename, 'r').xreadlines(): ++ uri = line.strip().split(" ")[0] ++ f = Gio.file_new_for_uri(uri) + +- if f.is_native(): +- try: +- info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, None) ++ if f.is_native(): ++ try: ++ info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, None) + +- if info and info.get_file_type() == Gio.FileType.DIRECTORY: +- paths.append(f) +- except GLib.GError: +- pass ++ if info and info.get_file_type() == Gio.FileType.DIRECTORY: ++ paths.append(f) ++ except GLib.GError: ++ pass + +- return paths ++ return paths + +- def _desktop_dir(self): +- config = os.getenv('XDG_CONFIG_HOME') ++ def _desktop_dir(self): ++ config = os.getenv('XDG_CONFIG_HOME') + +- if not config: +- config = os.path.expanduser('~/.config') ++ if not config: ++ config = os.path.expanduser('~/.config') + +- config = os.path.join(config, 'user-dirs.dirs') +- desktopdir = None ++ config = os.path.join(config, 'user-dirs.dirs') ++ desktopdir = None + +- if os.path.isfile(config): +- for line in file(config, 'r').xreadlines(): +- line = line.strip() ++ if os.path.isfile(config): ++ for line in file(config, 'r').xreadlines(): ++ line = line.strip() + +- if line.startswith('XDG_DESKTOP_DIR'): +- parts = line.split('=', 1) +- desktopdir = os.path.expandvars(parts[1].strip('"').strip("'")) +- break ++ if line.startswith('XDG_DESKTOP_DIR'): ++ parts = line.split('=', 1) ++ desktopdir = os.path.expandvars(parts[1].strip('"').strip("'")) ++ break + +- if not desktopdir: +- desktopdir = os.path.expanduser('~/Desktop') ++ if not desktopdir: ++ desktopdir = os.path.expanduser('~/Desktop') + +- return desktopdir ++ return desktopdir + +- # Callbacks +- def on_quick_open_activate(self, action): +- if not self._popup: +- self._create_popup() ++ # Callbacks ++ def on_quick_open_activate(self, action): ++ if not self._popup: ++ self._create_popup() + +- self._popup.show() ++ self._popup.show() + +- def on_popup_destroy(self, popup): +- self._plugin.set_popup_size(popup.get_final_size()) +- self._popup = None ++ def on_popup_destroy(self, popup): ++ self._plugin.set_popup_size(popup.get_final_size()) ++ self._popup = None + +- def on_activated(self, gfile): +- Pluma.commands_load_uri(self._window, gfile.get_uri(), None, -1) +- return True ++ def on_activated(self, gfile): ++ Pluma.commands_load_uri(self._window, gfile.get_uri(), None, -1) ++ return True + +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Completion.py b/plugins/snippets/snippets/Completion.py +old mode 100755 +new mode 100644 +index 1788ecd..a860d21 +--- a/plugins/snippets/snippets/Completion.py ++++ b/plugins/snippets/snippets/Completion.py +@@ -5,159 +5,159 @@ from LanguageManager import get_language_manager + from Snippet import Snippet + + class Proposal(GObject.Object, GtkSource.CompletionProposal): +- __gtype_name__ = "PlumaSnippetsProposal" +- +- def __init__(self, snippet): +- GObject.Object.__init__(self) +- self._snippet = Snippet(snippet) +- +- def snippet(self): +- return self._snippet.data +- +- # Interface implementation +- def do_get_markup(self): +- return self._snippet.display() +- +- def do_get_info(self): +- return self._snippet.data['text'] ++ __gtype_name__ = "PlumaSnippetsProposal" ++ ++ def __init__(self, snippet): ++ GObject.Object.__init__(self) ++ self._snippet = Snippet(snippet) ++ ++ def snippet(self): ++ return self._snippet.data ++ ++ # Interface implementation ++ def do_get_markup(self): ++ return self._snippet.display() ++ ++ def do_get_info(self): ++ return self._snippet.data['text'] + + class Provider(GObject.Object, GtkSource.CompletionProvider): +- __gtype_name__ = "PlumaSnippetsProvider" +- +- def __init__(self, name, language_id, handler): +- GObject.Object.__init__(self) +- +- self.name = name +- self.info_widget = None +- self.proposals = [] +- self.language_id = language_id +- self.handler = handler +- self.info_widget = None +- self.mark = None +- +- theme = Gtk.IconTheme.get_default() +- f, w, h = Gtk.icon_size_lookup(Gtk.IconSize.MENU) +- +- self.icon = theme.load_icon(Gtk.STOCK_JUSTIFY_LEFT, w, 0) +- +- def __del__(self): +- if self.mark: +- self.mark.get_buffer().delete_mark(self.mark) +- +- def set_proposals(self, proposals): +- self.proposals = proposals +- +- def mark_position(self, it): +- if not self.mark: +- self.mark = it.get_buffer().create_mark(None, it, True) +- else: +- self.mark.get_buffer().move_mark(self.mark, it) +- +- def get_word(self, context): +- (valid_context, it) = context.get_iter() +- if not valid_context: +- return None +- +- if it.starts_word() or it.starts_line() or not it.ends_word(): +- return None +- +- start = it.copy() +- +- if start.backward_word_start(): +- self.mark_position(start) +- return start.get_text(it) +- else: +- return None +- +- def do_get_start_iter(self, context, proposal): +- if not self.mark or self.mark.get_deleted(): +- return (False, None) +- +- return (True, self.mark.get_buffer().get_iter_at_mark(self.mark)) +- +- def do_match(self, context): +- return True +- +- def get_proposals(self, word): +- if self.proposals: +- proposals = self.proposals +- else: +- proposals = Library().get_snippets(None) +- +- if self.language_id: +- proposals += Library().get_snippets(self.language_id) +- +- # Filter based on the current word +- if word: +- proposals = filter(lambda x: x['tag'].startswith(word), proposals) +- +- return map(lambda x: Proposal(x), proposals) +- +- def do_populate(self, context): +- proposals = self.get_proposals(self.get_word(context)) +- context.add_proposals(self, proposals, True) +- +- def do_get_name(self): +- return self.name +- +- def do_activate_proposal(self, proposal, piter): +- return self.handler(proposal, piter) +- +- def do_get_info_widget(self, proposal): +- if not self.info_widget: +- view = Pluma.View.new_with_buffer(Pluma.Document()) +- manager = get_language_manager() +- +- lang = manager.get_language('snippets') +- view.get_buffer().set_language(lang) +- +- sw = Gtk.ScrolledWindow() +- sw.add(view) +- +- self.info_view = view +- self.info_widget = sw +- +- return self.info_widget +- +- def do_update_info(self, proposal, info): +- buf = self.info_view.get_buffer() +- +- buf.set_text(proposal.get_info()) +- buf.move_mark(buf.get_insert(), buf.get_start_iter()) +- buf.move_mark(buf.get_selection_bound(), buf.get_start_iter()) +- self.info_view.scroll_to_iter(buf.get_start_iter(), 0.0, False, 0.5, 0.5) +- +- def do_get_icon(self): +- return self.icon +- +- def do_get_activation(self): +- return GtkSource.CompletionActivation.USER_REQUESTED ++ __gtype_name__ = "PlumaSnippetsProvider" ++ ++ def __init__(self, name, language_id, handler): ++ GObject.Object.__init__(self) ++ ++ self.name = name ++ self.info_widget = None ++ self.proposals = [] ++ self.language_id = language_id ++ self.handler = handler ++ self.info_widget = None ++ self.mark = None ++ ++ theme = Gtk.IconTheme.get_default() ++ f, w, h = Gtk.icon_size_lookup(Gtk.IconSize.MENU) ++ ++ self.icon = theme.load_icon(Gtk.STOCK_JUSTIFY_LEFT, w, 0) ++ ++ def __del__(self): ++ if self.mark: ++ self.mark.get_buffer().delete_mark(self.mark) ++ ++ def set_proposals(self, proposals): ++ self.proposals = proposals ++ ++ def mark_position(self, it): ++ if not self.mark: ++ self.mark = it.get_buffer().create_mark(None, it, True) ++ else: ++ self.mark.get_buffer().move_mark(self.mark, it) ++ ++ def get_word(self, context): ++ (valid_context, it) = context.get_iter() ++ if not valid_context: ++ return None ++ ++ if it.starts_word() or it.starts_line() or not it.ends_word(): ++ return None ++ ++ start = it.copy() ++ ++ if start.backward_word_start(): ++ self.mark_position(start) ++ return start.get_text(it) ++ else: ++ return None ++ ++ def do_get_start_iter(self, context, proposal): ++ if not self.mark or self.mark.get_deleted(): ++ return (False, None) ++ ++ return (True, self.mark.get_buffer().get_iter_at_mark(self.mark)) ++ ++ def do_match(self, context): ++ return True ++ ++ def get_proposals(self, word): ++ if self.proposals: ++ proposals = self.proposals ++ else: ++ proposals = Library().get_snippets(None) ++ ++ if self.language_id: ++ proposals += Library().get_snippets(self.language_id) ++ ++ # Filter based on the current word ++ if word: ++ proposals = filter(lambda x: x['tag'].startswith(word), proposals) ++ ++ return map(lambda x: Proposal(x), proposals) ++ ++ def do_populate(self, context): ++ proposals = self.get_proposals(self.get_word(context)) ++ context.add_proposals(self, proposals, True) ++ ++ def do_get_name(self): ++ return self.name ++ ++ def do_activate_proposal(self, proposal, piter): ++ return self.handler(proposal, piter) ++ ++ def do_get_info_widget(self, proposal): ++ if not self.info_widget: ++ view = Pluma.View.new_with_buffer(Pluma.Document()) ++ manager = get_language_manager() ++ ++ lang = manager.get_language('snippets') ++ view.get_buffer().set_language(lang) ++ ++ sw = Gtk.ScrolledWindow() ++ sw.add(view) ++ ++ self.info_view = view ++ self.info_widget = sw ++ ++ return self.info_widget ++ ++ def do_update_info(self, proposal, info): ++ buf = self.info_view.get_buffer() ++ ++ buf.set_text(proposal.get_info()) ++ buf.move_mark(buf.get_insert(), buf.get_start_iter()) ++ buf.move_mark(buf.get_selection_bound(), buf.get_start_iter()) ++ self.info_view.scroll_to_iter(buf.get_start_iter(), 0.0, False, 0.5, 0.5) ++ ++ def do_get_icon(self): ++ return self.icon ++ ++ def do_get_activation(self): ++ return GtkSource.CompletionActivation.USER_REQUESTED + + class Defaults(GObject.Object, GtkSource.CompletionProvider): +- __gtype_name__ = "PlumaSnippetsDefaultsProvider" +- +- def __init__(self, handler): +- GObject.Object.__init__(self) +- +- self.handler = handler +- self.proposals = [] +- +- def set_defaults(self, defaults): +- self.proposals = [] +- +- for d in defaults: +- self.proposals.append(GtkSource.CompletionItem.new(d, d, None, None)) +- +- def do_get_name(self): +- return "" +- +- def do_activate_proposal(self, proposal, piter): +- return self.handler(proposal, piter) +- +- def do_populate(self, context): +- context.add_proposals(self, self.proposals, True) +- +- def do_get_activation(self): +- return GtkSource.CompletionActivation.USER_REQUESTED +- +-# ex:ts=8:et: ++ __gtype_name__ = "PlumaSnippetsDefaultsProvider" ++ ++ def __init__(self, handler): ++ GObject.Object.__init__(self) ++ ++ self.handler = handler ++ self.proposals = [] ++ ++ def set_defaults(self, defaults): ++ self.proposals = [] ++ ++ for d in defaults: ++ self.proposals.append(GtkSource.CompletionItem.new(d, d, None, None)) ++ ++ def do_get_name(self): ++ return "" ++ ++ def do_activate_proposal(self, proposal, piter): ++ return self.handler(proposal, piter) ++ ++ def do_populate(self, context): ++ context.add_proposals(self, self.proposals, True) ++ ++ def do_get_activation(self): ++ return GtkSource.CompletionActivation.USER_REQUESTED ++ ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Document.py b/plugins/snippets/snippets/Document.py +old mode 100755 +new mode 100644 +index 07faf2a..f1cc065 +--- a/plugins/snippets/snippets/Document.py ++++ b/plugins/snippets/snippets/Document.py +@@ -26,1070 +26,1068 @@ from Placeholder import * + import Completion + + class DynamicSnippet(dict): +- def __init__(self, text): +- self['text'] = text +- self.valid = True ++ def __init__(self, text): ++ self['text'] = text ++ self.valid = True + + class Document: +- TAB_KEY_VAL = (Gdk.KEY_Tab, \ +- Gdk.KEY_ISO_Left_Tab) +- SPACE_KEY_VAL = (Gdk.KEY_space,) +- +- def __init__(self, instance, view): +- self.view = None +- self.instance = instance +- +- self.placeholders = [] +- self.active_snippets = [] +- self.active_placeholder = None +- self.signal_ids = {} +- +- self.ordered_placeholders = [] +- self.update_placeholders = [] +- self.jump_placeholders = [] +- self.language_id = 0 +- self.timeout_update_id = 0 +- +- self.provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) +- +- # Always have a reference to the global snippets +- Library().ref(None) +- self.set_view(view) +- +- # Stop controlling the view. Remove all active snippets, remove references +- # to the view and the plugin instance, disconnect all signal handlers +- def stop(self): +- if self.timeout_update_id != 0: +- GLib.source_remove(self.timeout_update_id) +- self.timeout_update_id = 0 +- del self.update_placeholders[:] +- del self.jump_placeholders[:] +- +- # Always release the reference to the global snippets +- Library().unref(None) +- self.set_view(None) +- self.instance = None +- self.active_placeholder = None ++ TAB_KEY_VAL = (Gdk.KEY_Tab, \ ++ Gdk.KEY_ISO_Left_Tab) ++ SPACE_KEY_VAL = (Gdk.KEY_space,) ++ ++ def __init__(self, instance, view): ++ self.view = None ++ self.instance = instance ++ ++ self.placeholders = [] ++ self.active_snippets = [] ++ self.active_placeholder = None ++ self.signal_ids = {} ++ ++ self.ordered_placeholders = [] ++ self.update_placeholders = [] ++ self.jump_placeholders = [] ++ self.language_id = 0 ++ self.timeout_update_id = 0 ++ ++ self.provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) ++ ++ # Always have a reference to the global snippets ++ Library().ref(None) ++ self.set_view(view) ++ ++ # Stop controlling the view. Remove all active snippets, remove references ++ # to the view and the plugin instance, disconnect all signal handlers ++ def stop(self): ++ if self.timeout_update_id != 0: ++ GLib.source_remove(self.timeout_update_id) ++ self.timeout_update_id = 0 ++ del self.update_placeholders[:] ++ del self.jump_placeholders[:] ++ ++ # Always release the reference to the global snippets ++ Library().unref(None) ++ self.set_view(None) ++ self.instance = None ++ self.active_placeholder = None ++ ++ def disconnect_signal(self, obj, signal): ++ if (obj, signal) in self.signal_ids: ++ obj.disconnect(self.signal_ids[(obj, signal)]) ++ del self.signal_ids[(obj, signal)] ++ ++ def connect_signal(self, obj, signal, cb): ++ self.disconnect_signal(obj, signal) ++ self.signal_ids[(obj, signal)] = obj.connect(signal, cb) ++ ++ def connect_signal_after(self, obj, signal, cb): ++ self.disconnect_signal(obj, signal) ++ self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb) + +- def disconnect_signal(self, obj, signal): +- if (obj, signal) in self.signal_ids: +- obj.disconnect(self.signal_ids[(obj, signal)]) +- del self.signal_ids[(obj, signal)] +- +- def connect_signal(self, obj, signal, cb): +- self.disconnect_signal(obj, signal) +- self.signal_ids[(obj, signal)] = obj.connect(signal, cb) +- +- def connect_signal_after(self, obj, signal, cb): +- self.disconnect_signal(obj, signal) +- self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb) +- +- # Set the view to be controlled. Installs signal handlers and sets current +- # language. If there is already a view set this function will first remove +- # all currently active snippets and disconnect all current signals. So +- # self.set_view(None) will effectively remove all the control from the +- # current view +- def _set_view(self, view): +- if self.view: +- buf = self.view.get_buffer() +- +- # Remove signals +- signals = {self.view: ('key-press-event', 'destroy', +- 'notify::editable', 'drag-data-received', 'expose-event'), +- buf: ('notify::language', 'changed', 'cursor-moved', 'insert-text'), +- self.view.get_completion(): ('hide',)} +- +- for obj, sig in signals.items(): +- if obj: +- for s in sig: +- self.disconnect_signal(obj, s) +- +- # Remove all active snippets +- for snippet in list(self.active_snippets): +- self.deactivate_snippet(snippet, True) +- +- completion = self.view.get_completion() +- if completion: +- completion.remove_provider(self.provider) +- +- self.view = view +- +- if view != None: +- buf = view.get_buffer() +- +- self.connect_signal(view, 'destroy', self.on_view_destroy) +- +- if view.get_editable(): +- self.connect_signal(view, 'key-press-event', self.on_view_key_press) +- +- self.connect_signal(buf, 'notify::language', self.on_notify_language) +- self.connect_signal(view, 'notify::editable', self.on_notify_editable) +- self.connect_signal(view, 'drag-data-received', self.on_drag_data_received) +- self.connect_signal_after(view, 'draw', self.on_draw) +- +- self.update_language() +- +- completion = view.get_completion() +- completion.add_provider(self.provider) +- elif self.language_id != 0: +- langid = self.language_id +- +- self.language_id = None; +- self.provider.language_id = self.language_id +- +- if self.instance: +- self.instance.language_changed(self) +- +- Library().unref(langid) +- +- def set_view(self, view): +- if view == self.view: +- return +- +- self._set_view(view) +- +- # Call this whenever the language in the view changes. This makes sure that +- # the correct language is used when finding snippets +- def update_language(self): +- lang = self.view.get_buffer().get_language() +- +- if lang == None and self.language_id == None: +- return +- elif lang and lang.get_id() == self.language_id: +- return +- +- langid = self.language_id +- +- if lang: +- self.language_id = lang.get_id() +- else: +- self.language_id = None ++ # Set the view to be controlled. Installs signal handlers and sets current ++ # language. If there is already a view set this function will first remove ++ # all currently active snippets and disconnect all current signals. So ++ # self.set_view(None) will effectively remove all the control from the ++ # current view ++ def _set_view(self, view): ++ if self.view: ++ buf = self.view.get_buffer() ++ ++ # Remove signals ++ signals = {self.view: ('key-press-event', 'destroy', ++ 'notify::editable', 'drag-data-received', 'expose-event'), ++ buf: ('notify::language', 'changed', 'cursor-moved', 'insert-text'), ++ self.view.get_completion(): ('hide',)} ++ ++ for obj, sig in signals.items(): ++ if obj: ++ for s in sig: ++ self.disconnect_signal(obj, s) + +- if self.instance: +- self.instance.language_changed(self) ++ # Remove all active snippets ++ for snippet in list(self.active_snippets): ++ self.deactivate_snippet(snippet, True) ++ ++ completion = self.view.get_completion() ++ if completion: ++ completion.remove_provider(self.provider) ++ ++ self.view = view + +- if langid != 0: +- Library().unref(langid) ++ if view != None: ++ buf = view.get_buffer() + +- Library().ref(self.language_id) +- self.provider.language_id = self.language_id ++ self.connect_signal(view, 'destroy', self.on_view_destroy) ++ ++ if view.get_editable(): ++ self.connect_signal(view, 'key-press-event', self.on_view_key_press) + +- def accelerator_activate(self, keyval, mod): +- if not self.view or not self.view.get_editable(): +- return False ++ self.connect_signal(buf, 'notify::language', self.on_notify_language) ++ self.connect_signal(view, 'notify::editable', self.on_notify_editable) ++ self.connect_signal(view, 'drag-data-received', self.on_drag_data_received) ++ self.connect_signal_after(view, 'draw', self.on_draw) ++ ++ self.update_language() ++ ++ completion = view.get_completion() ++ completion.add_provider(self.provider) ++ elif self.language_id != 0: ++ langid = self.language_id + +- accelerator = Gtk.accelerator_name(keyval, mod) +- snippets = Library().from_accelerator(accelerator, \ +- self.language_id) ++ self.language_id = None; ++ self.provider.language_id = self.language_id ++ ++ if self.instance: ++ self.instance.language_changed(self) + +- snippets_debug('Accel!') ++ Library().unref(langid) + +- if len(snippets) == 0: +- return False +- elif len(snippets) == 1: +- self.apply_snippet(snippets[0]) +- else: +- # Do the fancy completion dialog +- provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) +- provider.set_proposals(snippets) ++ def set_view(self, view): ++ if view == self.view: ++ return + +- cm.show([provider], cm.create_context(None)) ++ self._set_view(view) + +- return True ++ # Call this whenever the language in the view changes. This makes sure that ++ # the correct language is used when finding snippets ++ def update_language(self): ++ lang = self.view.get_buffer().get_language() ++ ++ if lang == None and self.language_id == None: ++ return ++ elif lang and lang.get_id() == self.language_id: ++ return ++ ++ langid = self.language_id ++ ++ if lang: ++ self.language_id = lang.get_id() ++ else: ++ self.language_id = None + +- def first_snippet_inserted(self): +- buf = self.view.get_buffer() +- +- self.connect_signal(buf, 'changed', self.on_buffer_changed) +- self.connect_signal(buf, 'cursor-moved', self.on_buffer_cursor_moved) +- self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text) +- +- def last_snippet_removed(self): +- buf = self.view.get_buffer() +- self.disconnect_signal(buf, 'changed') +- self.disconnect_signal(buf, 'cursor-moved') +- self.disconnect_signal(buf, 'insert-text') +- +- def current_placeholder(self): +- buf = self.view.get_buffer() +- +- piter = buf.get_iter_at_mark(buf.get_insert()) +- found = [] +- +- for placeholder in self.placeholders: +- begin = placeholder.begin_iter() +- end = placeholder.end_iter() +- +- if piter.compare(begin) >= 0 and piter.compare(end) <= 0: +- found.append(placeholder) +- +- if self.active_placeholder in found: +- return self.active_placeholder +- elif len(found) > 0: +- return found[0] +- else: +- return None +- +- def advance_placeholder(self, direction): +- # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction +- buf = self.view.get_buffer() +- +- piter = buf.get_iter_at_mark(buf.get_insert()) +- found = current = next = None +- length = len(self.placeholders) +- +- placeholders = list(self.placeholders) +- +- if self.active_placeholder: +- begin = self.active_placeholder.begin_iter() +- end = self.active_placeholder.end_iter() +- +- if piter.compare(begin) >= 0 and piter.compare(end) <= 0: +- current = self.active_placeholder +- currentIndex = placeholders.index(self.active_placeholder) +- +- if direction == 1: +- # w = piter, x = begin, y = end, z = found +- nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \ +- x.compare(z.begin_iter()) < 0)) +- indexer = lambda x: x < length - 1 +- else: +- # w = piter, x = begin, y = end, z = prev +- nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \ +- x.compare(z.begin_iter()) >= 0)) +- indexer = lambda x: x > 0 +- +- for index in range(0, length): +- placeholder = placeholders[index] +- begin = placeholder.begin_iter() +- end = placeholder.end_iter() +- +- # Find the nearest placeholder +- if nearest(piter, begin, end, found): +- foundIndex = index +- found = placeholder +- +- # Find the current placeholder +- if piter.compare(begin) >= 0 and \ +- piter.compare(end) <= 0 and \ +- current == None: +- currentIndex = index +- current = placeholder +- +- if current and current != found and \ +- (current.begin_iter().compare(found.begin_iter()) == 0 or \ +- current.end_iter().compare(found.begin_iter()) == 0) and \ +- self.active_placeholder and \ +- current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0: +- # if current and found are at the same place, then +- # resolve the 'hugging' problem +- current = self.active_placeholder +- currentIndex = placeholders.index(current) +- +- if current: +- if indexer(currentIndex): +- next = placeholders[currentIndex + direction] +- elif found: +- next = found +- elif length > 0: +- next = self.placeholders[0] +- +- return current, next +- +- def next_placeholder(self): +- return self.advance_placeholder(1) +- +- def previous_placeholder(self): +- return self.advance_placeholder(-1) +- +- def cursor_on_screen(self): +- buf = self.view.get_buffer() +- self.view.scroll_mark_onscreen(buf.get_insert()) +- +- def set_active_placeholder(self, placeholder): +- self.active_placeholder = placeholder +- +- def goto_placeholder(self, current, next): +- last = None +- +- if current: +- # Signal this placeholder to end action +- self.view.get_completion().hide() +- current.leave() +- +- if current.__class__ == PlaceholderEnd: +- last = current +- +- self.set_active_placeholder(next) +- +- if next: +- next.enter() +- +- if next.__class__ == PlaceholderEnd: +- last = next +- elif len(next.defaults) > 1 and next.get_text() == next.default: +- provider = Completion.Defaults(self.on_default_activated) +- provider.set_defaults(next.defaults) +- +- cm = self.view.get_completion() +- cm.show([provider], cm.create_context(None)) +- +- if last: +- # This is the end of the placeholder, remove the snippet etc +- for snippet in list(self.active_snippets): +- if snippet.placeholders[0] == last: +- self.deactivate_snippet(snippet) +- break +- +- self.cursor_on_screen() +- +- return next != None +- +- def skip_to_next_placeholder(self): +- (current, next) = self.next_placeholder() +- return self.goto_placeholder(current, next) +- +- def skip_to_previous_placeholder(self): +- (current, prev) = self.previous_placeholder() +- return self.goto_placeholder(current, prev) +- +- def env_get_selected_text(self, buf): +- bounds = buf.get_selection_bounds() +- +- if bounds: +- return buf.get_text(bounds[0], bounds[1], False) +- else: +- return '' +- +- def env_get_current_word(self, buf): +- start, end = buffer_word_boundary(buf) +- +- return buf.get_text(start, end, False) +- +- def env_get_current_line(self, buf): +- start, end = buffer_line_boundary(buf) +- +- return buf.get_text(start, end, False) +- +- def env_get_current_line_number(self, buf): +- start, end = buffer_line_boundary(buf) +- +- return str(start.get_line() + 1) +- +- def env_get_document_uri(self, buf): +- location = buf.get_location() +- +- if location: +- return location.get_uri() +- else: +- return '' +- +- def env_get_document_name(self, buf): +- location = buf.get_location() +- +- if location: +- return location.get_basename() +- else: +- return '' ++ if self.instance: ++ self.instance.language_changed(self) ++ ++ if langid != 0: ++ Library().unref(langid) + +- def env_get_document_scheme(self, buf): +- location = buf.get_location() +- +- if location: +- return location.get_uri_scheme() +- else: +- return '' ++ Library().ref(self.language_id) ++ self.provider.language_id = self.language_id ++ ++ def accelerator_activate(self, keyval, mod): ++ if not self.view or not self.view.get_editable(): ++ return False ++ ++ accelerator = Gtk.accelerator_name(keyval, mod) ++ snippets = Library().from_accelerator(accelerator, \ ++ self.language_id) ++ ++ snippets_debug('Accel!') ++ ++ if len(snippets) == 0: ++ return False ++ elif len(snippets) == 1: ++ self.apply_snippet(snippets[0]) ++ else: ++ # Do the fancy completion dialog ++ provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) ++ provider.set_proposals(snippets) ++ ++ cm.show([provider], cm.create_context(None)) ++ ++ return True ++ ++ def first_snippet_inserted(self): ++ buf = self.view.get_buffer() ++ ++ self.connect_signal(buf, 'changed', self.on_buffer_changed) ++ self.connect_signal(buf, 'cursor-moved', self.on_buffer_cursor_moved) ++ self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text) + +- def env_get_document_path(self, buf): +- location = buf.get_location() +- +- if location: +- return location.get_path() +- else: +- return '' ++ def last_snippet_removed(self): ++ buf = self.view.get_buffer() ++ self.disconnect_signal(buf, 'changed') ++ self.disconnect_signal(buf, 'cursor-moved') ++ self.disconnect_signal(buf, 'insert-text') + +- def env_get_document_dir(self, buf): +- location = buf.get_location() ++ def current_placeholder(self): ++ buf = self.view.get_buffer() + +- if location: +- return location.get_parent().get_path() or '' +- else: +- return '' ++ piter = buf.get_iter_at_mark(buf.get_insert()) ++ found = [] ++ ++ for placeholder in self.placeholders: ++ begin = placeholder.begin_iter() ++ end = placeholder.end_iter() ++ ++ if piter.compare(begin) >= 0 and piter.compare(end) <= 0: ++ found.append(placeholder) ++ ++ if self.active_placeholder in found: ++ return self.active_placeholder ++ elif len(found) > 0: ++ return found[0] ++ else: ++ return None ++ ++ def advance_placeholder(self, direction): ++ # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction ++ buf = self.view.get_buffer() + +- def env_get_document_type(self, buf): +- typ = buf.get_mime_type() +- +- if typ: +- return typ +- else: +- return '' +- +- def env_get_documents_uri(self, buf): +- toplevel = self.view.get_toplevel() +- +- if isinstance(toplevel, Pluma.Window): +- documents_uri = [doc.get_location().get_uri() +- for doc in toplevel.get_documents() +- if doc.get_location() is not None] +- else: +- documents_uri = [] +- +- return ' '.join(documents_uri) +- +- def env_get_documents_path(self, buf): +- toplevel = self.view.get_toplevel() +- +- if isinstance(toplevel, Pluma.Window): +- documents_location = [doc.get_location() +- for doc in toplevel.get_documents() +- if doc.get_location() is not None] +- +- documents_path = [location.get_path() +- for location in documents_location +- if Pluma.utils_uri_has_file_scheme(location.get_uri())] +- else: +- documents_path = [] +- +- return ' '.join(documents_path) +- +- def update_environment(self): +- buf = self.view.get_buffer() +- +- variables = {'PLUMA_SELECTED_TEXT': self.env_get_selected_text, +- 'PLUMA_CURRENT_WORD': self.env_get_current_word, +- 'PLUMA_CURRENT_LINE': self.env_get_current_line, +- 'PLUMA_CURRENT_LINE_NUMBER': self.env_get_current_line_number, +- 'PLUMA_CURRENT_DOCUMENT_URI': self.env_get_document_uri, +- 'PLUMA_CURRENT_DOCUMENT_NAME': self.env_get_document_name, +- 'PLUMA_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme, +- 'PLUMA_CURRENT_DOCUMENT_PATH': self.env_get_document_path, +- 'PLUMA_CURRENT_DOCUMENT_DIR': self.env_get_document_dir, +- 'PLUMA_CURRENT_DOCUMENT_TYPE': self.env_get_document_type, +- 'PLUMA_DOCUMENTS_URI': self.env_get_documents_uri, +- 'PLUMA_DOCUMENTS_PATH': self.env_get_documents_path, +- } +- +- for var in variables: +- os.environ[var] = variables[var](buf) +- +- def uses_current_word(self, snippet): +- matches = re.findall('(\\\\*)\\$PLUMA_CURRENT_WORD', snippet['text']) +- +- for match in matches: +- if len(match) % 2 == 0: +- return True +- +- return False +- +- def uses_current_line(self, snippet): +- matches = re.findall('(\\\\*)\\$PLUMA_CURRENT_LINE', snippet['text']) +- +- for match in matches: +- if len(match) % 2 == 0: +- return True +- +- return False +- +- def apply_snippet(self, snippet, start = None, end = None): +- if not snippet.valid: +- return False +- +- buf = self.view.get_buffer() +- s = Snippet(snippet) +- +- if not start: +- start = buf.get_iter_at_mark(buf.get_insert()) +- +- if not end: +- end = buf.get_iter_at_mark(buf.get_selection_bound()) +- +- if start.equal(end) and self.uses_current_word(s): +- # There is no tab trigger and no selection and the snippet uses +- # the current word. Set start and end to the word boundary so that +- # it will be removed +- start, end = buffer_word_boundary(buf) +- elif start.equal(end) and self.uses_current_line(s): +- # There is no tab trigger and no selection and the snippet uses +- # the current line. Set start and end to the line boundary so that +- # it will be removed +- start, end = buffer_line_boundary(buf) +- +- # Set environmental variables +- self.update_environment() +- +- # You know, we could be in an end placeholder +- (current, next) = self.next_placeholder() +- if current and current.__class__ == PlaceholderEnd: +- self.goto_placeholder(current, None) +- +- buf.begin_user_action() +- +- # Remove the tag, selection or current word +- buf.delete(start, end) +- +- # Insert the snippet +- holders = len(self.placeholders) +- +- if len(self.active_snippets) == 0: +- self.first_snippet_inserted() +- +- sn = s.insert_into(self, start) +- self.active_snippets.append(sn) +- +- # Put cursor at first tab placeholder +- keys = filter(lambda x: x > 0, sn.placeholders.keys()) +- +- if len(keys) == 0: +- if 0 in sn.placeholders: +- self.goto_placeholder(self.active_placeholder, sn.placeholders[0]) +- else: +- buf.place_cursor(sn.begin_iter()) +- else: +- self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]]) ++ piter = buf.get_iter_at_mark(buf.get_insert()) ++ found = current = next = None ++ length = len(self.placeholders) ++ ++ placeholders = list(self.placeholders) ++ ++ if self.active_placeholder: ++ begin = self.active_placeholder.begin_iter() ++ end = self.active_placeholder.end_iter() ++ ++ if piter.compare(begin) >= 0 and piter.compare(end) <= 0: ++ current = self.active_placeholder ++ currentIndex = placeholders.index(self.active_placeholder) ++ ++ if direction == 1: ++ # w = piter, x = begin, y = end, z = found ++ nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \ ++ x.compare(z.begin_iter()) < 0)) ++ indexer = lambda x: x < length - 1 ++ else: ++ # w = piter, x = begin, y = end, z = prev ++ nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \ ++ x.compare(z.begin_iter()) >= 0)) ++ indexer = lambda x: x > 0 ++ ++ for index in range(0, length): ++ placeholder = placeholders[index] ++ begin = placeholder.begin_iter() ++ end = placeholder.end_iter() ++ ++ # Find the nearest placeholder ++ if nearest(piter, begin, end, found): ++ foundIndex = index ++ found = placeholder ++ ++ # Find the current placeholder ++ if piter.compare(begin) >= 0 and \ ++ piter.compare(end) <= 0 and \ ++ current == None: ++ currentIndex = index ++ current = placeholder ++ ++ if current and current != found and \ ++ (current.begin_iter().compare(found.begin_iter()) == 0 or \ ++ current.end_iter().compare(found.begin_iter()) == 0) and \ ++ self.active_placeholder and \ ++ current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0: ++ # if current and found are at the same place, then ++ # resolve the 'hugging' problem ++ current = self.active_placeholder ++ currentIndex = placeholders.index(current) ++ ++ if current: ++ if indexer(currentIndex): ++ next = placeholders[currentIndex + direction] ++ elif found: ++ next = found ++ elif length > 0: ++ next = self.placeholders[0] ++ ++ return current, next ++ ++ def next_placeholder(self): ++ return self.advance_placeholder(1) ++ ++ def previous_placeholder(self): ++ return self.advance_placeholder(-1) ++ ++ def cursor_on_screen(self): ++ buf = self.view.get_buffer() ++ self.view.scroll_mark_onscreen(buf.get_insert()) ++ ++ def set_active_placeholder(self, placeholder): ++ self.active_placeholder = placeholder ++ ++ def goto_placeholder(self, current, next): ++ last = None ++ ++ if current: ++ # Signal this placeholder to end action ++ self.view.get_completion().hide() ++ current.leave() ++ ++ if current.__class__ == PlaceholderEnd: ++ last = current ++ ++ self.set_active_placeholder(next) ++ ++ if next: ++ next.enter() ++ ++ if next.__class__ == PlaceholderEnd: ++ last = next ++ elif len(next.defaults) > 1 and next.get_text() == next.default: ++ provider = Completion.Defaults(self.on_default_activated) ++ provider.set_defaults(next.defaults) ++ ++ cm = self.view.get_completion() ++ cm.show([provider], cm.create_context(None)) ++ ++ if last: ++ # This is the end of the placeholder, remove the snippet etc ++ for snippet in list(self.active_snippets): ++ if snippet.placeholders[0] == last: ++ self.deactivate_snippet(snippet) ++ break ++ ++ self.cursor_on_screen() ++ ++ return next != None ++ ++ def skip_to_next_placeholder(self): ++ (current, next) = self.next_placeholder() ++ return self.goto_placeholder(current, next) ++ ++ def skip_to_previous_placeholder(self): ++ (current, prev) = self.previous_placeholder() ++ return self.goto_placeholder(current, prev) ++ ++ def env_get_selected_text(self, buf): ++ bounds = buf.get_selection_bounds() ++ ++ if bounds: ++ return buf.get_text(bounds[0], bounds[1], False) ++ else: ++ return '' ++ ++ def env_get_current_word(self, buf): ++ start, end = buffer_word_boundary(buf) ++ ++ return buf.get_text(start, end, False) ++ ++ def env_get_current_line(self, buf): ++ start, end = buffer_line_boundary(buf) ++ ++ return buf.get_text(start, end, False) ++ ++ def env_get_current_line_number(self, buf): ++ start, end = buffer_line_boundary(buf) ++ return str(start.get_line() + 1) ++ ++ def env_get_document_uri(self, buf): ++ location = buf.get_location() ++ ++ if location: ++ return location.get_uri() ++ else: ++ return '' ++ ++ def env_get_document_name(self, buf): ++ location = buf.get_location() ++ ++ if location: ++ return location.get_basename() ++ else: ++ return '' ++ ++ def env_get_document_scheme(self, buf): ++ location = buf.get_location() ++ ++ if location: ++ return location.get_uri_scheme() ++ else: ++ return '' ++ ++ def env_get_document_path(self, buf): ++ location = buf.get_location() ++ ++ if location: ++ return location.get_path() ++ else: ++ return '' ++ ++ def env_get_document_dir(self, buf): ++ location = buf.get_location() ++ ++ if location: ++ return location.get_parent().get_path() or '' ++ else: ++ return '' ++ ++ def env_get_document_type(self, buf): ++ typ = buf.get_mime_type() ++ ++ if typ: ++ return typ ++ else: ++ return '' ++ ++ def env_get_documents_uri(self, buf): ++ toplevel = self.view.get_toplevel() ++ ++ if isinstance(toplevel, Pluma.Window): ++ documents_uri = [doc.get_location().get_uri() ++ for doc in toplevel.get_documents() ++ if doc.get_location() is not None] ++ else: ++ documents_uri = [] ++ ++ return ' '.join(documents_uri) ++ ++ def env_get_documents_path(self, buf): ++ toplevel = self.view.get_toplevel() + +- if sn in self.active_snippets: +- # Check if we can get end_iter in view without moving the +- # current cursor position out of view +- cur = buf.get_iter_at_mark(buf.get_insert()) +- last = sn.end_iter() ++ if isinstance(toplevel, Pluma.Window): ++ documents_location = [doc.get_location() ++ for doc in toplevel.get_documents() ++ if doc.get_location() is not None] + +- curloc = self.view.get_iter_location(cur) +- lastloc = self.view.get_iter_location(last) ++ documents_path = [location.get_path() ++ for location in documents_location ++ if Pluma.utils_uri_has_file_scheme(location.get_uri())] ++ else: ++ documents_path = [] + +- if (lastloc.y + lastloc.height) - curloc.y <= \ +- self.view.get_visible_rect().height: +- self.view.scroll_mark_onscreen(sn.end_mark) ++ return ' '.join(documents_path) + +- buf.end_user_action() +- self.view.grab_focus() ++ def update_environment(self): ++ buf = self.view.get_buffer() + ++ variables = {'PLUMA_SELECTED_TEXT': self.env_get_selected_text, ++ 'PLUMA_CURRENT_WORD': self.env_get_current_word, ++ 'PLUMA_CURRENT_LINE': self.env_get_current_line, ++ 'PLUMA_CURRENT_LINE_NUMBER': self.env_get_current_line_number, ++ 'PLUMA_CURRENT_DOCUMENT_URI': self.env_get_document_uri, ++ 'PLUMA_CURRENT_DOCUMENT_NAME': self.env_get_document_name, ++ 'PLUMA_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme, ++ 'PLUMA_CURRENT_DOCUMENT_PATH': self.env_get_document_path, ++ 'PLUMA_CURRENT_DOCUMENT_DIR': self.env_get_document_dir, ++ 'PLUMA_CURRENT_DOCUMENT_TYPE': self.env_get_document_type, ++ 'PLUMA_DOCUMENTS_URI': self.env_get_documents_uri, ++ 'PLUMA_DOCUMENTS_PATH': self.env_get_documents_path, ++ } ++ ++ for var in variables: ++ os.environ[var] = variables[var](buf) ++ ++ def uses_current_word(self, snippet): ++ matches = re.findall('(\\\\*)\\$PLUMA_CURRENT_WORD', snippet['text']) ++ ++ for match in matches: ++ if len(match) % 2 == 0: + return True + +- def get_tab_tag(self, buf, end = None): +- if not end: +- end = buf.get_iter_at_mark(buf.get_insert()) ++ return False + +- start = end.copy() +- +- word = None +- +- if start.backward_word_start(): +- # Check if we were at a word start ourselves +- tmp = start.copy() +- tmp.forward_word_end() +- +- if tmp.equal(end): +- word = buf.get_text(start, end, False) +- else: +- start = end.copy() +- else: +- start = end.copy() +- +- if not word or word == '': +- if start.backward_char(): +- word = start.get_char() ++ def uses_current_line(self, snippet): ++ matches = re.findall('(\\\\*)\\$PLUMA_CURRENT_LINE', snippet['text']) + +- if word.isalnum() or word.isspace(): +- return (None, None, None) +- else: +- return (None, None, None) ++ for match in matches: ++ if len(match) % 2 == 0: ++ return True + +- return (word, start, end) ++ return False + +- def parse_and_run_snippet(self, data, iter): +- self.apply_snippet(DynamicSnippet(data), iter, iter) ++ def apply_snippet(self, snippet, start = None, end = None): ++ if not snippet.valid: ++ return False + +- def run_snippet_trigger(self, trigger, bounds): +- if not self.view: +- return False ++ buf = self.view.get_buffer() ++ s = Snippet(snippet) + +- snippets = Library().from_tag(trigger, self.language_id) +- buf = self.view.get_buffer() ++ if not start: ++ start = buf.get_iter_at_mark(buf.get_insert()) + +- if snippets: +- if len(snippets) == 1: +- return self.apply_snippet(snippets[0], bounds[0], bounds[1]) +- else: +- # Do the fancy completion dialog +- provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) +- provider.set_proposals(snippets) ++ if not end: ++ end = buf.get_iter_at_mark(buf.get_selection_bound()) + +- cm = self.view.get_completion() +- cm.show([provider], cm.create_context(None)) ++ if start.equal(end) and self.uses_current_word(s): ++ # There is no tab trigger and no selection and the snippet uses ++ # the current word. Set start and end to the word boundary so that ++ # it will be removed ++ start, end = buffer_word_boundary(buf) ++ elif start.equal(end) and self.uses_current_line(s): ++ # There is no tab trigger and no selection and the snippet uses ++ # the current line. Set start and end to the line boundary so that ++ # it will be removed ++ start, end = buffer_line_boundary(buf) + +- return True ++ # Set environmental variables ++ self.update_environment() + +- return False +- +- def run_snippet(self): +- if not self.view: +- return False ++ # You know, we could be in an end placeholder ++ (current, next) = self.next_placeholder() ++ if current and current.__class__ == PlaceholderEnd: ++ self.goto_placeholder(current, None) + +- buf = self.view.get_buffer() ++ buf.begin_user_action() + +- # get the word preceding the current insertion position +- (word, start, end) = self.get_tab_tag(buf) ++ # Remove the tag, selection or current word ++ buf.delete(start, end) + +- if not word: +- return self.skip_to_next_placeholder() ++ # Insert the snippet ++ holders = len(self.placeholders) + +- if not self.run_snippet_trigger(word, (start, end)): +- return self.skip_to_next_placeholder() +- else: +- return True ++ if len(self.active_snippets) == 0: ++ self.first_snippet_inserted() + +- def deactivate_snippet(self, snippet, force = False): +- buf = self.view.get_buffer() +- remove = [] +- ordered_remove = [] ++ sn = s.insert_into(self, start) ++ self.active_snippets.append(sn) + +- for tabstop in snippet.placeholders: +- if tabstop == -1: +- placeholders = snippet.placeholders[-1] +- else: +- placeholders = [snippet.placeholders[tabstop]] +- +- for placeholder in placeholders: +- if placeholder in self.placeholders: +- if placeholder in self.update_placeholders: +- placeholder.update_contents() +- +- self.update_placeholders.remove(placeholder) +- elif placeholder in self.jump_placeholders: +- placeholder[0].leave() ++ # Put cursor at first tab placeholder ++ keys = filter(lambda x: x > 0, sn.placeholders.keys()) + +- remove.append(placeholder) +- elif placeholder in self.ordered_placeholders: +- ordered_remove.append(placeholder) ++ if len(keys) == 0: ++ if 0 in sn.placeholders: ++ self.goto_placeholder(self.active_placeholder, sn.placeholders[0]) ++ else: ++ buf.place_cursor(sn.begin_iter()) ++ else: ++ self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]]) + +- for placeholder in remove: +- if placeholder == self.active_placeholder: +- self.active_placeholder = None ++ if sn in self.active_snippets: ++ # Check if we can get end_iter in view without moving the ++ # current cursor position out of view ++ cur = buf.get_iter_at_mark(buf.get_insert()) ++ last = sn.end_iter() + +- self.placeholders.remove(placeholder) +- self.ordered_placeholders.remove(placeholder) ++ curloc = self.view.get_iter_location(cur) ++ lastloc = self.view.get_iter_location(last) + +- placeholder.remove(force) ++ if (lastloc.y + lastloc.height) - curloc.y <= \ ++ self.view.get_visible_rect().height: ++ self.view.scroll_mark_onscreen(sn.end_mark) + +- for placeholder in ordered_remove: +- self.ordered_placeholders.remove(placeholder) +- placeholder.remove(force) ++ buf.end_user_action() ++ self.view.grab_focus() + +- snippet.deactivate() +- self.active_snippets.remove(snippet) ++ return True + +- if len(self.active_snippets) == 0: +- self.last_snippet_removed() ++ def get_tab_tag(self, buf, end = None): ++ if not end: ++ end = buf.get_iter_at_mark(buf.get_insert()) + +- self.view.queue_draw() ++ start = end.copy() ++ word = None + +- def update_snippet_contents(self): +- self.timeout_update_id = 0 ++ if start.backward_word_start(): ++ # Check if we were at a word start ourselves ++ tmp = start.copy() ++ tmp.forward_word_end() + +- for placeholder in self.update_placeholders: +- placeholder.update_contents() ++ if tmp.equal(end): ++ word = buf.get_text(start, end, False) ++ else: ++ start = end.copy() ++ else: ++ start = end.copy() + +- for placeholder in self.jump_placeholders: +- self.goto_placeholder(placeholder[0], placeholder[1]) +- +- del self.update_placeholders[:] +- del self.jump_placeholders[:] +- +- return False +- +- # Callbacks +- def on_view_destroy(self, view): +- self.stop() +- return +- +- def on_buffer_cursor_moved(self, buf): +- piter = buf.get_iter_at_mark(buf.get_insert()) +- +- # Check for all snippets if the cursor is outside its scope +- for snippet in list(self.active_snippets): +- if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted(): +- self.deactivate(snippet) +- else: +- begin = snippet.begin_iter() +- end = snippet.end_iter() +- +- if piter.compare(begin) < 0 or piter.compare(end) > 0: +- # Oh no! Remove the snippet this instant!! +- self.deactivate_snippet(snippet) +- +- current = self.current_placeholder() +- +- if current != self.active_placeholder: +- self.jump_placeholders.append((self.active_placeholder, current)) +- +- if self.timeout_update_id == 0: +- self.timeout_update_id = GLib.timeout_add(0, +- self.update_snippet_contents) +- +- def on_buffer_changed(self, buf): +- for snippet in list(self.active_snippets): +- begin = snippet.begin_iter() +- end = snippet.end_iter() +- +- if begin.compare(end) >= 0: +- # Begin collapsed on end, just remove it +- self.deactivate_snippet(snippet) +- +- current = self.current_placeholder() +- +- if current: +- if not current in self.update_placeholders: +- self.update_placeholders.append(current) +- +- if self.timeout_update_id == 0: +- self.timeout_update_id = GLib.timeout_add(0, \ +- self.update_snippet_contents) +- +- def on_buffer_insert_text(self, buf, piter, text, length): +- ctx = get_buffer_context(buf) +- +- # do nothing special if there is no context and no active +- # placeholder +- if (not ctx) and (not self.active_placeholder): +- return +- +- if not ctx: +- ctx = self.active_placeholder +- +- if not ctx in self.ordered_placeholders: +- return +- +- # move any marks that were incorrectly moved by this insertion +- # back to where they belong +- begin = ctx.begin_iter() +- end = ctx.end_iter() +- idx = self.ordered_placeholders.index(ctx) +- +- for placeholder in self.ordered_placeholders: +- if placeholder == ctx: +- continue +- +- ob = placeholder.begin_iter() +- oe = placeholder.end_iter() +- +- if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0): +- oidx = self.ordered_placeholders.index(placeholder) +- +- if oidx > idx and ob: +- buf.move_mark(placeholder.begin, end) +- elif oidx < idx and oe: +- buf.move_mark(placeholder.end, begin) +- elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0): +- buf.move_mark(placeholder.begin, end) +- elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0: +- buf.move_mark(placeholder.end, begin) +- +- def on_notify_language(self, buf, spec): +- self.update_language() +- +- def on_notify_editable(self, view, spec): +- self._set_view(view) +- +- def on_view_key_press(self, view, event): +- library = Library() +- +- state = event.get_state() +- +- if not (state & Gdk.ModifierType.CONTROL_MASK) and \ +- not (state & Gdk.ModifierType.MOD1_MASK) and \ +- event.keyval in self.TAB_KEY_VAL: +- if not state & Gdk.ModifierType.SHIFT_MASK: +- return self.run_snippet() +- else: +- return self.skip_to_previous_placeholder() +- elif not library.loaded and \ +- library.valid_accelerator(event.keyval, state): +- library.ensure_files() +- library.ensure(self.language_id) +- self.accelerator_activate(event.keyval, \ +- state & Gtk.accelerator_get_default_mod_mask()) +- +- return False +- +- def path_split(self, path, components=[]): +- head, tail = os.path.split(path) +- +- if not tail and head: +- return [head] + components +- elif tail: +- return self.path_split(head, [tail] + components) +- else: +- return components +- +- def relative_path(self, first, second, mime): +- prot1 = re.match('(^[a-z]+:\/\/|\/)(.*)', first) +- prot2 = re.match('(^[a-z]+:\/\/|\/)(.*)', second) +- +- if not prot1 or not prot2: +- return second +- +- # Different protocols +- if prot1.group(1) != prot2.group(1): +- return second +- +- # Split on backslash +- path1 = self.path_split(prot1.group(2)) +- path2 = self.path_split(prot2.group(2)) +- +- # Remove as long as common +- while path1 and path2 and path1[0] == path2[0]: +- path1.pop(0) +- path2.pop(0) +- +- # If we need to ../ more than 3 times, then just return +- # the absolute path +- if len(path1) - 1 > 3: +- return second +- +- if mime.startswith('x-directory'): +- # directory, special case +- if not path2: +- result = './' +- else: +- result = '../' * (len(path1) - 1) +- else: +- # Insert ../ +- result = '../' * (len(path1) - 1) +- +- if not path2: +- result = os.path.basename(second) +- +- if path2: +- result += os.path.join(*path2) +- +- return result +- +- def apply_uri_snippet(self, snippet, mime, uri): +- # Remove file scheme +- gfile = Gio.file_new_for_uri(uri) +- pathname = '' +- dirname = '' +- ruri = '' +- +- if Pluma.utils_uri_has_file_scheme(uri): +- pathname = gfile.get_path() +- dirname = gfile.get_parent().get_path() +- +- name = os.path.basename(uri) +- scheme = gfile.get_uri_scheme() +- +- os.environ['PLUMA_DROP_DOCUMENT_URI'] = uri +- os.environ['PLUMA_DROP_DOCUMENT_NAME'] = name +- os.environ['PLUMA_DROP_DOCUMENT_SCHEME'] = scheme +- os.environ['PLUMA_DROP_DOCUMENT_PATH'] = pathname +- os.environ['PLUMA_DROP_DOCUMENT_DIR'] = dirname +- os.environ['PLUMA_DROP_DOCUMENT_TYPE'] = mime +- +- buf = self.view.get_buffer() +- location = buf.get_location() +- if location: +- ruri = location.get_uri() +- +- relpath = self.relative_path(ruri, uri, mime) +- +- os.environ['PLUMA_DROP_DOCUMENT_RELATIVE_PATH'] = relpath +- +- mark = buf.get_mark('gtk_drag_target') +- +- if not mark: +- mark = buf.get_insert() +- +- piter = buf.get_iter_at_mark(mark) +- self.apply_snippet(snippet, piter, piter) +- +- def in_bounds(self, x, y): +- rect = self.view.get_visible_rect() +- rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, rect.x, rect.y) +- +- return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height) +- +- def on_drag_data_received(self, view, context, x, y, data, info, timestamp): +- uris = drop_get_uris(data) +- if not uris: +- return +- +- if not self.in_bounds(x, y): +- return +- +- uris.reverse() +- stop = False +- +- for uri in uris: +- try: +- mime = Gio.content_type_guess(uri) +- except: +- mime = None +- +- if not mime: +- continue +- +- snippets = Library().from_drop_target(mime, self.language_id) +- +- if snippets: +- stop = True +- self.apply_uri_snippet(snippets[0], mime, uri) +- +- if stop: +- context.finish(True, False, timestamp) +- view.stop_emission('drag-data-received') +- view.get_toplevel().present() +- view.grab_focus() +- +- def find_uri_target(self, context): +- lst = Gtk.target_list_add_uri_targets((), 0) +- +- return self.view.drag_dest_find_target(context, lst) +- +- def on_proposal_activated(self, proposal, piter): +- buf = self.view.get_buffer() +- bounds = buf.get_selection_bounds() +- +- if bounds: +- self.apply_snippet(proposal.snippet(), None, None) +- else: +- (word, start, end) = self.get_tab_tag(buf, piter) +- self.apply_snippet(proposal.snippet(), start, end) ++ if not word or word == '': ++ if start.backward_char(): ++ word = start.get_char() ++ ++ if word.isalnum() or word.isspace(): ++ return (None, None, None) ++ else: ++ return (None, None, None) ++ ++ return (word, start, end) ++ ++ def parse_and_run_snippet(self, data, iter): ++ self.apply_snippet(DynamicSnippet(data), iter, iter) ++ ++ def run_snippet_trigger(self, trigger, bounds): ++ if not self.view: ++ return False ++ ++ snippets = Library().from_tag(trigger, self.language_id) ++ buf = self.view.get_buffer() ++ ++ if snippets: ++ if len(snippets) == 1: ++ return self.apply_snippet(snippets[0], bounds[0], bounds[1]) ++ else: ++ # Do the fancy completion dialog ++ provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) ++ provider.set_proposals(snippets) ++ ++ cm = self.view.get_completion() ++ cm.show([provider], cm.create_context(None)) + + return True +- +- def on_default_activated(self, proposal, piter): +- buf = self.view.get_buffer() +- bounds = buf.get_selection_bounds() +- +- if bounds: +- buf.begin_user_action() +- buf.delete(bounds[0], bounds[1]) +- buf.insert(bounds[0], proposal.props.label) +- buf.end_user_action() +- +- return True ++ ++ return False ++ ++ def run_snippet(self): ++ if not self.view: ++ return False ++ ++ buf = self.view.get_buffer() ++ ++ # get the word preceding the current insertion position ++ (word, start, end) = self.get_tab_tag(buf) ++ ++ if not word: ++ return self.skip_to_next_placeholder() ++ ++ if not self.run_snippet_trigger(word, (start, end)): ++ return self.skip_to_next_placeholder() ++ else: ++ return True ++ ++ def deactivate_snippet(self, snippet, force = False): ++ buf = self.view.get_buffer() ++ remove = [] ++ ordered_remove = [] ++ ++ for tabstop in snippet.placeholders: ++ if tabstop == -1: ++ placeholders = snippet.placeholders[-1] ++ else: ++ placeholders = [snippet.placeholders[tabstop]] ++ ++ for placeholder in placeholders: ++ if placeholder in self.placeholders: ++ if placeholder in self.update_placeholders: ++ placeholder.update_contents() ++ ++ self.update_placeholders.remove(placeholder) ++ elif placeholder in self.jump_placeholders: ++ placeholder[0].leave() ++ ++ remove.append(placeholder) ++ elif placeholder in self.ordered_placeholders: ++ ordered_remove.append(placeholder) ++ ++ for placeholder in remove: ++ if placeholder == self.active_placeholder: ++ self.active_placeholder = None ++ ++ self.placeholders.remove(placeholder) ++ self.ordered_placeholders.remove(placeholder) ++ ++ placeholder.remove(force) ++ ++ for placeholder in ordered_remove: ++ self.ordered_placeholders.remove(placeholder) ++ placeholder.remove(force) ++ ++ snippet.deactivate() ++ self.active_snippets.remove(snippet) ++ ++ if len(self.active_snippets) == 0: ++ self.last_snippet_removed() ++ ++ self.view.queue_draw() ++ ++ def update_snippet_contents(self): ++ self.timeout_update_id = 0 ++ ++ for placeholder in self.update_placeholders: ++ placeholder.update_contents() ++ ++ for placeholder in self.jump_placeholders: ++ self.goto_placeholder(placeholder[0], placeholder[1]) ++ ++ del self.update_placeholders[:] ++ del self.jump_placeholders[:] ++ ++ return False ++ ++ # Callbacks ++ def on_view_destroy(self, view): ++ self.stop() ++ return ++ ++ def on_buffer_cursor_moved(self, buf): ++ piter = buf.get_iter_at_mark(buf.get_insert()) ++ ++ # Check for all snippets if the cursor is outside its scope ++ for snippet in list(self.active_snippets): ++ if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted(): ++ self.deactivate(snippet) ++ else: ++ begin = snippet.begin_iter() ++ end = snippet.end_iter() ++ ++ if piter.compare(begin) < 0 or piter.compare(end) > 0: ++ # Oh no! Remove the snippet this instant!! ++ self.deactivate_snippet(snippet) ++ ++ current = self.current_placeholder() ++ ++ if current != self.active_placeholder: ++ self.jump_placeholders.append((self.active_placeholder, current)) ++ ++ if self.timeout_update_id == 0: ++ self.timeout_update_id = GLib.timeout_add(0, ++ self.update_snippet_contents) ++ ++ def on_buffer_changed(self, buf): ++ for snippet in list(self.active_snippets): ++ begin = snippet.begin_iter() ++ end = snippet.end_iter() ++ ++ if begin.compare(end) >= 0: ++ # Begin collapsed on end, just remove it ++ self.deactivate_snippet(snippet) ++ ++ current = self.current_placeholder() ++ ++ if current: ++ if not current in self.update_placeholders: ++ self.update_placeholders.append(current) ++ ++ if self.timeout_update_id == 0: ++ self.timeout_update_id = GLib.timeout_add(0, \ ++ self.update_snippet_contents) ++ ++ def on_buffer_insert_text(self, buf, piter, text, length): ++ ctx = get_buffer_context(buf) ++ ++ # do nothing special if there is no context and no active ++ # placeholder ++ if (not ctx) and (not self.active_placeholder): ++ return ++ ++ if not ctx: ++ ctx = self.active_placeholder ++ ++ if not ctx in self.ordered_placeholders: ++ return ++ ++ # move any marks that were incorrectly moved by this insertion ++ # back to where they belong ++ begin = ctx.begin_iter() ++ end = ctx.end_iter() ++ idx = self.ordered_placeholders.index(ctx) ++ ++ for placeholder in self.ordered_placeholders: ++ if placeholder == ctx: ++ continue ++ ++ ob = placeholder.begin_iter() ++ oe = placeholder.end_iter() ++ ++ if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0): ++ oidx = self.ordered_placeholders.index(placeholder) ++ ++ if oidx > idx and ob: ++ buf.move_mark(placeholder.begin, end) ++ elif oidx < idx and oe: ++ buf.move_mark(placeholder.end, begin) ++ elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0): ++ buf.move_mark(placeholder.begin, end) ++ elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0: ++ buf.move_mark(placeholder.end, begin) ++ ++ def on_notify_language(self, buf, spec): ++ self.update_language() ++ ++ def on_notify_editable(self, view, spec): ++ self._set_view(view) ++ ++ def on_view_key_press(self, view, event): ++ library = Library() ++ ++ state = event.get_state() ++ ++ if not (state & Gdk.ModifierType.CONTROL_MASK) and \ ++ not (state & Gdk.ModifierType.MOD1_MASK) and \ ++ event.keyval in self.TAB_KEY_VAL: ++ if not state & Gdk.ModifierType.SHIFT_MASK: ++ return self.run_snippet() ++ else: ++ return self.skip_to_previous_placeholder() ++ elif not library.loaded and \ ++ library.valid_accelerator(event.keyval, state): ++ library.ensure_files() ++ library.ensure(self.language_id) ++ self.accelerator_activate(event.keyval, \ ++ state & Gtk.accelerator_get_default_mod_mask()) ++ ++ return False ++ ++ def path_split(self, path, components=[]): ++ head, tail = os.path.split(path) ++ ++ if not tail and head: ++ return [head] + components ++ elif tail: ++ return self.path_split(head, [tail] + components) ++ else: ++ return components ++ ++ def relative_path(self, first, second, mime): ++ prot1 = re.match('(^[a-z]+:\/\/|\/)(.*)', first) ++ prot2 = re.match('(^[a-z]+:\/\/|\/)(.*)', second) ++ ++ if not prot1 or not prot2: ++ return second ++ ++ # Different protocols ++ if prot1.group(1) != prot2.group(1): ++ return second ++ ++ # Split on backslash ++ path1 = self.path_split(prot1.group(2)) ++ path2 = self.path_split(prot2.group(2)) ++ ++ # Remove as long as common ++ while path1 and path2 and path1[0] == path2[0]: ++ path1.pop(0) ++ path2.pop(0) ++ ++ # If we need to ../ more than 3 times, then just return ++ # the absolute path ++ if len(path1) - 1 > 3: ++ return second ++ ++ if mime.startswith('x-directory'): ++ # directory, special case ++ if not path2: ++ result = './' ++ else: ++ result = '../' * (len(path1) - 1) ++ else: ++ # Insert ../ ++ result = '../' * (len(path1) - 1) ++ ++ if not path2: ++ result = os.path.basename(second) ++ ++ if path2: ++ result += os.path.join(*path2) ++ ++ return result ++ ++ def apply_uri_snippet(self, snippet, mime, uri): ++ # Remove file scheme ++ gfile = Gio.file_new_for_uri(uri) ++ pathname = '' ++ dirname = '' ++ ruri = '' ++ ++ if Pluma.utils_uri_has_file_scheme(uri): ++ pathname = gfile.get_path() ++ dirname = gfile.get_parent().get_path() ++ ++ name = os.path.basename(uri) ++ scheme = gfile.get_uri_scheme() ++ ++ os.environ['PLUMA_DROP_DOCUMENT_URI'] = uri ++ os.environ['PLUMA_DROP_DOCUMENT_NAME'] = name ++ os.environ['PLUMA_DROP_DOCUMENT_SCHEME'] = scheme ++ os.environ['PLUMA_DROP_DOCUMENT_PATH'] = pathname ++ os.environ['PLUMA_DROP_DOCUMENT_DIR'] = dirname ++ os.environ['PLUMA_DROP_DOCUMENT_TYPE'] = mime ++ ++ buf = self.view.get_buffer() ++ location = buf.get_location() ++ if location: ++ ruri = location.get_uri() ++ ++ relpath = self.relative_path(ruri, uri, mime) ++ ++ os.environ['PLUMA_DROP_DOCUMENT_RELATIVE_PATH'] = relpath ++ ++ mark = buf.get_mark('gtk_drag_target') ++ ++ if not mark: ++ mark = buf.get_insert() ++ ++ piter = buf.get_iter_at_mark(mark) ++ self.apply_snippet(snippet, piter, piter) ++ ++ def in_bounds(self, x, y): ++ rect = self.view.get_visible_rect() ++ rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, rect.x, rect.y) ++ ++ return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height) ++ ++ def on_drag_data_received(self, view, context, x, y, data, info, timestamp): ++ uris = drop_get_uris(data) ++ if not uris: ++ return ++ ++ if not self.in_bounds(x, y): ++ return ++ ++ uris.reverse() ++ stop = False ++ ++ for uri in uris: ++ try: ++ mime = Gio.content_type_guess(uri) ++ except: ++ mime = None ++ ++ if not mime: ++ continue ++ ++ snippets = Library().from_drop_target(mime, self.language_id) ++ ++ if snippets: ++ stop = True ++ self.apply_uri_snippet(snippets[0], mime, uri) ++ ++ if stop: ++ context.finish(True, False, timestamp) ++ view.stop_emission('drag-data-received') ++ view.get_toplevel().present() ++ view.grab_focus() ++ ++ def find_uri_target(self, context): ++ lst = Gtk.target_list_add_uri_targets((), 0) ++ ++ return self.view.drag_dest_find_target(context, lst) ++ ++ def on_proposal_activated(self, proposal, piter): ++ buf = self.view.get_buffer() ++ bounds = buf.get_selection_bounds() ++ ++ if bounds: ++ self.apply_snippet(proposal.snippet(), None, None) ++ else: ++ (word, start, end) = self.get_tab_tag(buf, piter) ++ self.apply_snippet(proposal.snippet(), start, end) ++ ++ return True ++ ++ def on_default_activated(self, proposal, piter): ++ buf = self.view.get_buffer() ++ bounds = buf.get_selection_bounds() ++ ++ if bounds: ++ buf.begin_user_action() ++ buf.delete(bounds[0], bounds[1]) ++ buf.insert(bounds[0], proposal.props.label) ++ buf.end_user_action() ++ ++ return True ++ else: ++ return False ++ ++ def iter_coords(self, piter): ++ rect = self.view.get_iter_location(piter) ++ rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.TEXT, rect.x, rect.y) ++ ++ return rect ++ ++ def placeholder_in_area(self, placeholder, area): ++ start = placeholder.begin_iter() ++ end = placeholder.end_iter() ++ ++ if not start or not end: ++ return False ++ ++ # Test if start is before bottom, and end is after top ++ start_rect = self.iter_coords(start) ++ end_rect = self.iter_coords(end) ++ ++ return start_rect.y <= area.y + area.height and \ ++ end_rect.y + end_rect.height >= area.y ++ ++ def draw_placeholder_rect(self, ctx, placeholder): ++ start = placeholder.begin_iter() ++ start_rect = self.iter_coords(start) ++ start_line = start.get_line() ++ ++ end = placeholder.end_iter() ++ end_rect = self.iter_coords(end) ++ end_line = end.get_line() ++ ++ line = start.copy() ++ line.set_line_offset(0) ++ geom = self.view.get_window(Gtk.TextWindowType.TEXT).get_geometry() ++ ++ ctx.translate(0.5, 0.5) ++ ++ while line.get_line() <= end_line: ++ ypos, height = self.view.get_line_yrange(line) ++ x_, ypos = self.view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, 0, ypos) ++ ++ if line.get_line() == start_line and line.get_line() == end_line: ++ # Simply draw a box, both are on the same line ++ ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1) ++ ctx.stroke() ++ elif line.get_line() == start_line or line.get_line() == end_line: ++ if line.get_line() == start_line: ++ rect = start_rect + else: +- return False +- +- def iter_coords(self, piter): +- rect = self.view.get_iter_location(piter) +- rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.TEXT, rect.x, rect.y) +- +- return rect +- +- def placeholder_in_area(self, placeholder, area): +- start = placeholder.begin_iter() +- end = placeholder.end_iter() +- +- if not start or not end: +- return False +- +- # Test if start is before bottom, and end is after top +- start_rect = self.iter_coords(start) +- end_rect = self.iter_coords(end) +- +- return start_rect.y <= area.y + area.height and \ +- end_rect.y + end_rect.height >= area.y +- +- def draw_placeholder_rect(self, ctx, placeholder): +- start = placeholder.begin_iter() +- start_rect = self.iter_coords(start) +- start_line = start.get_line() +- +- end = placeholder.end_iter() +- end_rect = self.iter_coords(end) +- end_line = end.get_line() +- +- line = start.copy() +- line.set_line_offset(0) +- geom = self.view.get_window(Gtk.TextWindowType.TEXT).get_geometry() +- +- ctx.translate(0.5, 0.5) +- +- while line.get_line() <= end_line: +- ypos, height = self.view.get_line_yrange(line) +- x_, ypos = self.view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, 0, ypos) +- +- if line.get_line() == start_line and line.get_line() == end_line: +- # Simply draw a box, both are on the same line +- ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1) +- ctx.stroke() +- elif line.get_line() == start_line or line.get_line() == end_line: +- if line.get_line() == start_line: +- rect = start_rect +- else: +- rect = end_rect +- +- ctx.move_to(0, rect.y + rect.height - 1) +- ctx.rel_line_to(rect.x, 0) +- ctx.rel_line_to(0, -rect.height + 1) +- ctx.rel_line_to(geom[2], 0) +- ctx.stroke() +- +- if not line.forward_line(): +- break +- +- def draw_placeholder_bar(self, ctx, placeholder): +- start = placeholder.begin_iter() +- start_rect = self.iter_coords(start) +- +- ctx.translate(0.5, 0.5) +- extend_width = 2.5 +- +- ctx.move_to(start_rect.x - extend_width, start_rect.y) +- ctx.rel_line_to(extend_width * 2, 0) +- +- ctx.move_to(start_rect.x, start_rect.y) +- ctx.rel_line_to(0, start_rect.height - 1) +- +- ctx.rel_move_to(-extend_width, 0) +- ctx.rel_line_to(extend_width * 2, 0) ++ rect = end_rect ++ ++ ctx.move_to(0, rect.y + rect.height - 1) ++ ctx.rel_line_to(rect.x, 0) ++ ctx.rel_line_to(0, -rect.height + 1) ++ ctx.rel_line_to(geom[2], 0) + ctx.stroke() + +- def draw_placeholder(self, ctx, placeholder): +- if isinstance(placeholder, PlaceholderEnd): +- return ++ if not line.forward_line(): ++ break + +- buf = self.view.get_buffer() ++ def draw_placeholder_bar(self, ctx, placeholder): ++ start = placeholder.begin_iter() ++ start_rect = self.iter_coords(start) + +- col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE) +- col.alpha = 0.5 +- Gdk.cairo_set_source_rgba(ctx, col) +- +- if placeholder.tabstop > 0: +- ctx.set_dash([], 0) +- else: +- ctx.set_dash([2], 0) ++ ctx.translate(0.5, 0.5) ++ extend_width = 2.5 + +- start = placeholder.begin_iter() +- end = placeholder.end_iter() ++ ctx.move_to(start_rect.x - extend_width, start_rect.y) ++ ctx.rel_line_to(extend_width * 2, 0) + +- if start.equal(end): +- self.draw_placeholder_bar(ctx, placeholder) +- else: +- self.draw_placeholder_rect(ctx, placeholder) ++ ctx.move_to(start_rect.x, start_rect.y) ++ ctx.rel_line_to(0, start_rect.height - 1) ++ ++ ctx.rel_move_to(-extend_width, 0) ++ ctx.rel_line_to(extend_width * 2, 0) ++ ctx.stroke() ++ ++ def draw_placeholder(self, ctx, placeholder): ++ if isinstance(placeholder, PlaceholderEnd): ++ return ++ ++ buf = self.view.get_buffer() ++ ++ col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE) ++ col.alpha = 0.5 ++ Gdk.cairo_set_source_rgba(ctx, col) ++ ++ if placeholder.tabstop > 0: ++ ctx.set_dash([], 0) ++ else: ++ ctx.set_dash([2], 0) ++ ++ start = placeholder.begin_iter() ++ end = placeholder.end_iter() ++ ++ if start.equal(end): ++ self.draw_placeholder_bar(ctx, placeholder) ++ else: ++ self.draw_placeholder_rect(ctx, placeholder) + +- def on_draw(self, view, ctx): +- window = view.get_window(Gtk.TextWindowType.TEXT) ++ def on_draw(self, view, ctx): ++ window = view.get_window(Gtk.TextWindowType.TEXT) + +- if not Gtk.cairo_should_draw_window(ctx, window): +- return False ++ if not Gtk.cairo_should_draw_window(ctx, window): ++ return False + +- ctx.set_line_width(1.0) +- Gtk.cairo_transform_to_window(ctx, view, window) +- clipped, clip = Gdk.cairo_get_clip_rectangle(ctx) ++ ctx.set_line_width(1.0) ++ Gtk.cairo_transform_to_window(ctx, view, window) ++ clipped, clip = Gdk.cairo_get_clip_rectangle(ctx) + +- if not clipped: +- return False ++ if not clipped: ++ return False + +- for placeholder in self.ordered_placeholders: +- if not self.placeholder_in_area(placeholder, clip): +- continue ++ for placeholder in self.ordered_placeholders: ++ if not self.placeholder_in_area(placeholder, clip): ++ continue + +- ctx.save() +- self.draw_placeholder(ctx, placeholder) +- ctx.restore() ++ ctx.save() ++ self.draw_placeholder(ctx, placeholder) ++ ctx.restore() + +- return False ++ return False + +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Exporter.py b/plugins/snippets/snippets/Exporter.py +old mode 100755 +new mode 100644 +index 18369a6..850c3a4 +--- a/plugins/snippets/snippets/Exporter.py ++++ b/plugins/snippets/snippets/Exporter.py +@@ -8,91 +8,91 @@ import xml.etree.ElementTree as et + from Helper import * + + class Exporter: +- def __init__(self, filename, snippets): +- self.filename = filename +- self.set_snippets(snippets) +- +- def set_snippets(self, snippets): +- self.snippets = {} +- +- for snippet in snippets: +- lang = snippet.language() +- +- if lang in self.snippets: +- self.snippets[lang].append(snippet) +- else: +- self.snippets[lang] = [snippet] +- +- def export_xml(self, dirname, language, snippets): +- # Create the root snippets node +- root = et.Element('snippets') +- +- # Create filename based on language +- if language: +- filename = os.path.join(dirname, language + '.xml') +- +- # Set the language attribute +- root.attrib['language'] = language +- else: +- filename = os.path.join(dirname, 'global.xml') +- +- # Add all snippets to the root node +- for snippet in snippets: +- root.append(snippet.to_xml()) +- +- # Write xml +- write_xml(root, filename, ('text', 'accelerator')) +- +- def export_archive(self, cmd): +- dirname = tempfile.mkdtemp() +- +- # Save current working directory and change to temporary directory +- curdir = os.getcwd() +- +- try: +- os.chdir(dirname) +- +- # Write snippet xml files +- for language, snippets in self.snippets.items(): +- self.export_xml(dirname, language , snippets) +- +- # Archive files +- status = os.system('%s "%s" *.xml' % (cmd, self.filename)) +- finally: +- os.chdir(curdir) +- +- if status != 0: +- return _('The archive "%s" could not be created' % self.filename) +- +- # Remove the temporary directory +- shutil.rmtree(dirname) +- +- def export_targz(self): +- self.export_archive('tar -c --gzip -f') +- +- def export_tarbz2(self): +- self.export_archive('tar -c --bzip2 -f') +- +- def export_tar(self): +- self.export_archive('tar -cf') +- +- def run(self): +- dirname = os.path.dirname(self.filename) +- if not os.path.exists(dirname): +- return _('Target directory "%s" does not exist') % dirname +- +- if not os.path.isdir(dirname): +- return _('Target directory "%s" is not a valid directory') % dirname +- +- (root, ext) = os.path.splitext(self.filename) +- +- actions = {'.tar.gz': self.export_targz, +- '.tar.bz2': self.export_tarbz2, +- '.tar': self.export_tar} +- +- for k, v in actions.items(): +- if self.filename.endswith(k): +- return v() +- +- return self.export_targz() +-# ex:ts=8:et: ++ def __init__(self, filename, snippets): ++ self.filename = filename ++ self.set_snippets(snippets) ++ ++ def set_snippets(self, snippets): ++ self.snippets = {} ++ ++ for snippet in snippets: ++ lang = snippet.language() ++ ++ if lang in self.snippets: ++ self.snippets[lang].append(snippet) ++ else: ++ self.snippets[lang] = [snippet] ++ ++ def export_xml(self, dirname, language, snippets): ++ # Create the root snippets node ++ root = et.Element('snippets') ++ ++ # Create filename based on language ++ if language: ++ filename = os.path.join(dirname, language + '.xml') ++ ++ # Set the language attribute ++ root.attrib['language'] = language ++ else: ++ filename = os.path.join(dirname, 'global.xml') ++ ++ # Add all snippets to the root node ++ for snippet in snippets: ++ root.append(snippet.to_xml()) ++ ++ # Write xml ++ write_xml(root, filename, ('text', 'accelerator')) ++ ++ def export_archive(self, cmd): ++ dirname = tempfile.mkdtemp() ++ ++ # Save current working directory and change to temporary directory ++ curdir = os.getcwd() ++ ++ try: ++ os.chdir(dirname) ++ ++ # Write snippet xml files ++ for language, snippets in self.snippets.items(): ++ self.export_xml(dirname, language , snippets) ++ ++ # Archive files ++ status = os.system('%s "%s" *.xml' % (cmd, self.filename)) ++ finally: ++ os.chdir(curdir) ++ ++ if status != 0: ++ return _('The archive "%s" could not be created' % self.filename) ++ ++ # Remove the temporary directory ++ shutil.rmtree(dirname) ++ ++ def export_targz(self): ++ self.export_archive('tar -c --gzip -f') ++ ++ def export_tarbz2(self): ++ self.export_archive('tar -c --bzip2 -f') ++ ++ def export_tar(self): ++ self.export_archive('tar -cf') ++ ++ def run(self): ++ dirname = os.path.dirname(self.filename) ++ if not os.path.exists(dirname): ++ return _('Target directory "%s" does not exist') % dirname ++ ++ if not os.path.isdir(dirname): ++ return _('Target directory "%s" is not a valid directory') % dirname ++ ++ (root, ext) = os.path.splitext(self.filename) ++ ++ actions = {'.tar.gz': self.export_targz, ++ '.tar.bz2': self.export_tarbz2, ++ '.tar': self.export_tar} ++ ++ for k, v in actions.items(): ++ if self.filename.endswith(k): ++ return v() ++ ++ return self.export_targz() ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Helper.py b/plugins/snippets/snippets/Helper.py +old mode 100755 +new mode 100644 +index d8a1967..6d440d0 +--- a/plugins/snippets/snippets/Helper.py ++++ b/plugins/snippets/snippets/Helper.py +@@ -23,164 +23,164 @@ import re + from gi.repository import Gtk + + def message_dialog(par, typ, msg): +- d = Gtk.MessageDialog(par, Gtk.DialogFlags.MODAL, typ, Gtk.ButtonsType.OK, msg) +- d.set_property('use-markup', True) ++ d = Gtk.MessageDialog(par, Gtk.DialogFlags.MODAL, typ, Gtk.ButtonsType.OK, msg) ++ d.set_property('use-markup', True) + +- d.run() +- d.destroy() ++ d.run() ++ d.destroy() + + def compute_indentation(view, piter): +- line = piter.get_line() +- start = view.get_buffer().get_iter_at_line(line) +- end = start.copy() +- ++ line = piter.get_line() ++ start = view.get_buffer().get_iter_at_line(line) ++ end = start.copy() ++ ++ ch = end.get_char() ++ ++ while (ch.isspace() and ch != '\r' and ch != '\n' and \ ++ end.compare(piter) < 0): ++ if not end.forward_char(): ++ break; ++ + ch = end.get_char() +- +- while (ch.isspace() and ch != '\r' and ch != '\n' and \ +- end.compare(piter) < 0): +- if not end.forward_char(): +- break; +- +- ch = end.get_char() +- +- if start.equal(end): +- return '' +- +- return start.get_slice(end) ++ ++ if start.equal(end): ++ return '' ++ ++ return start.get_slice(end) + + def markup_escape(text): +- return saxutils.escape(text) ++ return saxutils.escape(text) + + def spaces_instead_of_tabs(view, text): +- if not view.get_insert_spaces_instead_of_tabs(): +- return text ++ if not view.get_insert_spaces_instead_of_tabs(): ++ return text + +- return text.replace("\t", view.get_tab_width() * ' ') ++ return text.replace("\t", view.get_tab_width() * ' ') + + def insert_with_indent(view, piter, text, indentfirst = True, context = None): +- text = spaces_instead_of_tabs(view, text) +- lines = text.split('\n') +- buf = view.get_buffer() ++ text = spaces_instead_of_tabs(view, text) ++ lines = text.split('\n') ++ buf = view.get_buffer() ++ ++ buf._snippets_context = context + +- buf._snippets_context = context ++ if len(lines) == 1: ++ view.get_buffer().insert(piter, text) ++ else: ++ # Compute indentation ++ indent = compute_indentation(view, piter) ++ text = '' + +- if len(lines) == 1: +- view.get_buffer().insert(piter, text) +- else: +- # Compute indentation +- indent = compute_indentation(view, piter) +- text = '' ++ for i in range(0, len(lines)): ++ if indentfirst or i > 0: ++ text += indent + lines[i] + '\n' ++ else: ++ text += lines[i] + '\n' + +- for i in range(0, len(lines)): +- if indentfirst or i > 0: +- text += indent + lines[i] + '\n' +- else: +- text += lines[i] + '\n' +- +- buf.insert(piter, text[:-1]) ++ buf.insert(piter, text[:-1]) + +- buf._snippets_context = None ++ buf._snippets_context = None + + def get_buffer_context(buf): +- if hasattr(buf, "_snippets_context"): +- return buf._snippets_context +- return None ++ if hasattr(buf, "_snippets_context"): ++ return buf._snippets_context ++ return None + + def snippets_debug(*s): +- return ++ return + + def write_xml(node, f, cdata_nodes=()): +- assert node is not None ++ assert node is not None + +- if not hasattr(f, "write"): +- f = open(f, "wb") ++ if not hasattr(f, "write"): ++ f = open(f, "wb") + +- # Encoding +- f.write("\n") ++ # Encoding ++ f.write("\n") + +- _write_node(node, f, cdata_nodes) ++ _write_node(node, f, cdata_nodes) + + def _write_indent(file, text, indent): +- file.write(' ' * indent + text) ++ file.write(' ' * indent + text) + + def _write_node(node, file, cdata_nodes=(), indent=0): +- # write XML to file +- tag = node.tag +- +- if node is Comment: +- _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) +- elif node is ProcessingInstruction: +- _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) +- else: +- items = node.items() +- +- if items or node.text or len(node): +- _write_indent(file, "<" + tag.encode('utf-8'), indent) +- +- if items: +- items.sort() # lexical order +- for k, v in items: +- file.write(" %s=%s" % (k.encode('utf-8'), saxutils.quoteattr(v.encode('utf-8')))) +- if node.text or len(node): +- file.write(">") +- if node.text and node.text.strip() != "": +- if tag in cdata_nodes: +- file.write(_cdata(node.text)) +- else: +- file.write(saxutils.escape(node.text.encode('utf-8'))) +- else: +- file.write("\n") +- +- for n in node: +- _write_node(n, file, cdata_nodes, indent + 1) +- +- if not len(node): +- file.write("\n") +- else: +- _write_indent(file, "\n", \ +- indent) +- else: +- file.write(" />\n") +- +- if node.tail and node.tail.strip() != "": +- file.write(saxutils.escape(node.tail.encode('utf-8'))) ++ # write XML to file ++ tag = node.tag ++ ++ if node is Comment: ++ _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) ++ elif node is ProcessingInstruction: ++ _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) ++ else: ++ items = node.items() ++ ++ if items or node.text or len(node): ++ _write_indent(file, "<" + tag.encode('utf-8'), indent) ++ ++ if items: ++ items.sort() # lexical order ++ for k, v in items: ++ file.write(" %s=%s" % (k.encode('utf-8'), saxutils.quoteattr(v.encode('utf-8')))) ++ if node.text or len(node): ++ file.write(">") ++ if node.text and node.text.strip() != "": ++ if tag in cdata_nodes: ++ file.write(_cdata(node.text)) ++ else: ++ file.write(saxutils.escape(node.text.encode('utf-8'))) ++ else: ++ file.write("\n") ++ ++ for n in node: ++ _write_node(n, file, cdata_nodes, indent + 1) ++ ++ if not len(node): ++ file.write("\n") ++ else: ++ _write_indent(file, "\n", \ ++ indent) ++ else: ++ file.write(" />\n") ++ ++ if node.tail and node.tail.strip() != "": ++ file.write(saxutils.escape(node.tail.encode('utf-8'))) + + def _cdata(text, replace=string.replace): +- text = text.encode('utf-8') +- return '', ']]]]>') + ']]>' ++ text = text.encode('utf-8') ++ return '', ']]]]>') + ']]>' + + def buffer_word_boundary(buf): +- iter = buf.get_iter_at_mark(buf.get_insert()) +- start = iter.copy() +- +- if not iter.starts_word() and (iter.inside_word() or iter.ends_word()): +- start.backward_word_start() +- +- if not iter.ends_word() and iter.inside_word(): +- iter.forward_word_end() +- +- return (start, iter) ++ iter = buf.get_iter_at_mark(buf.get_insert()) ++ start = iter.copy() ++ ++ if not iter.starts_word() and (iter.inside_word() or iter.ends_word()): ++ start.backward_word_start() ++ ++ if not iter.ends_word() and iter.inside_word(): ++ iter.forward_word_end() ++ ++ return (start, iter) + + def buffer_line_boundary(buf): +- iter = buf.get_iter_at_mark(buf.get_insert()) +- start = iter.copy() +- start.set_line_offset(0) +- +- if not iter.ends_line(): +- iter.forward_to_line_end() +- +- return (start, iter) ++ iter = buf.get_iter_at_mark(buf.get_insert()) ++ start = iter.copy() ++ start.set_line_offset(0) ++ ++ if not iter.ends_line(): ++ iter.forward_to_line_end() ++ ++ return (start, iter) + + def drop_get_uris(selection): +- uris = [] +- if selection.targets_include_uri(): +- data = selection.get_data() +- lines = re.split('\\s*[\\n\\r]+\\s*', data.strip()) ++ uris = [] ++ if selection.targets_include_uri(): ++ data = selection.get_data() ++ lines = re.split('\\s*[\\n\\r]+\\s*', data.strip()) + +- for line in lines: +- if not line.startswith('#'): +- uris.append(line) ++ for line in lines: ++ if not line.startswith('#'): ++ uris.append(line) + +- return uris ++ return uris + +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Importer.py b/plugins/snippets/snippets/Importer.py +old mode 100755 +new mode 100644 +index b2e8723..c1d211e +--- a/plugins/snippets/snippets/Importer.py ++++ b/plugins/snippets/snippets/Importer.py +@@ -6,95 +6,95 @@ import shutil + from Library import * + + class Importer: +- def __init__(self, filename): +- self.filename = filename +- +- def import_destination(self, filename): +- userdir = Library().userdir +- +- filename = os.path.basename(filename) +- (root, ext) = os.path.splitext(filename) +- +- filename = os.path.join(userdir, root + ext) +- i = 1 +- +- while os.path.exists(filename): +- filename = os.path.join(userdir, root + '_' + str(i) + ext) +- i += 1 +- +- return filename +- +- def import_file(self, filename): +- if not os.path.exists(filename): +- return _('File "%s" does not exist') % filename +- +- if not os.path.isfile(filename): +- return _('File "%s" is not a valid snippets file') % filename +- +- # Find destination for file to copy to +- dest = self.import_destination(filename) +- +- # Copy file +- shutil.copy(filename, dest) +- +- # Add library +- if not Library().add_user_library(dest): +- return _('Imported file "%s" is not a valid snippets file') % os.path.basename(dest) +- +- def import_xml(self): +- return self.import_file(self.filename) +- +- def import_archive(self, cmd): +- dirname = tempfile.mkdtemp() +- status = os.system('cd %s; %s "%s"' % (dirname, cmd, self.filename)) +- +- if status != 0: +- return _('The archive "%s" could not be extracted' % self.filename) +- +- errors = [] +- +- # Now import all the files from the archive +- for f in os.listdir(dirname): +- f = os.path.join(dirname, f) +- +- if os.path.isfile(f): +- if self.import_file(f): +- errors.append(os.path.basename(f)) +- else: +- sys.stderr.write('Skipping %s, not a valid snippets file' % os.path.basename(f)) +- +- # Remove the temporary directory +- shutil.rmtree(dirname) +- +- if len(errors) > 0: +- return _('The following files could not be imported: %s') % ', '.join(errors) +- +- def import_targz(self): +- self.import_archive('tar -x --gzip -f') +- +- def import_tarbz2(self): +- self.import_archive('tar -x --bzip2 -f') +- +- def import_tar(self): +- self.import_archive('tar -xf') +- +- def run(self): +- if not os.path.exists(self.filename): +- return _('File "%s" does not exist') % self.filename +- +- if not os.path.isfile(self.filename): +- return _('File "%s" is not a valid snippets archive') % self.filename +- +- (root, ext) = os.path.splitext(self.filename) +- +- actions = {'.tar.gz': self.import_targz, +- '.tar.bz2': self.import_tarbz2, +- '.xml': self.import_xml, +- '.tar': self.import_tar} +- +- for k, v in actions.items(): +- if self.filename.endswith(k): +- return v() +- +- return _('File "%s" is not a valid snippets archive') % self.filename +-# ex:ts=8:et: ++ def __init__(self, filename): ++ self.filename = filename ++ ++ def import_destination(self, filename): ++ userdir = Library().userdir ++ ++ filename = os.path.basename(filename) ++ (root, ext) = os.path.splitext(filename) ++ ++ filename = os.path.join(userdir, root + ext) ++ i = 1 ++ ++ while os.path.exists(filename): ++ filename = os.path.join(userdir, root + '_' + str(i) + ext) ++ i += 1 ++ ++ return filename ++ ++ def import_file(self, filename): ++ if not os.path.exists(filename): ++ return _('File "%s" does not exist') % filename ++ ++ if not os.path.isfile(filename): ++ return _('File "%s" is not a valid snippets file') % filename ++ ++ # Find destination for file to copy to ++ dest = self.import_destination(filename) ++ ++ # Copy file ++ shutil.copy(filename, dest) ++ ++ # Add library ++ if not Library().add_user_library(dest): ++ return _('Imported file "%s" is not a valid snippets file') % os.path.basename(dest) ++ ++ def import_xml(self): ++ return self.import_file(self.filename) ++ ++ def import_archive(self, cmd): ++ dirname = tempfile.mkdtemp() ++ status = os.system('cd %s; %s "%s"' % (dirname, cmd, self.filename)) ++ ++ if status != 0: ++ return _('The archive "%s" could not be extracted' % self.filename) ++ ++ errors = [] ++ ++ # Now import all the files from the archive ++ for f in os.listdir(dirname): ++ f = os.path.join(dirname, f) ++ ++ if os.path.isfile(f): ++ if self.import_file(f): ++ errors.append(os.path.basename(f)) ++ else: ++ sys.stderr.write('Skipping %s, not a valid snippets file' % os.path.basename(f)) ++ ++ # Remove the temporary directory ++ shutil.rmtree(dirname) ++ ++ if len(errors) > 0: ++ return _('The following files could not be imported: %s') % ', '.join(errors) ++ ++ def import_targz(self): ++ self.import_archive('tar -x --gzip -f') ++ ++ def import_tarbz2(self): ++ self.import_archive('tar -x --bzip2 -f') ++ ++ def import_tar(self): ++ self.import_archive('tar -xf') ++ ++ def run(self): ++ if not os.path.exists(self.filename): ++ return _('File "%s" does not exist') % self.filename ++ ++ if not os.path.isfile(self.filename): ++ return _('File "%s" is not a valid snippets archive') % self.filename ++ ++ (root, ext) = os.path.splitext(self.filename) ++ ++ actions = {'.tar.gz': self.import_targz, ++ '.tar.bz2': self.import_tarbz2, ++ '.xml': self.import_xml, ++ '.tar': self.import_tar} ++ ++ for k, v in actions.items(): ++ if self.filename.endswith(k): ++ return v() ++ ++ return _('File "%s" is not a valid snippets archive') % self.filename ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/LanguageManager.py b/plugins/snippets/snippets/LanguageManager.py +old mode 100755 +new mode 100644 +index 1fb4347..e738333 +--- a/plugins/snippets/snippets/LanguageManager.py ++++ b/plugins/snippets/snippets/LanguageManager.py +@@ -7,15 +7,16 @@ global manager + manager = None + + def get_language_manager(): +- global manager +- +- if not manager: +- dirs = [] +- +- for d in Library().systemdirs: +- dirs.append(os.path.join(d, 'lang')) +- +- manager = GtkSource.LanguageManager() +- manager.set_search_path(dirs + manager.get_search_path()) +- +- return manager ++ global manager ++ ++ if not manager: ++ dirs = [] ++ ++ for d in Library().systemdirs: ++ dirs.append(os.path.join(d, 'lang')) ++ ++ manager = GtkSource.LanguageManager() ++ manager.set_search_path(dirs + manager.get_search_path()) ++ ++ return manager ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Library.py b/plugins/snippets/snippets/Library.py +old mode 100755 +new mode 100644 +index 5b3773a..f152082 +--- a/plugins/snippets/snippets/Library.py ++++ b/plugins/snippets/snippets/Library.py +@@ -27,973 +27,973 @@ import xml.etree.ElementTree as et + from Helper import * + + class NamespacedId: +- def __init__(self, namespace, id): +- if not id: +- self.id = None +- else: +- if namespace: +- self.id = namespace + '-' +- else: +- self.id = 'global-' +- +- self.id += id ++ def __init__(self, namespace, id): ++ if not id: ++ self.id = None ++ else: ++ if namespace: ++ self.id = namespace + '-' ++ else: ++ self.id = 'global-' ++ ++ self.id += id + + class SnippetData: +- PROPS = {'tag': '', 'text': '', 'description': 'New snippet', +- 'accelerator': '', 'drop-targets': ''} ++ PROPS = {'tag': '', 'text': '', 'description': 'New snippet', ++ 'accelerator': '', 'drop-targets': ''} + +- def __init__(self, node, library): +- self.priv_id = node.attrib.get('id') ++ def __init__(self, node, library): ++ self.priv_id = node.attrib.get('id') + +- self.set_library(library) +- self.valid = False +- self.set_node(node) ++ self.set_library(library) ++ self.valid = False ++ self.set_node(node) + +- def can_modify(self): +- return (self.library and (isinstance(self.library(), SnippetsUserFile))) ++ def can_modify(self): ++ return (self.library and (isinstance(self.library(), SnippetsUserFile))) + +- def set_library(self, library): +- if library: +- self.library = weakref.ref(library) +- else: +- self.library = None ++ def set_library(self, library): ++ if library: ++ self.library = weakref.ref(library) ++ else: ++ self.library = None ++ ++ self.id = NamespacedId(self.language(), self.priv_id).id ++ ++ def set_node(self, node): ++ if self.can_modify(): ++ self.node = node ++ else: ++ self.node = None ++ ++ self.init_snippet_data(node) ++ ++ def init_snippet_data(self, node): ++ if node == None: ++ return ++ ++ self.override = node.attrib.get('override') + +- self.id = NamespacedId(self.language(), self.priv_id).id ++ self.properties = {} ++ props = SnippetData.PROPS.copy() ++ ++ # Store all properties present ++ for child in node: ++ if child.tag in props: ++ del props[child.tag] ++ ++ # Normalize accelerator ++ if child.tag == 'accelerator' and child.text != None: ++ keyval, mod = Gtk.accelerator_parse(child.text) ++ ++ if Gtk.accelerator_valid(keyval, mod): ++ child.text = Gtk.accelerator_name(keyval, mod) ++ else: ++ child.text = '' + +- def set_node(self, node): + if self.can_modify(): +- self.node = node +- else: +- self.node = None +- +- self.init_snippet_data(node) +- +- def init_snippet_data(self, node): +- if node == None: +- return +- +- self.override = node.attrib.get('override') +- +- self.properties = {} +- props = SnippetData.PROPS.copy() +- +- # Store all properties present +- for child in node: +- if child.tag in props: +- del props[child.tag] +- +- # Normalize accelerator +- if child.tag == 'accelerator' and child.text != None: +- keyval, mod = Gtk.accelerator_parse(child.text) +- +- if Gtk.accelerator_valid(keyval, mod): +- child.text = Gtk.accelerator_name(keyval, mod) +- else: +- child.text = '' +- +- if self.can_modify(): +- self.properties[child.tag] = child +- else: +- self.properties[child.tag] = child.text or '' +- +- # Create all the props that were not found so we stay consistent +- for prop in props: +- if self.can_modify(): +- child = et.SubElement(node, prop) +- +- child.text = props[prop] +- self.properties[prop] = child +- else: +- self.properties[prop] = props[prop] +- +- self.check_validation() +- +- def check_validation(self): +- if not self['tag'] and not self['accelerator'] and not self['drop-targets']: +- return False +- +- library = Library() +- keyval, mod = Gtk.accelerator_parse(self['accelerator']) +- +- self.valid = library.valid_tab_trigger(self['tag']) and \ +- (not self['accelerator'] or library.valid_accelerator(keyval, mod)) +- +- def _format_prop(self, prop, value): +- if prop == 'drop-targets' and value != '': +- return re.split('\\s*[,;]\\s*', value) ++ self.properties[child.tag] = child + else: +- return value +- +- def __getitem__(self, prop): +- if prop in self.properties: +- if self.can_modify(): +- return self._format_prop(prop, self.properties[prop].text or '') +- else: +- return self._format_prop(prop, self.properties[prop] or '') +- +- return self._format_prop(prop, '') +- +- def __setitem__(self, prop, value): +- if not prop in self.properties: +- return +- +- if isinstance(value, list): +- value = ','.join(value) +- +- if not self.can_modify() and self.properties[prop] != value: +- # ohoh, this is not can_modify, but it needs to be changed... +- # make sure it is transfered to the changes file and set all the +- # fields. +- # This snippet data container will effectively become the container +- # for the newly created node, but transparently to whoever uses +- # it +- self._override() +- +- if self.can_modify() and self.properties[prop].text != value: +- if self.library(): +- self.library().tainted = True +- +- oldvalue = self.properties[prop].text +- self.properties[prop].text = value +- +- if prop == 'tag' or prop == 'accelerator' or prop == 'drop-targets': +- container = Library().container(self.language()) +- container.prop_changed(self, prop, oldvalue) +- +- self.check_validation() +- +- def language(self): +- if self.library and self.library(): +- return self.library().language +- else: +- return None +- +- def is_override(self): +- return self.override and Library().overridden[self.override] +- +- def to_xml(self): +- return self._create_xml() +- +- def _create_xml(self, parent=None, update=False, attrib={}): +- # Create a new node +- if parent != None: +- element = et.SubElement(parent, 'snippet', attrib) +- else: +- element = et.Element('snippet') +- +- # Create all the properties +- for p in self.properties: +- prop = et.SubElement(element, p) +- prop.text = self[p] +- +- if update: +- self.properties[p] = prop +- +- return element +- +- def _override(self): +- # Find the user file +- target = Library().get_user_library(self.language()) +- +- # Create a new node there with override +- element = self._create_xml(target.root, True, {'override': self.id}) +- +- # Create an override snippet data, feed it element so that it stores +- # all the values and then set the node to None so that it only contains +- # the values in .properties +- override = SnippetData(element, self.library()) +- override.set_node(None) +- override.id = self.id +- +- # Set our node to the new element +- self.node = element +- +- # Set the override to our id +- self.override = self.id +- self.id = None +- +- # Set the new library +- self.set_library(target) +- +- # The library is tainted because we added this snippet +- target.tainted = True +- +- # Add the override +- Library().overridden[self.override] = override +- +- def revert(self, snippet): +- userlib = self.library() +- self.set_library(snippet.library()) +- +- userlib.remove(self.node) +- +- self.set_node(None) +- +- # Copy the properties +- self.properties = snippet.properties +- +- # Set the id +- self.id = snippet.id +- +- # Reset the override flag +- self.override = None ++ self.properties[child.tag] = child.text or '' ++ ++ # Create all the props that were not found so we stay consistent ++ for prop in props: ++ if self.can_modify(): ++ child = et.SubElement(node, prop) ++ ++ child.text = props[prop] ++ self.properties[prop] = child ++ else: ++ self.properties[prop] = props[prop] ++ ++ self.check_validation() ++ ++ def check_validation(self): ++ if not self['tag'] and not self['accelerator'] and not self['drop-targets']: ++ return False ++ ++ library = Library() ++ keyval, mod = Gtk.accelerator_parse(self['accelerator']) ++ ++ self.valid = library.valid_tab_trigger(self['tag']) and \ ++ (not self['accelerator'] or library.valid_accelerator(keyval, mod)) ++ ++ def _format_prop(self, prop, value): ++ if prop == 'drop-targets' and value != '': ++ return re.split('\\s*[,;]\\s*', value) ++ else: ++ return value ++ ++ def __getitem__(self, prop): ++ if prop in self.properties: ++ if self.can_modify(): ++ return self._format_prop(prop, self.properties[prop].text or '') ++ else: ++ return self._format_prop(prop, self.properties[prop] or '') ++ ++ return self._format_prop(prop, '') ++ ++ def __setitem__(self, prop, value): ++ if not prop in self.properties: ++ return ++ ++ if isinstance(value, list): ++ value = ','.join(value) ++ ++ if not self.can_modify() and self.properties[prop] != value: ++ # ohoh, this is not can_modify, but it needs to be changed... ++ # make sure it is transfered to the changes file and set all the ++ # fields. ++ # This snippet data container will effectively become the container ++ # for the newly created node, but transparently to whoever uses ++ # it ++ self._override() ++ ++ if self.can_modify() and self.properties[prop].text != value: ++ if self.library(): ++ self.library().tainted = True ++ ++ oldvalue = self.properties[prop].text ++ self.properties[prop].text = value ++ ++ if prop == 'tag' or prop == 'accelerator' or prop == 'drop-targets': ++ container = Library().container(self.language()) ++ container.prop_changed(self, prop, oldvalue) ++ ++ self.check_validation() ++ ++ def language(self): ++ if self.library and self.library(): ++ return self.library().language ++ else: ++ return None ++ ++ def is_override(self): ++ return self.override and Library().overridden[self.override] ++ ++ def to_xml(self): ++ return self._create_xml() ++ ++ def _create_xml(self, parent=None, update=False, attrib={}): ++ # Create a new node ++ if parent != None: ++ element = et.SubElement(parent, 'snippet', attrib) ++ else: ++ element = et.Element('snippet') ++ ++ # Create all the properties ++ for p in self.properties: ++ prop = et.SubElement(element, p) ++ prop.text = self[p] ++ ++ if update: ++ self.properties[p] = prop ++ ++ return element ++ ++ def _override(self): ++ # Find the user file ++ target = Library().get_user_library(self.language()) ++ ++ # Create a new node there with override ++ element = self._create_xml(target.root, True, {'override': self.id}) ++ ++ # Create an override snippet data, feed it element so that it stores ++ # all the values and then set the node to None so that it only contains ++ # the values in .properties ++ override = SnippetData(element, self.library()) ++ override.set_node(None) ++ override.id = self.id ++ ++ # Set our node to the new element ++ self.node = element ++ ++ # Set the override to our id ++ self.override = self.id ++ self.id = None ++ ++ # Set the new library ++ self.set_library(target) ++ ++ # The library is tainted because we added this snippet ++ target.tainted = True ++ ++ # Add the override ++ Library().overridden[self.override] = override ++ ++ def revert(self, snippet): ++ userlib = self.library() ++ self.set_library(snippet.library()) ++ ++ userlib.remove(self.node) ++ ++ self.set_node(None) ++ ++ # Copy the properties ++ self.properties = snippet.properties ++ ++ # Set the id ++ self.id = snippet.id ++ ++ # Reset the override flag ++ self.override = None + + class SnippetsTreeBuilder(et.TreeBuilder): +- def __init__(self, start=None, end=None): +- et.TreeBuilder.__init__(self) +- self.set_start(start) +- self.set_end(end) +- +- def set_start(self, start): +- self._start_cb = start +- +- def set_end(self, end): +- self._end_cb = end +- +- def start(self, tag, attrs): +- result = et.TreeBuilder.start(self, tag, attrs) +- +- if self._start_cb: +- self._start_cb(result) +- +- return result +- +- def end(self, tag): +- result = et.TreeBuilder.end(self, tag) +- +- if self._end_cb: +- self._end_cb(result) +- +- return result ++ def __init__(self, start=None, end=None): ++ et.TreeBuilder.__init__(self) ++ self.set_start(start) ++ self.set_end(end) ++ ++ def set_start(self, start): ++ self._start_cb = start ++ ++ def set_end(self, end): ++ self._end_cb = end ++ ++ def start(self, tag, attrs): ++ result = et.TreeBuilder.start(self, tag, attrs) ++ ++ if self._start_cb: ++ self._start_cb(result) ++ ++ return result ++ ++ def end(self, tag): ++ result = et.TreeBuilder.end(self, tag) ++ ++ if self._end_cb: ++ self._end_cb(result) ++ ++ return result + + class LanguageContainer: +- def __init__(self, language): +- self.language = language +- self.snippets = [] +- self.snippets_by_prop = {'tag': {}, 'accelerator': {}, 'drop-targets': {}} +- self.accel_group = Gtk.AccelGroup() +- self._refs = 0 +- +- def _add_prop(self, snippet, prop, value=0): +- if value == 0: +- value = snippet[prop] +- +- if not value or value == '': +- return +- +- snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language)) +- +- if prop == 'accelerator': +- keyval, mod = Gtk.accelerator_parse(value) +- self.accel_group.connect(keyval, mod, 0, \ +- Library().accelerator_activated) +- +- snippets = self.snippets_by_prop[prop] +- +- if not isinstance(value, list): +- value = [value] +- +- for val in value: +- if val in snippets: +- snippets[val].append(snippet) +- else: +- snippets[val] = [snippet] +- +- def _remove_prop(self, snippet, prop, value=0): +- if value == 0: +- value = snippet[prop] +- +- if not value or value == '': +- return +- +- snippets_debug('Removed ', prop, ' ', value, ' from ', str(self.language)) +- +- if prop == 'accelerator': +- keyval, mod = Gtk.accelerator_parse(value) +- self.accel_group.disconnect_key(keyval, mod) +- +- snippets = self.snippets_by_prop[prop] +- +- if not isinstance(value, list): +- value = [value] +- +- for val in value: +- try: +- snippets[val].remove(snippet) +- except: +- True +- +- def append(self, snippet): +- tag = snippet['tag'] +- accelerator = snippet['accelerator'] +- +- self.snippets.append(snippet) +- +- self._add_prop(snippet, 'tag') +- self._add_prop(snippet, 'accelerator') +- self._add_prop(snippet, 'drop-targets') +- +- return snippet +- +- def remove(self, snippet): +- try: +- self.snippets.remove(snippet) +- except: +- True +- +- self._remove_prop(snippet, 'tag') +- self._remove_prop(snippet, 'accelerator') +- self._remove_prop(snippet, 'drop-targets') +- +- def prop_changed(self, snippet, prop, oldvalue): +- snippets_debug('PROP CHANGED (', prop, ')', oldvalue) +- +- self._remove_prop(snippet, prop, oldvalue) +- self._add_prop(snippet, prop) +- +- def from_prop(self, prop, value): +- snippets = self.snippets_by_prop[prop] +- +- if prop == 'drop-targets': +- s = [] +- +- # FIXME: change this to use +- # matevfs.mime_type_get_equivalence when it comes +- # available +- for key, val in snippets.items(): +- if not value.startswith(key): +- continue +- +- for snippet in snippets[key]: +- if not snippet in s: +- s.append(snippet) +- +- return s +- else: +- if value in snippets: +- return snippets[value] +- else: +- return [] +- +- def ref(self): +- self._refs += 1 +- +- return True +- +- def unref(self): +- if self._refs > 0: +- self._refs -= 1 +- +- return self._refs != 0 ++ def __init__(self, language): ++ self.language = language ++ self.snippets = [] ++ self.snippets_by_prop = {'tag': {}, 'accelerator': {}, 'drop-targets': {}} ++ self.accel_group = Gtk.AccelGroup() ++ self._refs = 0 ++ ++ def _add_prop(self, snippet, prop, value=0): ++ if value == 0: ++ value = snippet[prop] ++ ++ if not value or value == '': ++ return ++ ++ snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language)) ++ ++ if prop == 'accelerator': ++ keyval, mod = Gtk.accelerator_parse(value) ++ self.accel_group.connect(keyval, mod, 0, \ ++ Library().accelerator_activated) ++ ++ snippets = self.snippets_by_prop[prop] ++ ++ if not isinstance(value, list): ++ value = [value] ++ ++ for val in value: ++ if val in snippets: ++ snippets[val].append(snippet) ++ else: ++ snippets[val] = [snippet] ++ ++ def _remove_prop(self, snippet, prop, value=0): ++ if value == 0: ++ value = snippet[prop] ++ ++ if not value or value == '': ++ return ++ ++ snippets_debug('Removed ', prop, ' ', value, ' from ', str(self.language)) ++ ++ if prop == 'accelerator': ++ keyval, mod = Gtk.accelerator_parse(value) ++ self.accel_group.disconnect_key(keyval, mod) ++ ++ snippets = self.snippets_by_prop[prop] ++ ++ if not isinstance(value, list): ++ value = [value] ++ ++ for val in value: ++ try: ++ snippets[val].remove(snippet) ++ except: ++ True ++ ++ def append(self, snippet): ++ tag = snippet['tag'] ++ accelerator = snippet['accelerator'] ++ ++ self.snippets.append(snippet) ++ ++ self._add_prop(snippet, 'tag') ++ self._add_prop(snippet, 'accelerator') ++ self._add_prop(snippet, 'drop-targets') ++ ++ return snippet ++ ++ def remove(self, snippet): ++ try: ++ self.snippets.remove(snippet) ++ except: ++ True ++ ++ self._remove_prop(snippet, 'tag') ++ self._remove_prop(snippet, 'accelerator') ++ self._remove_prop(snippet, 'drop-targets') ++ ++ def prop_changed(self, snippet, prop, oldvalue): ++ snippets_debug('PROP CHANGED (', prop, ')', oldvalue) ++ ++ self._remove_prop(snippet, prop, oldvalue) ++ self._add_prop(snippet, prop) ++ ++ def from_prop(self, prop, value): ++ snippets = self.snippets_by_prop[prop] ++ ++ if prop == 'drop-targets': ++ s = [] ++ ++ # FIXME: change this to use ++ # matevfs.mime_type_get_equivalence when it comes ++ # available ++ for key, val in snippets.items(): ++ if not value.startswith(key): ++ continue ++ ++ for snippet in snippets[key]: ++ if not snippet in s: ++ s.append(snippet) ++ ++ return s ++ else: ++ if value in snippets: ++ return snippets[value] ++ else: ++ return [] ++ ++ def ref(self): ++ self._refs += 1 ++ ++ return True ++ ++ def unref(self): ++ if self._refs > 0: ++ self._refs -= 1 ++ ++ return self._refs != 0 + + class SnippetsSystemFile: +- def __init__(self, path=None): +- self.path = path +- self.loaded = False +- self.language = None +- self.ok = True +- self.need_id = True +- +- def load_error(self, message): +- sys.stderr.write("An error occurred loading " + self.path + ":\n") +- sys.stderr.write(message + "\nSnippets in this file will not be " \ +- "available, please correct or remove the file.\n") +- +- def _add_snippet(self, element): +- if not self.need_id or element.attrib.get('id'): +- self.loading_elements.append(element) +- +- def set_language(self, element): +- self.language = element.attrib.get('language') +- +- if self.language: +- self.language = self.language.lower() +- +- def _set_root(self, element): +- self.set_language(element) +- +- def _preprocess_element(self, element): +- if not self.loaded: +- if not element.tag == "snippets": +- self.load_error("Root element should be `snippets' instead " \ +- "of `%s'" % element.tag) +- return False +- else: +- self._set_root(element) +- self.loaded = True +- elif element.tag != 'snippet' and not self.insnippet: +- self.load_error("Element should be `snippet' instead of `%s'" \ +- % element.tag) +- return False +- else: +- self.insnippet = True +- +- return True +- +- def _process_element(self, element): +- if element.tag == 'snippet': +- self._add_snippet(element) +- self.insnippet = False +- +- return True +- +- def ensure(self): +- if not self.ok or self.loaded: +- return +- +- self.load() +- +- def parse_xml(self, readsize=16384): +- if not self.path: +- return +- +- elements = [] +- +- builder = SnippetsTreeBuilder( \ +- lambda node: elements.append((node, True)), \ +- lambda node: elements.append((node, False))) +- +- parser = et.XMLTreeBuilder(target=builder) +- self.insnippet = False +- +- try: +- f = open(self.path, "r") +- +- while True: +- data = f.read(readsize) +- +- if not data: +- break +- +- parser.feed(data) +- +- for element in elements: +- yield element +- +- del elements[:] +- +- f.close() +- except IOError: +- self.ok = False +- +- def load(self): +- if not self.ok: +- return +- +- snippets_debug("Loading library (" + str(self.language) + "): " + \ +- self.path) +- +- self.loaded = False +- self.ok = False +- self.loading_elements = [] +- +- for element in self.parse_xml(): +- if element[1]: +- if not self._preprocess_element(element[0]): +- del self.loading_elements[:] +- return +- else: +- if not self._process_element(element[0]): +- del self.loading_elements[:] +- return +- +- for element in self.loading_elements: +- snippet = Library().add_snippet(self, element) +- +- del self.loading_elements[:] +- self.ok = True +- +- # This function will get the language for a file by just inspecting the +- # root element of the file. This is provided so that a cache can be built +- # for which file contains which language. +- # It returns the name of the language +- def ensure_language(self): +- if not self.loaded: +- self.ok = False +- +- for element in self.parse_xml(256): +- if element[1]: +- if element[0].tag == 'snippets': +- self.set_language(element[0]) +- self.ok = True +- +- break +- +- def unload(self): +- snippets_debug("Unloading library (" + str(self.language) + "): " + \ +- self.path) +- self.language = None +- self.loaded = False +- self.ok = True ++ def __init__(self, path=None): ++ self.path = path ++ self.loaded = False ++ self.language = None ++ self.ok = True ++ self.need_id = True ++ ++ def load_error(self, message): ++ sys.stderr.write("An error occurred loading " + self.path + ":\n") ++ sys.stderr.write(message + "\nSnippets in this file will not be " \ ++ "available, please correct or remove the file.\n") ++ ++ def _add_snippet(self, element): ++ if not self.need_id or element.attrib.get('id'): ++ self.loading_elements.append(element) ++ ++ def set_language(self, element): ++ self.language = element.attrib.get('language') ++ ++ if self.language: ++ self.language = self.language.lower() ++ ++ def _set_root(self, element): ++ self.set_language(element) ++ ++ def _preprocess_element(self, element): ++ if not self.loaded: ++ if not element.tag == "snippets": ++ self.load_error("Root element should be `snippets' instead " \ ++ "of `%s'" % element.tag) ++ return False ++ else: ++ self._set_root(element) ++ self.loaded = True ++ elif element.tag != 'snippet' and not self.insnippet: ++ self.load_error("Element should be `snippet' instead of `%s'" \ ++ % element.tag) ++ return False ++ else: ++ self.insnippet = True ++ ++ return True ++ ++ def _process_element(self, element): ++ if element.tag == 'snippet': ++ self._add_snippet(element) ++ self.insnippet = False ++ ++ return True ++ ++ def ensure(self): ++ if not self.ok or self.loaded: ++ return ++ ++ self.load() ++ ++ def parse_xml(self, readsize=16384): ++ if not self.path: ++ return ++ ++ elements = [] ++ ++ builder = SnippetsTreeBuilder( \ ++ lambda node: elements.append((node, True)), \ ++ lambda node: elements.append((node, False))) ++ ++ parser = et.XMLTreeBuilder(target=builder) ++ self.insnippet = False ++ ++ try: ++ f = open(self.path, "r") ++ ++ while True: ++ data = f.read(readsize) ++ ++ if not data: ++ break ++ ++ parser.feed(data) ++ ++ for element in elements: ++ yield element ++ ++ del elements[:] ++ ++ f.close() ++ except IOError: ++ self.ok = False ++ ++ def load(self): ++ if not self.ok: ++ return ++ ++ snippets_debug("Loading library (" + str(self.language) + "): " + \ ++ self.path) ++ ++ self.loaded = False ++ self.ok = False ++ self.loading_elements = [] ++ ++ for element in self.parse_xml(): ++ if element[1]: ++ if not self._preprocess_element(element[0]): ++ del self.loading_elements[:] ++ return ++ else: ++ if not self._process_element(element[0]): ++ del self.loading_elements[:] ++ return ++ ++ for element in self.loading_elements: ++ snippet = Library().add_snippet(self, element) ++ ++ del self.loading_elements[:] ++ self.ok = True ++ ++ # This function will get the language for a file by just inspecting the ++ # root element of the file. This is provided so that a cache can be built ++ # for which file contains which language. ++ # It returns the name of the language ++ def ensure_language(self): ++ if not self.loaded: ++ self.ok = False ++ ++ for element in self.parse_xml(256): ++ if element[1]: ++ if element[0].tag == 'snippets': ++ self.set_language(element[0]) ++ self.ok = True ++ ++ break ++ ++ def unload(self): ++ snippets_debug("Unloading library (" + str(self.language) + "): " + \ ++ self.path) ++ self.language = None ++ self.loaded = False ++ self.ok = True + + class SnippetsUserFile(SnippetsSystemFile): +- def __init__(self, path=None): +- SnippetsSystemFile.__init__(self, path) +- self.tainted = False +- self.need_id = False +- +- def _set_root(self, element): +- SnippetsSystemFile._set_root(self, element) +- self.root = element +- +- def add_prop(self, node, tag, data): +- if data[tag]: +- prop = et.SubElement(node, tag) +- prop.text = data[tag] +- +- return prop +- else: +- return None +- +- def new_snippet(self, properties=None): +- if (not self.ok) or self.root == None: +- return None +- +- element = et.SubElement(self.root, 'snippet') +- +- if properties: +- for prop in properties: +- sub = et.SubElement(element, prop) +- sub.text = properties[prop] +- +- self.tainted = True +- +- return Library().add_snippet(self, element) +- +- def set_language(self, element): +- SnippetsSystemFile.set_language(self, element) +- +- filename = os.path.basename(self.path).lower() +- +- if not self.language and filename == "global.xml": +- self.modifier = True +- elif self.language and filename == self.language + ".xml": +- self.modifier = True +- else: +- self.modifier = False +- +- def create_root(self, language): +- if self.loaded: +- snippets_debug('Not creating root, already loaded') +- return +- +- if language: +- root = et.Element('snippets', {'language': language}) +- self.path = os.path.join(Library().userdir, language.lower() + '.xml') +- else: +- root = et.Element('snippets') +- self.path = os.path.join(Library().userdir, 'global.xml') +- +- self._set_root(root) +- self.loaded = True +- self.ok = True +- self.tainted = True +- self.save() +- +- def remove(self, element): +- try: +- self.root.remove(element) +- self.tainted = True +- except: +- return +- +- try: +- first = self.root[0] +- except: +- # No more elements, this library is useless now +- Library().remove_library(self) +- +- def save(self): +- if not self.ok or self.root == None or not self.tainted: +- return +- +- path = os.path.dirname(self.path) +- +- try: +- if not os.path.isdir(path): +- os.makedirs(path, 0755) +- except OSError: +- # TODO: this is bad... +- sys.stderr.write("Error in making dirs\n") +- +- try: +- write_xml(self.root, self.path, ('text', 'accelerator')) +- self.tainted = False +- except IOError: +- # Couldn't save, what to do +- sys.stderr.write("Could not save user snippets file to " + \ +- self.path + "\n") +- +- def unload(self): +- SnippetsSystemFile.unload(self) +- self.root = None ++ def __init__(self, path=None): ++ SnippetsSystemFile.__init__(self, path) ++ self.tainted = False ++ self.need_id = False ++ ++ def _set_root(self, element): ++ SnippetsSystemFile._set_root(self, element) ++ self.root = element ++ ++ def add_prop(self, node, tag, data): ++ if data[tag]: ++ prop = et.SubElement(node, tag) ++ prop.text = data[tag] ++ ++ return prop ++ else: ++ return None ++ ++ def new_snippet(self, properties=None): ++ if (not self.ok) or self.root == None: ++ return None ++ ++ element = et.SubElement(self.root, 'snippet') ++ ++ if properties: ++ for prop in properties: ++ sub = et.SubElement(element, prop) ++ sub.text = properties[prop] ++ ++ self.tainted = True ++ ++ return Library().add_snippet(self, element) ++ ++ def set_language(self, element): ++ SnippetsSystemFile.set_language(self, element) ++ ++ filename = os.path.basename(self.path).lower() ++ ++ if not self.language and filename == "global.xml": ++ self.modifier = True ++ elif self.language and filename == self.language + ".xml": ++ self.modifier = True ++ else: ++ self.modifier = False ++ ++ def create_root(self, language): ++ if self.loaded: ++ snippets_debug('Not creating root, already loaded') ++ return ++ ++ if language: ++ root = et.Element('snippets', {'language': language}) ++ self.path = os.path.join(Library().userdir, language.lower() + '.xml') ++ else: ++ root = et.Element('snippets') ++ self.path = os.path.join(Library().userdir, 'global.xml') ++ ++ self._set_root(root) ++ self.loaded = True ++ self.ok = True ++ self.tainted = True ++ self.save() ++ ++ def remove(self, element): ++ try: ++ self.root.remove(element) ++ self.tainted = True ++ except: ++ return ++ ++ try: ++ first = self.root[0] ++ except: ++ # No more elements, this library is useless now ++ Library().remove_library(self) ++ ++ def save(self): ++ if not self.ok or self.root == None or not self.tainted: ++ return ++ ++ path = os.path.dirname(self.path) ++ ++ try: ++ if not os.path.isdir(path): ++ os.makedirs(path, 0755) ++ except OSError: ++ # TODO: this is bad... ++ sys.stderr.write("Error in making dirs\n") ++ ++ try: ++ write_xml(self.root, self.path, ('text', 'accelerator')) ++ self.tainted = False ++ except IOError: ++ # Couldn't save, what to do ++ sys.stderr.write("Could not save user snippets file to " + \ ++ self.path + "\n") ++ ++ def unload(self): ++ SnippetsSystemFile.unload(self) ++ self.root = None + + 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 Library(Singleton): +- def __init_once__(self): +- self._accelerator_activated_cb = [] +- self.loaded = False +- self.check_buffer = Gtk.TextBuffer() +- +- def set_dirs(self, userdir, systemdirs): +- self.userdir = userdir +- self.systemdirs = systemdirs +- +- self.libraries = {} +- self.containers = {} +- self.overridden = {} +- self.loaded_ids = [] +- +- self.loaded = False +- +- def add_accelerator_callback(self, cb): +- self._accelerator_activated_cb.append(cb) +- +- def remove_accelerator_callback(self, cb): +- self._accelerator_activated_cb.remove(cb) +- +- def accelerator_activated(self, group, obj, keyval, mod): +- ret = False +- +- for cb in self._accelerator_activated_cb: +- ret = cb(group, obj, keyval, mod) +- +- if ret: +- break +- +- return ret +- +- def add_snippet(self, library, element): +- container = self.container(library.language) +- overrided = self.overrided(library, element) +- +- if overrided: +- overrided.set_library(library) +- snippets_debug('Snippet is overriden: ' + overrided['description']) +- return None +- +- snippet = SnippetData(element, library) +- +- if snippet.id in self.loaded_ids: +- snippets_debug('Not added snippet ' + str(library.language) + \ +- '::' + snippet['description'] + ' (duplicate)') +- return None +- +- snippet = container.append(snippet) +- snippets_debug('Added snippet ' + str(library.language) + '::' + \ +- snippet['description']) +- +- if snippet and snippet.override: +- self.add_override(snippet) +- +- if snippet.id: +- self.loaded_ids.append(snippet.id) +- +- return snippet +- +- def container(self, language): +- language = self.normalize_language(language) +- +- if not language in self.containers: +- self.containers[language] = LanguageContainer(language) +- +- return self.containers[language] +- +- def get_user_library(self, language): +- target = None +- +- if language in self.libraries: +- for library in self.libraries[language]: +- if isinstance(library, SnippetsUserFile) and library.modifier: +- target = library +- elif not isinstance(library, SnippetsUserFile): +- break +- +- if not target: +- # Create a new user file then +- snippets_debug('Creating a new user file for language ' + \ +- str(language)) +- target = SnippetsUserFile() +- target.create_root(language) +- self.add_library(target) +- +- return target +- +- def new_snippet(self, language, properties=None): +- language = self.normalize_language(language) +- library = self.get_user_library(language) +- +- return library.new_snippet(properties) +- +- def revert_snippet(self, snippet): +- # This will revert the snippet to the one it overrides +- if not snippet.can_modify() or not snippet.override in self.overridden: +- # It can't be reverted, shouldn't happen, but oh.. +- return +- +- # The snippet in self.overriden only contains the property contents and +- # the library it belongs to +- revertto = self.overridden[snippet.override] +- del self.overridden[snippet.override] +- +- if revertto: +- snippet.revert(revertto) +- +- if revertto.id: +- self.loaded_ids.append(revertto.id) +- +- def remove_snippet(self, snippet): +- if not snippet.can_modify() or snippet.is_override(): +- return +- +- # Remove from the library +- userlib = snippet.library() +- userlib.remove(snippet.node) +- +- # Remove from the container +- container = self.containers[userlib.language] ++ _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 Library(Singleton): ++ def __init_once__(self): ++ self._accelerator_activated_cb = [] ++ self.loaded = False ++ self.check_buffer = Gtk.TextBuffer() ++ ++ def set_dirs(self, userdir, systemdirs): ++ self.userdir = userdir ++ self.systemdirs = systemdirs ++ ++ self.libraries = {} ++ self.containers = {} ++ self.overridden = {} ++ self.loaded_ids = [] ++ ++ self.loaded = False ++ ++ def add_accelerator_callback(self, cb): ++ self._accelerator_activated_cb.append(cb) ++ ++ def remove_accelerator_callback(self, cb): ++ self._accelerator_activated_cb.remove(cb) ++ ++ def accelerator_activated(self, group, obj, keyval, mod): ++ ret = False ++ ++ for cb in self._accelerator_activated_cb: ++ ret = cb(group, obj, keyval, mod) ++ ++ if ret: ++ break ++ ++ return ret ++ ++ def add_snippet(self, library, element): ++ container = self.container(library.language) ++ overrided = self.overrided(library, element) ++ ++ if overrided: ++ overrided.set_library(library) ++ snippets_debug('Snippet is overriden: ' + overrided['description']) ++ return None ++ ++ snippet = SnippetData(element, library) ++ ++ if snippet.id in self.loaded_ids: ++ snippets_debug('Not added snippet ' + str(library.language) + \ ++ '::' + snippet['description'] + ' (duplicate)') ++ return None ++ ++ snippet = container.append(snippet) ++ snippets_debug('Added snippet ' + str(library.language) + '::' + \ ++ snippet['description']) ++ ++ if snippet and snippet.override: ++ self.add_override(snippet) ++ ++ if snippet.id: ++ self.loaded_ids.append(snippet.id) ++ ++ return snippet ++ ++ def container(self, language): ++ language = self.normalize_language(language) ++ ++ if not language in self.containers: ++ self.containers[language] = LanguageContainer(language) ++ ++ return self.containers[language] ++ ++ def get_user_library(self, language): ++ target = None ++ ++ if language in self.libraries: ++ for library in self.libraries[language]: ++ if isinstance(library, SnippetsUserFile) and library.modifier: ++ target = library ++ elif not isinstance(library, SnippetsUserFile): ++ break ++ ++ if not target: ++ # Create a new user file then ++ snippets_debug('Creating a new user file for language ' + \ ++ str(language)) ++ target = SnippetsUserFile() ++ target.create_root(language) ++ self.add_library(target) ++ ++ return target ++ ++ def new_snippet(self, language, properties=None): ++ language = self.normalize_language(language) ++ library = self.get_user_library(language) ++ ++ return library.new_snippet(properties) ++ ++ def revert_snippet(self, snippet): ++ # This will revert the snippet to the one it overrides ++ if not snippet.can_modify() or not snippet.override in self.overridden: ++ # It can't be reverted, shouldn't happen, but oh.. ++ return ++ ++ # The snippet in self.overriden only contains the property contents and ++ # the library it belongs to ++ revertto = self.overridden[snippet.override] ++ del self.overridden[snippet.override] ++ ++ if revertto: ++ snippet.revert(revertto) ++ ++ if revertto.id: ++ self.loaded_ids.append(revertto.id) ++ ++ def remove_snippet(self, snippet): ++ if not snippet.can_modify() or snippet.is_override(): ++ return ++ ++ # Remove from the library ++ userlib = snippet.library() ++ userlib.remove(snippet.node) ++ ++ # Remove from the container ++ container = self.containers[userlib.language] ++ container.remove(snippet) ++ ++ def overrided(self, library, element): ++ id = NamespacedId(library.language, element.attrib.get('id')).id ++ ++ if id in self.overridden: ++ snippet = SnippetData(element, None) ++ snippet.set_node(None) ++ ++ self.overridden[id] = snippet ++ return snippet ++ else: ++ return None ++ ++ def add_override(self, snippet): ++ snippets_debug('Add override:', snippet.override) ++ if not snippet.override in self.overridden: ++ self.overridden[snippet.override] = None ++ ++ def add_library(self, library): ++ library.ensure_language() ++ ++ if not library.ok: ++ snippets_debug('Library in wrong format, ignoring') ++ return False ++ ++ snippets_debug('Adding library (' + str(library.language) + '): ' + \ ++ library.path) ++ ++ if library.language in self.libraries: ++ # Make sure all the user files are before the system files ++ if isinstance(library, SnippetsUserFile): ++ self.libraries[library.language].insert(0, library) ++ else: ++ self.libraries[library.language].append(library) ++ else: ++ self.libraries[library.language] = [library] ++ ++ return True ++ ++ def remove_library(self, library): ++ if not library.ok: ++ return ++ ++ if library.path and os.path.isfile(library.path): ++ os.unlink(library.path) ++ ++ try: ++ self.libraries[library.language].remove(library) ++ except KeyError: ++ True ++ ++ container = self.containers[library.language] ++ ++ for snippet in list(container.snippets): ++ if snippet.library() == library: + container.remove(snippet) +- +- def overrided(self, library, element): +- id = NamespacedId(library.language, element.attrib.get('id')).id +- +- if id in self.overridden: +- snippet = SnippetData(element, None) +- snippet.set_node(None) +- +- self.overridden[id] = snippet +- return snippet +- else: +- return None +- +- def add_override(self, snippet): +- snippets_debug('Add override:', snippet.override) +- if not snippet.override in self.overridden: +- self.overridden[snippet.override] = None +- +- def add_library(self, library): +- library.ensure_language() +- +- if not library.ok: +- snippets_debug('Library in wrong format, ignoring') +- return False +- +- snippets_debug('Adding library (' + str(library.language) + '): ' + \ +- library.path) +- +- if library.language in self.libraries: +- # Make sure all the user files are before the system files +- if isinstance(library, SnippetsUserFile): +- self.libraries[library.language].insert(0, library) +- else: +- self.libraries[library.language].append(library) ++ ++ def add_user_library(self, path): ++ library = SnippetsUserFile(path) ++ return self.add_library(library) ++ ++ def add_system_library(self, path): ++ library = SnippetsSystemFile(path) ++ return self.add_library(library) ++ ++ def find_libraries(self, path, searched, addcb): ++ snippets_debug("Finding in: " + path) ++ ++ if not os.path.isdir(path): ++ return searched ++ ++ files = os.listdir(path) ++ searched.append(path) ++ ++ for f in files: ++ f = os.path.realpath(os.path.join(path, f)) ++ ++ # Determine what language this file provides snippets for ++ if os.path.isfile(f): ++ addcb(f) ++ ++ return searched ++ ++ def normalize_language(self, language): ++ if language: ++ return language.lower() ++ ++ return language ++ ++ def remove_container(self, language): ++ for snippet in self.containers[language].snippets: ++ if snippet.id in self.loaded_ids: ++ self.loaded_ids.remove(snippet.id) ++ ++ if snippet.override in self.overridden: ++ del self.overridden[snippet.override] ++ ++ del self.containers[language] ++ ++ def get_accel_group(self, language): ++ language = self.normalize_language(language) ++ container = self.container(language) ++ ++ self.ensure(language) ++ return container.accel_group ++ ++ def save(self, language): ++ language = self.normalize_language(language) ++ ++ if language in self.libraries: ++ for library in self.libraries[language]: ++ if isinstance(library, SnippetsUserFile): ++ library.save() + else: +- self.libraries[library.language] = [library] +- +- return True +- +- def remove_library(self, library): +- if not library.ok: +- return +- +- if library.path and os.path.isfile(library.path): +- os.unlink(library.path) +- +- try: +- self.libraries[library.language].remove(library) +- except KeyError: +- True +- +- container = self.containers[library.language] +- +- for snippet in list(container.snippets): +- if snippet.library() == library: +- container.remove(snippet) +- +- def add_user_library(self, path): +- library = SnippetsUserFile(path) +- return self.add_library(library) +- +- def add_system_library(self, path): +- library = SnippetsSystemFile(path) +- return self.add_library(library) +- +- def find_libraries(self, path, searched, addcb): +- snippets_debug("Finding in: " + path) +- +- if not os.path.isdir(path): +- return searched +- +- files = os.listdir(path) +- searched.append(path) +- +- for f in files: +- f = os.path.realpath(os.path.join(path, f)) +- +- # Determine what language this file provides snippets for +- if os.path.isfile(f): +- addcb(f) +- +- return searched +- +- def normalize_language(self, language): +- if language: +- return language.lower() +- +- return language +- +- def remove_container(self, language): +- for snippet in self.containers[language].snippets: +- if snippet.id in self.loaded_ids: +- self.loaded_ids.remove(snippet.id) +- +- if snippet.override in self.overridden: +- del self.overridden[snippet.override] +- +- del self.containers[language] +- +- def get_accel_group(self, language): +- language = self.normalize_language(language) +- container = self.container(language) +- +- self.ensure(language) +- return container.accel_group +- +- def save(self, language): +- language = self.normalize_language(language) +- +- if language in self.libraries: +- for library in self.libraries[language]: +- if isinstance(library, SnippetsUserFile): +- library.save() +- else: +- break +- +- def ref(self, language): +- language = self.normalize_language(language) +- +- snippets_debug('Ref:', language) +- self.container(language).ref() +- +- def unref(self, language): +- language = self.normalize_language(language) +- +- snippets_debug('Unref:', language) +- +- if language in self.containers: +- if not self.containers[language].unref() and \ +- language in self.libraries: +- +- for library in self.libraries[language]: +- library.unload() +- +- self.remove_container(language) +- +- def ensure(self, language): +- self.ensure_files() +- language = self.normalize_language(language) +- +- # Ensure language as well as the global snippets (None) +- for lang in (None, language): +- if lang in self.libraries: +- # Ensure the container exists +- self.container(lang) +- +- for library in self.libraries[lang]: +- library.ensure() +- +- def ensure_files(self): +- if self.loaded: +- return +- +- searched = [] +- searched = self.find_libraries(self.userdir, searched, \ +- self.add_user_library) +- +- for d in self.systemdirs: +- searched = self.find_libraries(d, searched, \ +- self.add_system_library) ++ break + +- self.loaded = True ++ def ref(self, language): ++ language = self.normalize_language(language) ++ ++ snippets_debug('Ref:', language) ++ self.container(language).ref() ++ ++ def unref(self, language): ++ language = self.normalize_language(language) ++ ++ snippets_debug('Unref:', language) ++ ++ if language in self.containers: ++ if not self.containers[language].unref() and \ ++ language in self.libraries: ++ ++ for library in self.libraries[language]: ++ library.unload() ++ ++ self.remove_container(language) ++ ++ def ensure(self, language): ++ self.ensure_files() ++ language = self.normalize_language(language) ++ ++ # Ensure language as well as the global snippets (None) ++ for lang in (None, language): ++ if lang in self.libraries: ++ # Ensure the container exists ++ self.container(lang) ++ ++ for library in self.libraries[lang]: ++ library.ensure() ++ ++ def ensure_files(self): ++ if self.loaded: ++ return ++ ++ searched = [] ++ searched = self.find_libraries(self.userdir, searched, \ ++ self.add_user_library) ++ ++ for d in self.systemdirs: ++ searched = self.find_libraries(d, searched, \ ++ self.add_system_library) ++ ++ self.loaded = True ++ ++ def valid_accelerator(self, keyval, mod): ++ mod &= Gtk.accelerator_get_default_mod_mask() ++ ++ return (mod and (Gdk.keyval_to_unicode(keyval) or \ ++ keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1))) ++ ++ def valid_tab_trigger(self, trigger): ++ if not trigger: ++ return True ++ ++ if trigger.isdigit(): ++ return False ++ ++ self.check_buffer.set_text(trigger) ++ ++ start, end = self.check_buffer.get_bounds() ++ text = self.check_buffer.get_text(start, end, False) ++ ++ s = start.copy() ++ e = end.copy() ++ ++ end.backward_word_start() ++ start.forward_word_end() ++ ++ return (s.equal(end) and e.equal(start)) or (len(text) == 1 and not (text.isalnum() or text.isspace())) ++ ++ # Snippet getters ++ # =============== ++ def _from_prop(self, prop, value, language=None): ++ self.ensure_files() ++ ++ result = [] ++ language = self.normalize_language(language) ++ ++ if not language in self.containers: ++ return [] ++ ++ self.ensure(language) ++ result = self.containers[language].from_prop(prop, value) ++ ++ if len(result) == 0 and language and None in self.containers: ++ result = self.containers[None].from_prop(prop, value) ++ ++ return result ++ ++ # Get snippets for a given language ++ def get_snippets(self, language=None): ++ self.ensure_files() ++ language = self.normalize_language(language) ++ ++ if not language in self.libraries: ++ return [] ++ ++ snippets = [] ++ self.ensure(language) ++ ++ return list(self.containers[language].snippets) ++ ++ # Get snippets for a given accelerator ++ def from_accelerator(self, accelerator, language=None): ++ return self._from_prop('accelerator', accelerator, language) ++ ++ # Get snippets for a given tag ++ def from_tag(self, tag, language=None): ++ return self._from_prop('tag', tag, language) ++ ++ # Get snippets for a given drop target ++ def from_drop_target(self, drop_target, language=None): ++ return self._from_prop('drop-targets', drop_target, language) + +- def valid_accelerator(self, keyval, mod): +- mod &= Gtk.accelerator_get_default_mod_mask() +- +- return (mod and (Gdk.keyval_to_unicode(keyval) or \ +- keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1))) +- +- def valid_tab_trigger(self, trigger): +- if not trigger: +- return True +- +- if trigger.isdigit(): +- return False +- +- self.check_buffer.set_text(trigger) +- +- start, end = self.check_buffer.get_bounds() +- text = self.check_buffer.get_text(start, end, False) +- +- s = start.copy() +- e = end.copy() +- +- end.backward_word_start() +- start.forward_word_end() +- +- return (s.equal(end) and e.equal(start)) or (len(text) == 1 and not (text.isalnum() or text.isspace())) +- +- # Snippet getters +- # =============== +- def _from_prop(self, prop, value, language=None): +- self.ensure_files() +- +- result = [] +- language = self.normalize_language(language) +- +- if not language in self.containers: +- return [] +- +- self.ensure(language) +- result = self.containers[language].from_prop(prop, value) +- +- if len(result) == 0 and language and None in self.containers: +- result = self.containers[None].from_prop(prop, value) +- +- return result +- +- # Get snippets for a given language +- def get_snippets(self, language=None): +- self.ensure_files() +- language = self.normalize_language(language) +- +- if not language in self.libraries: +- return [] +- +- snippets = [] +- self.ensure(language) +- +- return list(self.containers[language].snippets) +- +- # Get snippets for a given accelerator +- def from_accelerator(self, accelerator, language=None): +- return self._from_prop('accelerator', accelerator, language) +- +- # Get snippets for a given tag +- def from_tag(self, tag, language=None): +- return self._from_prop('tag', tag, language) +- +- # Get snippets for a given drop target +- def from_drop_target(self, drop_target, language=None): +- return self._from_prop('drop-targets', drop_target, language) +- +-# ex:ts=8:et: ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Manager.py b/plugins/snippets/snippets/Manager.py +old mode 100755 +new mode 100644 +index f496b1c..9760fa7 +--- a/plugins/snippets/snippets/Manager.py ++++ b/plugins/snippets/snippets/Manager.py +@@ -30,1119 +30,1119 @@ from Document import Document + from LanguageManager import get_language_manager + + class Manager: +- NAME_COLUMN = 0 +- SORT_COLUMN = 1 +- LANG_COLUMN = 2 +- SNIPPET_COLUMN = 3 +- TARGET_URI = 105 +- +- model = None +- drag_icons = ('mate-mime-application-x-tarz', 'mate-package', 'package') +- default_export_name = _('Snippets archive') + '.tar.gz' +- dragging = False +- dnd_target_list = [Gtk.TargetEntry.new('text/uri-list', 0, TARGET_URI)] +- +- def __init__(self, datadir): +- self.datadir = datadir +- self.snippet = None +- self.dlg = None +- self._temp_export = None +- self.snippets_doc = None +- self.manager = None +- self.default_size = None +- +- self.key_press_id = 0 +- self.run() +- +- def get_language_snippets(self, path, name = None): +- library = Library() +- +- name = self.get_language(path) +- nodes = library.get_snippets(name) +- +- return nodes +- +- def add_new_snippet_node(self, parent): +- return self.model.append(parent, ('' + _('Add a new snippet...') + \ +- '', '', None, None)) +- +- def fill_language(self, piter, expand=True): +- # Remove all children +- child = self.model.iter_children(piter) +- +- while child and self.model.remove(child): +- True +- +- path = self.model.get_path(piter) +- nodes = self.get_language_snippets(path) +- language = self.get_language(path) +- +- Library().ref(language) +- +- if nodes: +- for node in nodes: +- self.add_snippet(piter, node) +- else: +- # Add node that tells there are no snippets currently +- self.add_new_snippet_node(piter) ++ NAME_COLUMN = 0 ++ SORT_COLUMN = 1 ++ LANG_COLUMN = 2 ++ SNIPPET_COLUMN = 3 ++ TARGET_URI = 105 ++ ++ model = None ++ drag_icons = ('mate-mime-application-x-tarz', 'mate-package', 'package') ++ default_export_name = _('Snippets archive') + '.tar.gz' ++ dragging = False ++ dnd_target_list = [Gtk.TargetEntry.new('text/uri-list', 0, TARGET_URI)] ++ ++ def __init__(self, datadir): ++ self.datadir = datadir ++ self.snippet = None ++ self.dlg = None ++ self._temp_export = None ++ self.snippets_doc = None ++ self.manager = None ++ self.default_size = None ++ ++ self.key_press_id = 0 ++ self.run() ++ ++ def get_language_snippets(self, path, name = None): ++ library = Library() ++ ++ name = self.get_language(path) ++ nodes = library.get_snippets(name) ++ ++ return nodes ++ ++ def add_new_snippet_node(self, parent): ++ return self.model.append(parent, ('' + _('Add a new snippet...') + \ ++ '', '', None, None)) ++ ++ def fill_language(self, piter, expand=True): ++ # Remove all children ++ child = self.model.iter_children(piter) ++ ++ while child and self.model.remove(child): ++ True ++ ++ path = self.model.get_path(piter) ++ nodes = self.get_language_snippets(path) ++ language = self.get_language(path) ++ ++ Library().ref(language) ++ ++ if nodes: ++ for node in nodes: ++ self.add_snippet(piter, node) ++ else: ++ # Add node that tells there are no snippets currently ++ self.add_new_snippet_node(piter) ++ ++ if expand: ++ self.tree_view.expand_row(path, False) ++ ++ def build_model(self, force_reload = False): ++ window = Pluma.App.get_default().get_active_window() ++ ++ if window: ++ view = window.get_active_view() ++ ++ if not view: ++ current_lang = None ++ else: ++ current_lang = view.get_buffer().get_language() ++ source_view = self['source_view_snippet'] + +- if expand: +- self.tree_view.expand_row(path, False) ++ else: ++ current_lang = None + +- def build_model(self, force_reload = False): +- window = Pluma.App.get_default().get_active_window() +- +- if window: +- view = window.get_active_view() ++ tree_view = self['tree_view_snippets'] ++ expand = None + +- if not view: +- current_lang = None +- else: +- current_lang = view.get_buffer().get_language() +- source_view = self['source_view_snippet'] ++ if not self.model or force_reload: ++ self.model = Gtk.TreeStore(str, str, GObject.Object, object) ++ self.model.set_sort_column_id(self.SORT_COLUMN, Gtk.SortType.ASCENDING) ++ manager = get_language_manager() ++ langs = [manager.get_language(x) for x in manager.get_language_ids()] ++ langs.sort(key=lambda x: x.get_name()) + +- else: +- current_lang = None +- +- tree_view = self['tree_view_snippets'] +- expand = None +- +- if not self.model or force_reload: +- self.model = Gtk.TreeStore(str, str, GObject.Object, object) +- self.model.set_sort_column_id(self.SORT_COLUMN, Gtk.SortType.ASCENDING) +- manager = get_language_manager() +- langs = [manager.get_language(x) for x in manager.get_language_ids()] +- langs.sort(key=lambda x: x.get_name()) +- +- piter = self.model.append(None, (_('Global'), '', None, None)) +- # Add dummy node +- self.model.append(piter, ('', '', None, None)) +- +- nm = None +- +- if current_lang: +- nm = current_lang.get_name() +- +- for lang in langs: +- name = lang.get_name() +- parent = self.model.append(None, (name, name, lang, None)) +- +- # Add dummy node +- self.model.append(parent, ('', '', None, None)) +- +- if (nm == name): +- expand = parent +- else: +- if current_lang: +- piter = self.model.get_iter_first() +- nm = current_lang.get_name() +- +- while piter: +- lang = self.model.get_value(piter, \ +- self.SORT_COLUMN) +- +- if lang == nm: +- expand = piter +- break; +- +- piter = self.model.iter_next(piter) +- +- tree_view.set_model(self.model) +- +- if not expand: +- expand = self.model.get_iter_first() +- +- tree_view.expand_row(self.model.get_path(expand), False) +- self.select_iter(expand) +- +- def get_cell_data_pixbuf_cb(self, column, cell, model, iter, data): +- snippet = model.get_value(iter, self.SNIPPET_COLUMN) +- +- if snippet and not snippet.valid: +- cell.set_property('stock-id', Gtk.STOCK_DIALOG_ERROR) +- else: +- cell.set_property('stock-id', None) +- +- cell.set_property('xalign', 1.0) +- +- def get_cell_data_cb(self, column, cell, model, iter, data): +- snippet = model.get_value(iter, self.SNIPPET_COLUMN) +- +- cell.set_property('editable', snippet != None) +- cell.set_property('markup', model.get_value(iter, self.NAME_COLUMN)) +- +- def on_tree_view_drag_data_get(self, widget, context, selection_data, info, time): +- gfile = Gio.file_new_for_path(self._temp_export) +- selection_data.set_uris([gfile.get_uri()]) +- +- def on_tree_view_drag_begin(self, widget, context): +- self.dragging = True +- +- if self._temp_export: +- shutil.rmtree(os.path.dirname(self._temp_export)) +- self._temp_export = None +- +- if self.dnd_name: +- Gtk.drag_set_icon_name(context, self.dnd_name, 0, 0) +- +- dirname = tempfile.mkdtemp() +- filename = os.path.join(dirname, self.default_export_name) +- +- # Generate temporary file name +- self.export_snippets(filename, False) +- self._temp_export = filename +- +- def on_tree_view_drag_end(self, widget, context): +- self.dragging = False +- +- def on_tree_view_drag_data_received(self, widget, context, x, y, selection, info, timestamp): +- uris = selection.get_uris() +- +- self.import_snippets(uris) +- +- def on_tree_view_drag_motion(self, widget, context, x, y, timestamp): +- # Return False if we are dragging +- if self.dragging: +- return False +- +- # Check uri target +- if not Gtk.targets_include_uri(context.targets): +- return False +- +- # Check action +- action = None +- if context.suggested_action == Gdk.DragAction.COPY: +- action = Gdk.DragAction.COPY +- else: +- for act in context.actions: +- if act == Gdk.DragAction.COPY: +- action = Gdk.DragAction.COPY +- break +- +- if action == Gdk.DragAction.COPY: +- context.drag_status(Gdk.DragAction.COPY, timestamp) +- return True +- else: +- return False +- +- def build_dnd(self): +- tv = self.tree_view +- +- # Set it as a drag source for exporting snippets +- tv.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, self.dnd_target_list, Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) +- +- # Set it as a drag destination for importing snippets +- tv.drag_dest_set(Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP, +- self.dnd_target_list, Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) +- +- tv.connect('drag_data_get', self.on_tree_view_drag_data_get) +- tv.connect('drag_begin', self.on_tree_view_drag_begin) +- tv.connect('drag_end', self.on_tree_view_drag_end) +- tv.connect('drag_data_received', self.on_tree_view_drag_data_received) +- tv.connect('drag_motion', self.on_tree_view_drag_motion) +- +- theme = Gtk.IconTheme.get_for_screen(tv.get_screen()) +- +- self.dnd_name = None +- for name in self.drag_icons: +- icon = theme.lookup_icon(name, Gtk.IconSize.DND, 0) +- +- if icon: +- self.dnd_name = name +- break +- +- def build_tree_view(self): +- self.tree_view = self['tree_view_snippets'] +- +- self.column = Gtk.TreeViewColumn(None) +- +- self.renderer = Gtk.CellRendererText() +- self.column.pack_start(self.renderer, False) +- self.column.set_cell_data_func(self.renderer, self.get_cell_data_cb, None) +- +- renderer = Gtk.CellRendererPixbuf() +- self.column.pack_start(renderer, True) +- self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb, None) +- +- self.tree_view.append_column(self.column) +- +- self.renderer.connect('edited', self.on_cell_edited) +- self.renderer.connect('editing-started', self.on_cell_editing_started) +- +- selection = self.tree_view.get_selection() +- selection.set_mode(Gtk.SelectionMode.MULTIPLE) +- selection.connect('changed', self.on_tree_view_selection_changed) +- +- self.build_dnd() +- +- def build(self): +- self.builder = Gtk.Builder() +- self.builder.add_from_file(os.path.join(self.datadir, 'ui', 'snippets.ui')) +- +- handlers_dic = { +- 'on_dialog_snippets_response': self.on_dialog_snippets_response, +- 'on_dialog_snippets_destroy': self.on_dialog_snippets_destroy, +- 'on_button_new_snippet_clicked': self.on_button_new_snippet_clicked, +- 'on_button_import_snippets_clicked': self.on_button_import_snippets_clicked, +- 'on_button_export_snippets_clicked': self.on_button_export_snippets_clicked, +- 'on_button_remove_snippet_clicked': self.on_button_remove_snippet_clicked, +- 'on_entry_tab_trigger_focus_out': self.on_entry_tab_trigger_focus_out, +- 'on_entry_tab_trigger_changed': self.on_entry_tab_trigger_changed, +- 'on_entry_accelerator_focus_out': self.on_entry_accelerator_focus_out, +- 'on_entry_accelerator_focus_in': self.on_entry_accelerator_focus_in, +- 'on_entry_accelerator_key_press': self.on_entry_accelerator_key_press, +- 'on_source_view_snippet_focus_out': self.on_source_view_snippet_focus_out, +- 'on_tree_view_snippets_row_expanded': self.on_tree_view_snippets_row_expanded, +- 'on_tree_view_snippets_key_press': self.on_tree_view_snippets_key_press} +- +- self.builder.connect_signals(handlers_dic) +- +- self.build_tree_view() +- self.build_model() +- +- image = self['image_remove'] +- image.set_from_stock(Gtk.STOCK_REMOVE, Gtk.IconSize.SMALL_TOOLBAR) ++ piter = self.model.append(None, (_('Global'), '', None, None)) ++ # Add dummy node ++ self.model.append(piter, ('', '', None, None)) + +- source_view = self['source_view_snippet'] +- manager = get_language_manager() +- lang = manager.get_language('snippets') +- +- if lang: +- source_view.get_buffer().set_highlight_syntax(True) +- source_view.get_buffer().set_language(lang) +- self.snippets_doc = Document(None, source_view) +- +- combo = self['combo_drop_targets'] +- +- entry = combo.get_child() +- entry.connect('focus-out-event', self.on_entry_drop_targets_focus_out) +- entry.connect('drag-data-received', self.on_entry_drop_targets_drag_data_received) +- +- lst = entry.drag_dest_get_target_list() +- lst.add_uri_targets(self.TARGET_URI) +- +- self.dlg = self['dialog_snippets'] +- +- if self.default_size: +- self.dlg.set_default_size(*self.default_size) +- +- def __getitem__(self, key): +- return self.builder.get_object(key) +- +- def is_filled(self, piter): +- if not self.model.iter_has_child(piter): +- return True +- +- child = self.model.iter_children(piter) +- nm = self.model.get_value(child, self.NAME_COLUMN) +- lang = self.model.get_value(child, self.LANG_COLUMN) +- snippet = self.model.get_value(child, self.SNIPPET_COLUMN) +- +- return (lang or snippet or nm) +- +- def fill_if_needed(self, piter, expand=True): +- if not self.is_filled(piter): +- self.fill_language(piter, expand) +- +- def find_iter(self, parent, snippet): +- self.fill_if_needed(parent) +- piter = self.model.iter_children(parent) +- +- while (piter): +- node = self.model.get_value(piter, self.SNIPPET_COLUMN) +- +- if node == snippet.data: +- return piter +- +- piter = self.model.iter_next(piter) +- +- return None +- +- def selected_snippets_state(self): +- snippets = self.selected_snippets(False) +- override = False +- remove = False +- system = False +- +- for snippet in snippets: +- if not snippet: +- continue +- +- if snippet.is_override(): +- override = True +- elif snippet.can_modify(): +- remove = True +- else: +- system = True +- +- # No need to continue if both are found +- if override and remove: +- break +- +- return (override, remove, system) +- +- def update_buttons(self): +- button_remove = self['button_remove_snippet'] +- button_new = self['button_new_snippet'] +- image_remove = self['image_remove'] +- +- button_new.set_sensitive(self.language_path != None) +- override, remove, system = self.selected_snippets_state() +- +- if not (override ^ remove) or system: +- button_remove.set_sensitive(False) +- image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) +- else: +- button_remove.set_sensitive(True) +- +- if override: +- image_remove.set_from_stock(Gtk.STOCK_UNDO, Gtk.IconSize.BUTTON) +- tooltip = _('Revert selected snippet') +- else: +- image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) +- tooltip = _('Delete selected snippet') +- +- button_remove.set_tooltip_text(tooltip) +- +- def snippet_changed(self, piter = None): +- if piter: +- node = self.model.get_value(piter, self.SNIPPET_COLUMN) +- s = Snippet(node) +- else: +- s = self.snippet +- piter = self.find_iter(self.model.get_iter(self.language_path), s) ++ nm = None ++ ++ if current_lang: ++ nm = current_lang.get_name() ++ ++ for lang in langs: ++ name = lang.get_name() ++ parent = self.model.append(None, (name, name, lang, None)) ++ ++ # Add dummy node ++ self.model.append(parent, ('', '', None, None)) ++ ++ if (nm == name): ++ expand = parent ++ else: ++ if current_lang: ++ piter = self.model.get_iter_first() ++ nm = current_lang.get_name() ++ ++ while piter: ++ lang = self.model.get_value(piter, \ ++ self.SORT_COLUMN) ++ ++ if lang == nm: ++ expand = piter ++ break; ++ ++ piter = self.model.iter_next(piter) ++ ++ tree_view.set_model(self.model) ++ ++ if not expand: ++ expand = self.model.get_iter_first() ++ ++ tree_view.expand_row(self.model.get_path(expand), False) ++ self.select_iter(expand) ++ ++ def get_cell_data_pixbuf_cb(self, column, cell, model, iter, data): ++ snippet = model.get_value(iter, self.SNIPPET_COLUMN) ++ ++ if snippet and not snippet.valid: ++ cell.set_property('stock-id', Gtk.STOCK_DIALOG_ERROR) ++ else: ++ cell.set_property('stock-id', None) ++ ++ cell.set_property('xalign', 1.0) ++ ++ def get_cell_data_cb(self, column, cell, model, iter, data): ++ snippet = model.get_value(iter, self.SNIPPET_COLUMN) ++ ++ cell.set_property('editable', snippet != None) ++ cell.set_property('markup', model.get_value(iter, self.NAME_COLUMN)) ++ ++ def on_tree_view_drag_data_get(self, widget, context, selection_data, info, time): ++ gfile = Gio.file_new_for_path(self._temp_export) ++ selection_data.set_uris([gfile.get_uri()]) ++ ++ def on_tree_view_drag_begin(self, widget, context): ++ self.dragging = True ++ ++ if self._temp_export: ++ shutil.rmtree(os.path.dirname(self._temp_export)) ++ self._temp_export = None ++ ++ if self.dnd_name: ++ Gtk.drag_set_icon_name(context, self.dnd_name, 0, 0) ++ ++ dirname = tempfile.mkdtemp() ++ filename = os.path.join(dirname, self.default_export_name) ++ ++ # Generate temporary file name ++ self.export_snippets(filename, False) ++ self._temp_export = filename ++ ++ def on_tree_view_drag_end(self, widget, context): ++ self.dragging = False ++ ++ def on_tree_view_drag_data_received(self, widget, context, x, y, selection, info, timestamp): ++ uris = selection.get_uris() ++ ++ self.import_snippets(uris) ++ ++ def on_tree_view_drag_motion(self, widget, context, x, y, timestamp): ++ # Return False if we are dragging ++ if self.dragging: ++ return False ++ ++ # Check uri target ++ if not Gtk.targets_include_uri(context.targets): ++ return False ++ ++ # Check action ++ action = None ++ if context.suggested_action == Gdk.DragAction.COPY: ++ action = Gdk.DragAction.COPY ++ else: ++ for act in context.actions: ++ if act == Gdk.DragAction.COPY: ++ action = Gdk.DragAction.COPY ++ break ++ ++ if action == Gdk.DragAction.COPY: ++ context.drag_status(Gdk.DragAction.COPY, timestamp) ++ return True ++ else: ++ return False ++ ++ def build_dnd(self): ++ tv = self.tree_view ++ ++ # Set it as a drag source for exporting snippets ++ tv.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, self.dnd_target_list, Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) ++ ++ # Set it as a drag destination for importing snippets ++ tv.drag_dest_set(Gtk.DestDefaults.HIGHLIGHT | Gtk.DestDefaults.DROP, ++ self.dnd_target_list, Gdk.DragAction.DEFAULT | Gdk.DragAction.COPY) ++ ++ tv.connect('drag_data_get', self.on_tree_view_drag_data_get) ++ tv.connect('drag_begin', self.on_tree_view_drag_begin) ++ tv.connect('drag_end', self.on_tree_view_drag_end) ++ tv.connect('drag_data_received', self.on_tree_view_drag_data_received) ++ tv.connect('drag_motion', self.on_tree_view_drag_motion) ++ ++ theme = Gtk.IconTheme.get_for_screen(tv.get_screen()) ++ ++ self.dnd_name = None ++ for name in self.drag_icons: ++ icon = theme.lookup_icon(name, Gtk.IconSize.DND, 0) ++ ++ if icon: ++ self.dnd_name = name ++ break ++ ++ def build_tree_view(self): ++ self.tree_view = self['tree_view_snippets'] ++ ++ self.column = Gtk.TreeViewColumn(None) ++ ++ self.renderer = Gtk.CellRendererText() ++ self.column.pack_start(self.renderer, False) ++ self.column.set_cell_data_func(self.renderer, self.get_cell_data_cb, None) ++ ++ renderer = Gtk.CellRendererPixbuf() ++ self.column.pack_start(renderer, True) ++ self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb, None) ++ ++ self.tree_view.append_column(self.column) ++ ++ self.renderer.connect('edited', self.on_cell_edited) ++ self.renderer.connect('editing-started', self.on_cell_editing_started) ++ ++ selection = self.tree_view.get_selection() ++ selection.set_mode(Gtk.SelectionMode.MULTIPLE) ++ selection.connect('changed', self.on_tree_view_selection_changed) ++ ++ self.build_dnd() ++ ++ def build(self): ++ self.builder = Gtk.Builder() ++ self.builder.add_from_file(os.path.join(self.datadir, 'ui', 'snippets.ui')) ++ ++ handlers_dic = { ++ 'on_dialog_snippets_response': self.on_dialog_snippets_response, ++ 'on_dialog_snippets_destroy': self.on_dialog_snippets_destroy, ++ 'on_button_new_snippet_clicked': self.on_button_new_snippet_clicked, ++ 'on_button_import_snippets_clicked': self.on_button_import_snippets_clicked, ++ 'on_button_export_snippets_clicked': self.on_button_export_snippets_clicked, ++ 'on_button_remove_snippet_clicked': self.on_button_remove_snippet_clicked, ++ 'on_entry_tab_trigger_focus_out': self.on_entry_tab_trigger_focus_out, ++ 'on_entry_tab_trigger_changed': self.on_entry_tab_trigger_changed, ++ 'on_entry_accelerator_focus_out': self.on_entry_accelerator_focus_out, ++ 'on_entry_accelerator_focus_in': self.on_entry_accelerator_focus_in, ++ 'on_entry_accelerator_key_press': self.on_entry_accelerator_key_press, ++ 'on_source_view_snippet_focus_out': self.on_source_view_snippet_focus_out, ++ 'on_tree_view_snippets_row_expanded': self.on_tree_view_snippets_row_expanded, ++ 'on_tree_view_snippets_key_press': self.on_tree_view_snippets_key_press} + +- if piter: +- nm = s.display() +- +- self.model.set_value(piter, self.NAME_COLUMN, nm) +- self.model.set_value(piter, self.SORT_COLUMN, nm) +- self.update_buttons() +- self.entry_tab_trigger_update_valid() ++ self.builder.connect_signals(handlers_dic) + ++ self.build_tree_view() ++ self.build_model() ++ ++ image = self['image_remove'] ++ image.set_from_stock(Gtk.STOCK_REMOVE, Gtk.IconSize.SMALL_TOOLBAR) ++ ++ source_view = self['source_view_snippet'] ++ manager = get_language_manager() ++ lang = manager.get_language('snippets') ++ ++ if lang: ++ source_view.get_buffer().set_highlight_syntax(True) ++ source_view.get_buffer().set_language(lang) ++ self.snippets_doc = Document(None, source_view) ++ ++ combo = self['combo_drop_targets'] ++ ++ entry = combo.get_child() ++ entry.connect('focus-out-event', self.on_entry_drop_targets_focus_out) ++ entry.connect('drag-data-received', self.on_entry_drop_targets_drag_data_received) ++ ++ lst = entry.drag_dest_get_target_list() ++ lst.add_uri_targets(self.TARGET_URI) ++ ++ self.dlg = self['dialog_snippets'] ++ ++ if self.default_size: ++ self.dlg.set_default_size(*self.default_size) ++ ++ def __getitem__(self, key): ++ return self.builder.get_object(key) ++ ++ def is_filled(self, piter): ++ if not self.model.iter_has_child(piter): ++ return True ++ ++ child = self.model.iter_children(piter) ++ nm = self.model.get_value(child, self.NAME_COLUMN) ++ lang = self.model.get_value(child, self.LANG_COLUMN) ++ snippet = self.model.get_value(child, self.SNIPPET_COLUMN) ++ ++ return (lang or snippet or nm) ++ ++ def fill_if_needed(self, piter, expand=True): ++ if not self.is_filled(piter): ++ self.fill_language(piter, expand) ++ ++ def find_iter(self, parent, snippet): ++ self.fill_if_needed(parent) ++ piter = self.model.iter_children(parent) ++ ++ while (piter): ++ node = self.model.get_value(piter, self.SNIPPET_COLUMN) ++ ++ if node == snippet.data: + return piter + +- def add_snippet(self, parent, snippet): +- piter = self.model.append(parent, ('', '', None, snippet)) +- +- return self.snippet_changed(piter) ++ piter = self.model.iter_next(piter) + +- def run(self): +- if not self.dlg: +- self.build() +- self.dlg.show() ++ return None ++ ++ def selected_snippets_state(self): ++ snippets = self.selected_snippets(False) ++ override = False ++ remove = False ++ system = False ++ ++ for snippet in snippets: ++ if not snippet: ++ continue ++ ++ if snippet.is_override(): ++ override = True ++ elif snippet.can_modify(): ++ remove = True ++ else: ++ system = True ++ ++ # No need to continue if both are found ++ if override and remove: ++ break ++ ++ return (override, remove, system) ++ ++ def update_buttons(self): ++ button_remove = self['button_remove_snippet'] ++ button_new = self['button_new_snippet'] ++ image_remove = self['image_remove'] ++ ++ button_new.set_sensitive(self.language_path != None) ++ override, remove, system = self.selected_snippets_state() ++ ++ if not (override ^ remove) or system: ++ button_remove.set_sensitive(False) ++ image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) ++ else: ++ button_remove.set_sensitive(True) ++ ++ if override: ++ image_remove.set_from_stock(Gtk.STOCK_UNDO, Gtk.IconSize.BUTTON) ++ tooltip = _('Revert selected snippet') ++ else: ++ image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) ++ tooltip = _('Delete selected snippet') ++ ++ button_remove.set_tooltip_text(tooltip) ++ ++ def snippet_changed(self, piter = None): ++ if piter: ++ node = self.model.get_value(piter, self.SNIPPET_COLUMN) ++ s = Snippet(node) ++ else: ++ s = self.snippet ++ piter = self.find_iter(self.model.get_iter(self.language_path), s) ++ ++ if piter: ++ nm = s.display() ++ ++ self.model.set_value(piter, self.NAME_COLUMN, nm) ++ self.model.set_value(piter, self.SORT_COLUMN, nm) ++ self.update_buttons() ++ self.entry_tab_trigger_update_valid() ++ ++ return piter ++ ++ def add_snippet(self, parent, snippet): ++ piter = self.model.append(parent, ('', '', None, snippet)) ++ ++ return self.snippet_changed(piter) ++ ++ def run(self): ++ if not self.dlg: ++ self.build() ++ self.dlg.show() ++ else: ++ self.build_model() ++ self.dlg.present() ++ ++ def snippet_from_iter(self, model, piter): ++ parent = model.iter_parent(piter) ++ ++ if parent: ++ return model.get_value(piter, self.SNIPPET_COLUMN) ++ else: ++ return None ++ ++ def language_snippets(self, model, parent, as_path=False): ++ self.fill_if_needed(parent, False) ++ piter = model.iter_children(parent) ++ snippets = [] ++ ++ if not piter: ++ return snippets ++ ++ while piter: ++ snippet = self.snippet_from_iter(model, piter) ++ ++ if snippet: ++ if as_path: ++ snippets.append(model.get_path(piter)) + else: +- self.build_model() +- self.dlg.present() ++ snippets.append(snippet) ++ ++ piter = model.iter_next(piter) ++ ++ return snippets + +- def snippet_from_iter(self, model, piter): ++ def selected_snippets(self, include_languages=True, as_path=False): ++ selection = self.tree_view.get_selection() ++ (model, paths) = selection.get_selected_rows() ++ snippets = [] ++ ++ if paths and len(paths) != 0: ++ for p in paths: ++ piter = model.get_iter(p) + parent = model.iter_parent(piter) +- +- if parent: +- return model.get_value(piter, self.SNIPPET_COLUMN) +- else: +- return None +- +- def language_snippets(self, model, parent, as_path=False): +- self.fill_if_needed(parent, False) +- piter = model.iter_children(parent) +- snippets = [] +- ++ + if not piter: +- return snippets +- +- while piter: +- snippet = self.snippet_from_iter(model, piter) +- +- if snippet: +- if as_path: +- snippets.append(model.get_path(piter)) +- else: +- snippets.append(snippet) +- +- piter = model.iter_next(piter) +- +- return snippets +- +- def selected_snippets(self, include_languages=True, as_path=False): +- selection = self.tree_view.get_selection() +- (model, paths) = selection.get_selected_rows() +- snippets = [] +- +- if paths and len(paths) != 0: +- for p in paths: +- piter = model.get_iter(p) +- parent = model.iter_parent(piter) +- +- if not piter: +- continue +- +- if parent: +- snippet = self.snippet_from_iter(model, piter) +- +- if not snippet: +- continue +- +- if as_path: +- snippets.append(p) +- else: +- snippets.append(snippet) +- elif include_languages: +- snippets += self.language_snippets(model, piter, as_path) +- +- return snippets +- +- def selected_snippet(self): +- selection = self.tree_view.get_selection() +- (model, paths) = selection.get_selected_rows() +- +- if len(paths) == 1: +- piter = model.get_iter(paths[0]) +- parent = model.iter_parent(piter) +- snippet = self.snippet_from_iter(model, piter) +- +- return parent, piter, snippet +- else: +- return None, None, None ++ continue + +- def selection_changed(self): +- if not self.snippet: +- sens = False ++ if parent: ++ snippet = self.snippet_from_iter(model, piter) + +- self['entry_tab_trigger'].set_text('') +- self['entry_accelerator'].set_text('') +- buf = self['source_view_snippet'].get_buffer() +- buf.begin_not_undoable_action() +- buf.set_text('') +- buf.end_not_undoable_action() +- self['combo_drop_targets'].get_child().set_text('') ++ if not snippet: ++ continue + ++ if as_path: ++ snippets.append(p) ++ else: ++ snippets.append(snippet) ++ elif include_languages: ++ snippets += self.language_snippets(model, piter, as_path) ++ ++ return snippets ++ ++ def selected_snippet(self): ++ selection = self.tree_view.get_selection() ++ (model, paths) = selection.get_selected_rows() ++ ++ if len(paths) == 1: ++ piter = model.get_iter(paths[0]) ++ parent = model.iter_parent(piter) ++ snippet = self.snippet_from_iter(model, piter) ++ ++ return parent, piter, snippet ++ else: ++ return None, None, None ++ ++ def selection_changed(self): ++ if not self.snippet: ++ sens = False ++ ++ self['entry_tab_trigger'].set_text('') ++ self['entry_accelerator'].set_text('') ++ buf = self['source_view_snippet'].get_buffer() ++ buf.begin_not_undoable_action() ++ buf.set_text('') ++ buf.end_not_undoable_action() ++ self['combo_drop_targets'].get_child().set_text('') ++ ++ else: ++ sens = True ++ ++ self['entry_tab_trigger'].set_text(self.snippet['tag']) ++ self['entry_accelerator'].set_text( \ ++ self.snippet.accelerator_display()) ++ self['combo_drop_targets'].get_child().set_text(', '.join(self.snippet['drop-targets'])) ++ ++ buf = self['source_view_snippet'].get_buffer() ++ buf.begin_not_undoable_action() ++ buf.set_text(self.snippet['text']) ++ buf.end_not_undoable_action() ++ ++ ++ for name in ['source_view_snippet', 'label_tab_trigger', ++ 'entry_tab_trigger', 'label_accelerator', ++ 'entry_accelerator', 'label_drop_targets', ++ 'combo_drop_targets']: ++ self[name].set_sensitive(sens) ++ ++ self.update_buttons() ++ ++ def select_iter(self, piter, unselect=True): ++ selection = self.tree_view.get_selection() ++ ++ if unselect: ++ selection.unselect_all() ++ ++ selection.select_iter(piter) ++ ++ self.tree_view.scroll_to_cell(self.model.get_path(piter), None, \ ++ True, 0.5, 0.5) ++ ++ def get_language(self, path): ++ if path.get_indices()[0] == 0: ++ return None ++ else: ++ return self.model.get_value(self.model.get_iter(path), \ ++ self.LANG_COLUMN).get_id() ++ ++ def new_snippet(self, properties=None): ++ if not self.language_path: ++ return None ++ ++ snippet = Library().new_snippet(self.get_language(self.language_path), properties) ++ ++ return Snippet(snippet) ++ ++ def get_dummy(self, parent): ++ if not self.model.iter_n_children(parent) == 1: ++ return None ++ ++ dummy = self.model.iter_children(parent) ++ ++ if not self.model.get_value(dummy, self.SNIPPET_COLUMN): ++ return dummy ++ ++ return None ++ ++ def unref_languages(self): ++ piter = self.model.get_iter_first() ++ library = Library() ++ ++ while piter: ++ if self.is_filled(piter): ++ language = self.get_language(self.model.get_path(piter)) ++ library.save(language) ++ ++ library.unref(language) ++ ++ piter = self.model.iter_next(piter) ++ ++ # Callbacks ++ def on_dialog_snippets_destroy(self, dlg): ++ # Remove temporary drag export ++ if self._temp_export: ++ shutil.rmtree(os.path.dirname(self._temp_export)) ++ self._temp_export = None ++ ++ if self.snippets_doc: ++ self.snippets_doc.stop() ++ ++ self.manager = None ++ self.unref_languages() ++ self.snippet = None ++ self.model = None ++ self.dlg = None ++ ++ def on_dialog_snippets_response(self, dlg, resp): ++ ++ alloc = dlg.get_allocation() ++ self.default_size = [alloc.width, alloc.height] ++ ++ if resp == Gtk.ResponseType.HELP: ++ Pluma.help_display(self, 'pluma', 'pluma-snippets-plugin') ++ return ++ ++ self.dlg.destroy() ++ ++ def on_cell_editing_started(self, renderer, editable, path): ++ piter = self.model.get_iter(path) ++ ++ if not self.model.iter_parent(piter): ++ renderer.stop_editing(True) ++ editable.remove_widget() ++ elif isinstance(editable, Gtk.Entry): ++ if self.snippet: ++ editable.set_text(self.snippet['description']) ++ else: ++ # This is the `Add a new snippet...` item ++ editable.set_text('') ++ ++ editable.grab_focus() ++ ++ def on_cell_edited(self, cell, path, new_text): ++ if new_text != '': ++ piter = self.model.get_iter(path) ++ node = self.model.get_value(piter, self.SNIPPET_COLUMN) ++ ++ if node: ++ if node == self.snippet.data: ++ s = self.snippet + else: +- sens = True +- +- self['entry_tab_trigger'].set_text(self.snippet['tag']) +- self['entry_accelerator'].set_text( \ +- self.snippet.accelerator_display()) +- self['combo_drop_targets'].get_child().set_text(', '.join(self.snippet['drop-targets'])) +- +- buf = self['source_view_snippet'].get_buffer() +- buf.begin_not_undoable_action() +- buf.set_text(self.snippet['text']) +- buf.end_not_undoable_action() +- +- +- for name in ['source_view_snippet', 'label_tab_trigger', +- 'entry_tab_trigger', 'label_accelerator', +- 'entry_accelerator', 'label_drop_targets', +- 'combo_drop_targets']: +- self[name].set_sensitive(sens) +- +- self.update_buttons() +- +- def select_iter(self, piter, unselect=True): +- selection = self.tree_view.get_selection() +- +- if unselect: +- selection.unselect_all() +- +- selection.select_iter(piter) +- +- self.tree_view.scroll_to_cell(self.model.get_path(piter), None, \ +- True, 0.5, 0.5) +- +- def get_language(self, path): +- if path.get_indices()[0] == 0: +- return None +- else: +- return self.model.get_value(self.model.get_iter(path), \ +- self.LANG_COLUMN).get_id() +- +- def new_snippet(self, properties=None): +- if not self.language_path: +- return None +- +- snippet = Library().new_snippet(self.get_language(self.language_path), properties) +- +- return Snippet(snippet) +- +- def get_dummy(self, parent): +- if not self.model.iter_n_children(parent) == 1: +- return None +- +- dummy = self.model.iter_children(parent) +- +- if not self.model.get_value(dummy, self.SNIPPET_COLUMN): +- return dummy +- +- return None +- +- def unref_languages(self): +- piter = self.model.get_iter_first() +- library = Library() +- +- while piter: +- if self.is_filled(piter): +- language = self.get_language(self.model.get_path(piter)) +- library.save(language) +- +- library.unref(language) +- +- piter = self.model.iter_next(piter) +- +- # Callbacks +- def on_dialog_snippets_destroy(self, dlg): +- # Remove temporary drag export +- if self._temp_export: +- shutil.rmtree(os.path.dirname(self._temp_export)) +- self._temp_export = None +- +- if self.snippets_doc: +- self.snippets_doc.stop() +- +- self.manager = None +- self.unref_languages() +- self.snippet = None +- self.model = None +- self.dlg = None +- +- def on_dialog_snippets_response(self, dlg, resp): +- +- alloc = dlg.get_allocation() +- self.default_size = [alloc.width, alloc.height] +- +- if resp == Gtk.ResponseType.HELP: +- Pluma.help_display(self, 'pluma', 'pluma-snippets-plugin') +- return +- +- self.dlg.destroy() +- +- def on_cell_editing_started(self, renderer, editable, path): +- piter = self.model.get_iter(path) +- +- if not self.model.iter_parent(piter): +- renderer.stop_editing(True) +- editable.remove_widget() +- elif isinstance(editable, Gtk.Entry): +- if self.snippet: +- editable.set_text(self.snippet['description']) +- else: +- # This is the `Add a new snippet...` item +- editable.set_text('') +- +- editable.grab_focus() +- +- def on_cell_edited(self, cell, path, new_text): +- if new_text != '': +- piter = self.model.get_iter(path) +- node = self.model.get_value(piter, self.SNIPPET_COLUMN) +- +- if node: +- if node == self.snippet.data: +- s = self.snippet +- else: +- s = Snippet(node) +- +- s['description'] = new_text +- self.snippet_changed(piter) +- self.select_iter(piter) +- else: +- # This is the `Add a new snippet...` item +- # We create a new snippet +- snippet = self.new_snippet({'description': new_text}) +- +- if snippet: +- self.model.set_value(piter, self.SNIPPET_COLUMN, snippet.data) +- self.snippet_changed(piter) +- self.snippet = snippet +- self.selection_changed() +- +- def on_entry_accelerator_focus_out(self, entry, event): +- if not self.snippet: +- return +- +- entry.set_text(self.snippet.accelerator_display()) +- +- def entry_tab_trigger_update_valid(self): +- entry = self['entry_tab_trigger'] +- text = entry.get_text() +- +- if text and not Library().valid_tab_trigger(text): +- img = self['image_tab_trigger'] +- img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.BUTTON) +- img.show() +- +- #self['hbox_tab_trigger'].set_spacing(3) +- tip = _('This is not a valid Tab trigger. Triggers can either contain letters or a single (non-alphanumeric) character like: {, [, etc.') +- +- entry.set_tooltip_text(tip) +- img.set_tooltip_text(tip) +- else: +- self['image_tab_trigger'].hide() +- #self['hbox_tab_trigger'].set_spacing(0) +- entry.set_tooltip_text(_('Single word the snippet is activated with after pressing Tab')) +- +- return False ++ s = Snippet(node) + +- def on_entry_tab_trigger_focus_out(self, entry, event): +- if not self.snippet: +- return +- +- text = entry.get_text() +- +- # save tag +- self.snippet['tag'] = text +- self.snippet_changed() +- +- def on_entry_drop_targets_focus_out(self, entry, event): +- if not self.snippet: +- return +- +- text = entry.get_text() +- +- # save drop targets +- self.snippet['drop-targets'] = text +- self.snippet_changed() +- +- def on_entry_tab_trigger_changed(self, entry): +- self.entry_tab_trigger_update_valid() +- +- def on_source_view_snippet_focus_out(self, source_view, event): +- if not self.snippet: +- return +- +- buf = source_view.get_buffer() +- text = buf.get_text(buf.get_start_iter(), \ +- buf.get_end_iter(), False) +- +- self.snippet['text'] = text +- self.snippet_changed() +- +- def on_button_new_snippet_clicked(self, button): +- snippet = self.new_snippet() +- +- if not snippet: +- return +- +- parent = self.model.get_iter(self.language_path) +- path = self.model.get_path(parent) +- +- dummy = self.get_dummy(parent) +- +- if dummy: +- # Remove the dummy +- self.model.remove(dummy) +- +- # Add the snippet +- piter = self.add_snippet(parent, snippet.data) ++ s['description'] = new_text ++ self.snippet_changed(piter) + self.select_iter(piter) ++ else: ++ # This is the `Add a new snippet...` item ++ # We create a new snippet ++ snippet = self.new_snippet({'description': new_text}) + +- if not self.tree_view.row_expanded(path): +- self.tree_view.expand_row(path, False) +- self.select_iter(piter) +- +- self.tree_view.grab_focus() +- +- path = self.model.get_path(piter) +- self.tree_view.set_cursor(path, self.column, True) +- +- def file_filter(self, name, pattern): +- fil = Gtk.FileFilter() +- fil.set_name(name) +- +- for p in pattern: +- fil.add_pattern(p) +- +- return fil +- +- def import_snippets(self, filenames): +- success = True +- +- for filename in filenames: +- if not Pluma.utils_uri_has_file_scheme(filename): +- continue +- +- # Remove file:// +- gfile = Gio.file_new_for_uri(filename) +- filename = gfile.get_path() +- +- importer = Importer(filename) +- error = importer.run() +- +- if error: +- message = _('The following error occurred while importing: %s') % error +- success = False +- message_dialog(self.dlg, Gtk.MessageType.ERROR, message) +- +- self.build_model(True) +- +- if success: +- message = _('Import successfully completed') +- message_dialog(self.dlg, Gtk.MessageType.INFO, message) +- +- def on_import_response(self, dialog, response): +- if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.CLOSE: +- dialog.destroy() +- return +- +- f = dialog.get_uris() +- dialog.destroy() +- +- self.import_snippets(f) +- +- def on_button_import_snippets_clicked(self, button): +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_("Import snippets"), +- action=Gtk.FileChooserAction.OPEN, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) +- +- dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml'))) +- dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) +- dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) +- dlg.add_filter(self.file_filter(_('Single snippets file'), ('*.xml',))) +- dlg.add_filter(self.file_filter(_('All files'), '*')) +- +- dlg.connect('response', self.on_import_response) +- dlg.set_local_only(True) +- +- dlg.show() +- +- def export_snippets_real(self, filename, snippets, show_dialogs=True): +- export = Exporter(filename, snippets) +- error = export.run() +- +- if error: +- message = _('The following error occurred while exporting: %s') % error +- msgtype = Gtk.MessageType.ERROR +- retval = False +- else: +- message = _('Export successfully completed') +- msgtype = Gtk.MessageType.INFO +- retval = True +- +- if show_dialogs: +- message_dialog(self.dlg, msgtype, message) +- +- return retval +- +- def on_export_response(self, dialog, response): +- filename = dialog.get_filename() +- snippets = dialog._export_snippets +- +- dialog.destroy() +- +- if response != Gtk.ResponseType.OK: +- return +- +- self.export_snippets_real(filename, snippets); +- +- def export_snippets(self, filename=None, show_dialogs=True): +- snippets = self.selected_snippets() +- +- if not snippets or len(snippets) == 0: +- return False +- +- usersnippets = [] +- systemsnippets = [] +- +- # Iterate through snippets and look for system snippets +- for snippet in snippets: +- if snippet.can_modify(): +- usersnippets.append(snippet) +- else: +- systemsnippets.append(snippet) +- +- export_snippets = snippets +- +- if len(systemsnippets) != 0 and show_dialogs: +- # Ask if system snippets should also be exported +- message = _('Do you want to include selected system snippets in your export?') +- mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL, +- type=Gtk.MessageType.QUESTION, +- buttons=Gtk.ButtonsType.YES_NO, +- message_format=message) +- mes.set_property('use-markup', True) +- resp = mes.run() +- mes.destroy() +- +- if resp == Gtk.ResponseType.NO: +- export_snippets = usersnippets +- elif resp != Gtk.ResponseType.YES: +- return False +- +- if len(export_snippets) == 0 and show_dialogs: +- message = _('There are no snippets selected to be exported') +- message_dialog(self.dlg, Gtk.MessageType.INFO, message) +- return False +- +- if not filename: +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), +- action=Gtk.FileChooserAction.SAVE, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) +- +- dlg._export_snippets = export_snippets +- dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) +- dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) +- dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) +- +- dlg.add_filter(self.file_filter(_('All files'), '*')) +- dlg.set_do_overwrite_confirmation(True) +- dlg.set_current_name(self.default_export_name) +- +- dlg.connect('response', self.on_export_response) +- dlg.set_local_only(True) +- +- dlg.show() +- return True +- else: +- return self.export_snippets_real(filename, export_snippets, show_dialogs) +- +- def on_button_export_snippets_clicked(self, button): +- snippets = self.selected_snippets() +- +- if not snippets or len(snippets) == 0: +- return +- +- usersnippets = [] +- systemsnippets = [] +- +- # Iterate through snippets and look for system snippets +- for snippet in snippets: +- if snippet.can_modify(): +- usersnippets.append(snippet) +- else: +- systemsnippets.append(snippet) +- +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), +- action=Gtk.FileChooserAction.SAVE, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) +- +- dlg._export_snippets = snippets +- +- if len(systemsnippets) != 0: +- # Ask if system snippets should also be exported +- message = _('Do you want to include selected system snippets in your export?') +- mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL, +- type=Gtk.MessageType.QUESTION, +- buttons=Gtk.ButtonsType.YES_NO, +- message_format=message) +- mes.set_property('use-markup', True) +- resp = mes.run() +- mes.destroy() +- +- if resp == Gtk.ResponseType.NO: +- dlg._export_snippets = usersnippets +- elif resp != Gtk.ResponseType.YES: +- dlg.destroy() +- return +- +- if len(dlg._export_snippets) == 0: +- dlg.destroy() +- +- message = _('There are no snippets selected to be exported') +- message_dialog(self.dlg, Gtk.MessageType.INFO, message) +- return +- +- dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) +- dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) +- dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) +- +- dlg.add_filter(self.file_filter(_('All files'), '*')) +- dlg.set_do_overwrite_confirmation(True) +- dlg.set_current_name(self.default_export_name) +- +- dlg.connect('response', self.on_export_response) +- dlg.set_local_only(True) +- +- dlg.show() +- +- def remove_snippet_revert(self, path, piter): +- node = self.snippet_from_iter(self.model, piter) +- Library().revert_snippet(node) +- +- return piter +- +- def remove_snippet_delete(self, path, piter): +- node = self.snippet_from_iter(self.model, piter) +- parent = self.model.iter_parent(piter) +- +- Library().remove_snippet(node) +- idx = path.get_indices() +- +- if self.model.remove(piter): +- return piter +- elif idx[-1] != 0: +- self.select_iter(self.model.get_iter((idx[0], idx[1] - 1))) +- else: +- dummy = self.add_new_snippet_node(parent) +- self.tree_view.expand_row(self.model.get_path(parent), False) +- return dummy +- +- def on_button_remove_snippet_clicked(self, button): +- override, remove, system = self.selected_snippets_state() +- +- if not (override ^ remove) or system: +- return +- +- paths = self.selected_snippets(include_languages=False, as_path=True) +- +- if override: +- action = self.remove_snippet_revert +- else: +- action = self.remove_snippet_delete +- +- # Remove selection +- self.tree_view.get_selection().unselect_all() +- +- # Create tree row references +- references = [] +- for path in paths: +- references.append(Gtk.TreeRowReference(self.model, path)) +- +- # Remove/revert snippets +- select = None +- for reference in references: +- path = reference.get_path() +- piter = self.model.get_iter(path) +- +- res = action(path, piter) +- +- if res: +- select = res +- +- if select: +- self.select_iter(select) +- +- self.selection_changed() +- +- def set_accelerator(self, keyval, mod): +- accelerator = Gtk.accelerator_name(keyval, mod) +- self.snippet['accelerator'] = accelerator +- +- return True +- +- def on_entry_accelerator_key_press(self, entry, event): +- source_view = self['source_view_snippet'] ++ if snippet: ++ self.model.set_value(piter, self.SNIPPET_COLUMN, snippet.data) ++ self.snippet_changed(piter) ++ self.snippet = snippet ++ self.selection_changed() + +- if event.keyval == Gdk.keyval_from_name('Escape'): +- # Reset +- entry.set_text(self.snippet.accelerator_display()) +- self.tree_view.grab_focus() +- +- return True +- elif event.keyval == Gdk.keyval_from_name('Delete') or \ +- event.keyval == Gdk.keyval_from_name('BackSpace'): +- # Remove the accelerator +- entry.set_text('') +- self.snippet['accelerator'] = '' +- self.tree_view.grab_focus() +- +- self.snippet_changed() +- return True +- elif Library().valid_accelerator(event.keyval, event.state): +- # New accelerator +- self.set_accelerator(event.keyval, \ +- event.state & Gtk.accelerator_get_default_mod_mask()) +- entry.set_text(self.snippet.accelerator_display()) +- self.snippet_changed() +- self.tree_view.grab_focus() ++ def on_entry_accelerator_focus_out(self, entry, event): ++ if not self.snippet: ++ return + +- else: +- return True +- +- def on_entry_accelerator_focus_in(self, entry, event): +- if self.snippet['accelerator']: +- entry.set_text(_('Type a new shortcut, or press Backspace to clear')) +- else: +- entry.set_text(_('Type a new shortcut')) +- +- def update_language_path(self): +- model, paths = self.tree_view.get_selection().get_selected_rows() +- +- # Check if all have the same language parent +- current_parent = None ++ entry.set_text(self.snippet.accelerator_display()) + +- for path in paths: +- piter = model.get_iter(path) +- parent = model.iter_parent(piter) +- +- if parent: +- path = model.get_path(parent) +- +- if current_parent != None and current_parent != path: +- current_parent = None +- break +- else: +- current_parent = path +- +- self.language_path = current_parent +- +- def on_tree_view_selection_changed(self, selection): +- parent, piter, node = self.selected_snippet() +- +- if self.snippet: +- self.on_entry_tab_trigger_focus_out(self['entry_tab_trigger'], +- None) +- self.on_source_view_snippet_focus_out(self['source_view_snippet'], +- None) +- self.on_entry_drop_targets_focus_out(self['combo_drop_targets'].get_child(), +- None) +- +- self.update_language_path() +- +- if node: +- self.snippet = Snippet(node) +- else: +- self.snippet = None ++ def entry_tab_trigger_update_valid(self): ++ entry = self['entry_tab_trigger'] ++ text = entry.get_text() ++ ++ if text and not Library().valid_tab_trigger(text): ++ img = self['image_tab_trigger'] ++ img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.BUTTON) ++ img.show() ++ ++ #self['hbox_tab_trigger'].set_spacing(3) ++ tip = _('This is not a valid Tab trigger. Triggers can either contain letters or a single (non-alphanumeric) character like: {, [, etc.') ++ ++ entry.set_tooltip_text(tip) ++ img.set_tooltip_text(tip) ++ else: ++ self['image_tab_trigger'].hide() ++ #self['hbox_tab_trigger'].set_spacing(0) ++ entry.set_tooltip_text(_('Single word the snippet is activated with after pressing Tab')) ++ ++ return False ++ ++ def on_entry_tab_trigger_focus_out(self, entry, event): ++ if not self.snippet: ++ return ++ ++ text = entry.get_text() ++ ++ # save tag ++ self.snippet['tag'] = text ++ self.snippet_changed() ++ ++ def on_entry_drop_targets_focus_out(self, entry, event): ++ if not self.snippet: ++ return ++ ++ text = entry.get_text() ++ ++ # save drop targets ++ self.snippet['drop-targets'] = text ++ self.snippet_changed() ++ ++ def on_entry_tab_trigger_changed(self, entry): ++ self.entry_tab_trigger_update_valid() ++ ++ def on_source_view_snippet_focus_out(self, source_view, event): ++ if not self.snippet: ++ return ++ ++ buf = source_view.get_buffer() ++ text = buf.get_text(buf.get_start_iter(), \ ++ buf.get_end_iter(), False) ++ ++ self.snippet['text'] = text ++ self.snippet_changed() ++ ++ def on_button_new_snippet_clicked(self, button): ++ snippet = self.new_snippet() ++ ++ if not snippet: ++ return ++ ++ parent = self.model.get_iter(self.language_path) ++ path = self.model.get_path(parent) ++ ++ dummy = self.get_dummy(parent) ++ ++ if dummy: ++ # Remove the dummy ++ self.model.remove(dummy) ++ ++ # Add the snippet ++ piter = self.add_snippet(parent, snippet.data) ++ self.select_iter(piter) ++ ++ if not self.tree_view.row_expanded(path): ++ self.tree_view.expand_row(path, False) ++ self.select_iter(piter) ++ ++ self.tree_view.grab_focus() ++ ++ path = self.model.get_path(piter) ++ self.tree_view.set_cursor(path, self.column, True) ++ ++ def file_filter(self, name, pattern): ++ fil = Gtk.FileFilter() ++ fil.set_name(name) ++ ++ for p in pattern: ++ fil.add_pattern(p) ++ ++ return fil ++ ++ def import_snippets(self, filenames): ++ success = True ++ ++ for filename in filenames: ++ if not Pluma.utils_uri_has_file_scheme(filename): ++ continue ++ ++ # Remove file:// ++ gfile = Gio.file_new_for_uri(filename) ++ filename = gfile.get_path() ++ ++ importer = Importer(filename) ++ error = importer.run() ++ ++ if error: ++ message = _('The following error occurred while importing: %s') % error ++ success = False ++ message_dialog(self.dlg, Gtk.MessageType.ERROR, message) ++ ++ self.build_model(True) ++ ++ if success: ++ message = _('Import successfully completed') ++ message_dialog(self.dlg, Gtk.MessageType.INFO, message) + +- self.selection_changed() ++ def on_import_response(self, dialog, response): ++ if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.CLOSE: ++ dialog.destroy() ++ return + +- def iter_after(self, target, after): +- if not after: +- return True ++ f = dialog.get_uris() ++ dialog.destroy() + +- tp = self.model.get_path(target) +- ap = self.model.get_path(after) +- +- if tp[0] > ap[0] or (tp[0] == ap[0] and (len(ap) == 1 or tp[1] > ap[1])): +- return True +- ++ self.import_snippets(f) ++ ++ def on_button_import_snippets_clicked(self, button): ++ dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_("Import snippets"), ++ action=Gtk.FileChooserAction.OPEN, ++ buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, ++ Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) ++ ++ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml'))) ++ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) ++ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) ++ dlg.add_filter(self.file_filter(_('Single snippets file'), ('*.xml',))) ++ dlg.add_filter(self.file_filter(_('All files'), '*')) ++ ++ dlg.connect('response', self.on_import_response) ++ dlg.set_local_only(True) ++ ++ dlg.show() ++ ++ def export_snippets_real(self, filename, snippets, show_dialogs=True): ++ export = Exporter(filename, snippets) ++ error = export.run() ++ ++ if error: ++ message = _('The following error occurred while exporting: %s') % error ++ msgtype = Gtk.MessageType.ERROR ++ retval = False ++ else: ++ message = _('Export successfully completed') ++ msgtype = Gtk.MessageType.INFO ++ retval = True ++ ++ if show_dialogs: ++ message_dialog(self.dlg, msgtype, message) ++ ++ return retval ++ ++ def on_export_response(self, dialog, response): ++ filename = dialog.get_filename() ++ snippets = dialog._export_snippets ++ ++ dialog.destroy() ++ ++ if response != Gtk.ResponseType.OK: ++ return ++ ++ self.export_snippets_real(filename, snippets); ++ ++ def export_snippets(self, filename=None, show_dialogs=True): ++ snippets = self.selected_snippets() ++ ++ if not snippets or len(snippets) == 0: ++ return False ++ ++ usersnippets = [] ++ systemsnippets = [] ++ ++ # Iterate through snippets and look for system snippets ++ for snippet in snippets: ++ if snippet.can_modify(): ++ usersnippets.append(snippet) ++ else: ++ systemsnippets.append(snippet) ++ ++ export_snippets = snippets ++ ++ if len(systemsnippets) != 0 and show_dialogs: ++ # Ask if system snippets should also be exported ++ message = _('Do you want to include selected system snippets in your export?') ++ mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL, ++ type=Gtk.MessageType.QUESTION, ++ buttons=Gtk.ButtonsType.YES_NO, ++ message_format=message) ++ mes.set_property('use-markup', True) ++ resp = mes.run() ++ mes.destroy() ++ ++ if resp == Gtk.ResponseType.NO: ++ export_snippets = usersnippets ++ elif resp != Gtk.ResponseType.YES: + return False +- +- def on_tree_view_snippets_key_press(self, treeview, event): +- if event.keyval == Gdk.keyval_from_name('Delete'): +- self.on_button_remove_snippet_clicked(None) +- return True +- +- def on_tree_view_snippets_row_expanded(self, treeview, piter, path): +- # Check if it is already filled +- self.fill_if_needed(piter) +- self.select_iter(piter) +- +- def on_entry_drop_targets_drag_data_received(self, entry, context, x, y, selection_data, info, timestamp): +- uris = drop_get_uris(selection_data) +- +- if not uris: +- return +- +- if entry.get_text(): +- mimes = [entry.get_text()] +- else: +- mimes = [] +- +- for uri in uris: +- try: +- mime = Gio.content_type_guess(uri) +- except: +- mime = None +- +- if mime: +- mimes.append(mime) +- +- entry.set_text(', '.join(mimes)) +- self.on_entry_drop_targets_focus_out(entry, None) +- context.finish(True, False, timestamp) +- +- entry.stop_emission('drag_data_received') +-# ex:ts=8:et: ++ ++ if len(export_snippets) == 0 and show_dialogs: ++ message = _('There are no snippets selected to be exported') ++ message_dialog(self.dlg, Gtk.MessageType.INFO, message) ++ return False ++ ++ if not filename: ++ dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), ++ action=Gtk.FileChooserAction.SAVE, ++ buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, ++ Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) ++ ++ dlg._export_snippets = export_snippets ++ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) ++ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) ++ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) ++ ++ dlg.add_filter(self.file_filter(_('All files'), '*')) ++ dlg.set_do_overwrite_confirmation(True) ++ dlg.set_current_name(self.default_export_name) ++ ++ dlg.connect('response', self.on_export_response) ++ dlg.set_local_only(True) ++ ++ dlg.show() ++ return True ++ else: ++ return self.export_snippets_real(filename, export_snippets, show_dialogs) ++ ++ def on_button_export_snippets_clicked(self, button): ++ snippets = self.selected_snippets() ++ ++ if not snippets or len(snippets) == 0: ++ return ++ ++ usersnippets = [] ++ systemsnippets = [] ++ ++ # Iterate through snippets and look for system snippets ++ for snippet in snippets: ++ if snippet.can_modify(): ++ usersnippets.append(snippet) ++ else: ++ systemsnippets.append(snippet) ++ ++ dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), ++ action=Gtk.FileChooserAction.SAVE, ++ buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, ++ Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) ++ ++ dlg._export_snippets = snippets ++ ++ if len(systemsnippets) != 0: ++ # Ask if system snippets should also be exported ++ message = _('Do you want to include selected system snippets in your export?') ++ mes = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL, ++ type=Gtk.MessageType.QUESTION, ++ buttons=Gtk.ButtonsType.YES_NO, ++ message_format=message) ++ mes.set_property('use-markup', True) ++ resp = mes.run() ++ mes.destroy() ++ ++ if resp == Gtk.ResponseType.NO: ++ dlg._export_snippets = usersnippets ++ elif resp != Gtk.ResponseType.YES: ++ dlg.destroy() ++ return ++ ++ if len(dlg._export_snippets) == 0: ++ dlg.destroy() ++ ++ message = _('There are no snippets selected to be exported') ++ message_dialog(self.dlg, Gtk.MessageType.INFO, message) ++ return ++ ++ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) ++ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) ++ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',))) ++ ++ dlg.add_filter(self.file_filter(_('All files'), '*')) ++ dlg.set_do_overwrite_confirmation(True) ++ dlg.set_current_name(self.default_export_name) ++ ++ dlg.connect('response', self.on_export_response) ++ dlg.set_local_only(True) ++ ++ dlg.show() ++ ++ def remove_snippet_revert(self, path, piter): ++ node = self.snippet_from_iter(self.model, piter) ++ Library().revert_snippet(node) ++ ++ return piter ++ ++ def remove_snippet_delete(self, path, piter): ++ node = self.snippet_from_iter(self.model, piter) ++ parent = self.model.iter_parent(piter) ++ ++ Library().remove_snippet(node) ++ idx = path.get_indices() ++ ++ if self.model.remove(piter): ++ return piter ++ elif idx[-1] != 0: ++ self.select_iter(self.model.get_iter((idx[0], idx[1] - 1))) ++ else: ++ dummy = self.add_new_snippet_node(parent) ++ self.tree_view.expand_row(self.model.get_path(parent), False) ++ return dummy ++ ++ def on_button_remove_snippet_clicked(self, button): ++ override, remove, system = self.selected_snippets_state() ++ ++ if not (override ^ remove) or system: ++ return ++ ++ paths = self.selected_snippets(include_languages=False, as_path=True) ++ ++ if override: ++ action = self.remove_snippet_revert ++ else: ++ action = self.remove_snippet_delete ++ ++ # Remove selection ++ self.tree_view.get_selection().unselect_all() ++ ++ # Create tree row references ++ references = [] ++ for path in paths: ++ references.append(Gtk.TreeRowReference(self.model, path)) ++ ++ # Remove/revert snippets ++ select = None ++ for reference in references: ++ path = reference.get_path() ++ piter = self.model.get_iter(path) ++ ++ res = action(path, piter) ++ ++ if res: ++ select = res ++ ++ if select: ++ self.select_iter(select) ++ ++ self.selection_changed() ++ ++ def set_accelerator(self, keyval, mod): ++ accelerator = Gtk.accelerator_name(keyval, mod) ++ self.snippet['accelerator'] = accelerator ++ ++ return True ++ ++ def on_entry_accelerator_key_press(self, entry, event): ++ source_view = self['source_view_snippet'] ++ ++ if event.keyval == Gdk.keyval_from_name('Escape'): ++ # Reset ++ entry.set_text(self.snippet.accelerator_display()) ++ self.tree_view.grab_focus() ++ ++ return True ++ elif event.keyval == Gdk.keyval_from_name('Delete') or \ ++ event.keyval == Gdk.keyval_from_name('BackSpace'): ++ # Remove the accelerator ++ entry.set_text('') ++ self.snippet['accelerator'] = '' ++ self.tree_view.grab_focus() ++ ++ self.snippet_changed() ++ return True ++ elif Library().valid_accelerator(event.keyval, event.state): ++ # New accelerator ++ self.set_accelerator(event.keyval, \ ++ event.state & Gtk.accelerator_get_default_mod_mask()) ++ entry.set_text(self.snippet.accelerator_display()) ++ self.snippet_changed() ++ self.tree_view.grab_focus() ++ ++ else: ++ return True ++ ++ def on_entry_accelerator_focus_in(self, entry, event): ++ if self.snippet['accelerator']: ++ entry.set_text(_('Type a new shortcut, or press Backspace to clear')) ++ else: ++ entry.set_text(_('Type a new shortcut')) ++ ++ def update_language_path(self): ++ model, paths = self.tree_view.get_selection().get_selected_rows() ++ ++ # Check if all have the same language parent ++ current_parent = None ++ ++ for path in paths: ++ piter = model.get_iter(path) ++ parent = model.iter_parent(piter) ++ ++ if parent: ++ path = model.get_path(parent) ++ ++ if current_parent != None and current_parent != path: ++ current_parent = None ++ break ++ else: ++ current_parent = path ++ ++ self.language_path = current_parent ++ ++ def on_tree_view_selection_changed(self, selection): ++ parent, piter, node = self.selected_snippet() ++ ++ if self.snippet: ++ self.on_entry_tab_trigger_focus_out(self['entry_tab_trigger'], ++ None) ++ self.on_source_view_snippet_focus_out(self['source_view_snippet'], ++ None) ++ self.on_entry_drop_targets_focus_out(self['combo_drop_targets'].get_child(), ++ None) ++ ++ self.update_language_path() ++ ++ if node: ++ self.snippet = Snippet(node) ++ else: ++ self.snippet = None ++ ++ self.selection_changed() ++ ++ def iter_after(self, target, after): ++ if not after: ++ return True ++ ++ tp = self.model.get_path(target) ++ ap = self.model.get_path(after) ++ ++ if tp[0] > ap[0] or (tp[0] == ap[0] and (len(ap) == 1 or tp[1] > ap[1])): ++ return True ++ ++ return False ++ ++ def on_tree_view_snippets_key_press(self, treeview, event): ++ if event.keyval == Gdk.keyval_from_name('Delete'): ++ self.on_button_remove_snippet_clicked(None) ++ return True ++ ++ def on_tree_view_snippets_row_expanded(self, treeview, piter, path): ++ # Check if it is already filled ++ self.fill_if_needed(piter) ++ self.select_iter(piter) ++ ++ def on_entry_drop_targets_drag_data_received(self, entry, context, x, y, selection_data, info, timestamp): ++ uris = drop_get_uris(selection_data) ++ ++ if not uris: ++ return ++ ++ if entry.get_text(): ++ mimes = [entry.get_text()] ++ else: ++ mimes = [] ++ ++ for uri in uris: ++ try: ++ mime = Gio.content_type_guess(uri) ++ except: ++ mime = None ++ ++ if mime: ++ mimes.append(mime) ++ ++ entry.set_text(', '.join(mimes)) ++ self.on_entry_drop_targets_focus_out(entry, None) ++ context.finish(True, False, timestamp) ++ ++ entry.stop_emission('drag_data_received') ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Parser.py b/plugins/snippets/snippets/Parser.py +old mode 100755 +new mode 100644 +index 0c638df..280ce0c +--- a/plugins/snippets/snippets/Parser.py ++++ b/plugins/snippets/snippets/Parser.py +@@ -21,239 +21,239 @@ import sys + from SubstitutionParser import SubstitutionParser + + class Token: +- def __init__(self, klass, data): +- self.klass = klass +- self.data = data +- +- def __str__(self): +- return '%s: [%s]' % (self.klass, self.data) +- +- def __eq__(self, other): +- return self.klass == other.klass and self.data == other.data +- +- def __ne__(self, other): +- return not self.__eq__(other) ++ def __init__(self, klass, data): ++ self.klass = klass ++ self.data = data ++ ++ def __str__(self): ++ return '%s: [%s]' % (self.klass, self.data) ++ ++ def __eq__(self, other): ++ return self.klass == other.klass and self.data == other.data ++ ++ def __ne__(self, other): ++ return not self.__eq__(other) + + class Parser: +- SREG_ENV = '[A-Z_]+' +- SREG_ID = '[0-9]+' +- +- REG_ESCAPE = re.compile('(\\$(%s|\\(|\\{|<|%s)|`|\\\\)' % (SREG_ENV, SREG_ID)) +- +- def __init__(self, **kwargs): +- for k, v in kwargs.items(): +- setattr(self, k, v) +- +- self.position = 0 +- self.data_length = len(self.data) +- +- self.RULES = (self._match_env, self._match_regex, self._match_placeholder, self._match_shell, self._match_eval, self._text) +- +- def remains(self): +- return self.data[self.position:] +- +- def next_char(self): +- if self.position + 1 >= self.data_length: +- return '' +- else: +- return self.data[self.position + 1] +- +- def char(self): +- if self.position >= self.data_length: +- return '' +- else: +- return self.data[self.position] +- +- def token(self): +- self.tktext = '' +- +- while self.position < self.data_length: +- try: +- # Get first character +- func = {'$': self._rule, +- '`': self._try_match_shell}[self.char()] +- except: +- func = self._text +- +- # Detect end of text token +- if func != self._text and self.tktext != '': +- return Token('text', self.tktext) +- +- tk = func() +- +- if tk: +- return tk +- +- if self.tktext != '': +- return Token('text', self.tktext) +- +- def _need_escape(self): +- text = self.remains()[1:] +- +- if text == '': +- return False +- +- return self.REG_ESCAPE.match(text) +- +- def _escape(self): +- if not self._need_escape(): +- return +- +- # Increase position with 1 +- self.position += 1 +- +- def _text(self): +- if self.char() == '\\': +- self._escape() +- +- self.tktext += self.char() +- self.position += 1 +- +- def _rule(self): +- for rule in self.RULES: +- res = rule() +- +- if res: +- return res +- +- def _match_env(self): +- text = self.remains() +- match = re.match('\\$(%s)' % self.SREG_ENV, text) or re.match('\\${(%s)}' % self.SREG_ENV, text) +- +- if match: +- self.position += len(match.group(0)) +- return Token('environment', match.group(1)) +- +- def _parse_list(self, lst): +- pos = 0 +- length = len(lst) +- items = [] +- last = None +- +- while pos < length: +- char = lst[pos] +- next = pos < length - 1 and lst[pos + 1] +- +- if char == '\\' and (next == ',' or next == ']'): +- char = next +- pos += 1 +- elif char == ',': +- if last != None: +- items.append(last) +- +- last = None +- pos += 1 +- continue +- +- last = (last != None and last + char) or char +- pos += 1 +- ++ SREG_ENV = '[A-Z_]+' ++ SREG_ID = '[0-9]+' ++ ++ REG_ESCAPE = re.compile('(\\$(%s|\\(|\\{|<|%s)|`|\\\\)' % (SREG_ENV, SREG_ID)) ++ ++ def __init__(self, **kwargs): ++ for k, v in kwargs.items(): ++ setattr(self, k, v) ++ ++ self.position = 0 ++ self.data_length = len(self.data) ++ ++ self.RULES = (self._match_env, self._match_regex, self._match_placeholder, self._match_shell, self._match_eval, self._text) ++ ++ def remains(self): ++ return self.data[self.position:] ++ ++ def next_char(self): ++ if self.position + 1 >= self.data_length: ++ return '' ++ else: ++ return self.data[self.position + 1] ++ ++ def char(self): ++ if self.position >= self.data_length: ++ return '' ++ else: ++ return self.data[self.position] ++ ++ def token(self): ++ self.tktext = '' ++ ++ while self.position < self.data_length: ++ try: ++ # Get first character ++ func = {'$': self._rule, ++ '`': self._try_match_shell}[self.char()] ++ except: ++ func = self._text ++ ++ # Detect end of text token ++ if func != self._text and self.tktext != '': ++ return Token('text', self.tktext) ++ ++ tk = func() ++ ++ if tk: ++ return tk ++ ++ if self.tktext != '': ++ return Token('text', self.tktext) ++ ++ def _need_escape(self): ++ text = self.remains()[1:] ++ ++ if text == '': ++ return False ++ ++ return self.REG_ESCAPE.match(text) ++ ++ def _escape(self): ++ if not self._need_escape(): ++ return ++ ++ # Increase position with 1 ++ self.position += 1 ++ ++ def _text(self): ++ if self.char() == '\\': ++ self._escape() ++ ++ self.tktext += self.char() ++ self.position += 1 ++ ++ def _rule(self): ++ for rule in self.RULES: ++ res = rule() ++ ++ if res: ++ return res ++ ++ def _match_env(self): ++ text = self.remains() ++ match = re.match('\\$(%s)' % self.SREG_ENV, text) or re.match('\\${(%s)}' % self.SREG_ENV, text) ++ ++ if match: ++ self.position += len(match.group(0)) ++ return Token('environment', match.group(1)) ++ ++ def _parse_list(self, lst): ++ pos = 0 ++ length = len(lst) ++ items = [] ++ last = None ++ ++ while pos < length: ++ char = lst[pos] ++ next = pos < length - 1 and lst[pos + 1] ++ ++ if char == '\\' and (next == ',' or next == ']'): ++ char = next ++ pos += 1 ++ elif char == ',': + if last != None: +- items.append(last) +- +- return items +- +- def _parse_default(self, default): +- match = re.match('^\\s*(\\\\)?(\\[((\\\\]|[^\\]])+)\\]\\s*)$', default) +- +- if not match: +- return [default] +- +- groups = match.groups() +- +- if groups[0]: +- return [groups[1]] +- +- return self._parse_list(groups[2]) +- +- def _match_placeholder(self): +- text = self.remains() +- +- match = re.match('\\${(%s)(:((\\\\\\}|[^}])+))?}' % self.SREG_ID, text) or re.match('\\$(%s)' % self.SREG_ID, text) +- +- if not match: +- return None +- +- groups = match.groups() +- default = '' +- tabstop = int(groups[0]) +- self.position += len(match.group(0)) +- +- if len(groups) > 1 and groups[2]: +- default = self._parse_default(groups[2].replace('\\}', '}')) +- +- return Token('placeholder', {'tabstop': tabstop, 'default': default}) +- +- def _match_shell(self): +- text = self.remains() +- match = re.match('`((%s):)?((\\\\`|[^`])+?)`' % self.SREG_ID, text) or re.match('\\$\\(((%s):)?((\\\\\\)|[^\\)])+?)\\)' % self.SREG_ID, text) +- +- if not match: +- return None +- +- groups = match.groups() +- tabstop = (groups[1] and int(groups[1])) or -1 +- self.position += len(match.group(0)) +- +- if text[0] == '`': +- contents = groups[2].replace('\\`', '`') +- else: +- contents = groups[2].replace('\\)', ')') +- +- return Token('shell', {'tabstop': tabstop, 'contents': contents}) +- +- def _try_match_shell(self): +- return self._match_shell() or self._text() +- +- def _eval_options(self, options): +- reg = re.compile(self.SREG_ID) +- tabstop = -1 +- depend = [] +- +- options = options.split(':') +- +- for opt in options: +- if reg.match(opt): +- tabstop = int(opt) +- else: +- depend += self._parse_list(opt[1:-1]) +- +- return (tabstop, depend) +- +- def _match_eval(self): +- text = self.remains() +- +- options = '((%s)|\\[([0-9, ]+)\\])' % self.SREG_ID +- match = re.match('\\$<((%s:)*)((\\\\>|[^>])+?)>' % options, text) +- +- if not match: +- return None +- +- groups = match.groups() +- (tabstop, depend) = (groups[0] and self._eval_options(groups[0][:-1])) or (-1, []) +- self.position += len(match.group(0)) +- +- return Token('eval', {'tabstop': tabstop, 'dependencies': depend, 'contents': groups[5].replace('\\>', '>')}) +- +- def _match_regex(self): +- text = self.remains() +- +- content = '((?:\\\\[/]|\\\\}|[^/}])+)' +- match = re.match('\\${(?:(%s):)?\\s*(%s|\\$([A-Z_]+))?[/]%s[/]%s(?:[/]([a-zA-Z]*))?}' % (self.SREG_ID, self.SREG_ID, content, content), text) +- +- if not match: +- return None +- +- groups = match.groups() +- tabstop = (groups[0] and int(groups[0])) or -1 +- inp = (groups[2] or (groups[1] and int(groups[1]))) or '' +- +- pattern = re.sub('\\\\([/}])', '\\1', groups[3]) +- substitution = re.sub('\\\\([/}])', '\\1', groups[4]) +- modifiers = groups[5] or '' +- +- self.position += len(match.group(0)) +- +- return Token('regex', {'tabstop': tabstop, 'input': inp, 'pattern': pattern, 'substitution': substitution, 'modifiers': modifiers}) +- +-# ex:ts=8:et: ++ items.append(last) ++ ++ last = None ++ pos += 1 ++ continue ++ ++ last = (last != None and last + char) or char ++ pos += 1 ++ ++ if last != None: ++ items.append(last) ++ ++ return items ++ ++ def _parse_default(self, default): ++ match = re.match('^\\s*(\\\\)?(\\[((\\\\]|[^\\]])+)\\]\\s*)$', default) ++ ++ if not match: ++ return [default] ++ ++ groups = match.groups() ++ ++ if groups[0]: ++ return [groups[1]] ++ ++ return self._parse_list(groups[2]) ++ ++ def _match_placeholder(self): ++ text = self.remains() ++ ++ match = re.match('\\${(%s)(:((\\\\\\}|[^}])+))?}' % self.SREG_ID, text) or re.match('\\$(%s)' % self.SREG_ID, text) ++ ++ if not match: ++ return None ++ ++ groups = match.groups() ++ default = '' ++ tabstop = int(groups[0]) ++ self.position += len(match.group(0)) ++ ++ if len(groups) > 1 and groups[2]: ++ default = self._parse_default(groups[2].replace('\\}', '}')) ++ ++ return Token('placeholder', {'tabstop': tabstop, 'default': default}) ++ ++ def _match_shell(self): ++ text = self.remains() ++ match = re.match('`((%s):)?((\\\\`|[^`])+?)`' % self.SREG_ID, text) or re.match('\\$\\(((%s):)?((\\\\\\)|[^\\)])+?)\\)' % self.SREG_ID, text) ++ ++ if not match: ++ return None ++ ++ groups = match.groups() ++ tabstop = (groups[1] and int(groups[1])) or -1 ++ self.position += len(match.group(0)) ++ ++ if text[0] == '`': ++ contents = groups[2].replace('\\`', '`') ++ else: ++ contents = groups[2].replace('\\)', ')') ++ ++ return Token('shell', {'tabstop': tabstop, 'contents': contents}) ++ ++ def _try_match_shell(self): ++ return self._match_shell() or self._text() ++ ++ def _eval_options(self, options): ++ reg = re.compile(self.SREG_ID) ++ tabstop = -1 ++ depend = [] ++ ++ options = options.split(':') ++ ++ for opt in options: ++ if reg.match(opt): ++ tabstop = int(opt) ++ else: ++ depend += self._parse_list(opt[1:-1]) ++ ++ return (tabstop, depend) ++ ++ def _match_eval(self): ++ text = self.remains() ++ ++ options = '((%s)|\\[([0-9, ]+)\\])' % self.SREG_ID ++ match = re.match('\\$<((%s:)*)((\\\\>|[^>])+?)>' % options, text) ++ ++ if not match: ++ return None ++ ++ groups = match.groups() ++ (tabstop, depend) = (groups[0] and self._eval_options(groups[0][:-1])) or (-1, []) ++ self.position += len(match.group(0)) ++ ++ return Token('eval', {'tabstop': tabstop, 'dependencies': depend, 'contents': groups[5].replace('\\>', '>')}) ++ ++ def _match_regex(self): ++ text = self.remains() ++ ++ content = '((?:\\\\[/]|\\\\}|[^/}])+)' ++ match = re.match('\\${(?:(%s):)?\\s*(%s|\\$([A-Z_]+))?[/]%s[/]%s(?:[/]([a-zA-Z]*))?}' % (self.SREG_ID, self.SREG_ID, content, content), text) ++ ++ if not match: ++ return None ++ ++ groups = match.groups() ++ tabstop = (groups[0] and int(groups[0])) or -1 ++ inp = (groups[2] or (groups[1] and int(groups[1]))) or '' ++ ++ pattern = re.sub('\\\\([/}])', '\\1', groups[3]) ++ substitution = re.sub('\\\\([/}])', '\\1', groups[4]) ++ modifiers = groups[5] or '' ++ ++ self.position += len(match.group(0)) ++ ++ return Token('regex', {'tabstop': tabstop, 'input': inp, 'pattern': pattern, 'substitution': substitution, 'modifiers': modifiers}) ++ ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Placeholder.py b/plugins/snippets/snippets/Placeholder.py +old mode 100755 +new mode 100644 +index 5fa6e55..9edf099 +--- a/plugins/snippets/snippets/Placeholder.py ++++ b/plugins/snippets/snippets/Placeholder.py +@@ -29,671 +29,671 @@ from Helper import * + + # These are places in a view where the cursor can go and do things + class Placeholder: +- def __init__(self, view, tabstop, defaults, begin): +- self.ok = True +- self.done = False +- self.buf = view.get_buffer() +- self.view = view +- self.has_references = False +- self.mirrors = [] +- self.leave_mirrors = [] +- self.tabstop = tabstop +- self.set_default(defaults) +- self.prev_contents = self.default +- self.set_mark_gravity() +- +- if begin: +- self.begin = self.buf.create_mark(None, begin, self.mark_gravity[0]) +- else: +- self.begin = None +- +- self.end = None +- +- def __str__(self): +- return '%s (%s)' % (str(self.__class__), str(self.default)) +- +- def set_mark_gravity(self): +- self.mark_gravity = [True, False] +- +- def set_default(self, defaults): +- self.default = None +- self.defaults = [] +- +- if not defaults: +- return +- +- for d in defaults: +- dm = self.expand_environment(d) +- +- if dm: +- self.defaults.append(dm) +- +- if not self.default: +- self.default = dm +- +- if dm != d: +- break +- +- +- def literal(self, s): +- return repr(s) +- +- def format_environment(self, s): +- return s +- +- def re_environment(self, m): +- if m.group(1) or not m.group(2) in os.environ: +- return '$' + m.group(2) +- else: +- return self.format_environment(os.environ[m.group(2)]) +- +- def expand_environment(self, text): +- if not text: +- return text +- +- return re.sub('(\\\\)?\\$([A-Z_]+)', self.re_environment, text) +- +- def get_iter(self, mark): +- if mark and not mark.get_deleted(): +- return self.buf.get_iter_at_mark(mark) +- else: +- return None +- +- def begin_iter(self): +- return self.get_iter(self.begin) +- +- def end_iter(self): +- return self.get_iter(self.end) +- +- def run_last(self, placeholders): +- begin = self.begin_iter() +- self.end = self.buf.create_mark(None, begin, self.mark_gravity[1]) +- +- if self.default: +- insert_with_indent(self.view, begin, self.default, False, self) +- +- def remove(self, force = False): +- if self.begin and not self.begin.get_deleted(): +- self.buf.delete_mark(self.begin) +- +- if self.end and not self.end.get_deleted(): +- self.buf.delete_mark(self.end) +- +- # Do something on beginning this placeholder +- def enter(self): +- if not self.begin or self.begin.get_deleted(): +- return +- +- self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) +- +- if self.end: +- self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) +- else: +- self.buf.move_mark(self.buf.get_selection_bound(), self.begin_iter()) +- +- def get_text(self): +- if self.begin and self.end: +- biter = self.begin_iter() +- eiter = self.end_iter() +- +- if biter and eiter: +- return self.buf.get_text(self.begin_iter(), self.end_iter(), False) +- else: +- return '' +- else: +- return '' +- +- def add_mirror(self, mirror, onleave = False): +- mirror.has_references = True +- +- if onleave: +- self.leave_mirrors.append(mirror) +- else: +- self.mirrors.append(mirror) +- +- def set_text(self, text): +- if self.begin.get_deleted() or self.end.get_deleted(): +- return +- +- # Set from self.begin to self.end to text! +- self.buf.begin_user_action() +- # Remove everything between self.begin and self.end +- begin = self.begin_iter() +- self.buf.delete(begin, self.end_iter()) +- +- # Insert the text from the mirror +- insert_with_indent(self.view, begin, text, True, self) +- self.buf.end_user_action() +- +- self.update_contents() +- +- def update_contents(self): +- prev = self.prev_contents +- self.prev_contents = self.get_text() +- +- if prev != self.get_text(): +- for mirror in self.mirrors: +- if not mirror.update(self): +- return +- +- def update_leave_mirrors(self): +- # Notify mirrors +- for mirror in self.leave_mirrors: +- if not mirror.update(self): +- return +- +- # Do something on ending this placeholder +- def leave(self): +- self.update_leave_mirrors() +- +- def find_mirrors(self, text, placeholders): +- mirrors = [] +- +- while (True): +- m = re.search('(\\\\)?\\$(?:{([0-9]+)}|([0-9]+))', text) +- +- if not m: +- break +- +- # Skip escaped mirrors +- if m.group(1): +- text = text[m.end():] +- continue +- +- tabstop = int(m.group(2) or m.group(3)) +- +- if tabstop in placeholders: +- if not tabstop in mirrors: +- mirrors.append(tabstop) +- +- text = text[m.end():] +- else: +- self.ok = False +- return None +- +- return mirrors +- +-# This is an placeholder which inserts a mirror of another Placeholder ++ def __init__(self, view, tabstop, defaults, begin): ++ self.ok = True ++ self.done = False ++ self.buf = view.get_buffer() ++ self.view = view ++ self.has_references = False ++ self.mirrors = [] ++ self.leave_mirrors = [] ++ self.tabstop = tabstop ++ self.set_default(defaults) ++ self.prev_contents = self.default ++ self.set_mark_gravity() ++ ++ if begin: ++ self.begin = self.buf.create_mark(None, begin, self.mark_gravity[0]) ++ else: ++ self.begin = None ++ ++ self.end = None ++ ++ def __str__(self): ++ return '%s (%s)' % (str(self.__class__), str(self.default)) ++ ++ def set_mark_gravity(self): ++ self.mark_gravity = [True, False] ++ ++ def set_default(self, defaults): ++ self.default = None ++ self.defaults = [] ++ ++ if not defaults: ++ return ++ ++ for d in defaults: ++ dm = self.expand_environment(d) ++ ++ if dm: ++ self.defaults.append(dm) ++ ++ if not self.default: ++ self.default = dm ++ ++ if dm != d: ++ break ++ ++ def literal(self, s): ++ return repr(s) ++ ++ def format_environment(self, s): ++ return s ++ ++ def re_environment(self, m): ++ if m.group(1) or not m.group(2) in os.environ: ++ return '$' + m.group(2) ++ else: ++ return self.format_environment(os.environ[m.group(2)]) ++ ++ def expand_environment(self, text): ++ if not text: ++ return text ++ ++ return re.sub('(\\\\)?\\$([A-Z_]+)', self.re_environment, text) ++ ++ def get_iter(self, mark): ++ if mark and not mark.get_deleted(): ++ return self.buf.get_iter_at_mark(mark) ++ else: ++ return None ++ ++ def begin_iter(self): ++ return self.get_iter(self.begin) ++ ++ def end_iter(self): ++ return self.get_iter(self.end) ++ ++ def run_last(self, placeholders): ++ begin = self.begin_iter() ++ self.end = self.buf.create_mark(None, begin, self.mark_gravity[1]) ++ ++ if self.default: ++ insert_with_indent(self.view, begin, self.default, False, self) ++ ++ def remove(self, force = False): ++ if self.begin and not self.begin.get_deleted(): ++ self.buf.delete_mark(self.begin) ++ ++ if self.end and not self.end.get_deleted(): ++ self.buf.delete_mark(self.end) ++ ++ # Do something on beginning this placeholder ++ def enter(self): ++ if not self.begin or self.begin.get_deleted(): ++ return ++ ++ self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) ++ ++ if self.end: ++ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) ++ else: ++ self.buf.move_mark(self.buf.get_selection_bound(), self.begin_iter()) ++ ++ def get_text(self): ++ if self.begin and self.end: ++ biter = self.begin_iter() ++ eiter = self.end_iter() ++ ++ if biter and eiter: ++ return self.buf.get_text(self.begin_iter(), self.end_iter(), False) ++ else: ++ return '' ++ else: ++ return '' ++ ++ def add_mirror(self, mirror, onleave = False): ++ mirror.has_references = True ++ ++ if onleave: ++ self.leave_mirrors.append(mirror) ++ else: ++ self.mirrors.append(mirror) ++ ++ def set_text(self, text): ++ if self.begin.get_deleted() or self.end.get_deleted(): ++ return ++ ++ # Set from self.begin to self.end to text! ++ self.buf.begin_user_action() ++ # Remove everything between self.begin and self.end ++ begin = self.begin_iter() ++ self.buf.delete(begin, self.end_iter()) ++ ++ # Insert the text from the mirror ++ insert_with_indent(self.view, begin, text, True, self) ++ self.buf.end_user_action() ++ ++ self.update_contents() ++ ++ def update_contents(self): ++ prev = self.prev_contents ++ self.prev_contents = self.get_text() ++ ++ if prev != self.get_text(): ++ for mirror in self.mirrors: ++ if not mirror.update(self): ++ return ++ ++ def update_leave_mirrors(self): ++ # Notify mirrors ++ for mirror in self.leave_mirrors: ++ if not mirror.update(self): ++ return ++ ++ # Do something on ending this placeholder ++ def leave(self): ++ self.update_leave_mirrors() ++ ++ def find_mirrors(self, text, placeholders): ++ mirrors = [] ++ ++ while (True): ++ m = re.search('(\\\\)?\\$(?:{([0-9]+)}|([0-9]+))', text) ++ ++ if not m: ++ break ++ ++ # Skip escaped mirrors ++ if m.group(1): ++ text = text[m.end():] ++ continue ++ ++ tabstop = int(m.group(2) or m.group(3)) ++ ++ if tabstop in placeholders: ++ if not tabstop in mirrors: ++ mirrors.append(tabstop) ++ ++ text = text[m.end():] ++ else: ++ self.ok = False ++ return None ++ ++ return mirrors ++ ++# This is an placeholder which inserts a mirror of another Placeholder + class PlaceholderMirror(Placeholder): +- def __init__(self, view, tabstop, begin): +- Placeholder.__init__(self, view, -1, None, begin) +- self.mirror_stop = tabstop +- +- def update(self, mirror): +- self.set_text(mirror.get_text()) +- return True +- +- def run_last(self, placeholders): +- Placeholder.run_last(self, placeholders) +- +- if self.mirror_stop in placeholders: +- mirror = placeholders[self.mirror_stop] +- +- mirror.add_mirror(self) +- +- if mirror.default: +- self.set_text(mirror.default) +- else: +- self.ok = False ++ def __init__(self, view, tabstop, begin): ++ Placeholder.__init__(self, view, -1, None, begin) ++ self.mirror_stop = tabstop ++ ++ def update(self, mirror): ++ self.set_text(mirror.get_text()) ++ return True ++ ++ def run_last(self, placeholders): ++ Placeholder.run_last(self, placeholders) ++ ++ if self.mirror_stop in placeholders: ++ mirror = placeholders[self.mirror_stop] ++ ++ mirror.add_mirror(self) ++ ++ if mirror.default: ++ self.set_text(mirror.default) ++ else: ++ self.ok = False + + # This placeholder indicates the end of a snippet + class PlaceholderEnd(Placeholder): +- def __init__(self, view, begin, default): +- Placeholder.__init__(self, view, 0, default, begin) +- +- def run_last(self, placeholders): +- Placeholder.run_last(self, placeholders) +- +- # Remove the begin mark and set the begin mark +- # to the end mark, this is needed so the end placeholder won't contain +- # any text +- +- if not self.default: +- self.mark_gravity[0] = False +- self.buf.delete_mark(self.begin) +- self.begin = self.buf.create_mark(None, self.end_iter(), self.mark_gravity[0]) +- +- def enter(self): +- if self.begin and not self.begin.get_deleted(): +- self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) +- +- if self.end and not self.end.get_deleted(): +- self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) +- +- def leave(self): +- self.enter() +- +-# This placeholder is used to expand a command with embedded mirrors ++ def __init__(self, view, begin, default): ++ Placeholder.__init__(self, view, 0, default, begin) ++ ++ def run_last(self, placeholders): ++ Placeholder.run_last(self, placeholders) ++ ++ # Remove the begin mark and set the begin mark ++ # to the end mark, this is needed so the end placeholder won't contain ++ # any text ++ ++ if not self.default: ++ self.mark_gravity[0] = False ++ self.buf.delete_mark(self.begin) ++ self.begin = self.buf.create_mark(None, self.end_iter(), self.mark_gravity[0]) ++ ++ def enter(self): ++ if self.begin and not self.begin.get_deleted(): ++ self.buf.move_mark(self.buf.get_insert(), self.begin_iter()) ++ ++ if self.end and not self.end.get_deleted(): ++ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter()) ++ ++ def leave(self): ++ self.enter() ++ ++# This placeholder is used to expand a command with embedded mirrors + class PlaceholderExpand(Placeholder): +- def __init__(self, view, tabstop, begin, s): +- Placeholder.__init__(self, view, tabstop, None, begin) +- +- self.mirror_text = {0: ''} +- self.timeout_id = None +- self.cmd = s +- self.instant_update = False +- +- def __str__(self): +- s = Placeholder.__str__(self) +- +- return s + ' ' + self.cmd +- +- def get_mirrors(self, placeholders): +- return self.find_mirrors(self.cmd, placeholders) +- +- # Check if all substitution placeholders are accounted for +- def run_last(self, placeholders): +- Placeholder.run_last(self, placeholders) +- +- self.ok = True +- mirrors = self.get_mirrors(placeholders) +- +- if mirrors: +- allDefault = True +- +- for mirror in mirrors: +- p = placeholders[mirror] +- p.add_mirror(self, not self.instant_update) +- self.mirror_text[p.tabstop] = p.default +- +- if not p.default and not isinstance(p, PlaceholderExpand): +- allDefault = False +- +- if allDefault: +- self.update(None) +- self.default = self.get_text() or None +- else: +- self.update(None) +- self.default = self.get_text() or None +- +- if self.tabstop == -1: +- self.done = True +- +- def re_placeholder(self, m, formatter): +- if m.group(1): +- return '"$' + m.group(2) + '"' +- else: +- if m.group(3): +- index = int(m.group(3)) +- else: +- index = int(m.group(4)) +- +- return formatter(self.mirror_text[index]) +- +- def remove_timeout(self): +- if self.timeout_id != None: +- GLib.source_remove(self.timeout_id) +- self.timeout_id = None +- +- def install_timeout(self): +- self.remove_timeout() +- self.timeout_id = GLib.timeout_add(1000, self.timeout_cb) ++ def __init__(self, view, tabstop, begin, s): ++ Placeholder.__init__(self, view, tabstop, None, begin) + +- def timeout_cb(self): +- self.timeout_id = None +- +- return False +- +- def format_environment(self, text): +- return self.literal(text) +- +- def substitute(self, text, formatter = None): +- formatter = formatter or self.literal +- +- # substitute all mirrors, but also environmental variables +- text = re.sub('(\\\\)?\\$({([0-9]+)}|([0-9]+))', lambda m: self.re_placeholder(m, formatter), +- text) +- +- return self.expand_environment(text) +- +- def run_update(self): +- text = self.substitute(self.cmd) +- +- if text: +- ret = self.expand(text) +- +- if ret: +- self.update_leave_mirrors() +- else: +- ret = True +- +- return ret +- +- def update(self, mirror): +- text = None +- +- if mirror: +- self.mirror_text[mirror.tabstop] = mirror.get_text() +- +- # Check if all substitutions have been made +- for tabstop in self.mirror_text: +- if tabstop == 0: +- continue +- +- if self.mirror_text[tabstop] == None: +- return False +- +- return self.run_update() +- +- def expand(self, text): +- return True ++ self.mirror_text = {0: ''} ++ self.timeout_id = None ++ self.cmd = s ++ self.instant_update = False ++ ++ def __str__(self): ++ s = Placeholder.__str__(self) ++ ++ return s + ' ' + self.cmd ++ ++ def get_mirrors(self, placeholders): ++ return self.find_mirrors(self.cmd, placeholders) ++ ++ # Check if all substitution placeholders are accounted for ++ def run_last(self, placeholders): ++ Placeholder.run_last(self, placeholders) ++ ++ self.ok = True ++ mirrors = self.get_mirrors(placeholders) ++ ++ if mirrors: ++ allDefault = True ++ ++ for mirror in mirrors: ++ p = placeholders[mirror] ++ p.add_mirror(self, not self.instant_update) ++ self.mirror_text[p.tabstop] = p.default ++ ++ if not p.default and not isinstance(p, PlaceholderExpand): ++ allDefault = False ++ ++ if allDefault: ++ self.update(None) ++ self.default = self.get_text() or None ++ else: ++ self.update(None) ++ self.default = self.get_text() or None ++ ++ if self.tabstop == -1: ++ self.done = True ++ ++ def re_placeholder(self, m, formatter): ++ if m.group(1): ++ return '"$' + m.group(2) + '"' ++ else: ++ if m.group(3): ++ index = int(m.group(3)) ++ else: ++ index = int(m.group(4)) ++ ++ return formatter(self.mirror_text[index]) ++ ++ def remove_timeout(self): ++ if self.timeout_id != None: ++ GLib.source_remove(self.timeout_id) ++ self.timeout_id = None ++ ++ def install_timeout(self): ++ self.remove_timeout() ++ self.timeout_id = GLib.timeout_add(1000, self.timeout_cb) ++ ++ def timeout_cb(self): ++ self.timeout_id = None ++ ++ return False ++ ++ def format_environment(self, text): ++ return self.literal(text) ++ ++ def substitute(self, text, formatter = None): ++ formatter = formatter or self.literal ++ ++ # substitute all mirrors, but also environmental variables ++ text = re.sub('(\\\\)?\\$({([0-9]+)}|([0-9]+))', lambda m: self.re_placeholder(m, formatter), ++ text) ++ ++ return self.expand_environment(text) ++ ++ def run_update(self): ++ text = self.substitute(self.cmd) ++ ++ if text: ++ ret = self.expand(text) ++ ++ if ret: ++ self.update_leave_mirrors() ++ else: ++ ret = True ++ ++ return ret ++ ++ def update(self, mirror): ++ text = None ++ ++ if mirror: ++ self.mirror_text[mirror.tabstop] = mirror.get_text() ++ ++ # Check if all substitutions have been made ++ for tabstop in self.mirror_text: ++ if tabstop == 0: ++ continue ++ ++ if self.mirror_text[tabstop] == None: ++ return False ++ ++ return self.run_update() ++ ++ def expand(self, text): ++ return True + + # The shell placeholder executes commands in a subshell + class PlaceholderShell(PlaceholderExpand): +- def __init__(self, view, tabstop, begin, s): +- PlaceholderExpand.__init__(self, view, tabstop, begin, s) +- +- self.shell = None +- self.remove_me = False +- +- def close_shell(self): +- self.shell.stdout.close() +- self.shell = None +- +- def timeout_cb(self): +- PlaceholderExpand.timeout_cb(self) +- self.remove_timeout() +- +- if not self.shell: +- return False ++ def __init__(self, view, tabstop, begin, s): ++ PlaceholderExpand.__init__(self, view, tabstop, begin, s) + +- GLib.source_remove(self.watch_id) +- self.close_shell() ++ self.shell = None ++ self.remove_me = False + +- if self.remove_me: +- PlaceholderExpand.remove(self) ++ def close_shell(self): ++ self.shell.stdout.close() ++ self.shell = None + +- message_dialog(None, Gtk.MessageType.ERROR, 'Execution of the shell ' \ +- 'command (%s) exceeded the maximum time; ' \ +- 'execution aborted.' % self.command) +- +- return False +- +- def process_close(self): +- self.close_shell() +- self.remove_timeout() ++ def timeout_cb(self): ++ PlaceholderExpand.timeout_cb(self) ++ self.remove_timeout() + +- self.set_text(str.join('', self.shell_output).rstrip('\n')) +- +- if self.default == None: +- self.default = self.get_text() +- self.leave() +- +- if self.remove_me: +- PlaceholderExpand.remove(self, True) +- +- def process_cb(self, source, condition): +- if condition & GObject.IO_IN: +- line = source.readline() +- +- if len(line) > 0: +- try: +- line = unicode(line, 'utf-8') +- except: +- line = unicode(line, locale.getdefaultlocale()[1], +- 'replace') +- +- self.shell_output += line +- self.install_timeout() +- +- return True +- +- self.process_close() +- return False +- +- def literal_replace(self, match): +- return "\\%s" % (match.group(0)) +- +- def literal(self, text): +- return '"' + re.sub('([\\\\"])', self.literal_replace, text) + '"' +- +- def expand(self, text): ++ if not self.shell: ++ return False ++ ++ GLib.source_remove(self.watch_id) ++ self.close_shell() ++ ++ if self.remove_me: ++ PlaceholderExpand.remove(self) ++ ++ message_dialog(None, Gtk.MessageType.ERROR, 'Execution of the shell ' \ ++ 'command (%s) exceeded the maximum time; ' \ ++ 'execution aborted.' % self.command) ++ ++ return False ++ ++ def process_close(self): ++ self.close_shell() ++ self.remove_timeout() ++ ++ self.set_text(str.join('', self.shell_output).rstrip('\n')) ++ ++ if self.default == None: ++ self.default = self.get_text() ++ self.leave() ++ ++ if self.remove_me: ++ PlaceholderExpand.remove(self, True) ++ ++ def process_cb(self, source, condition): ++ if condition & GObject.IO_IN: ++ line = source.readline() ++ ++ if len(line) > 0: ++ try: ++ line = unicode(line, 'utf-8') ++ except: ++ line = unicode(line, locale.getdefaultlocale()[1], ++ 'replace') ++ ++ self.shell_output += line ++ self.install_timeout() ++ ++ return True ++ ++ self.process_close() ++ return False ++ ++ def literal_replace(self, match): ++ return "\\%s" % (match.group(0)) ++ ++ def literal(self, text): ++ return '"' + re.sub('([\\\\"])', self.literal_replace, text) + '"' ++ ++ def expand(self, text): ++ self.remove_timeout() ++ ++ if self.shell: ++ GLib.source_remove(self.watch_id) ++ self.close_shell() ++ ++ popen_args = { ++ 'cwd' : None, ++ 'shell': True, ++ 'env' : os.environ, ++ 'stdout': subprocess.PIPE ++ } ++ ++ self.command = text ++ self.shell = subprocess.Popen(text, **popen_args) ++ self.shell_output = '' ++ self.watch_id = GLib.io_add_watch(self.shell.stdout, GObject.IO_IN | \ ++ GObject.IO_HUP, self.process_cb) ++ self.install_timeout() ++ ++ return True ++ ++ def remove(self, force = False): ++ if not force and self.shell: ++ # Still executing shell command ++ self.remove_me = True ++ else: ++ if force: + self.remove_timeout() + + if self.shell: +- GLib.source_remove(self.watch_id) +- self.close_shell() +- +- popen_args = { +- 'cwd' : None, +- 'shell': True, +- 'env' : os.environ, +- 'stdout': subprocess.PIPE +- } +- +- self.command = text +- self.shell = subprocess.Popen(text, **popen_args) +- self.shell_output = '' +- self.watch_id = GLib.io_add_watch(self.shell.stdout, GObject.IO_IN | \ +- GObject.IO_HUP, self.process_cb) +- self.install_timeout() +- +- return True +- +- def remove(self, force = False): +- if not force and self.shell: +- # Still executing shell command +- self.remove_me = True +- else: +- if force: +- self.remove_timeout() +- +- if self.shell: +- self.close_shell() +- +- PlaceholderExpand.remove(self, force) ++ self.close_shell() ++ ++ PlaceholderExpand.remove(self, force) + + class TimeoutError(Exception): +- def __init__(self, value): +- self.value = value +- +- def __str__(self): +- return repr(self.value) ++ def __init__(self, value): ++ self.value = value ++ ++ def __str__(self): ++ return repr(self.value) + + # The python placeholder evaluates commands in python + class PlaceholderEval(PlaceholderExpand): +- def __init__(self, view, tabstop, refs, begin, s, namespace): +- PlaceholderExpand.__init__(self, view, tabstop, begin, s) +- +- self.fdread = 0 +- self.remove_me = False +- self.namespace = namespace +- +- self.refs = [] +- +- if refs: +- for ref in refs: +- self.refs.append(int(ref.strip())) +- +- def get_mirrors(self, placeholders): +- mirrors = PlaceholderExpand.get_mirrors(self, placeholders) +- +- if not self.ok: +- return None +- +- for ref in self.refs: +- if ref in placeholders: +- if ref not in mirrors: +- mirrors.append(ref) +- else: +- self.ok = False +- return None +- +- return mirrors +- +- # SIGALRM is not supported on all platforms (e.g. windows). Timeout +- # with SIGALRM will not be used on those platforms. This will +- # potentially block pluma if you have a placeholder which gets stuck, +- # but it's better than not supporting them at all. At some point we +- # might have proper thread support and we can fix this in a better way +- def timeout_supported(self): +- return hasattr(signal, 'SIGALRM') +- +- def timeout_cb(self, signum = 0, frame = 0): +- raise TimeoutError, "Operation timed out (>2 seconds)" +- +- def install_timeout(self): +- if not self.timeout_supported(): +- return +- +- if self.timeout_id != None: +- self.remove_timeout() +- +- self.timeout_id = signal.signal(signal.SIGALRM, self.timeout_cb) +- signal.alarm(2) +- +- def remove_timeout(self): +- if not self.timeout_supported(): +- return +- +- if self.timeout_id != None: +- signal.alarm(0) +- +- signal.signal(signal.SIGALRM, self.timeout_id) +- +- self.timeout_id = None +- +- def expand(self, text): ++ def __init__(self, view, tabstop, refs, begin, s, namespace): ++ PlaceholderExpand.__init__(self, view, tabstop, begin, s) ++ ++ self.fdread = 0 ++ self.remove_me = False ++ self.namespace = namespace ++ ++ self.refs = [] ++ ++ if refs: ++ for ref in refs: ++ self.refs.append(int(ref.strip())) ++ ++ def get_mirrors(self, placeholders): ++ mirrors = PlaceholderExpand.get_mirrors(self, placeholders) ++ ++ if not self.ok: ++ return None ++ ++ for ref in self.refs: ++ if ref in placeholders: ++ if ref not in mirrors: ++ mirrors.append(ref) ++ else: ++ self.ok = False ++ return None ++ ++ return mirrors ++ ++ # SIGALRM is not supported on all platforms (e.g. windows). Timeout ++ # with SIGALRM will not be used on those platforms. This will ++ # potentially block pluma if you have a placeholder which gets stuck, ++ # but it's better than not supporting them at all. At some point we ++ # might have proper thread support and we can fix this in a better way ++ def timeout_supported(self): ++ return hasattr(signal, 'SIGALRM') ++ ++ def timeout_cb(self, signum = 0, frame = 0): ++ raise TimeoutError, "Operation timed out (>2 seconds)" ++ ++ def install_timeout(self): ++ if not self.timeout_supported(): ++ return ++ ++ if self.timeout_id != None: ++ self.remove_timeout() ++ ++ self.timeout_id = signal.signal(signal.SIGALRM, self.timeout_cb) ++ signal.alarm(2) ++ ++ def remove_timeout(self): ++ if not self.timeout_supported(): ++ return ++ ++ if self.timeout_id != None: ++ signal.alarm(0) ++ ++ signal.signal(signal.SIGALRM, self.timeout_id) ++ ++ self.timeout_id = None ++ ++ def expand(self, text): ++ self.remove_timeout() ++ ++ text = text.strip() ++ self.command = text ++ ++ if not self.command or self.command == '': ++ self.set_text('') ++ return ++ ++ text = "def process_snippet():\n\t" + "\n\t".join(text.split("\n")) ++ ++ if 'process_snippet' in self.namespace: ++ del self.namespace['process_snippet'] ++ ++ try: ++ exec text in self.namespace ++ except: ++ traceback.print_exc() ++ ++ if 'process_snippet' in self.namespace: ++ try: ++ # Install a sigalarm signal. This is a HACK to make sure ++ # pluma doesn't get freezed by someone creating a python ++ # placeholder which for instance loops indefinately. Since ++ # the code is executed synchronously it will hang pluma. With ++ # the alarm signal we raise an exception and catch this ++ # (see below). We show an error message and return False. ++ # ___this is a HACK___ and should be fixed properly (I just ++ # don't know how) ++ self.install_timeout() ++ result = self.namespace['process_snippet']() ++ self.remove_timeout() ++ except TimeoutError: + self.remove_timeout() + +- text = text.strip() +- self.command = text ++ message_dialog(None, Gtk.MessageType.ERROR, \ ++ _('Execution of the Python command (%s) exceeds the maximum ' \ ++ 'time, execution aborted.') % self.command) + +- if not self.command or self.command == '': +- self.set_text('') +- return ++ return False ++ except Exception, detail: ++ self.remove_timeout() + +- text = "def process_snippet():\n\t" + "\n\t".join(text.split("\n")) +- +- if 'process_snippet' in self.namespace: +- del self.namespace['process_snippet'] ++ message_dialog(None, Gtk.MessageType.ERROR, ++ _('Execution of the Python command (%s) failed: %s') % ++ (self.command, detail)) + +- try: +- exec text in self.namespace +- except: +- traceback.print_exc() +- +- if 'process_snippet' in self.namespace: +- try: +- # Install a sigalarm signal. This is a HACK to make sure +- # pluma doesn't get freezed by someone creating a python +- # placeholder which for instance loops indefinately. Since +- # the code is executed synchronously it will hang pluma. With +- # the alarm signal we raise an exception and catch this +- # (see below). We show an error message and return False. +- # ___this is a HACK___ and should be fixed properly (I just +- # don't know how) +- self.install_timeout() +- result = self.namespace['process_snippet']() +- self.remove_timeout() +- except TimeoutError: +- self.remove_timeout() +- +- message_dialog(None, Gtk.MessageType.ERROR, \ +- _('Execution of the Python command (%s) exceeds the maximum ' \ +- 'time, execution aborted.') % self.command) +- +- return False +- except Exception, detail: +- self.remove_timeout() +- +- message_dialog(None, Gtk.MessageType.ERROR, +- _('Execution of the Python command (%s) failed: %s') % +- (self.command, detail)) +- +- return False +- +- if result == None: +- # sys.stderr.write("%s:\n>> %s\n" % (_('The following python code, run in a snippet, does not return a value'), "\n>> ".join(self.command.split("\n")))) +- result = '' +- +- self.set_text(str(result)) +- +- return True ++ return False ++ ++ if result == None: ++ # sys.stderr.write("%s:\n>> %s\n" % (_('The following python code, run in a snippet, does not return a value'), "\n>> ".join(self.command.split("\n")))) ++ result = '' ++ ++ self.set_text(str(result)) ++ ++ return True + + # Regular expression placeholder + class PlaceholderRegex(PlaceholderExpand): +- def __init__(self, view, tabstop, begin, inp, pattern, substitution, modifiers): +- PlaceholderExpand.__init__(self, view, tabstop, begin, '') +- +- self.instant_update = True +- self.inp = inp +- self.pattern = pattern +- self.substitution = substitution +- +- self.init_modifiers(modifiers) +- +- def init_modifiers(self, modifiers): +- mods = {'I': re.I, +- 'L': re.L, +- 'M': re.M, +- 'S': re.S, +- 'U': re.U, +- 'X': re.X} +- +- self.modifiers = 0 +- +- for modifier in modifiers: +- if modifier in mods: +- self.modifiers |= mods[modifier] +- +- def get_mirrors(self, placeholders): +- mirrors = self.find_mirrors(self.pattern, placeholders) + self.find_mirrors(self.substitution, placeholders) +- +- if isinstance(self.inp, int): +- if self.inp not in placeholders: +- self.ok = False +- return None +- elif self.inp not in mirrors: +- mirrors.append(self.inp) +- +- return mirrors +- +- def literal(self, s): +- return re.escape(s) +- +- def get_input(self): +- if isinstance(self.inp, int): +- return self.mirror_text[self.inp] +- elif self.inp in os.environ: +- return os.environ[self.inp] +- else: +- return '' +- +- def run_update(self): +- pattern = self.substitute(self.pattern) +- substitution = self.substitute(self.substitution, SubstitutionParser.escape_substitution) +- +- if pattern: +- return self.expand(pattern, substitution) +- +- return True +- +- def expand(self, pattern, substitution): +- # Try to compile pattern +- try: +- regex = re.compile(pattern, self.modifiers) +- except re.error, message: +- sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message)) +- return False +- +- inp = self.get_input() +- match = regex.search(inp) +- +- if not match: +- self.set_text(inp) +- else: +- groups = match.groupdict() +- +- idx = 0 +- for group in match.groups(): +- groups[str(idx + 1)] = group +- idx += 1 +- +- groups['0'] = match.group(0) +- +- parser = SubstitutionParser(substitution, groups) +- self.set_text(parser.parse()) +- +- return True +-# ex:ts=8:et: ++ def __init__(self, view, tabstop, begin, inp, pattern, substitution, modifiers): ++ PlaceholderExpand.__init__(self, view, tabstop, begin, '') ++ ++ self.instant_update = True ++ self.inp = inp ++ self.pattern = pattern ++ self.substitution = substitution ++ ++ self.init_modifiers(modifiers) ++ ++ def init_modifiers(self, modifiers): ++ mods = {'I': re.I, ++ 'L': re.L, ++ 'M': re.M, ++ 'S': re.S, ++ 'U': re.U, ++ 'X': re.X} ++ ++ self.modifiers = 0 ++ ++ for modifier in modifiers: ++ if modifier in mods: ++ self.modifiers |= mods[modifier] ++ ++ def get_mirrors(self, placeholders): ++ mirrors = self.find_mirrors(self.pattern, placeholders) + self.find_mirrors(self.substitution, placeholders) ++ ++ if isinstance(self.inp, int): ++ if self.inp not in placeholders: ++ self.ok = False ++ return None ++ elif self.inp not in mirrors: ++ mirrors.append(self.inp) ++ ++ return mirrors ++ ++ def literal(self, s): ++ return re.escape(s) ++ ++ def get_input(self): ++ if isinstance(self.inp, int): ++ return self.mirror_text[self.inp] ++ elif self.inp in os.environ: ++ return os.environ[self.inp] ++ else: ++ return '' ++ ++ def run_update(self): ++ pattern = self.substitute(self.pattern) ++ substitution = self.substitute(self.substitution, SubstitutionParser.escape_substitution) ++ ++ if pattern: ++ return self.expand(pattern, substitution) ++ ++ return True ++ ++ def expand(self, pattern, substitution): ++ # Try to compile pattern ++ try: ++ regex = re.compile(pattern, self.modifiers) ++ except re.error, message: ++ sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message)) ++ return False ++ ++ inp = self.get_input() ++ match = regex.search(inp) ++ ++ if not match: ++ self.set_text(inp) ++ else: ++ groups = match.groupdict() ++ ++ idx = 0 ++ for group in match.groups(): ++ groups[str(idx + 1)] = group ++ idx += 1 ++ ++ groups['0'] = match.group(0) ++ ++ parser = SubstitutionParser(substitution, groups) ++ self.set_text(parser.parse()) ++ ++ return True ++ ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Snippet.py b/plugins/snippets/snippets/Snippet.py +old mode 100755 +new mode 100644 +index 2d7f67d..192b036 +--- a/plugins/snippets/snippets/Snippet.py ++++ b/plugins/snippets/snippets/Snippet.py +@@ -23,333 +23,334 @@ from Parser import Parser, Token + from Helper import * + + class EvalUtilities: +- def __init__(self, view=None): +- self.view = view +- self._init_namespace() +- +- def _init_namespace(self): +- self.namespace = { +- '__builtins__': __builtins__, +- 'align': self.util_align, +- 'readfile': self.util_readfile, +- 'filesize': self.util_filesize +- } +- +- def _real_len(self, s, tablen = 0): +- if tablen == 0: +- tablen = self.view.get_tab_width() +- +- return len(s.expandtabs(tablen)) +- +- def _filename_to_uri(self, filename): +- gfile = Gio.file_new_for_path(filename) +- +- return gfile.get_uri() +- +- def util_readfile(self, filename): +- stream = Gio.file_new_for_path(filename).read() +- +- if not stream: +- return '' +- +- res = stream.read() +- stream.close() +- +- return res +- +- def util_filesize(self, filename): +- gfile = Gio.file_new_for_path(filename) +- info = gfile.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE) +- +- if not info: +- return 0 +- +- return info.get_size() +- +- def util_align(self, items): +- maxlen = [] +- tablen = self.view.get_tab_width() +- +- for row in range(0, len(items)): +- for col in range(0, len(items[row]) - 1): +- if row == 0: +- maxlen.append(0) +- +- items[row][col] += "\t" +- rl = self._real_len(items[row][col], tablen) +- +- if (rl > maxlen[col]): +- maxlen[col] = rl +- +- result = '' +- +- for row in range(0, len(items)): +- for col in range(0, len(items[row]) - 1): +- item = items[row][col] +- +- result += item + ("\t" * ((maxlen[col] - \ +- self._real_len(item, tablen)) / tablen)) +- +- result += items[row][len(items[row]) - 1] +- +- if row != len(items) - 1: +- result += "\n" +- +- return result ++ def __init__(self, view=None): ++ self.view = view ++ self._init_namespace() ++ ++ def _init_namespace(self): ++ self.namespace = { ++ '__builtins__': __builtins__, ++ 'align': self.util_align, ++ 'readfile': self.util_readfile, ++ 'filesize': self.util_filesize ++ } ++ ++ def _real_len(self, s, tablen = 0): ++ if tablen == 0: ++ tablen = self.view.get_tab_width() ++ ++ return len(s.expandtabs(tablen)) ++ ++ def _filename_to_uri(self, filename): ++ gfile = Gio.file_new_for_path(filename) ++ ++ return gfile.get_uri() ++ ++ def util_readfile(self, filename): ++ stream = Gio.file_new_for_path(filename).read() ++ ++ if not stream: ++ return '' ++ ++ res = stream.read() ++ stream.close() ++ ++ return res ++ ++ def util_filesize(self, filename): ++ gfile = Gio.file_new_for_path(filename) ++ info = gfile.query_info(Gio.FILE_ATTRIBUTE_STANDARD_SIZE) ++ ++ if not info: ++ return 0 ++ ++ return info.get_size() ++ ++ def util_align(self, items): ++ maxlen = [] ++ tablen = self.view.get_tab_width() ++ ++ for row in range(0, len(items)): ++ for col in range(0, len(items[row]) - 1): ++ if row == 0: ++ maxlen.append(0) ++ ++ items[row][col] += "\t" ++ rl = self._real_len(items[row][col], tablen) ++ ++ if (rl > maxlen[col]): ++ maxlen[col] = rl ++ ++ result = '' ++ ++ for row in range(0, len(items)): ++ for col in range(0, len(items[row]) - 1): ++ item = items[row][col] ++ ++ result += item + ("\t" * ((maxlen[col] - \ ++ self._real_len(item, tablen)) / tablen)) ++ ++ result += items[row][len(items[row]) - 1] ++ ++ if row != len(items) - 1: ++ result += "\n" ++ ++ return result + + class Snippet: +- def __init__(self, data): +- self.data = data +- +- def __getitem__(self, prop): +- return self.data[prop] +- +- def __setitem__(self, prop, value): +- self.data[prop] = value +- +- def accelerator_display(self): +- accel = self['accelerator'] +- +- if accel: +- keyval, mod = Gtk.accelerator_parse(accel) +- accel = Gtk.accelerator_get_label(keyval, mod) +- +- return accel or '' +- +- def display(self): +- nm = markup_escape(self['description']) +- +- tag = self['tag'] +- accel = self.accelerator_display() +- detail = [] +- +- if tag and tag != '': +- detail.append(tag) +- +- if accel and accel != '': +- detail.append(accel) +- +- if not detail: +- return nm +- else: +- return nm + ' (' + markup_escape(str.join(', ', detail)) + \ +- ')' +- +- def _add_placeholder(self, placeholder): +- if placeholder.tabstop in self.placeholders: +- if placeholder.tabstop == -1: +- self.placeholders[-1].append(placeholder) +- self.plugin_data.ordered_placeholders.append(placeholder) +- elif placeholder.tabstop == -1: +- self.placeholders[-1] = [placeholder] +- self.plugin_data.ordered_placeholders.append(placeholder) +- else: +- self.placeholders[placeholder.tabstop] = placeholder +- self.plugin_data.ordered_placeholders.append(placeholder) +- +- def _insert_text(self, text): +- # Insert text keeping indentation in mind +- indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n')) +- self._view.get_buffer().insert(self._insert_iter(), indented) +- +- def _insert_iter(self): +- return self._view.get_buffer().get_iter_at_mark(self._insert_mark) +- +- def _create_environment(self, data): +- val = ((data in os.environ) and os.environ[data]) or '' +- +- # Get all the current indentation +- all_indent = compute_indentation(self._view, self._insert_iter()) +- +- # Substract initial indentation to get the snippet indentation +- indent = all_indent[len(self._indent):] +- +- # Keep indentation +- return unicode.join('\n' + unicode(indent), val.split('\n')) +- +- def _create_placeholder(self, data): +- tabstop = data['tabstop'] +- begin = self._insert_iter() +- +- if tabstop == 0: +- # End placeholder +- return PlaceholderEnd(self._view, begin, data['default']) +- elif tabstop in self.placeholders: +- # Mirror placeholder +- return PlaceholderMirror(self._view, tabstop, begin) +- else: +- # Default placeholder +- return Placeholder(self._view, tabstop, data['default'], begin) +- +- def _create_shell(self, data): +- begin = self._insert_iter() +- return PlaceholderShell(self._view, data['tabstop'], begin, data['contents']) +- +- def _create_eval(self, data): +- begin = self._insert_iter() +- return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace) +- +- def _create_regex(self, data): +- begin = self._insert_iter() +- return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers']) +- +- def _create_text(self, data): +- return data +- +- def _invalid_placeholder(self, placeholder, remove): +- buf = self._view.get_buffer() +- +- # Remove the text because this placeholder is invalid +- if placeholder.default and remove: +- buf.delete(placeholder.begin_iter(), placeholder.end_iter()) +- +- placeholder.remove() +- +- if placeholder.tabstop == -1: +- index = self.placeholders[-1].index(placeholder) +- del self.placeholders[-1][index] +- else: +- del self.placeholders[placeholder.tabstop] +- +- self.plugin_data.ordered_placeholders.remove(placeholder) +- +- def _parse(self, plugin_data): +- # Initialize current variables +- self._view = plugin_data.view +- self._indent = compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark)) +- self._utils = EvalUtilities(self._view) +- self.placeholders = {} +- self._insert_mark = self.end_mark +- self.plugin_data = plugin_data +- +- # Create parser +- parser = Parser(data=self['text']) +- +- # Parse tokens +- while (True): +- token = parser.token() +- +- if not token: +- break +- +- try: +- val = {'environment': self._create_environment, +- 'placeholder': self._create_placeholder, +- 'shell': self._create_shell, +- 'eval': self._create_eval, +- 'regex': self._create_regex, +- 'text': self._create_text}[token.klass](token.data) +- except: +- sys.stderr.write('Token class not supported: %s\n' % token.klass) +- continue +- +- if isinstance(val, basestring): +- # Insert text +- self._insert_text(val) +- else: +- # Insert placeholder +- self._add_placeholder(val) +- +- # Create end placeholder if there isn't one yet +- if 0 not in self.placeholders: +- self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None) +- self.plugin_data.ordered_placeholders.append(self.placeholders[0]) +- +- # Make sure run_last is ran for all placeholders and remove any +- # non `ok` placeholders +- for tabstop in self.placeholders.copy(): +- ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]] +- +- for placeholder in ph: +- placeholder.run_last(self.placeholders) +- +- if not placeholder.ok or placeholder.done: +- self._invalid_placeholder(placeholder, not placeholder.ok) +- +- # Remove all the Expand placeholders which have a tabstop because +- # they can be used to mirror, but they shouldn't be real tabstops +- # (if they have mirrors installed). This is problably a bit of +- # a dirty hack :) +- if -1 not in self.placeholders: +- self.placeholders[-1] = [] +- +- for tabstop in self.placeholders.copy(): +- placeholder = self.placeholders[tabstop] +- +- if tabstop != -1: +- if isinstance(placeholder, PlaceholderExpand) and \ +- placeholder.has_references: +- # Add to anonymous placeholders +- self.placeholders[-1].append(placeholder) +- +- # Remove placeholder +- del self.placeholders[tabstop] +- +- self.plugin_data = None +- +- def insert_into(self, plugin_data, insert): +- buf = plugin_data.view.get_buffer() +- last_index = 0 +- +- # Find closest mark at current insertion, so that we may insert +- # our marks in the correct order +- (current, next) = plugin_data.next_placeholder() +- +- if current: +- # Insert AFTER current +- last_index = plugin_data.placeholders.index(current) + 1 +- elif next: +- # Insert BEFORE next +- last_index = plugin_data.placeholders.index(next) +- else: +- # Insert at first position +- last_index = 0 +- +- # lastIndex now contains the position of the last mark +- # Create snippet bounding marks +- self.begin_mark = buf.create_mark(None, insert, True) +- self.end_mark = buf.create_mark(None, insert, False) +- +- # Now parse the contents of this snippet, create Placeholders +- # and insert the placholder marks in the marks array of plugin_data +- self._parse(plugin_data) +- +- # So now all of the snippet is in the buffer, we have all our +- # placeholders right here, what's next, put all marks in the +- # plugin_data.marks +- k = self.placeholders.keys() +- k.sort(reverse=True) +- +- plugin_data.placeholders.insert(last_index, self.placeholders[0]) +- last_iter = self.placeholders[0].end_iter() +- +- for tabstop in k: +- if tabstop != -1 and tabstop != 0: +- placeholder = self.placeholders[tabstop] +- end_iter = placeholder.end_iter() +- +- if last_iter.compare(end_iter) < 0: +- last_iter = end_iter +- +- # Inserting placeholder +- plugin_data.placeholders.insert(last_index, placeholder) +- +- # Move end mark to last placeholder +- buf.move_mark(self.end_mark, last_iter) +- +- return self +- +- def deactivate(self): +- buf = self.begin_mark.get_buffer() +- +- buf.delete_mark(self.begin_mark) +- buf.delete_mark(self.end_mark) +- +- self.placeholders = {} +- +- def begin_iter(self): +- return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark) +- +- def end_iter(self): +- return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark) +-# ex:ts=8:et: ++ def __init__(self, data): ++ self.data = data ++ ++ def __getitem__(self, prop): ++ return self.data[prop] ++ ++ def __setitem__(self, prop, value): ++ self.data[prop] = value ++ ++ def accelerator_display(self): ++ accel = self['accelerator'] ++ ++ if accel: ++ keyval, mod = Gtk.accelerator_parse(accel) ++ accel = Gtk.accelerator_get_label(keyval, mod) ++ ++ return accel or '' ++ ++ def display(self): ++ nm = markup_escape(self['description']) ++ ++ tag = self['tag'] ++ accel = self.accelerator_display() ++ detail = [] ++ ++ if tag and tag != '': ++ detail.append(tag) ++ ++ if accel and accel != '': ++ detail.append(accel) ++ ++ if not detail: ++ return nm ++ else: ++ return nm + ' (' + markup_escape(str.join(', ', detail)) + \ ++ ')' ++ ++ def _add_placeholder(self, placeholder): ++ if placeholder.tabstop in self.placeholders: ++ if placeholder.tabstop == -1: ++ self.placeholders[-1].append(placeholder) ++ self.plugin_data.ordered_placeholders.append(placeholder) ++ elif placeholder.tabstop == -1: ++ self.placeholders[-1] = [placeholder] ++ self.plugin_data.ordered_placeholders.append(placeholder) ++ else: ++ self.placeholders[placeholder.tabstop] = placeholder ++ self.plugin_data.ordered_placeholders.append(placeholder) ++ ++ def _insert_text(self, text): ++ # Insert text keeping indentation in mind ++ indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n')) ++ self._view.get_buffer().insert(self._insert_iter(), indented) ++ ++ def _insert_iter(self): ++ return self._view.get_buffer().get_iter_at_mark(self._insert_mark) ++ ++ def _create_environment(self, data): ++ val = ((data in os.environ) and os.environ[data]) or '' ++ ++ # Get all the current indentation ++ all_indent = compute_indentation(self._view, self._insert_iter()) ++ ++ # Substract initial indentation to get the snippet indentation ++ indent = all_indent[len(self._indent):] ++ ++ # Keep indentation ++ return unicode.join('\n' + unicode(indent), val.split('\n')) ++ ++ def _create_placeholder(self, data): ++ tabstop = data['tabstop'] ++ begin = self._insert_iter() ++ ++ if tabstop == 0: ++ # End placeholder ++ return PlaceholderEnd(self._view, begin, data['default']) ++ elif tabstop in self.placeholders: ++ # Mirror placeholder ++ return PlaceholderMirror(self._view, tabstop, begin) ++ else: ++ # Default placeholder ++ return Placeholder(self._view, tabstop, data['default'], begin) ++ ++ def _create_shell(self, data): ++ begin = self._insert_iter() ++ return PlaceholderShell(self._view, data['tabstop'], begin, data['contents']) ++ ++ def _create_eval(self, data): ++ begin = self._insert_iter() ++ return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace) ++ ++ def _create_regex(self, data): ++ begin = self._insert_iter() ++ return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers']) ++ ++ def _create_text(self, data): ++ return data ++ ++ def _invalid_placeholder(self, placeholder, remove): ++ buf = self._view.get_buffer() ++ ++ # Remove the text because this placeholder is invalid ++ if placeholder.default and remove: ++ buf.delete(placeholder.begin_iter(), placeholder.end_iter()) ++ ++ placeholder.remove() ++ ++ if placeholder.tabstop == -1: ++ index = self.placeholders[-1].index(placeholder) ++ del self.placeholders[-1][index] ++ else: ++ del self.placeholders[placeholder.tabstop] ++ ++ self.plugin_data.ordered_placeholders.remove(placeholder) ++ ++ def _parse(self, plugin_data): ++ # Initialize current variables ++ self._view = plugin_data.view ++ self._indent = compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark)) ++ self._utils = EvalUtilities(self._view) ++ self.placeholders = {} ++ self._insert_mark = self.end_mark ++ self.plugin_data = plugin_data ++ ++ # Create parser ++ parser = Parser(data=self['text']) ++ ++ # Parse tokens ++ while (True): ++ token = parser.token() ++ ++ if not token: ++ break ++ ++ try: ++ val = {'environment': self._create_environment, ++ 'placeholder': self._create_placeholder, ++ 'shell': self._create_shell, ++ 'eval': self._create_eval, ++ 'regex': self._create_regex, ++ 'text': self._create_text}[token.klass](token.data) ++ except: ++ sys.stderr.write('Token class not supported: %s\n' % token.klass) ++ continue ++ ++ if isinstance(val, basestring): ++ # Insert text ++ self._insert_text(val) ++ else: ++ # Insert placeholder ++ self._add_placeholder(val) ++ ++ # Create end placeholder if there isn't one yet ++ if 0 not in self.placeholders: ++ self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None) ++ self.plugin_data.ordered_placeholders.append(self.placeholders[0]) ++ ++ # Make sure run_last is ran for all placeholders and remove any ++ # non `ok` placeholders ++ for tabstop in self.placeholders.copy(): ++ ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]] ++ ++ for placeholder in ph: ++ placeholder.run_last(self.placeholders) ++ ++ if not placeholder.ok or placeholder.done: ++ self._invalid_placeholder(placeholder, not placeholder.ok) ++ ++ # Remove all the Expand placeholders which have a tabstop because ++ # they can be used to mirror, but they shouldn't be real tabstops ++ # (if they have mirrors installed). This is problably a bit of ++ # a dirty hack :) ++ if -1 not in self.placeholders: ++ self.placeholders[-1] = [] ++ ++ for tabstop in self.placeholders.copy(): ++ placeholder = self.placeholders[tabstop] ++ ++ if tabstop != -1: ++ if isinstance(placeholder, PlaceholderExpand) and \ ++ placeholder.has_references: ++ # Add to anonymous placeholders ++ self.placeholders[-1].append(placeholder) ++ ++ # Remove placeholder ++ del self.placeholders[tabstop] ++ ++ self.plugin_data = None ++ ++ def insert_into(self, plugin_data, insert): ++ buf = plugin_data.view.get_buffer() ++ last_index = 0 ++ ++ # Find closest mark at current insertion, so that we may insert ++ # our marks in the correct order ++ (current, next) = plugin_data.next_placeholder() ++ ++ if current: ++ # Insert AFTER current ++ last_index = plugin_data.placeholders.index(current) + 1 ++ elif next: ++ # Insert BEFORE next ++ last_index = plugin_data.placeholders.index(next) ++ else: ++ # Insert at first position ++ last_index = 0 ++ ++ # lastIndex now contains the position of the last mark ++ # Create snippet bounding marks ++ self.begin_mark = buf.create_mark(None, insert, True) ++ self.end_mark = buf.create_mark(None, insert, False) ++ ++ # Now parse the contents of this snippet, create Placeholders ++ # and insert the placholder marks in the marks array of plugin_data ++ self._parse(plugin_data) ++ ++ # So now all of the snippet is in the buffer, we have all our ++ # placeholders right here, what's next, put all marks in the ++ # plugin_data.marks ++ k = self.placeholders.keys() ++ k.sort(reverse=True) ++ ++ plugin_data.placeholders.insert(last_index, self.placeholders[0]) ++ last_iter = self.placeholders[0].end_iter() ++ ++ for tabstop in k: ++ if tabstop != -1 and tabstop != 0: ++ placeholder = self.placeholders[tabstop] ++ end_iter = placeholder.end_iter() ++ ++ if last_iter.compare(end_iter) < 0: ++ last_iter = end_iter ++ ++ # Inserting placeholder ++ plugin_data.placeholders.insert(last_index, placeholder) ++ ++ # Move end mark to last placeholder ++ buf.move_mark(self.end_mark, last_iter) ++ ++ return self ++ ++ def deactivate(self): ++ buf = self.begin_mark.get_buffer() ++ ++ buf.delete_mark(self.begin_mark) ++ buf.delete_mark(self.end_mark) ++ ++ self.placeholders = {} ++ ++ def begin_iter(self): ++ return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark) ++ ++ def end_iter(self): ++ return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark) ++ ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/SubstitutionParser.py b/plugins/snippets/snippets/SubstitutionParser.py +old mode 100755 +new mode 100644 +index a41f5a6..246f4da +--- a/plugins/snippets/snippets/SubstitutionParser.py ++++ b/plugins/snippets/snippets/SubstitutionParser.py +@@ -18,185 +18,185 @@ + import re + + class ParseError(Exception): +- def __str__(self): +- return 'Parse error, resume next' ++ def __str__(self): ++ return 'Parse error, resume next' + + class Modifiers: +- def _first_char(s): +- first = (s != '' and s[0]) or '' +- rest = (len(s) > 1 and s[1:]) or '' +- +- return first, rest +- +- def upper_first(s): +- first, rest = Modifiers._first_char(s) +- +- return '%s%s' % (first.upper(), rest) +- +- def upper(s): +- return s.upper() +- +- def lower_first(s): +- first, rest = Modifiers._first_char(s) +- +- return '%s%s' % (first.lower(), rest) +- +- def lower(s): +- return s.lower() +- +- def title(s): +- return s.title() +- +- upper_first = staticmethod(upper_first) +- upper = staticmethod(upper) +- lower_first = staticmethod(lower_first) +- lower = staticmethod(lower) +- title = staticmethod(title) +- _first_char = staticmethod(_first_char) ++ def _first_char(s): ++ first = (s != '' and s[0]) or '' ++ rest = (len(s) > 1 and s[1:]) or '' ++ ++ return first, rest ++ ++ def upper_first(s): ++ first, rest = Modifiers._first_char(s) ++ ++ return '%s%s' % (first.upper(), rest) ++ ++ def upper(s): ++ return s.upper() ++ ++ def lower_first(s): ++ first, rest = Modifiers._first_char(s) ++ ++ return '%s%s' % (first.lower(), rest) ++ ++ def lower(s): ++ return s.lower() ++ ++ def title(s): ++ return s.title() ++ ++ upper_first = staticmethod(upper_first) ++ upper = staticmethod(upper) ++ lower_first = staticmethod(lower_first) ++ lower = staticmethod(lower) ++ title = staticmethod(title) ++ _first_char = staticmethod(_first_char) + + class SubstitutionParser: +- REG_ID = '[0-9]+' +- REG_NAME = '[a-zA-Z_]+' +- REG_MOD = '[a-zA-Z]+' +- REG_ESCAPE = '\\\\|\\(\\?|,|\\)' +- +- def __init__(self, pattern, groups = {}, modifiers = {}): +- self.pattern = pattern +- self.groups = groups +- +- self.REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (self.REG_ID, self.REG_ID, self.REG_NAME, self.REG_MOD) +- self.modifiers = {'u': Modifiers.upper_first, +- 'U': Modifiers.upper, +- 'l': Modifiers.lower_first, +- 'L': Modifiers.lower, +- 't': Modifiers.title} +- +- for k, v in modifiers.items(): +- self.modifiers[k] = v +- +- def parse(self): +- result, tokens = self._parse(self.pattern, None) +- +- return result +- +- def _parse(self, tokens, terminator): +- result = '' +- +- while tokens != '': +- if self._peek(tokens) == '' or self._peek(tokens) == terminator: +- tokens = self._remains(tokens) +- break +- +- try: +- res, tokens = self._expr(tokens, terminator) +- except ParseError: +- res, tokens = self._text(tokens) +- +- result += res +- +- return result, tokens +- +- def _peek(self, tokens, num = 0): +- return (num < len(tokens) and tokens[num]) +- +- def _token(self, tokens): +- if tokens == '': +- return '', ''; +- +- return tokens[0], (len(tokens) > 1 and tokens[1:]) or '' +- +- def _remains(self, tokens, num = 1): +- return (num < len(tokens) and tokens[num:]) or '' +- +- def _expr(self, tokens, terminator): +- if tokens == '': +- return '' +- +- try: +- return {'\\': self._escape, +- '(': self._condition}[self._peek(tokens)](tokens, terminator) +- except KeyError: +- raise ParseError +- +- def _text(self, tokens): +- return self._token(tokens) +- +- def _substitute(self, group, modifiers = ''): +- result = (self.groups.has_key(group) and self.groups[group]) or '' +- +- for modifier in modifiers: +- if self.modifiers.has_key(modifier): +- result = self.modifiers[modifier](result) +- +- return result +- +- def _match_group(self, tokens): +- match = re.match('\\\\%s' % self.REG_GROUP, tokens) +- +- if not match: +- return None, tokens +- +- return self._substitute(match.group(1) or match.group(2), match.group(3) or ''), tokens[match.end():] +- +- def _escape(self, tokens, terminator): +- # Try to match a group +- result, tokens = self._match_group(tokens) +- +- if result != None: +- return result, tokens +- +- s = self.REG_GROUP +- +- if terminator: +- s += '|%s' % re.escape(terminator) +- +- match = re.match('\\\\(\\\\%s|%s)' % (s, self.REG_ESCAPE), tokens) +- +- if not match: +- raise ParseError +- +- return match.group(1), tokens[match.end():] +- +- def _condition_value(self, tokens): +- match = re.match('\\\\?%s\s*' % self.REG_GROUP, tokens) +- +- if not match: +- return None, tokens +- +- groups = match.groups() +- name = groups[0] or groups[1] +- +- return self.groups.has_key(name) and self.groups[name] != None, tokens[match.end():] +- +- def _condition(self, tokens, terminator): +- # Match ? after ( +- if self._peek(tokens, 1) != '?': +- raise ParseError +- +- # Remove initial (? token +- tokens = self._remains(tokens, 2) +- condition, tokens = self._condition_value(tokens) +- +- if condition == None or self._peek(tokens) != ',': +- raise ParseError +- +- truepart, tokens = self._parse(self._remains(tokens), ',') +- +- if truepart == None: +- raise ParseError +- +- falsepart, tokens = self._parse(tokens, ')') +- +- if falsepart == None: +- raise ParseError +- +- if condition: +- return truepart, tokens +- else: +- return falsepart, tokens +- +- def escape_substitution(substitution): +- return re.sub('(%s|%s)' % (self.REG_GROUP, self.REG_ESCAPE), '\\\\\\1', substitution) +- +- escapesubstitution = staticmethod(escape_substitution) +-# ex:ts=8:et: ++ REG_ID = '[0-9]+' ++ REG_NAME = '[a-zA-Z_]+' ++ REG_MOD = '[a-zA-Z]+' ++ REG_ESCAPE = '\\\\|\\(\\?|,|\\)' ++ ++ def __init__(self, pattern, groups = {}, modifiers = {}): ++ self.pattern = pattern ++ self.groups = groups ++ ++ self.REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (self.REG_ID, self.REG_ID, self.REG_NAME, self.REG_MOD) ++ self.modifiers = {'u': Modifiers.upper_first, ++ 'U': Modifiers.upper, ++ 'l': Modifiers.lower_first, ++ 'L': Modifiers.lower, ++ 't': Modifiers.title} ++ ++ for k, v in modifiers.items(): ++ self.modifiers[k] = v ++ ++ def parse(self): ++ result, tokens = self._parse(self.pattern, None) ++ ++ return result ++ ++ def _parse(self, tokens, terminator): ++ result = '' ++ ++ while tokens != '': ++ if self._peek(tokens) == '' or self._peek(tokens) == terminator: ++ tokens = self._remains(tokens) ++ break ++ ++ try: ++ res, tokens = self._expr(tokens, terminator) ++ except ParseError: ++ res, tokens = self._text(tokens) ++ ++ result += res ++ ++ return result, tokens ++ ++ def _peek(self, tokens, num = 0): ++ return (num < len(tokens) and tokens[num]) ++ ++ def _token(self, tokens): ++ if tokens == '': ++ return '', ''; ++ ++ return tokens[0], (len(tokens) > 1 and tokens[1:]) or '' ++ ++ def _remains(self, tokens, num = 1): ++ return (num < len(tokens) and tokens[num:]) or '' ++ ++ def _expr(self, tokens, terminator): ++ if tokens == '': ++ return '' ++ ++ try: ++ return {'\\': self._escape, ++ '(': self._condition}[self._peek(tokens)](tokens, terminator) ++ except KeyError: ++ raise ParseError ++ ++ def _text(self, tokens): ++ return self._token(tokens) ++ ++ def _substitute(self, group, modifiers = ''): ++ result = (self.groups.has_key(group) and self.groups[group]) or '' ++ ++ for modifier in modifiers: ++ if self.modifiers.has_key(modifier): ++ result = self.modifiers[modifier](result) ++ ++ return result ++ ++ def _match_group(self, tokens): ++ match = re.match('\\\\%s' % self.REG_GROUP, tokens) ++ ++ if not match: ++ return None, tokens ++ ++ return self._substitute(match.group(1) or match.group(2), match.group(3) or ''), tokens[match.end():] ++ ++ def _escape(self, tokens, terminator): ++ # Try to match a group ++ result, tokens = self._match_group(tokens) ++ ++ if result != None: ++ return result, tokens ++ ++ s = self.REG_GROUP ++ ++ if terminator: ++ s += '|%s' % re.escape(terminator) ++ ++ match = re.match('\\\\(\\\\%s|%s)' % (s, self.REG_ESCAPE), tokens) ++ ++ if not match: ++ raise ParseError ++ ++ return match.group(1), tokens[match.end():] ++ ++ def _condition_value(self, tokens): ++ match = re.match('\\\\?%s\s*' % self.REG_GROUP, tokens) ++ ++ if not match: ++ return None, tokens ++ ++ groups = match.groups() ++ name = groups[0] or groups[1] ++ ++ return self.groups.has_key(name) and self.groups[name] != None, tokens[match.end():] ++ ++ def _condition(self, tokens, terminator): ++ # Match ? after ( ++ if self._peek(tokens, 1) != '?': ++ raise ParseError ++ ++ # Remove initial (? token ++ tokens = self._remains(tokens, 2) ++ condition, tokens = self._condition_value(tokens) ++ ++ if condition == None or self._peek(tokens) != ',': ++ raise ParseError ++ ++ truepart, tokens = self._parse(self._remains(tokens), ',') ++ ++ if truepart == None: ++ raise ParseError ++ ++ falsepart, tokens = self._parse(tokens, ')') ++ ++ if falsepart == None: ++ raise ParseError ++ ++ if condition: ++ return truepart, tokens ++ else: ++ return falsepart, tokens ++ ++ def escape_substitution(substitution): ++ return re.sub('(%s|%s)' % (self.REG_GROUP, self.REG_ESCAPE), '\\\\\\1', substitution) ++ ++ escapesubstitution = staticmethod(escape_substitution) ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/WindowHelper.py b/plugins/snippets/snippets/WindowHelper.py +old mode 100755 +new mode 100644 +index 3b08d12..296ff03 +--- a/plugins/snippets/snippets/WindowHelper.py ++++ b/plugins/snippets/snippets/WindowHelper.py +@@ -25,127 +25,127 @@ from Document import Document + from Library import Library + + class WindowHelper: +- def __init__(self, plugin): +- self.plugin = plugin +- self.current_controller = None +- self.current_language = None +- self.signal_ids = {} +- +- def run(self, window): +- self.window = window +- +- self.insert_menu() +- +- self.accel_group = Library().get_accel_group(None) +- +- window.add_accel_group(self.accel_group) +- window.connect('tab-added', self.on_tab_added) +- +- # Add controllers to all the current views +- for view in self.window.get_views(): +- if isinstance(view, Pluma.View) and not self.has_controller(view): +- view._snippet_controller = Document(self, view) +- +- self.update() +- +- def stop(self): +- self.window.remove_accel_group(self.accel_group) +- self.accel_group = None +- +- self.remove_menu() +- +- # Iterate over all the tabs and remove every controller +- for view in self.window.get_views(): +- if isinstance(view, Pluma.View) and self.has_controller(view): +- view._snippet_controller.stop() +- view._snippet_controller = None +- +- self.window = None +- self.plugin = None +- +- def insert_menu(self): +- manager = self.window.get_ui_manager() +- +- self.action_group = Gtk.ActionGroup("PlumaSnippetPluginActions") +- self.action_group.set_translation_domain('pluma') +- self.action_group.add_actions([('ManageSnippets', None, +- _('Manage _Snippets...'), \ +- None, _('Manage snippets'), \ +- self.on_action_snippets_activate)]) +- +- self.merge_id = manager.new_merge_id() +- manager.insert_action_group(self.action_group, -1) +- manager.add_ui(self.merge_id, '/MenuBar/ToolsMenu/ToolsOps_5', \ +- 'ManageSnippets', 'ManageSnippets', Gtk.UIManagerItemType.MENUITEM, False) +- +- def remove_menu(self): +- manager = self.window.get_ui_manager() +- manager.remove_ui(self.merge_id) +- manager.remove_action_group(self.action_group) +- self.action_group = None +- +- def find_snippet(self, snippets, tag): +- result = [] +- +- for snippet in snippets: +- if Snippet(snippet)['tag'] == tag: +- result.append(snippet) +- +- return result +- +- def has_controller(self, view): +- return hasattr(view, '_snippet_controller') and view._snippet_controller +- +- def update_language(self): +- if not self.window: +- return +- +- if self.current_language: +- accel_group = Library().get_accel_group( \ +- self.current_language) +- self.window.remove_accel_group(accel_group) +- +- if self.current_controller: +- self.current_language = self.current_controller.language_id +- +- if self.current_language != None: +- accel_group = Library().get_accel_group( \ +- self.current_language) +- self.window.add_accel_group(accel_group) +- else: +- self.current_language = None +- +- def language_changed(self, controller): +- if controller == self.current_controller: +- self.update_language() +- +- def update(self): +- view = self.window.get_active_view() +- +- if not view or not self.has_controller(view): +- return +- +- controller = view._snippet_controller +- +- if controller != self.current_controller: +- self.current_controller = controller +- self.update_language() +- +- # Callbacks +- +- def on_tab_added(self, window, tab): +- # Create a new controller for this tab if it has a standard pluma view +- view = tab.get_view() +- +- if isinstance(view, Pluma.View) and not self.has_controller(view): +- view._snippet_controller = Document(self, view) +- +- self.update() +- +- def on_action_snippets_activate(self, item): +- self.plugin.create_configure_dialog() +- +- def accelerator_activated(self, keyval, mod): +- return self.current_controller.accelerator_activate(keyval, mod) +- +-# ex:ts=8:et: ++ def __init__(self, plugin): ++ self.plugin = plugin ++ self.current_controller = None ++ self.current_language = None ++ self.signal_ids = {} ++ ++ def run(self, window): ++ self.window = window ++ ++ self.insert_menu() ++ ++ self.accel_group = Library().get_accel_group(None) ++ ++ window.add_accel_group(self.accel_group) ++ window.connect('tab-added', self.on_tab_added) ++ ++ # Add controllers to all the current views ++ for view in self.window.get_views(): ++ if isinstance(view, Pluma.View) and not self.has_controller(view): ++ view._snippet_controller = Document(self, view) ++ ++ self.update() ++ ++ def stop(self): ++ self.window.remove_accel_group(self.accel_group) ++ self.accel_group = None ++ ++ self.remove_menu() ++ ++ # Iterate over all the tabs and remove every controller ++ for view in self.window.get_views(): ++ if isinstance(view, Pluma.View) and self.has_controller(view): ++ view._snippet_controller.stop() ++ view._snippet_controller = None ++ ++ self.window = None ++ self.plugin = None ++ ++ def insert_menu(self): ++ manager = self.window.get_ui_manager() ++ ++ self.action_group = Gtk.ActionGroup("PlumaSnippetPluginActions") ++ self.action_group.set_translation_domain('pluma') ++ self.action_group.add_actions([('ManageSnippets', None, ++ _('Manage _Snippets...'), \ ++ None, _('Manage snippets'), \ ++ self.on_action_snippets_activate)]) ++ ++ self.merge_id = manager.new_merge_id() ++ manager.insert_action_group(self.action_group, -1) ++ manager.add_ui(self.merge_id, '/MenuBar/ToolsMenu/ToolsOps_5', \ ++ 'ManageSnippets', 'ManageSnippets', Gtk.UIManagerItemType.MENUITEM, False) ++ ++ def remove_menu(self): ++ manager = self.window.get_ui_manager() ++ manager.remove_ui(self.merge_id) ++ manager.remove_action_group(self.action_group) ++ self.action_group = None ++ ++ def find_snippet(self, snippets, tag): ++ result = [] ++ ++ for snippet in snippets: ++ if Snippet(snippet)['tag'] == tag: ++ result.append(snippet) ++ ++ return result ++ ++ def has_controller(self, view): ++ return hasattr(view, '_snippet_controller') and view._snippet_controller ++ ++ def update_language(self): ++ if not self.window: ++ return ++ ++ if self.current_language: ++ accel_group = Library().get_accel_group( \ ++ self.current_language) ++ self.window.remove_accel_group(accel_group) ++ ++ if self.current_controller: ++ self.current_language = self.current_controller.language_id ++ ++ if self.current_language != None: ++ accel_group = Library().get_accel_group( \ ++ self.current_language) ++ self.window.add_accel_group(accel_group) ++ else: ++ self.current_language = None ++ ++ def language_changed(self, controller): ++ if controller == self.current_controller: ++ self.update_language() ++ ++ def update(self): ++ view = self.window.get_active_view() ++ ++ if not view or not self.has_controller(view): ++ return ++ ++ controller = view._snippet_controller ++ ++ if controller != self.current_controller: ++ self.current_controller = controller ++ self.update_language() ++ ++ # Callbacks ++ ++ def on_tab_added(self, window, tab): ++ # Create a new controller for this tab if it has a standard pluma view ++ view = tab.get_view() ++ ++ if isinstance(view, Pluma.View) and not self.has_controller(view): ++ view._snippet_controller = Document(self, view) ++ ++ self.update() ++ ++ def on_action_snippets_activate(self, item): ++ self.plugin.create_configure_dialog() ++ ++ def accelerator_activated(self, keyval, mod): ++ return self.current_controller.accelerator_activate(keyval, mod) ++ ++# ex:ts=4:et: +diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py +old mode 100755 +new mode 100644 +index d005e89..8642406 +--- a/plugins/snippets/snippets/__init__.py ++++ b/plugins/snippets/snippets/__init__.py +@@ -23,71 +23,73 @@ from Library import Library + from Manager import Manager + + class SnippetsPlugin(GObject.Object, Peas.Activatable): +- __gtype_name__ = "SnippetsPlugin" ++ __gtype_name__ = "SnippetsPlugin" + +- object = GObject.Property(type=GObject.Object) ++ object = GObject.Property(type=GObject.Object) + +- def __init__(self): +- GObject.Object.__init__(self) ++ def __init__(self): ++ GObject.Object.__init__(self) + +- self.dlg = None ++ self.dlg = None + +- def system_dirs(self): +- if 'XDG_DATA_DIRS' in os.environ: +- datadirs = os.environ['XDG_DATA_DIRS'] +- else: +- datadirs = '/usr/local/share' + os.pathsep + '/usr/share' ++ def system_dirs(self): ++ if 'XDG_DATA_DIRS' in os.environ: ++ datadirs = os.environ['XDG_DATA_DIRS'] ++ else: ++ datadirs = '/usr/local/share' + os.pathsep + '/usr/share' + +- dirs = [] ++ dirs = [] + +- for d in datadirs.split(os.pathsep): +- d = os.path.join(d, 'pluma', 'plugins', 'snippets') ++ for d in datadirs.split(os.pathsep): ++ d = os.path.join(d, 'pluma', 'plugins', 'snippets') + +- if os.path.isdir(d): +- dirs.append(d) ++ if os.path.isdir(d): ++ dirs.append(d) + +- dirs.append(self.plugin_info.get_data_dir()) +- return dirs ++ dirs.append(self.plugin_info.get_data_dir()) ++ return dirs + +- def do_activate(self): +- library = Library() +- library.add_accelerator_callback(self.accelerator_activated) ++ def do_activate(self): ++ library = Library() ++ library.add_accelerator_callback(self.accelerator_activated) + +- snippetsdir = os.path.join(GLib.get_user_config_dir(), '/pluma/snippets') +- library.set_dirs(snippetsdir, self.system_dirs()) ++ snippetsdir = os.path.join(GLib.get_user_config_dir(), '/pluma/snippets') ++ library.set_dirs(snippetsdir, self.system_dirs()) + +- self._helper = WindowHelper(self) ++ self._helper = WindowHelper(self) + +- window = self.object +- self._helper.run(window) ++ window = self.object ++ self._helper.run(window) + +- def do_deactivate(self): +- library = Library() +- library.remove_accelerator_callback(self.accelerator_activated) ++ def do_deactivate(self): ++ library = Library() ++ library.remove_accelerator_callback(self.accelerator_activated) + +- self._helper.stop() +- self._helper = None ++ self._helper.stop() ++ self._helper = None + +- def do_update_state(self): +- self._helper.update() ++ def do_update_state(self): ++ self._helper.update() + +- def create_configure_dialog(self): +- if not self.dlg: +- self.dlg = Manager(self.plugin_info.get_data_dir()) +- else: +- self.dlg.run() ++ def create_configure_dialog(self): ++ if not self.dlg: ++ self.dlg = Manager(self.plugin_info.get_data_dir()) ++ else: ++ self.dlg.run() + +- window = Pluma.App.get_default().get_active_window() ++ window = Pluma.App.get_default().get_active_window() + +- if window: +- self.dlg.dlg.set_transient_for(window) ++ if window: ++ self.dlg.dlg.set_transient_for(window) + +- return self.dlg.dlg ++ return self.dlg.dlg + +- def accelerator_activated(self, group, obj, keyval, mod): +- ret = False ++ def accelerator_activated(self, group, obj, keyval, mod): ++ ret = False + +- if self._helper: +- ret = self._helper.accelerator_activated(keyval, mod) ++ if self._helper: ++ ret = self._helper.accelerator_activated(keyval, mod) + +- return ret ++ return ret ++ ++# ex:ts=4:et: +-- +2.21.0 + diff --git a/pluma_0001-Switch-to-Python-3.patch b/pluma_0001-Switch-to-Python-3.patch new file mode 100644 index 0000000..ca74728 --- /dev/null +++ b/pluma_0001-Switch-to-Python-3.patch @@ -0,0 +1,87 @@ +From d76c3e37d1212eaa3cac2f293d539bddf9a250f9 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Mon, 27 May 2019 15:56:31 +0200 +Subject: [PATCH 1/2] Switch to Python 3 + +--- + configure.ac | 2 +- + plugins/externaltools/externaltools.plugin.desktop.in | 2 +- + plugins/pythonconsole/pythonconsole.plugin.desktop.in | 2 +- + plugins/quickopen/quickopen.plugin.desktop.in | 2 +- + plugins/snippets/snippets.plugin.desktop.in | 2 +- + pluma/pluma-plugins-engine.c | 2 +- + 6 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/configure.ac b/configure.ac +index ceb9a5c..c381583 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -189,7 +189,7 @@ else + have_introspection=no + fi + +-AM_PATH_PYTHON([2.7]) ++AM_PATH_PYTHON([3.0]) + + dnl ================================================================ + dnl GSettings related settings +diff --git a/plugins/externaltools/externaltools.plugin.desktop.in b/plugins/externaltools/externaltools.plugin.desktop.in +index b3261e7..b1a7f8b 100644 +--- a/plugins/externaltools/externaltools.plugin.desktop.in ++++ b/plugins/externaltools/externaltools.plugin.desktop.in +@@ -1,5 +1,5 @@ + [Plugin] +-Loader=python ++Loader=python3 + Module=externaltools + IAge=2 + _Name=External Tools +diff --git a/plugins/pythonconsole/pythonconsole.plugin.desktop.in b/plugins/pythonconsole/pythonconsole.plugin.desktop.in +index 50d2a7a..4ecbd30 100644 +--- a/plugins/pythonconsole/pythonconsole.plugin.desktop.in ++++ b/plugins/pythonconsole/pythonconsole.plugin.desktop.in +@@ -1,5 +1,5 @@ + [Plugin] +-Loader=python ++Loader=python3 + Module=pythonconsole + IAge=2 + _Name=Python Console +diff --git a/plugins/quickopen/quickopen.plugin.desktop.in b/plugins/quickopen/quickopen.plugin.desktop.in +index 891a0c3..547e430 100644 +--- a/plugins/quickopen/quickopen.plugin.desktop.in ++++ b/plugins/quickopen/quickopen.plugin.desktop.in +@@ -1,5 +1,5 @@ + [Plugin] +-Loader=python ++Loader=python3 + Module=quickopen + IAge=2 + _Name=Quick Open +diff --git a/plugins/snippets/snippets.plugin.desktop.in b/plugins/snippets/snippets.plugin.desktop.in +index ea960f3..ba1ecbc 100644 +--- a/plugins/snippets/snippets.plugin.desktop.in ++++ b/plugins/snippets/snippets.plugin.desktop.in +@@ -1,5 +1,5 @@ + [Plugin] +-Loader=python ++Loader=python3 + Module=snippets + IAge=2 + _Name=Snippets +diff --git a/pluma/pluma-plugins-engine.c b/pluma/pluma-plugins-engine.c +index e765b0f..c670e62 100644 +--- a/pluma/pluma-plugins-engine.c ++++ b/pluma/pluma-plugins-engine.c +@@ -60,7 +60,7 @@ pluma_plugins_engine_init (PlumaPluginsEngine *engine) + + pluma_debug (DEBUG_PLUGINS); + +- peas_engine_enable_loader (PEAS_ENGINE (engine), "python"); ++ peas_engine_enable_loader (PEAS_ENGINE (engine), "python3"); + + engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, + PLUMA_TYPE_PLUGINS_ENGINE, +-- +2.21.0 + diff --git a/pluma_0001-disable-python-plugins-again.patch b/pluma_0001-disable-python-plugins-again.patch deleted file mode 100644 index ba3affd..0000000 --- a/pluma_0001-disable-python-plugins-again.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 59054bb7dfbd5f6f985ddd1712da22262425b8a6 Mon Sep 17 00:00:00 2001 -From: raveit65 -Date: Fri, 20 Apr 2018 16:34:35 +0200 -Subject: [PATCH] disable python plugins again - ---- - configure.ac | 14 -------------- - plugins/Makefile.am | 8 -------- - 2 files changed, 22 deletions(-) - -diff --git a/configure.ac b/configure.ac -index 8ccb37d..177d305 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -181,8 +181,6 @@ else - have_introspection=no - fi - --AM_PATH_PYTHON([2.7]) -- - dnl ================================================================ - dnl GSettings related settings - dnl ================================================================ -@@ -233,21 +231,9 @@ pixmaps/Makefile - plugins/Makefile - plugins/changecase/Makefile - plugins/docinfo/Makefile --plugins/externaltools/data/Makefile --plugins/externaltools/Makefile --plugins/externaltools/scripts/Makefile --plugins/externaltools/tools/Makefile - plugins/filebrowser/Makefile - plugins/filebrowser/org.mate.pluma.plugins.filebrowser.gschema.xml - plugins/modelines/Makefile --plugins/pythonconsole/Makefile --plugins/pythonconsole/pythonconsole/Makefile --plugins/quickopen/Makefile --plugins/quickopen/quickopen/Makefile --plugins/snippets/data/lang/Makefile --plugins/snippets/data/Makefile --plugins/snippets/Makefile --plugins/snippets/snippets/Makefile - plugins/sort/Makefile - plugins/spell/Makefile - plugins/spell/org.mate.pluma.plugins.spell.gschema.xml -diff --git a/plugins/Makefile.am b/plugins/Makefile.am -index 28ae891..c80139c 100644 ---- a/plugins/Makefile.am -+++ b/plugins/Makefile.am -@@ -1,12 +1,8 @@ - DIST_SUBDIRS = \ - changecase \ - docinfo \ -- externaltools \ - filebrowser \ - modelines \ -- pythonconsole \ -- quickopen \ -- snippets \ - sort \ - spell \ - taglist \ -@@ -16,12 +12,8 @@ DIST_SUBDIRS = \ - SUBDIRS = \ - changecase \ - docinfo \ -- externaltools \ - filebrowser \ - modelines \ -- pythonconsole \ -- quickopen \ -- snippets \ - sort \ - taglist \ - time \ --- -2.13.6 - diff --git a/pluma_0001-externaltools-plugin-change-code-for-Python-2-3-comp.patch b/pluma_0001-externaltools-plugin-change-code-for-Python-2-3-comp.patch new file mode 100644 index 0000000..4e355f4 --- /dev/null +++ b/pluma_0001-externaltools-plugin-change-code-for-Python-2-3-comp.patch @@ -0,0 +1,1780 @@ +From a53f0408ea3fb6d8a19ba75f93f6315375fbf978 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Thu, 23 May 2019 20:42:23 +0200 +Subject: [PATCH] externaltools plugin: change code for Python 2 & 3 + compatibility. + +--- + plugins/externaltools/tools/__init__.py | 13 +- + plugins/externaltools/tools/capture.py | 21 +- + plugins/externaltools/tools/functions.py | 6 +- + plugins/externaltools/tools/library.py | 78 ++- + plugins/externaltools/tools/manager.py | 209 ++---- + plugins/externaltools/tools/outputpanel.py | 9 +- + plugins/externaltools/tools/outputpanel.ui | 22 +- + plugins/externaltools/tools/tools.ui | 706 ++++++++++----------- + 8 files changed, 469 insertions(+), 595 deletions(-) + +diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py +index 153d6c6..b0b67dc 100755 +--- a/plugins/externaltools/tools/__init__.py ++++ b/plugins/externaltools/tools/__init__.py +@@ -16,14 +16,13 @@ + # along with this program; if not, write to the Free Software + # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +-__all__ = ('ExternalToolsPlugin', 'Manager', 'OutputPanel', 'Capture', 'UniqueById') ++__all__ = ('ExternalToolsPlugin', ) + + from gi.repository import GObject, Gtk, Peas, Pluma +-from manager import Manager +-from library import ToolLibrary +-from outputpanel import OutputPanel +-from capture import Capture +-from functions import * ++from .manager import Manager ++from .library import ToolLibrary ++from .outputpanel import OutputPanel ++from .functions import * + + class ToolMenu(object): + def __init__(self, library, window, panel, plugin, menupath): +@@ -214,7 +213,7 @@ class ExternalToolsPlugin(GObject.Object, Peas.Activatable): + bottom = window.get_bottom_panel() + bottom.add_item_with_icon(self._output_buffer.panel, + _("Shell Output"), +- Gtk.STOCK_EXECUTE) ++ "system-run") + + def do_deactivate(self): + window = self.object +diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py +index 73ce270..67d12bf 100755 +--- a/plugins/externaltools/tools/capture.py ++++ b/plugins/externaltools/tools/capture.py +@@ -18,7 +18,9 @@ + + __all__ = ('Capture', ) + +-import os, sys, signal ++import os ++import sys ++import signal + import locale + import subprocess + import fcntl +@@ -39,7 +41,7 @@ class Capture(GObject.Object): + 'end-execute' : (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_INT,)) + } + +- def __init__(self, command, cwd = None, env = {}): ++ def __init__(self, command, cwd=None, env={}): + GObject.GObject.__init__(self) + self.pipe = None + self.env = env +@@ -58,6 +60,8 @@ class Capture(GObject.Object): + self.flags = flags + + def set_input(self, text): ++ if text and not isinstance(text, bytes): ++ text = text.encode("utf-8") + self.input_text = text + + def set_cwd(self, cwd): +@@ -87,7 +91,7 @@ class Capture(GObject.Object): + + try: + self.pipe = subprocess.Popen(self.command, **popen_args) +- except OSError, e: ++ except OSError as e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return +@@ -116,7 +120,7 @@ class Capture(GObject.Object): + # IO + if self.input_text is not None: + # Write async, in chunks of something +- self.write_buffer = str(self.input_text) ++ self.write_buffer = self.input_text + + if self.idle_write_chunk(): + self.idle_write_id = GLib.idle_add(self.idle_write_chunk) +@@ -136,7 +140,7 @@ class Capture(GObject.Object): + self.pipe.stdin.write(self.write_buffer[:m]) + + if m == l: +- self.write_buffer = '' ++ self.write_buffer = b'' + self.pipe.stdin.close() + + self.idle_write_id = 0 +@@ -157,11 +161,10 @@ class Capture(GObject.Object): + + if len(line) > 0: + try: +- line = unicode(line, 'utf-8') ++ line = line.decode('utf-8') + except: +- line = unicode(line, +- locale.getdefaultlocale()[1], +- 'replace') ++ line = line.decode(locale.getdefaultlocale()[1], ++ 'replace') + + self.read_buffer += line + lines = self.read_buffer.splitlines(True) +diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py +index dd4f82b..e76689b 100755 +--- a/plugins/externaltools/tools/functions.py ++++ b/plugins/externaltools/tools/functions.py +@@ -18,8 +18,8 @@ + + import os + from gi.repository import Gio, Gdk, Gtk, GtkSource, Pluma +-from outputpanel import OutputPanel +-from capture import * ++from .outputpanel import OutputPanel ++from .capture import * + + def default(val, d): + if val is not None: +@@ -131,8 +131,6 @@ def run_external_tool(window, panel, node): + elif input_type == 'selection' or input_type == 'selection-document': + try: + start, end = document.get_selection_bounds() +- +- print start, end + except ValueError: + if input_type == 'selection-document': + start, end = document.get_bounds() +diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py +index 186c33f..ff3fa9b 100755 +--- a/plugins/externaltools/tools/library.py ++++ b/plugins/externaltools/tools/library.py +@@ -19,19 +19,21 @@ + import os + import re + import locale ++import codecs + from gi.repository import GLib + ++ + 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 = super(Singleton, cls).__new__(cls, *args, **kwargs) + cls._instance.__init_once__() + + return cls._instance + ++ + class ToolLibrary(Singleton): + def __init_once__(self): + self.locations = [] +@@ -46,7 +48,7 @@ class ToolLibrary(Singleton): + + # self.locations[0] is where we save the custom scripts + toolsdir = os.path.join(GLib.get_user_config_dir(), 'pluma/tools') +- self.locations.insert(0, toolsdir); ++ self.locations.insert(0, toolsdir) + + if not os.path.isdir(self.locations[0]): + os.makedirs(self.locations[0]) +@@ -74,7 +76,7 @@ class ToolLibrary(Singleton): + if not os.path.isfile(filename): + return + +- print "External tools: importing old tools into the new store..." ++ print("External tools: importing old tools into the new store...") + + xtree = et.parse(filename) + xroot = xtree.getroot() +@@ -97,7 +99,7 @@ class ToolLibrary(Singleton): + + tool.save_with_script(xtool.text) + +- def get_full_path(self, path, mode='r', system = True, local = True): ++ def get_full_path(self, path, mode='r', system=True, local=True): + assert (system or local) + if path is None: + return None +@@ -123,6 +125,7 @@ class ToolLibrary(Singleton): + os.mkdir(dirname) + return path + ++ + class ToolDirectory(object): + def __init__(self, parent, dirname): + super(ToolDirectory, self).__init__() +@@ -145,8 +148,7 @@ class ToolDirectory(object): + continue + for i in os.listdir(d): + elements[i] = None +- keys = elements.keys() +- keys.sort() ++ keys = sorted(elements.keys()) + return keys + + def _load(self): +@@ -196,7 +198,7 @@ class ToolDirectory(object): + class Tool(object): + RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$') + +- def __init__(self, parent, filename = None): ++ def __init__(self, parent, filename=None): + super(Tool, self).__init__() + self.parent = parent + self.library = parent.library +@@ -212,7 +214,7 @@ class Tool(object): + if value.strip() == '': + return [] + else: +- return map(lambda x: x.strip(), value.split(',')) ++ return [x.strip() for x in value.split(',')] + + def _from_list(self, value): + return ','.join(value) +@@ -231,7 +233,7 @@ class Tool(object): + if filename is None: + return + +- fp = file(filename, 'r', 1) ++ fp = codecs.open(filename, 'r', encoding='utf-8') + in_block = False + lang = locale.getlocale(locale.LC_MESSAGES)[0] + +@@ -239,8 +241,10 @@ class Tool(object): + if not in_block: + in_block = line.startswith('# [Pluma Tool]') + continue +- if line.startswith('##') or line.startswith('# #'): continue +- if not line.startswith('# '): break ++ if line.startswith('##') or line.startswith('# #'): ++ continue ++ if not line.startswith('# '): ++ break + + try: + (key, value) = [i.strip() for i in line[2:].split('=', 1)] +@@ -266,9 +270,6 @@ class Tool(object): + def is_local(self): + return self.library.get_full_path(self.get_path(), system=False) is not None + +- def is_global(self): +- return self.library.get_full_path(self.get_path(), local=False) is not None +- + def get_path(self): + if self.filename is not None: + return os.path.join(self.parent.get_path(), self.filename) +@@ -284,7 +285,8 @@ class Tool(object): + + def get_applicability(self): + applicability = self._properties.get('Applicability') +- if applicability: return applicability ++ if applicability: ++ return applicability + return 'all' + + def set_applicability(self, value): +@@ -294,7 +296,8 @@ class Tool(object): + + def get_name(self): + name = self._properties.get('Name') +- if name: return name ++ if name: ++ return name + return os.path.basename(self.filename) + + def set_name(self, value): +@@ -304,7 +307,8 @@ class Tool(object): + + def get_shortcut(self): + shortcut = self._properties.get('Shortcut') +- if shortcut: return shortcut ++ if shortcut: ++ return shortcut + return None + + def set_shortcut(self, value): +@@ -314,7 +318,8 @@ class Tool(object): + + def get_comment(self): + comment = self._properties.get('Comment') +- if comment: return comment ++ if comment: ++ return comment + return self.filename + + def set_comment(self, value): +@@ -324,7 +329,8 @@ class Tool(object): + + def get_input(self): + input = self._properties.get('Input') +- if input: return input ++ if input: ++ return input + return 'nothing' + + def set_input(self, value): +@@ -334,7 +340,8 @@ class Tool(object): + + def get_output(self): + output = self._properties.get('Output') +- if output: return output ++ if output: ++ return output + return 'output-panel' + + def set_output(self, value): +@@ -344,7 +351,8 @@ class Tool(object): + + def get_save_files(self): + save_files = self._properties.get('Save-files') +- if save_files: return save_files ++ if save_files: ++ return save_files + return 'nothing' + + def set_save_files(self, value): +@@ -354,7 +362,8 @@ class Tool(object): + + def get_languages(self): + languages = self._properties.get('Languages') +- if languages: return languages ++ if languages: ++ return languages + return [] + + def set_languages(self, value): +@@ -370,7 +379,7 @@ class Tool(object): + if filename is None: + return True + +- fp = open(filename, 'r', 1) ++ fp = codecs.open(filename, 'r', encoding='utf-8') + for line in fp: + if line.strip() == '': + continue +@@ -386,7 +395,7 @@ class Tool(object): + if filename is None: + return ["#!/bin/sh\n"] + +- fp = open(filename, 'r', 1) ++ fp = codecs.open(filename, 'r', encoding='utf-8') + lines = list() + + # before entering the data block +@@ -396,7 +405,8 @@ class Tool(object): + lines.append(line) + # in the block: + for line in fp: +- if line.startswith('##'): continue ++ if line.startswith('##'): ++ continue + if not (line.startswith('# ') and '=' in line): + # after the block: strip one emtpy line (if present) + if line.strip() != '': +@@ -410,7 +420,7 @@ class Tool(object): + + def _dump_properties(self): + lines = ['# [Pluma Tool]'] +- for item in self._properties.iteritems(): ++ for item in self._properties.items(): + if item[0] in self._transform: + lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1]))) + elif item[1] is not None: +@@ -419,7 +429,7 @@ class Tool(object): + + def save_with_script(self, script): + filename = self.library.get_full_path(self.filename, 'w') +- fp = open(filename, 'w', 1) ++ fp = codecs.open(filename, 'w', encoding='utf-8') + + # Make sure to first print header (shebang, modeline), then + # properties, and then actual content +@@ -430,7 +440,6 @@ class Tool(object): + # Parse + for line in script: + line = line.rstrip("\n") +- + if not inheader: + content.append(line) + elif line.startswith('#!'): +@@ -453,7 +462,7 @@ class Tool(object): + fp.write(line + "\n") + + fp.close() +- os.chmod(filename, 0750) ++ os.chmod(filename, 0o750) + self.changed = False + + def save(self): +@@ -478,16 +487,17 @@ class Tool(object): + + if __name__ == '__main__': + library = ToolLibrary() ++ library.set_locations(os.path.expanduser("~/.config/pluma/tools")) + + def print_tool(t, indent): +- print indent * " " + "%s: %s" % (t.filename, t.name) ++ print(indent * " " + "%s: %s" % (t.filename, t.name)) + + def print_dir(d, indent): +- print indent * " " + d.dirname + '/' ++ print(indent * " " + d.dirname + '/') + for i in d.subdirs: +- print_dir(i, indent+1) ++ print_dir(i, indent + 1) + for i in d.tools: +- print_tool(i, indent+1) ++ print_tool(i, indent + 1) + + print_dir(library.tree, 0) + +diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py +index 4da0deb..b8e143b 100755 +--- a/plugins/externaltools/tools/manager.py ++++ b/plugins/externaltools/tools/manager.py +@@ -19,71 +19,41 @@ + __all__ = ('Manager', ) + + import os.path +-from library import * +-from functions import * ++import re ++from .library import * ++from .functions import * + import hashlib + from xml.sax import saxutils + from gi.repository import GObject, Gio, Gdk, Gtk, GtkSource, Pluma + +-class LanguagesPopup(Gtk.Window): +- __gtype_name__ = "LanguagePopup" ++class LanguagesPopup(Gtk.Popover): ++ __gtype_name__ = "LanguagesPopup" + + COLUMN_NAME = 0 + COLUMN_ID = 1 + COLUMN_ENABLED = 2 + +- def __init__(self, languages): +- Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP) ++ def __init__(self, widget, languages): ++ Gtk.Popover.__init__(self, relative_to=widget) + +- self.set_default_size(200, 200) + self.props.can_focus = True + + self.build() + self.init_languages(languages) + +- self.show() +- +- self.grab_add() +- +- self.keyboard = None +- device_manager = Gdk.Display.get_device_manager(self.get_window().get_display()) +- for device in device_manager.list_devices(Gdk.DeviceType.MASTER): +- if device.get_source() == Gdk.InputSource.KEYBOARD: +- self.keyboard = device +- break +- +- self.pointer = device_manager.get_client_pointer() +- +- if self.keyboard is not None: +- self.keyboard.grab(self.get_window(), +- Gdk.GrabOwnership.WINDOW, False, +- Gdk.EventMask.KEY_PRESS_MASK | +- Gdk.EventMask.KEY_RELEASE_MASK, +- None, Gdk.CURRENT_TIME) +- self.pointer.grab(self.get_window(), +- Gdk.GrabOwnership.WINDOW, False, +- Gdk.EventMask.BUTTON_PRESS_MASK | +- Gdk.EventMask.BUTTON_RELEASE_MASK | +- Gdk.EventMask.POINTER_MOTION_MASK | +- Gdk.EventMask.ENTER_NOTIFY_MASK | +- Gdk.EventMask.LEAVE_NOTIFY_MASK | +- Gdk.EventMask.PROXIMITY_IN_MASK | +- Gdk.EventMask.PROXIMITY_OUT_MASK | +- Gdk.EventMask.SCROLL_MASK, +- None, Gdk.CURRENT_TIME) +- + self.view.get_selection().select_path((0,)) + + def build(self): + self.model = Gtk.ListStore(str, str, bool) + + self.sw = Gtk.ScrolledWindow() ++ self.sw.set_size_request(-1, 200) + self.sw.show() + +- self.sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) ++ self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + +- self.view = Gtk.TreeView(self.model) ++ self.view = Gtk.TreeView(model=self.model) + self.view.show() + + self.view.set_headers_visible(False) +@@ -92,16 +62,16 @@ class LanguagesPopup(Gtk.Window): + + renderer = Gtk.CellRendererToggle() + column.pack_start(renderer, False) +- column.set_attributes(renderer, active=self.COLUMN_ENABLED) ++ column.add_attribute(renderer, 'active', self.COLUMN_ENABLED) + + renderer.connect('toggled', self.on_language_toggled) + + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) +- column.set_attributes(renderer, text=self.COLUMN_NAME) ++ column.add_attribute(renderer, 'text', self.COLUMN_NAME) + + self.view.append_column(column) +- self.view.set_row_separator_func(self.on_separator) ++ self.view.set_row_separator_func(self.on_separator, None) + + self.sw.add(self.view) + +@@ -124,7 +94,7 @@ class LanguagesPopup(Gtk.Window): + self.model.foreach(self.enabled_languages, ret) + return ret + +- def on_separator(self, model, piter): ++ def on_separator(self, model, piter, user_data=None): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' + +@@ -142,7 +112,7 @@ class LanguagesPopup(Gtk.Window): + self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages]) + + def correct_all(self, model, path, piter, enabled): +- if path == (0,): ++ if path.get_indices()[0] == 0: + return False + + model.set_value(piter, self.COLUMN_ENABLED, enabled) +@@ -158,113 +128,6 @@ class LanguagesPopup(Gtk.Window): + else: + self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False) + +- def do_key_press_event(self, event): +- if event.keyval == Gdk.KEY_Escape: +- self.destroy() +- return True +- else: +- event.window = self.view.get_bin_window() +- return self.view.event(event) +- +- def do_key_release_event(self, event): +- event.window = self.view.get_bin_window() +- return self.view.event(event) +- +- def in_window(self, event, window=None): +- if not window: +- window = self.get_window() +- +- geometry = window.get_geometry() +- origin = window.get_origin() +- +- return event.x_root >= origin[1] and \ +- event.x_root <= origin[1] + geometry[2] and \ +- event.y_root >= origin[2] and \ +- event.y_root <= origin[2] + geometry[3] +- +- def do_destroy(self): +- if self.keyboard: +- self.keyboard.ungrab(Gdk.CURRENT_TIME) +- self.pointer.ungrab(Gdk.CURRENT_TIME) +- +- return Gtk.Window.do_destroy(self) +- +- def setup_event(self, event, window): +- fr = event.window.get_origin() +- to = window.get_origin() +- +- event.window = window +- event.x += fr[1] - to[1] +- event.y += fr[2] - to[2] +- +- def resolve_widgets(self, root): +- res = [root] +- +- if isinstance(root, Gtk.Container): +- root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None) +- +- return res +- +- def resolve_windows(self, window): +- if not window: +- return [] +- +- res = [window] +- res.extend(window.get_children()) +- +- return res +- +- def propagate_mouse_event(self, event, reverse=True): +- allwidgets = self.resolve_widgets(self.get_child()) +- +- if reverse: +- allwidgets.reverse() +- +- for widget in allwidgets: +- windows = self.resolve_windows(widget.get_window()) +- windows.reverse() +- +- for window in windows: +- if not (window.get_events() & event.type): +- continue +- +- if self.in_window(event, window): +- self.setup_event(event, window) +- +- if widget.event(event): +- return True +- +- return False +- +- def do_button_press_event(self, event): +- if not self.in_window(event): +- self.destroy() +- else: +- return self.propagate_mouse_event(event) +- +- def do_button_release_event(self, event): +- if not self.in_window(event): +- self.destroy() +- else: +- return self.propagate_mouse_event(event) +- +- def do_scroll_event(self, event): +- return self.propagate_mouse_event(event, False) +- +- def do_motion_notify_event(self, event): +- return self.propagate_mouse_event(event) +- +- def do_enter_notify_event(self, event): +- return self.propagate_mouse_event(event) +- +- def do_leave_notify_event(self, event): +- return self.propagate_mouse_event(event) +- +- def do_proximity_in_event(self, event): +- return self.propagate_mouse_event(event) +- +- def do_proximity_out_event(self, event): +- return self.propagate_mouse_event(event) + + class Manager(GObject.Object): + TOOL_COLUMN = 0 # For Tree +@@ -450,7 +313,9 @@ class Manager(GObject.Object): + n1 = t1.name + n2 = t2.name + +- return cmp(n1.lower(), n2.lower()) ++ n1 = n1.lower() ++ n2 = n2.lower() ++ return (n1 > n2) - (n1 < n2) + + def __init_tools_view(self): + # Tools column +@@ -499,8 +364,8 @@ class Manager(GObject.Object): + else: + return None, None + +- def compute_hash(self, string): +- return hashlib.md5(string).hexdigest() ++ def compute_hash(self, stringofbytes): ++ return hashlib.md5(stringofbytes).hexdigest() + + def save_current_tool(self): + if self.current_node is None: +@@ -521,7 +386,10 @@ class Manager(GObject.Object): + buf = self['commands'].get_buffer() + (start, end) = buf.get_bounds() + script = buf.get_text(start, end, False) +- h = self.compute_hash(script) ++ scriptbytes = script ++ if not isinstance(scriptbytes, bytes): ++ scriptbytes = scriptbytes.encode('utf-8') ++ h = self.compute_hash(scriptbytes) + if h != self.script_hash: + # script has changed -> save it + self.current_node.save_with_script([line + "\n" for line in script.splitlines()]) +@@ -573,6 +441,9 @@ class Manager(GObject.Object): + buf.set_text(script) + buf.end_not_undoable_action() + ++ if not isinstance(script, bytes): ++ script = script.encode('utf-8') ++ + self.script_hash = self.compute_hash(script) + contenttype, uncertain = Gio.content_type_guess(None, script) + lmanager = GtkSource.LanguageManager.get_default() +@@ -703,7 +574,7 @@ class Manager(GObject.Object): + if language in node.languages: + node.languages.remove(language) + +- self._tool_rows[node] = filter(lambda x: x.valid(), self._tool_rows[node]) ++ self._tool_rows[node] = [x for x in self._tool_rows[node] if x.valid()] + + if not self._tool_rows[node]: + del self._tool_rows[node] +@@ -714,7 +585,9 @@ class Manager(GObject.Object): + self.script_hash = None + + if self.model.iter_is_valid(piter): +- self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), False) ++ self.view.set_cursor(self.model.get_path(piter), ++ self.view.get_column(self.TOOL_COLUMN), ++ False) + + self.view.grab_focus() + +@@ -799,19 +672,19 @@ class Manager(GObject.Object): + + def on_accelerator_key_press(self, entry, event): + mask = event.state & Gtk.accelerator_get_default_mod_mask() ++ keyname = Gdk.keyval_name(event.keyval) + +- if event.keyval == Gdk.KEY_Escape: ++ if keyname == 'Escape': + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + return True +- elif event.keyval == Gdk.KEY_Delete \ +- or event.keyval == Gdk.KEY_BackSpace: ++ elif keyname == 'Delete' or keyname == 'BackSpace': + entry.set_text('') + self.remove_accelerator(self.current_node) + self.current_node.shortcut = None + self['commands'].grab_focus() + return True +- elif event.keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1): ++ elif re.match('^F(:1[012]?|[2-9])$', keyname): + # New accelerator + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) +@@ -911,7 +784,7 @@ class Manager(GObject.Object): + ret = None + + if node: +- ref = Gtk.TreeRowReference(self.model, self.model.get_path(piter)) ++ ref = Gtk.TreeRowReference.new(self.model, self.model.get_path(piter)) + + # Update languages, make sure to inhibit selection change stuff + self.view.get_selection().handler_block(self.selection_changed_id) +@@ -966,12 +839,8 @@ class Manager(GObject.Object): + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def on_languages_button_clicked(self, button): +- popup = LanguagesPopup(self.current_node.languages) +- popup.set_transient_for(self.dialog) +- +- origin = button.get_window().get_origin() +- popup.move(origin[1], origin[2] - popup.get_allocation().height) +- +- popup.connect('destroy', self.update_languages) ++ popup = LanguagesPopup(button, self.current_node.languages) ++ popup.show() ++ popup.connect('closed', self.update_languages) + + # ex:et:ts=4: +diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py +index e063eb2..39fd99a 100755 +--- a/plugins/externaltools/tools/outputpanel.py ++++ b/plugins/externaltools/tools/outputpanel.py +@@ -20,11 +20,12 @@ + __all__ = ('OutputPanel', 'UniqueById') + + import os +-from weakref import WeakKeyDictionary +-from capture import * + import re +-import linkparsing +-import filelookup ++ ++from weakref import WeakKeyDictionary ++from .capture import * ++from . import linkparsing ++from . import filelookup + from gi.repository import GLib, Gdk, Gtk, Pango, Pluma + + class UniqueById: +diff --git a/plugins/externaltools/tools/outputpanel.ui b/plugins/externaltools/tools/outputpanel.ui +index 01904a6..30f2e33 100644 +--- a/plugins/externaltools/tools/outputpanel.ui ++++ b/plugins/externaltools/tools/outputpanel.ui +@@ -1,5 +1,5 @@ + +- + + +- ++ + True + False ++ True ++ True + + + True + False + True ++ True + in + + +@@ -33,9 +36,8 @@ Version: 2.91.3 + + + +- False +- True +- 0 ++ 0 ++ 0 + + + +@@ -43,7 +45,6 @@ Version: 2.91.3 + True + False + 6 +- vertical + 6 + end + +@@ -57,16 +58,15 @@ Version: 2.91.3 + + + +- True +- True ++ False ++ False + 0 + + + + +- False +- True +- 1 ++ 1 ++ 0 + + + +diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui +index afdd3f9..21de842 100644 +--- a/plugins/externaltools/tools/tools.ui ++++ b/plugins/externaltools/tools/tools.ui +@@ -1,30 +1,45 @@ +- ++ ++ + +- ++ ++ ++ True ++ ++ + + + +- ++ + + + + +- Nothing +- nothing ++ All documents ++ all + + +- Current document +- document ++ All documents except untitled ones ++ titled + + +- All documents +- all ++ Local files only ++ local ++ ++ ++ Remote files only ++ remote ++ ++ ++ Untitled documents only ++ untitled + + + + + ++ + ++ + + + +@@ -62,7 +77,7 @@ + + + +- ++ + Nothing + nothing + +@@ -92,7 +107,7 @@ + + + +- ++ + + + +@@ -101,72 +116,105 @@ + + + +- All documents +- all +- +- +- All documents except untitled ones +- titled +- +- +- Local files only +- local ++ Nothing ++ nothing + + +- Remote files only +- remote ++ Current document ++ document + + +- Untitled documents only +- untitled ++ All documents ++ all + + + +- +- True +- + ++ False + External Tools Manager + 750 + 500 + dialog + True +- +- +- ++ ++ ++ ++ ++ ++ + +- ++ + True ++ False ++ vertical ++ ++ ++ True ++ False ++ end ++ ++ ++ gtk-help ++ True ++ True ++ True ++ False ++ True ++ ++ ++ False ++ False ++ 0 ++ ++ ++ ++ ++ gtk-close ++ True ++ True ++ True ++ False ++ True ++ ++ ++ False ++ False ++ 1 ++ ++ ++ ++ ++ False ++ False ++ end ++ 0 ++ ++ + +- ++ + True + True +- 6 ++ True ++ True + 275 + +- ++ + True +- 6 +- +- +- True +- 0 +- _Tools: +- True +- view +- +- +- False +- False +- 0 +- +- ++ False ++ 6 ++ 6 ++ 6 ++ 6 ++ True ++ True ++ vertical ++ 6 + + + True + False +- automatic +- automatic ++ True ++ True + in + + +@@ -174,29 +222,49 @@ + True + False + True ++ ++ ++ + + + + +- 1 ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ _Tools: ++ True ++ view ++ 0 ++ ++ ++ 0 ++ 0 + + + +- ++ + True +- 6 ++ False ++ start + + + True + False + True + False +- ++ start ++ + + + True ++ False + gtk-new +- 4 + + + +@@ -204,344 +272,311 @@ + False + False + 0 ++ True + + + +- ++ ++ True + False ++ True + False +- ++ start ++ + +- ++ + True +- gtk-revert-to-saved +- 4 ++ False ++ gtk-delete + + + + + False + False +- end +- 2 ++ 1 ++ True + + + +- +- True ++ + False +- True + False +- ++ start ++ + +- ++ + True +- gtk-delete +- 4 ++ False ++ gtk-revert-to-saved + + + + + False + False +- end +- 1 ++ 2 ++ True + + + + +- False +- False +- 2 ++ 0 ++ 2 + + + + +- False +- False ++ True ++ True + + + +- ++ + True +- 6 ++ False ++ True ++ True ++ vertical ++ 6 + +- +- True +- 0 +- 0.5 +- _Edit: +- commands +- True +- +- +- False +- False +- 0 +- +- +- +- ++ + True ++ False ++ True ++ True ++ vertical ++ 6 ++ 6 + +- ++ + True +- ++ False ++ _Applicability: ++ True ++ 0 + + +- False +- False +- 0 ++ 0 ++ 5 ++ ++ ++ ++ ++ True ++ False ++ _Output: ++ True ++ 0 ++ ++ ++ 0 ++ 4 ++ ++ ++ ++ ++ True ++ False ++ _Input: ++ True ++ 0 ++ ++ ++ 0 ++ 3 + + + +- ++ + True +- 6 +- 2 +- 6 +- 6 ++ False ++ _Save: ++ True ++ 0 ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ _Shortcut Key: ++ True ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ model_output + +- +- True +- True +- +- +- +- +- +- 1 +- 2 +- 1 +- 2 +- GTK_EXPAND | GTK_SHRINK | GTK_FILL +- GTK_FILL +- ++ ++ ++ 0 ++ + ++ ++ ++ 1 ++ 4 ++ ++ ++ ++ ++ True ++ False ++ model_input + +- +- True +- +- +- True +- model_applicability +- +- +- +- 0 +- +- +- +- +- 0 +- False +- True +- +- +- +- +- True +- True +- +- +- True +- +- +- +- True +- All Languages +- 0 +- 0.5 +- PANGO_ELLIPSIZE_MIDDLE +- +- +- +- +- +- +- 1 +- True +- True +- +- +- +- +- 1 +- 2 +- 5 +- 6 +- GTK_EXPAND | GTK_SHRINK | GTK_FILL +- GTK_FILL +- ++ ++ ++ 0 ++ + ++ ++ ++ 1 ++ 3 ++ ++ ++ ++ ++ True ++ False ++ model_save_files + +- +- True +- model_output +- +- +- +- 0 +- +- +- +- +- 1 +- 2 +- 4 +- 5 +- GTK_EXPAND | GTK_SHRINK | GTK_FILL +- GTK_FILL +- ++ ++ ++ 0 ++ + ++ ++ ++ 1 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ ++ ++ ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ True ++ True ++ in ++ ++ ++ commands_buffer ++ True ++ True ++ False ++ GTK_SOURCE_SMART_HOME_END_AFTER ++ 2 ++ True ++ False ++ True ++ ++ ++ ++ ++ 0 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False + +- ++ + True +- model_input ++ False + +- +- +- 0 +- ++ ++ True ++ False ++ False ++ ++ ++ ++ True ++ False ++ All Languages ++ middle ++ 0 ++ 0.5 ++ ++ ++ + + + + 1 +- 2 +- 3 +- 4 +- GTK_EXPAND | GTK_SHRINK | GTK_FILL +- GTK_FILL ++ 0 + + + +- +- model_save_files ++ + True ++ False ++ model_applicability + +- ++ + + 0 + + + + +- 1 +- 2 +- 2 +- 3 +- GTK_EXPAND | GTK_SHRINK | GTK_FILL +- GTK_FILL +- +- +- +- +- True +- 0 +- _Applicability: +- True +- applicability +- +- +- 5 +- 6 +- GTK_FILL +- +- +- +- +- +- True +- 0 +- _Output: +- True +- output +- +- +- 4 +- 5 +- GTK_FILL +- +- +- +- +- +- True +- 0 +- _Input: +- True +- input +- +- +- 3 +- 4 +- GTK_FILL +- +- +- +- +- +- 0 +- _Save: +- True +- save-files +- True +- +- +- 2 +- 3 +- GTK_FILL +- +- +- +- +- +- True +- 0 +- _Shortcut Key: +- True +- accelerator +- +- +- 1 +- 2 +- GTK_FILL +- +- +- +- +- +- True +- True +- automatic +- automatic +- in +- +- +- commands_buffer +- True +- True +- False +- GTK_SOURCE_SMART_HOME_END_AFTER +- 2 +- True +- False +- True +- +- +- +- +- 2 ++ 0 ++ 0 + + + + +- 1 ++ 1 ++ 5 + + + + +- 1 ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ _Edit: ++ True ++ 0 ++ 0.5 ++ ++ ++ 0 ++ 0 + + + +@@ -557,47 +592,6 @@ + 1 + + +- +- +- True +- end +- +- +- gtk-help +- True +- True +- True +- False +- True +- +- +- False +- False +- 0 +- +- +- +- +- gtk-close +- True +- True +- True +- False +- True +- +- +- False +- False +- 1 +- +- +- +- +- False +- end +- 0 +- +- + + + +-- +2.21.0 + diff --git a/pluma_0001-pythonconsole-plugin-change-source-code-for-Python-2.patch b/pluma_0001-pythonconsole-plugin-change-source-code-for-Python-2.patch new file mode 100644 index 0000000..f65310d --- /dev/null +++ b/pluma_0001-pythonconsole-plugin-change-source-code-for-Python-2.patch @@ -0,0 +1,798 @@ +From a54a8e7bf8e86f87316b5026e6e85d6d3fdab226 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Wed, 22 May 2019 18:02:59 +0200 +Subject: [PATCH] pythonconsole plugin: change source code for Python 2 & 3 + compatibility. + +Also drop mateconf and use gsettings for preferences. +Preferences are now triggered as a PeasGtk.Configurable. +--- + configure.ac | 1 + + plugins/pythonconsole/Makefile.am | 19 +- + ...pluma.plugins.pythonconsole.gschema.xml.in | 30 +++ + .../pythonconsole/pythonconsole/__init__.py | 37 +-- + plugins/pythonconsole/pythonconsole/config.py | 158 ++++++------ + plugins/pythonconsole/pythonconsole/config.ui | 224 +++++++++--------- + .../pythonconsole/pythonconsole/console.py | 68 ++++-- + 7 files changed, 304 insertions(+), 233 deletions(-) + create mode 100644 plugins/pythonconsole/org.mate.pluma.plugins.pythonconsole.gschema.xml.in + +diff --git a/configure.ac b/configure.ac +index 1cdbb7e..ceb9a5c 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -250,6 +250,7 @@ plugins/filebrowser/org.mate.pluma.plugins.filebrowser.gschema.xml + plugins/modelines/Makefile + plugins/pythonconsole/Makefile + plugins/pythonconsole/pythonconsole/Makefile ++plugins/pythonconsole/org.mate.pluma.plugins.pythonconsole.gschema.xml + plugins/quickopen/Makefile + plugins/quickopen/quickopen/Makefile + plugins/snippets/data/lang/Makefile +diff --git a/plugins/pythonconsole/Makefile.am b/plugins/pythonconsole/Makefile.am +index 0a9ff96..51a2e4a 100644 +--- a/plugins/pythonconsole/Makefile.am ++++ b/plugins/pythonconsole/Makefile.am +@@ -7,9 +7,22 @@ plugin_in_files = pythonconsole.plugin.desktop.in + + plugin_DATA = $(plugin_in_files:.plugin.desktop.in=.plugin) + +-EXTRA_DIST = $(plugin_in_files) ++pythonconsole_gschema_in = org.mate.pluma.plugins.pythonconsole.gschema.xml.in ++gsettings_SCHEMAS = $(pythonconsole_gschema_in:.xml.in=.xml) ++@GSETTINGS_RULES@ + +-CLEANFILES = $(plugin_DATA) +-DISTCLEANFILES = $(plugin_DATA) ++EXTRA_DIST = \ ++ $(plugin_in_files) \ ++ $(pythonconsole_gschema_in) ++ ++CLEANFILES = \ ++ $(plugin_DATA) \ ++ $(gsettings_SCHEMAS_in) \ ++ $(gsettings_SCHEMAS) ++ ++DISTCLEANFILES = \ ++ $(plugin_DATA) \ ++ $(gsettings_SCHEMAS_in) \ ++ $(gsettings_SCHEMAS) + + -include $(top_srcdir)/git.mk +diff --git a/plugins/pythonconsole/org.mate.pluma.plugins.pythonconsole.gschema.xml.in b/plugins/pythonconsole/org.mate.pluma.plugins.pythonconsole.gschema.xml.in +new file mode 100644 +index 0000000..7897fa9 +--- /dev/null ++++ b/plugins/pythonconsole/org.mate.pluma.plugins.pythonconsole.gschema.xml.in +@@ -0,0 +1,30 @@ ++ ++ ++ ++ '#314e6c' ++ Command Color Text ++ The command color text ++ ++ ++ '#990000' ++ Error Color Text ++ The error color text ++ ++ ++ true ++ Whether to use the system font ++ ++ If true, the terminal will use the desktop-global standard ++ font if it’s monospace (and the most similar font it can ++ come up with otherwise). ++ ++ ++ ++ 'Monospace 10' ++ Font ++ ++ A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”. ++ ++ ++ ++ +diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py +index 07d13c7..171b367 100755 +--- a/plugins/pythonconsole/pythonconsole/__init__.py ++++ b/plugins/pythonconsole/pythonconsole/__init__.py +@@ -24,22 +24,22 @@ + # Bits from pluma Python Console Plugin + # Copyrignt (C), 2005 Raphaël Slinckx + +-from gi.repository import GObject, Gtk, Peas, Pluma ++from gi.repository import GObject, Gtk, Peas, PeasGtk, Pluma + +-from console import PythonConsole +-from config import PythonConsoleConfigDialog +-from config import PythonConsoleConfig ++from .console import PythonConsole ++from .config import PythonConsoleConfigWidget ++from .config import PythonConsoleConfig + + PYTHON_ICON = 'text-x-python' + +-class PythonConsolePlugin(GObject.Object, Peas.Activatable): ++class PythonConsolePlugin(GObject.Object, Peas.Activatable, PeasGtk.Configurable): + __gtype_name__ = "PythonConsolePlugin" + + object = GObject.Property(type=GObject.Object) + + def __init__(self): + GObject.Object.__init__(self) +- self.dlg = None ++ self.config_widget = None + + def do_activate(self): + window = self.object +@@ -47,8 +47,8 @@ class PythonConsolePlugin(GObject.Object, Peas.Activatable): + self._console = PythonConsole(namespace = {'__builtins__' : __builtins__, + 'pluma' : Pluma, + 'window' : window}) +- self._console.eval('print "You can access the main window through ' \ +- '\'window\' :\\n%s" % window', False) ++ self._console.eval('print("You can access the main window through ' \ ++ '\'window\' :\\n%s" % window)', False) + bottom = window.get_bottom_panel() + image = Gtk.Image() + image.set_from_icon_name(PYTHON_ICON, Gtk.IconSize.MENU) +@@ -61,22 +61,9 @@ class PythonConsolePlugin(GObject.Object, Peas.Activatable): + bottom = window.get_bottom_panel() + bottom.remove_item(self._console) + +-def create_configure_dialog(self): +- +- if not self.dlg: +- self.dlg = PythonConsoleConfigDialog(self.get_data_dir()) +- +- dialog = self.dlg.dialog() +- window = pluma.app_get_default().get_active_window() +- if window: +- dialog.set_transient_for(window) +- +- return dialog +- +-# Here we dynamically insert create_configure_dialog based on if configuration +-# is enabled. This has to be done like this because pluma checks if a plugin +-# is configurable solely on the fact that it has this member defined or not +-if PythonConsoleConfig.enabled(): +- PythonConsolePlugin.create_configure_dialog = create_configure_dialog ++ def do_create_configure_widget(self): ++ if not self.config_widget: ++ self.config_widget = PythonConsoleConfigWidget(self.plugin_info.get_data_dir()) ++ return self.config_widget.configure_widget() + + # ex:et:ts=4: +diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py +index fce0c9d..f973e38 100755 +--- a/plugins/pythonconsole/pythonconsole/config.py ++++ b/plugins/pythonconsole/pythonconsole/config.py +@@ -25,110 +25,126 @@ + # Copyrignt (C), 2005 Raphaël Slinckx + + import os +-import gtk ++from gi.repository import Gio, Gtk, Gdk + +-__all__ = ('PythonConsoleConfig', 'PythonConsoleConfigDialog') +- +-MATECONF_KEY_BASE = '/apps/pluma/plugins/pythonconsole' +-MATECONF_KEY_COMMAND_COLOR = MATECONF_KEY_BASE + '/command-color' +-MATECONF_KEY_ERROR_COLOR = MATECONF_KEY_BASE + '/error-color' +- +-DEFAULT_COMMAND_COLOR = '#314e6c' # Blue Shadow +-DEFAULT_ERROR_COLOR = '#990000' # Accent Red Dark ++__all__ = ('PythonConsoleConfig', 'PythonConsoleConfigWidget') + + class PythonConsoleConfig(object): +- try: +- import mateconf +- except ImportError: +- mateconf = None +- +- def __init__(self): +- pass + +- @staticmethod +- def enabled(): +- return PythonConsoleConfig.mateconf != None ++ CONSOLE_KEY_BASE = 'org.mate.pluma.plugins.pythonconsole' ++ CONSOLE_KEY_COMMAND_COLOR = 'command-color' ++ CONSOLE_KEY_ERROR_COLOR = 'error-color' ++ CONSOLE_KEY_USE_SYSTEM_FONT = 'use-system-font' ++ CONSOLE_KEY_FONT = 'font' + +- @staticmethod +- def add_handler(handler): +- if PythonConsoleConfig.mateconf: +- PythonConsoleConfig.mateconf.client_get_default().notify_add(MATECONF_KEY_BASE, handler) ++ INTERFACE_KEY_BASE = 'org.mate.interface' ++ INTERFACE_KEY_MONOSPACE_FONT_NAME = 'monospace-font-name' + + color_command = property( +- lambda self: self.mateconf_get_str(MATECONF_KEY_COMMAND_COLOR, DEFAULT_COMMAND_COLOR), +- lambda self, value: self.mateconf_set_str(MATECONF_KEY_COMMAND_COLOR, value)) ++ lambda self: self.console_settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR), ++ lambda self, value: self.console_settings.set_string(self.CONSOLE_KEY_COMMAND_COLOR, value) ++ ) + + color_error = property( +- lambda self: self.mateconf_get_str(MATECONF_KEY_ERROR_COLOR, DEFAULT_ERROR_COLOR), +- lambda self, value: self.mateconf_set_str(MATECONF_KEY_ERROR_COLOR, value)) ++ lambda self: self.console_settings.get_string(self.CONSOLE_KEY_ERROR_COLOR), ++ lambda self, value: self.console_settings.set_string(self.CONSOLE_KEY_ERROR_COLOR, value) ++ ) + +- @staticmethod +- def mateconf_get_str(key, default=''): +- if not PythonConsoleConfig.mateconf: +- return default ++ use_system_font = property( ++ lambda self: self.console_settings.get_boolean(self.CONSOLE_KEY_USE_SYSTEM_FONT), ++ lambda self, value: self.console_settings.set_boolean(self.CONSOLE_KEY_USE_SYSTEM_FONT, value) ++ ) + +- val = PythonConsoleConfig.mateconf.client_get_default().get(key) +- if val is not None and val.type == mateconf.VALUE_STRING: +- return val.get_string() +- else: +- return default ++ font = property( ++ lambda self: self.console_settings.get_string(self.CONSOLE_KEY_FONT), ++ lambda self, value: self.console_settings.set_string(self.CONSOLE_KEY_FONT, value) ++ ) + +- @staticmethod +- def mateconf_set_str(key, value): +- if not PythonConsoleConfig.mateconf: +- return ++ monospace_font_name = property( ++ lambda self: self.interface_settings.get_string(self.INTERFACE_KEY_MONOSPACE_FONT_NAME) ++ ) ++ ++ console_settings = Gio.Settings.new(CONSOLE_KEY_BASE) ++ interface_settings = Gio.Settings.new(INTERFACE_KEY_BASE) ++ ++ def __init__(self): ++ object.__init__(self) ++ ++ @classmethod ++ def enabled(self): ++ return self.console_settings != None ++ ++ @classmethod ++ def add_handler(self, handler): ++ self.console_settings.connect("changed", handler) ++ self.interface_settings.connect("changed", handler) + +- v = PythonConsoleConfig.mateconf.Value(mateconf.VALUE_STRING) +- v.set_string(value) +- PythonConsoleConfig.mateconf.client_get_default().set(key, v) + +-class PythonConsoleConfigDialog(object): ++class PythonConsoleConfigWidget(object): ++ ++ CONSOLE_KEY_BASE = 'org.mate.pluma.plugins.pythonconsole' ++ CONSOLE_KEY_COMMAND_COLOR = 'command-color' ++ CONSOLE_KEY_ERROR_COLOR = 'error-color' + + def __init__(self, datadir): + object.__init__(self) +- self._dialog = None ++ self._widget = None + self._ui_path = os.path.join(datadir, 'ui', 'config.ui') +- self.config = PythonConsoleConfig() ++ self._config = PythonConsoleConfig() ++ self._ui = Gtk.Builder() + +- def dialog(self): +- if self._dialog is None: +- self._ui = gtk.Builder() ++ def configure_widget(self): ++ if self._widget is None: + self._ui.add_from_file(self._ui_path) + + self.set_colorbutton_color(self._ui.get_object('colorbutton-command'), +- self.config.color_command) ++ self._config.color_command) + self.set_colorbutton_color(self._ui.get_object('colorbutton-error'), +- self.config.color_error) +- ++ self._config.color_error) ++ checkbox = self._ui.get_object('checkbox-system-font') ++ checkbox.set_active(self._config.use_system_font) ++ self._fontbutton = self._ui.get_object('fontbutton-font') ++ self._fontbutton.set_font_name(self._config.font) ++ self.on_checkbox_system_font_toggled(checkbox) + self._ui.connect_signals(self) + +- self._dialog = self._ui.get_object('dialog-config') +- self._dialog.show_all() +- else: +- self._dialog.present() ++ self._widget = self._ui.get_object('widget-config') ++ self._widget.show_all() + +- return self._dialog ++ return self._widget + + @staticmethod + def set_colorbutton_color(colorbutton, value): +- try: +- color = gtk.gdk.color_parse(value) +- except ValueError: +- pass # Default color in config.ui used +- else: +- colorbutton.set_color(color) ++ rgba = Gdk.RGBA() ++ parsed = rgba.parse(value) + +- def on_dialog_config_response(self, dialog, response_id): +- self._dialog.destroy() +- +- def on_dialog_config_destroy(self, dialog): +- self._dialog = None +- self._ui = None ++ if parsed: ++ colorbutton.set_rgba(rgba) + + def on_colorbutton_command_color_set(self, colorbutton): +- self.config.color_command = colorbutton.get_color().to_string() ++ self._config.color_command = colorbutton.get_color().to_string() + + def on_colorbutton_error_color_set(self, colorbutton): +- self.config.color_error = colorbutton.get_color().to_string() ++ self._config.color_error = colorbutton.get_color().to_string() ++ ++ def on_checkbox_system_font_toggled(self, checkbox): ++ val = checkbox.get_active() ++ self._config.use_system_font = val ++ self._fontbutton.set_sensitive(not val) ++ ++ def on_fontbutton_font_set(self, fontbutton): ++ self._config.font = fontbutton.get_font_name() ++ ++ def on_widget_config_parent_set(self, widget, oldparent): ++ # Set icon in dialog close button. ++ try: ++ actionarea = widget.get_toplevel().get_action_area() ++ image = Gtk.Image.new_from_icon_name("window-close", ++ Gtk.IconSize.BUTTON) ++ for button in actionarea.get_children(): ++ button.set_image(image) ++ button.set_property("always-show-image", True) ++ except: ++ pass + + # ex:et:ts=4: +diff --git a/plugins/pythonconsole/pythonconsole/config.ui b/plugins/pythonconsole/pythonconsole/config.ui +index 8d337d6..392be7d 100644 +--- a/plugins/pythonconsole/pythonconsole/config.ui ++++ b/plugins/pythonconsole/pythonconsole/config.ui +@@ -1,126 +1,124 @@ + +- ++ + + +- ++ + True + False +- window-close +- +- +- False +- center-on-parent +- True +- dialog +- +- +- +- ++ 6 ++ 6 ++ 6 ++ 6 ++ vertical ++ 6 ++ 6 ++ ++ ++ ++ True ++ False ++ _Error color: ++ True ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ True ++ rgb(153,0,0) ++ ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ True ++ rgb(49,78,108) ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ + True + False +- +- +- True +- False +- end +- +- +- _Close +- True +- True +- True +- image1 +- True +- +- +- True +- True +- 0 +- +- +- +- +- False +- False +- end +- 0 +- +- +- +- +- True +- False +- 6 +- 2 +- 2 +- 6 +- 6 +- +- +- True +- False +- C_ommand color: +- True +- colorbutton-command +- 0 +- +- +- +- +- True +- False +- _Error color: +- True +- colorbutton-error +- 0 +- +- +- 1 +- 2 +- +- +- +- +- True +- True +- True +- #31314e4e6c6c +- +- +- +- 1 +- 2 +- +- +- +- +- True +- True +- True +- #999900000000 +- +- +- +- 1 +- 2 +- 1 +- 2 +- +- +- +- +- False +- True +- 1 +- +- ++ C_ommand color: ++ True ++ 0 + ++ ++ 0 ++ 0 ++ + +- +- button1 +- + +- ++ ++ True ++ False ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ Use system fixed width font ++ True ++ True ++ False ++ start ++ True ++ True ++ ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ start ++ Font: ++ ++ ++ 0 ++ 4 ++ ++ ++ ++ ++ True ++ True ++ True ++ Sans 12 ++ ++ ++ ++ ++ 1 ++ 4 ++ + + + +diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py +index 75f60e4..0fd9c7c 100755 +--- a/plugins/pythonconsole/pythonconsole/console.py ++++ b/plugins/pythonconsole/pythonconsole/console.py +@@ -30,7 +30,7 @@ import re + import traceback + from gi.repository import GObject, Gdk, Gtk, Pango + +-from config import PythonConsoleConfig ++from .config import PythonConsoleConfig + + __all__ = ('PythonConsole', 'OutFile') + +@@ -40,13 +40,14 @@ class PythonConsole(Gtk.ScrolledWindow): + 'grab-focus' : 'override', + } + ++ DEFAULT_FONT = "Monospace 10" ++ + def __init__(self, namespace = {}): + Gtk.ScrolledWindow.__init__(self) + + self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.set_shadow_type(Gtk.ShadowType.IN) + self.view = Gtk.TextView() +- self.view.modify_font(Pango.font_description_from_string('Monospace')) + self.view.set_editable(True) + self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.add(self.view) +@@ -57,7 +58,8 @@ class PythonConsole(Gtk.ScrolledWindow): + self.error = buffer.create_tag("error") + self.command = buffer.create_tag("command") + +- PythonConsoleConfig.add_handler(self.apply_preferences) ++ self.config = PythonConsoleConfig() ++ self.config.add_handler(self.apply_preferences) + self.apply_preferences() + + self.__spaces_pattern = re.compile(r'^\s+') +@@ -88,9 +90,28 @@ class PythonConsole(Gtk.ScrolledWindow): + self.view.grab_focus() + + def apply_preferences(self, *args): +- config = PythonConsoleConfig() +- self.error.set_property("foreground", config.color_error) +- self.command.set_property("foreground", config.color_command) ++ self.error.set_property("foreground", self.config.color_error) ++ self.command.set_property("foreground", self.config.color_command) ++ ++ if self.config.use_system_font: ++ font_name = self.config.monospace_font_name ++ else: ++ font_name = self.config.font ++ ++ font_desc = None ++ try: ++ font_desc = Pango.FontDescription(font_name) ++ except: ++ try: ++ font_desc = Pango.FontDescription(self.config.monospace_font_name) ++ except: ++ try: ++ font_desc = Pango.FontDescription(self.DEFAULT_FONT) ++ except: ++ pass ++ ++ if font_desc: ++ self.view.modify_font(font_desc) + + def stop(self): + self.namespace = None +@@ -98,11 +119,13 @@ class PythonConsole(Gtk.ScrolledWindow): + def __key_press_event_cb(self, view, event): + modifier_mask = Gtk.accelerator_get_default_mod_mask() + event_state = event.state & modifier_mask ++ keyname = Gdk.keyval_name(event.keyval) + +- if event.keyval == Gdk.KEY_D and event_state == Gdk.ModifierType.CONTROL_MASK: ++ if keyname == "d" and event_state == Gdk.ModifierType.CONTROL_MASK: + self.destroy() + +- elif event.keyval == Gdk.KEY_Return and event_state == Gdk.ModifierType.CONTROL_MASK: ++ elif keyname == "Return" and \ ++ event_state == Gdk.ModifierType.CONTROL_MASK: + # Get the command + buffer = view.get_buffer() + inp_mark = buffer.get_mark("input") +@@ -128,7 +151,7 @@ class PythonConsole(Gtk.ScrolledWindow): + GObject.idle_add(self.scroll_to_end) + return True + +- elif event.keyval == Gdk.KEY_Return: ++ elif keyname == "Return": + # Get the marks + buffer = view.get_buffer() + lin_mark = buffer.get_mark("input-line") +@@ -172,22 +195,22 @@ class PythonConsole(Gtk.ScrolledWindow): + GObject.idle_add(self.scroll_to_end) + return True + +- elif event.keyval == Gdk.KEY_KP_Down or event.keyval == Gdk.KEY_Down: ++ elif keyname == "KP_Down" or keyname == "Down": + # Next entry from history + view.emit_stop_by_name("key_press_event") + self.history_down() + GObject.idle_add(self.scroll_to_end) + return True + +- elif event.keyval == Gdk.KEY_KP_Up or event.keyval == Gdk.KEY_Up: ++ elif keyname == "KP_Up" or keyname == "Up": + # Previous entry from history + view.emit_stop_by_name("key_press_event") + self.history_up() + GObject.idle_add(self.scroll_to_end) + return True + +- elif event.keyval == Gdk.KEY_KP_Left or event.keyval == Gdk.KEY_Left or \ +- event.keyval == Gdk.KEY_BackSpace: ++ elif keyname == "KP_Left" or keyname == "Left" or \ ++ keyname == "BackSpace": + buffer = view.get_buffer() + inp = buffer.get_iter_at_mark(buffer.get_mark("input")) + cur = buffer.get_iter_at_mark(buffer.get_insert()) +@@ -200,7 +223,7 @@ class PythonConsole(Gtk.ScrolledWindow): + # For the console we enable smart/home end behavior incoditionally + # since it is useful when editing python + +- elif (event.keyval == Gdk.KEY_KP_Home or event.keyval == Gdk.KEY_Home) and \ ++ elif (keyname == "KP_Home" or keyname == "Home") and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + # Go to the begin of the command instead of the begin of the line + buffer = view.get_buffer() +@@ -219,7 +242,7 @@ class PythonConsole(Gtk.ScrolledWindow): + buffer.place_cursor(iter) + return True + +- elif (event.keyval == Gdk.KEY_KP_End or event.keyval == Gdk.KEY_End) and \ ++ elif (keyname == "KP_End" or keyname == "End") and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + + buffer = view.get_buffer() +@@ -323,15 +346,18 @@ class PythonConsole(Gtk.ScrolledWindow): + # eval and exec are broken in how they deal with utf8-encoded + # strings so we have to explicitly decode the command before + # passing it along +- command = command.decode('utf8') ++ try: ++ command = command.decode('utf8') ++ except: ++ pass + + try: + try: + r = eval(command, self.namespace, self.namespace) + if r is not None: +- print `r` ++ print(repr(r)) + except SyntaxError: +- exec command in self.namespace ++ exec(command, self.namespace) + except: + if hasattr(sys, 'last_type') and sys.last_type == SystemExit: + self.destroy() +@@ -343,7 +369,7 @@ class PythonConsole(Gtk.ScrolledWindow): + + def destroy(self): + pass +- #gtk.ScrolledWindow.destroy(self) ++ #Gtk.ScrolledWindow.destroy(self) + + class OutFile: + """A fake output file object. It sends output to a TK test widget, +@@ -361,8 +387,8 @@ class OutFile: + def readlines(self): return [] + def write(self, s): self.console.write(s, self.tag) + def writelines(self, l): self.console.write(l, self.tag) +- def seek(self, a): raise IOError, (29, 'Illegal seek') +- def tell(self): raise IOError, (29, 'Illegal seek') ++ def seek(self, a): raise IOError(29, 'Illegal seek') ++ def tell(self): raise IOError(29, 'Illegal seek') + truncate = tell + + # ex:et:ts=4: +-- +2.21.0 + diff --git a/pluma_0001-quickopen-plugin-change-code-for-Python-2-3-compatib.patch b/pluma_0001-quickopen-plugin-change-code-for-Python-2-3-compatib.patch new file mode 100644 index 0000000..cf919d6 --- /dev/null +++ b/pluma_0001-quickopen-plugin-change-code-for-Python-2-3-compatib.patch @@ -0,0 +1,427 @@ +From bcb21731aacc344cdcd73631346633b6c50d4ec0 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Thu, 23 May 2019 20:20:13 +0200 +Subject: [PATCH] quickopen plugin: change code for Python 2 & 3 compatibility. + +--- + plugins/quickopen/quickopen/__init__.py | 2 +- + plugins/quickopen/quickopen/popup.py | 124 ++++++++++++-------- + plugins/quickopen/quickopen/virtualdirs.py | 8 +- + plugins/quickopen/quickopen/windowhelper.py | 28 +++-- + 4 files changed, 94 insertions(+), 68 deletions(-) + +diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py +index 3ae72a4..17ccdf7 100644 +--- a/plugins/quickopen/quickopen/__init__.py ++++ b/plugins/quickopen/quickopen/__init__.py +@@ -18,7 +18,7 @@ + # Boston, MA 02110-1301, USA. + + from gi.repository import GObject, Peas +-from windowhelper import WindowHelper ++from .windowhelper import WindowHelper + + class QuickOpenPlugin(GObject.Object, Peas.Activatable): + __gtype_name__ = "QuickOpenPlugin" +diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py +index c6cc801..be40509 100644 +--- a/plugins/quickopen/quickopen/popup.py ++++ b/plugins/quickopen/quickopen/popup.py +@@ -18,10 +18,11 @@ + # Boston, MA 02110-1301, USA. + + import os ++import sys + import fnmatch + import xml.sax.saxutils + from gi.repository import GObject, Gio, GLib, Gdk, Gtk, Pango, Pluma +-from virtualdirs import VirtualDirectory ++from .virtualdirs import VirtualDirectory + + class Popup(Gtk.Dialog): + __gtype_name__ = "QuickOpenPopup" +@@ -30,11 +31,12 @@ class Popup(Gtk.Dialog): + Gtk.Dialog.__init__(self, + title=_('Quick Open'), + parent=window, +- flags=Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) +- +- self._open_button = self.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT) ++ flags=Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL) + ++ self._add_button(_("_Cancel"), Gtk.ResponseType.CANCEL, "process-stop") ++ self._open_button = self._add_button(_("_Open"), ++ Gtk.ResponseType.ACCEPT, ++ "document-open") + self._handler = handler + self._build_ui() + +@@ -48,7 +50,10 @@ class Popup(Gtk.Dialog): + self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + + accel_group = Gtk.AccelGroup() +- accel_group.connect(Gdk.KEY_l, Gdk.ModifierType.CONTROL_MASK, 0, self.on_focus_entry) ++ accel_group.connect(Gdk.keyval_from_name('l'), ++ Gdk.ModifierType.CONTROL_MASK, ++ 0, ++ self.on_focus_entry) + + self.add_accel_group(accel_group) + +@@ -63,10 +68,11 @@ class Popup(Gtk.Dialog): + return self._size + + def _build_ui(self): ++ self.set_border_width(5) + vbox = self.get_content_area() + vbox.set_spacing(3) + +- self._entry = Gtk.Entry() ++ self._entry = Gtk.SearchEntry() + + self._entry.connect('changed', self.on_changed) + self._entry.connect('key-press-event', self.on_key_press_event) +@@ -78,7 +84,10 @@ class Popup(Gtk.Dialog): + tv = Gtk.TreeView() + tv.set_headers_visible(False) + +- self._store = Gtk.ListStore(Gio.Icon, str, GObject.Object, Gio.FileType) ++ self._store = Gtk.ListStore(Gio.Icon, ++ str, ++ GObject.Object, ++ Gio.FileType) + tv.set_model(self._store) + + self._treeview = tv +@@ -106,7 +115,7 @@ class Popup(Gtk.Dialog): + vbox.pack_start(sw, True, True, 0) + + lbl = Gtk.Label() +- lbl.set_alignment(0, 0.5) ++ lbl.set_halign(Gtk.Align.START) + lbl.set_ellipsize(Pango.EllipsizeMode.MIDDLE) + self._info_label = lbl + +@@ -129,19 +138,22 @@ class Popup(Gtk.Dialog): + cell.set_property('cell-background-set', False) + cell.set_property('style-set', False) + +- def _icon_from_stock(self, stock): +- theme = Gtk.icon_theme_get_default() +- size = Gtk.icon_size_lookup(Gtk.IconSize.MENU) +- pixbuf = theme.load_icon(stock, size[0], Gtk.IconLookupFlags.USE_BUILTIN) +- +- return pixbuf ++ def _add_button(self, label, response, icon=None): ++ button = self.add_button(label, response) ++ if icon: ++ image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON) ++ button.set_image(image) ++ button.set_property("always-show-image", True) ++ return button + + def _list_dir(self, gfile): + entries = [] + + try: +- ret = gfile.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, None) +- except GLib.GError: ++ ret = gfile.enumerate_children("standard::*", ++ Gio.FileQueryInfoFlags.NONE, ++ None) ++ except GLib.Error: + pass + + if isinstance(ret, Gio.FileEnumerator): +@@ -151,27 +163,23 @@ class Popup(Gtk.Dialog): + if not entry: + break + +- entries.append((gfile.get_child(entry.get_name()), entry)) ++ if not entry.get_is_backup(): ++ entries.append((gfile.get_child(entry.get_name()), entry)) + else: + entries = ret + + children = [] + + for entry in entries: +- children.append((entry[0], entry[1].get_name(), entry[1].get_file_type(), entry[1].get_icon())) ++ children.append((entry[0], ++ entry[1].get_name(), ++ entry[1].get_file_type(), ++ entry[1].get_icon())) + + return children + +- def _compare_entries(self, a, b, lpart): +- if lpart in a: +- if lpart in b: +- return cmp(a.index(lpart), b.index(lpart)) +- else: +- return -1 +- elif lpart in b: +- return 1 +- else: +- return 0 ++ def _key_entries(self, pos): ++ return pos if pos >= 0 else sys.maxsize + + def _match_glob(self, s, glob): + if glob: +@@ -185,8 +193,7 @@ class Popup(Gtk.Dialog): + + if not d in self._cache: + entries = self._list_dir(d) +- entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) +- ++ entries.sort(key=lambda x: x[1].lower()) + self._cache[d] = entries + else: + entries = self._cache[d] +@@ -212,7 +219,7 @@ class Popup(Gtk.Dialog): + (not lpart or len(parts) == 1): + found.append(entry) + +- found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)) ++ found.sort(key=lambda x: self._key_entries(x[1].lower().find(lpart))) + + if lpart == '..': + newdirs.append(d.get_parent()) +@@ -251,7 +258,9 @@ class Popup(Gtk.Dialog): + return os.sep.join(out) + + def _get_icon(self, f): +- query = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileQueryInfoFlags.NONE, None) ++ query = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON, ++ Gio.FileQueryInfoFlags.NONE, ++ None) + + if not query: + return None +@@ -304,7 +313,10 @@ class Popup(Gtk.Dialog): + for d in self._dirs: + if isinstance(d, VirtualDirectory): + for entry in d.enumerate_children("standard::*", 0, None): +- self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type())) ++ self._append_to_store((entry[1].get_icon(), ++ xml.sax.saxutils.escape(entry[1].get_name()), ++ entry[0], ++ entry[1].get_file_type())) + + def _set_busy(self, busy): + if busy: +@@ -337,14 +349,17 @@ class Popup(Gtk.Dialog): + for d in self._dirs: + for entry in self.do_search_dir(parts, d): + pathparts = self._make_parts(d, entry[0], parts) +- self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2])) ++ self._append_to_store((entry[3], ++ self.make_markup(parts, pathparts), ++ entry[0], ++ entry[2])) + + piter = self._store.get_iter_first() + + if piter: +- self._treeview.get_selection().select_path(self._store.get_path(piter)) ++ path = self._store.get_path(piter) ++ self._treeview.get_selection().select_path(path) + +- self.get_window().set_cursor(None) + self._set_busy(False) + + def do_show(self): +@@ -366,7 +381,7 @@ class Popup(Gtk.Dialog): + model, rows = selection.get_selected_rows() + start = rows[0] + +- self._shift_start = Gtk.TreeRowReference(self._store, start) ++ self._shift_start = Gtk.TreeRowReference.new(self._store, start) + else: + start = self._shift_start.get_path() + +@@ -419,7 +434,7 @@ class Popup(Gtk.Dialog): + else: + self._select_index(num - 1, hasctrl, hasshift) + else: +- idx = path[0] ++ idx = path.get_indices()[0] + + if idx + howmany < 0: + self._select_index(0, hasctrl, hasshift) +@@ -461,19 +476,22 @@ class Popup(Gtk.Dialog): + if text[i] == os.sep: + break + +- self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep) ++ self._entry.set_text(os.path.join(text[:i], ++ os.path.basename(info[0].get_uri())) + os.sep) + self._entry.set_position(-1) + self._entry.grab_focus() + return True + + if rows and ret: +- self.destroy() ++ # We destroy the popup in an idle callback to work around a crash that happens with ++ # GTK_IM_MODULE=xim. See https://bugzilla.gnome.org/show_bug.cgi?id=737711 . ++ GLib.idle_add(self.destroy) + + if not rows: + gfile = self._direct_file() + + if gfile and self._handler(gfile): +- self.destroy() ++ GLib.idle_add(self.destroy) + else: + ret = False + else: +@@ -495,20 +513,24 @@ class Popup(Gtk.Dialog): + + def on_key_press_event(self, widget, event): + move_mapping = { +- Gdk.KEY_Down: 1, +- Gdk.KEY_Up: -1, +- Gdk.KEY_Page_Down: 5, +- Gdk.KEY_Page_Up: -5 ++ "Down": 1, ++ "Up": -1, ++ "Page_Down": 5, ++ "Page_Up": -5 + } + +- if event.keyval == Gdk.KEY_Escape: ++ keyname = Gdk.keyval_name(event.keyval) ++ ++ if keyname == "Escape": + self.destroy() + return True +- elif event.keyval in move_mapping: +- return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK) +- elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: ++ elif keyname in move_mapping: ++ return self._move_selection(move_mapping[keyname], ++ event.state & Gdk.ModifierType.CONTROL_MASK, ++ event.state & Gdk.ModifierType.SHIFT_MASK) ++ elif keyname in ["Return", "KP_Enter", "Tab", "ISO_Left_Tab"]: + return self._activate() +- elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.CONTROL_MASK: ++ elif keyname == "space" and event.state & Gdk.ModifierType.CONTROL_MASK: + self.toggle_cursor() + + return False +diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py +index 53d716a..7bf66b8 100644 +--- a/plugins/quickopen/quickopen/virtualdirs.py ++++ b/plugins/quickopen/quickopen/virtualdirs.py +@@ -38,7 +38,9 @@ class VirtualDirectory(object): + return + + try: +- info = child.query_info("standard::*", Gio.FileQueryInfoFlags.NONE, None) ++ info = child.query_info("standard::*", ++ Gio.FileQueryInfoFlags.NONE, ++ None) + + if info: + self._children.append((child, info)) +@@ -46,7 +48,7 @@ class VirtualDirectory(object): + pass + + class RecentDocumentsDirectory(VirtualDirectory): +- def __init__(self, maxitems=10): ++ def __init__(self, maxitems=200): + VirtualDirectory.__init__(self, 'recent') + + self._maxitems = maxitems +@@ -56,7 +58,7 @@ class RecentDocumentsDirectory(VirtualDirectory): + manager = Gtk.RecentManager.get_default() + + items = manager.get_items() +- items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited())) ++ items.sort(key=lambda a: a.get_visited(), reverse=True) + + added = 0 + +diff --git a/plugins/quickopen/quickopen/windowhelper.py b/plugins/quickopen/quickopen/windowhelper.py +index 19e44cb..7f9ab12 100644 +--- a/plugins/quickopen/quickopen/windowhelper.py ++++ b/plugins/quickopen/quickopen/windowhelper.py +@@ -17,11 +17,12 @@ + # Foundation, Inc., 51 Franklin St, Fifth Floor, + # Boston, MA 02110-1301, USA. + +-from popup import Popup + import os ++import codecs + from gi.repository import Gio, GLib, Gtk, Pluma +-from virtualdirs import RecentDocumentsDirectory +-from virtualdirs import CurrentDocumentsDirectory ++from .popup import Popup ++from .virtualdirs import RecentDocumentsDirectory ++from .virtualdirs import CurrentDocumentsDirectory + + ui_str = """ + +@@ -60,12 +61,13 @@ class WindowHelper: + + def _install_menu(self): + manager = self._window.get_ui_manager() ++ action = Gtk.Action.new("QuickOpen", ++ _("Quick open"), ++ _("Quickly open documents")) ++ action.set_icon_name("document-open") ++ action.connect("activate", self.on_quick_open_activate) + self._action_group = Gtk.ActionGroup("PlumaQuickOpenPluginActions") +- self._action_group.add_actions([ +- ("QuickOpen", Gtk.STOCK_OPEN, _("Quick open"), +- 'O', _("Quickly open documents"), +- self.on_quick_open_activate) +- ]) ++ self._action_group.add_action_with_accel(action, "O") + + manager.insert_action_group(self._action_group, -1) + self._ui_id = manager.add_ui_from_string(ui_str) +@@ -95,10 +97,10 @@ class WindowHelper: + if uri: + gfile = Gio.file_new_for_uri(uri) + +- if gfile.is_native(): ++ if gfile and gfile.is_native(): + paths.append(gfile) + +- except StandardError: ++ except Exception: + pass + + # Recent documents +@@ -128,14 +130,14 @@ class WindowHelper: + self._popup.connect('destroy', self.on_popup_destroy) + + def _local_bookmarks(self): +- filename = os.path.expanduser('~/.gtk-bookmarks') ++ filename = os.path.expanduser('~/.config/gtk-3.0/bookmarks') + + if not os.path.isfile(filename): + return [] + + paths = [] + +- for line in file(filename, 'r').xreadlines(): ++ for line in codecs.open(filename, 'r', encoding='utf-8'): + uri = line.strip().split(" ")[0] + f = Gio.file_new_for_uri(uri) + +@@ -160,7 +162,7 @@ class WindowHelper: + desktopdir = None + + if os.path.isfile(config): +- for line in file(config, 'r').xreadlines(): ++ for line in codecs.open(config, 'r', encoding='utf-8'): + line = line.strip() + + if line.startswith('XDG_DESKTOP_DIR'): +-- +2.21.0 + diff --git a/pluma_0001-snippets-plugin-change-code-for-Python-2-3-compatibi.patch b/pluma_0001-snippets-plugin-change-code-for-Python-2-3-compatibi.patch new file mode 100644 index 0000000..3d497e7 --- /dev/null +++ b/pluma_0001-snippets-plugin-change-code-for-Python-2-3-compatibi.patch @@ -0,0 +1,2182 @@ +From 2056b6f2fcc6f0fd5a190f68c4310129535a7bf5 Mon Sep 17 00:00:00 2001 +From: Patrick Monnerat +Date: Mon, 27 May 2019 18:16:18 +0200 +Subject: [PATCH] snippets plugin: change code for Python 2 & 3 compatibility + +--- + plugins/snippets/snippets/Completion.py | 23 +- + plugins/snippets/snippets/Document.py | 151 ++--- + plugins/snippets/snippets/Exporter.py | 4 +- + plugins/snippets/snippets/Helper.py | 32 +- + plugins/snippets/snippets/Importer.py | 2 +- + plugins/snippets/snippets/LanguageManager.py | 3 +- + plugins/snippets/snippets/Library.py | 43 +- + plugins/snippets/snippets/Manager.py | 76 ++- + plugins/snippets/snippets/Parser.py | 2 +- + plugins/snippets/snippets/Placeholder.py | 85 ++- + plugins/snippets/snippets/Snippet.py | 46 +- + plugins/snippets/snippets/WindowHelper.py | 4 +- + plugins/snippets/snippets/__init__.py | 19 +- + plugins/snippets/snippets/snippets.ui | 679 +++++++------------ + 14 files changed, 528 insertions(+), 641 deletions(-) + +diff --git a/plugins/snippets/snippets/Completion.py b/plugins/snippets/snippets/Completion.py +index a860d21..ad0ad75 100644 +--- a/plugins/snippets/snippets/Completion.py ++++ b/plugins/snippets/snippets/Completion.py +@@ -1,14 +1,14 @@ + from gi.repository import GObject, Gtk, GtkSource, Pluma + +-from Library import Library +-from LanguageManager import get_language_manager +-from Snippet import Snippet ++from .Library import Library ++from .LanguageManager import get_language_manager ++from .Snippet import Snippet + + class Proposal(GObject.Object, GtkSource.CompletionProposal): + __gtype_name__ = "PlumaSnippetsProposal" + + def __init__(self, snippet): +- GObject.Object.__init__(self) ++ super(Proposal, self).__init__() + self._snippet = Snippet(snippet) + + def snippet(self): +@@ -25,7 +25,7 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): + __gtype_name__ = "PlumaSnippetsProvider" + + def __init__(self, name, language_id, handler): +- GObject.Object.__init__(self) ++ super(Provider, self).__init__() + + self.name = name + self.info_widget = None +@@ -38,7 +38,10 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): + theme = Gtk.IconTheme.get_default() + f, w, h = Gtk.icon_size_lookup(Gtk.IconSize.MENU) + +- self.icon = theme.load_icon(Gtk.STOCK_JUSTIFY_LEFT, w, 0) ++ try: ++ self.icon = theme.load_icon("format-justify-left", w, 0) ++ except: ++ self.icon = None + + def __del__(self): + if self.mark: +@@ -89,9 +92,9 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): + + # Filter based on the current word + if word: +- proposals = filter(lambda x: x['tag'].startswith(word), proposals) ++ proposals = (x for x in proposals if x['tag'].startswith(word)) + +- return map(lambda x: Proposal(x), proposals) ++ return [Proposal(x) for x in proposals] + + def do_populate(self, context): + proposals = self.get_proposals(self.get_word(context)) +@@ -113,6 +116,10 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): + + sw = Gtk.ScrolledWindow() + sw.add(view) ++ sw.show_all() ++ ++ # Fixed size ++ sw.set_size_request(300, 200) + + self.info_view = view + self.info_widget = sw +diff --git a/plugins/snippets/snippets/Document.py b/plugins/snippets/snippets/Document.py +index f1cc065..f40186b 100644 +--- a/plugins/snippets/snippets/Document.py ++++ b/plugins/snippets/snippets/Document.py +@@ -18,12 +18,12 @@ + import os + import re + +-from gi.repository import GLib, Gio, Gdk, Gtk, GtkSource, Pluma ++from gi.repository import GLib, Gio, Gdk, Gtk, Pluma + +-from Library import Library +-from Snippet import Snippet +-from Placeholder import * +-import Completion ++from .Library import Library ++from .Snippet import Snippet ++from .Placeholder import * ++from . import Completion + + class DynamicSnippet(dict): + def __init__(self, text): +@@ -31,9 +31,7 @@ class DynamicSnippet(dict): + self.valid = True + + class Document: +- TAB_KEY_VAL = (Gdk.KEY_Tab, \ +- Gdk.KEY_ISO_Left_Tab) +- SPACE_KEY_VAL = (Gdk.KEY_space,) ++ TAB_KEY_VAL = ('Tab', 'ISO_Left_Tab') + + def __init__(self, instance, view): + self.view = None +@@ -109,7 +107,7 @@ class Document: + self.deactivate_snippet(snippet, True) + + completion = self.view.get_completion() +- if completion: ++ if completion and self.provider in completion.get_providers(): + completion.remove_provider(self.provider) + + self.view = view +@@ -182,8 +180,6 @@ class Document: + snippets = Library().from_accelerator(accelerator, \ + self.language_id) + +- snippets_debug('Accel!') +- + if len(snippets) == 0: + return False + elif len(snippets) == 1: +@@ -193,6 +189,7 @@ class Document: + provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + provider.set_proposals(snippets) + ++ cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + return True +@@ -266,7 +263,6 @@ class Document: + + # Find the nearest placeholder + if nearest(piter, begin, end, found): +- foundIndex = index + found = placeholder + + # Find the current placeholder +@@ -276,7 +272,7 @@ class Document: + currentIndex = index + current = placeholder + +- if current and current != found and \ ++ if current and found and current != found and \ + (current.begin_iter().compare(found.begin_iter()) == 0 or \ + current.end_iter().compare(found.begin_iter()) == 0) and \ + self.active_placeholder and \ +@@ -363,57 +359,60 @@ class Document: + + def env_get_current_word(self, buf): + start, end = buffer_word_boundary(buf) +- + return buf.get_text(start, end, False) + + def env_get_current_line(self, buf): + start, end = buffer_line_boundary(buf) +- + return buf.get_text(start, end, False) + + def env_get_current_line_number(self, buf): + start, end = buffer_line_boundary(buf) + return str(start.get_line() + 1) + +- def env_get_document_uri(self, buf): +- location = buf.get_location() +- ++ def location_uri_for_env(self, location): + if location: + return location.get_uri() +- else: +- return '' +- +- def env_get_document_name(self, buf): +- location = buf.get_location() ++ return '' + ++ def location_name_for_env(self, location): + if location: + return location.get_basename() +- else: +- return '' +- +- def env_get_document_scheme(self, buf): +- location = buf.get_location() ++ return '' + ++ def location_scheme_for_env(self, location): + if location: + return location.get_uri_scheme() +- else: +- return '' +- +- def env_get_document_path(self, buf): +- location = buf.get_location() ++ return '' + ++ def location_path_for_env(self, location): + if location: + return location.get_path() +- else: +- return '' +- +- def env_get_document_dir(self, buf): +- location = buf.get_location() ++ return '' + ++ def location_dir_for_env(self, location): + if location: +- return location.get_parent().get_path() or '' +- else: +- return '' ++ parent = location.get_parent() ++ ++ if parent and parent.has_uri_scheme('file'): ++ return parent.get_path() or '' ++ ++ return '' ++ ++ def env_add_for_location(self, environ, location, prefix): ++ parts = { ++ 'URI': self.location_uri_for_env, ++ 'NAME': self.location_name_for_env, ++ 'SCHEME': self.location_scheme_for_env, ++ 'PATH': self.location_path_for_env, ++ 'DIR': self.location_dir_for_env, ++ } ++ ++ for k in parts: ++ v = parts[k](location) ++ key = prefix + '_' + k ++ environ[key] = str(v) ++ ++ return environ + + def env_get_document_type(self, buf): + typ = buf.get_mime_type() +@@ -451,25 +450,30 @@ class Document: + + return ' '.join(documents_path) + +- def update_environment(self): ++ def get_environment(self): + buf = self.view.get_buffer() ++ environ = {} ++ ++ for k in os.environ: ++ # Get the original environment ++ v = os.environ[k] ++ environ[k] = v + +- variables = {'PLUMA_SELECTED_TEXT': self.env_get_selected_text, ++ variables = { ++ 'PLUMA_SELECTED_TEXT': self.env_get_selected_text, + 'PLUMA_CURRENT_WORD': self.env_get_current_word, + 'PLUMA_CURRENT_LINE': self.env_get_current_line, + 'PLUMA_CURRENT_LINE_NUMBER': self.env_get_current_line_number, +- 'PLUMA_CURRENT_DOCUMENT_URI': self.env_get_document_uri, +- 'PLUMA_CURRENT_DOCUMENT_NAME': self.env_get_document_name, +- 'PLUMA_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme, +- 'PLUMA_CURRENT_DOCUMENT_PATH': self.env_get_document_path, +- 'PLUMA_CURRENT_DOCUMENT_DIR': self.env_get_document_dir, + 'PLUMA_CURRENT_DOCUMENT_TYPE': self.env_get_document_type, + 'PLUMA_DOCUMENTS_URI': self.env_get_documents_uri, + 'PLUMA_DOCUMENTS_PATH': self.env_get_documents_path, + } + + for var in variables: +- os.environ[var] = variables[var](buf) ++ environ[var] = variables[var](buf) ++ ++ self.env_add_for_location(environ, buf.get_location(), 'PLUMA_CURRENT_DOCUMENT') ++ return environ + + def uses_current_word(self, snippet): + matches = re.findall('(\\\\*)\\$PLUMA_CURRENT_WORD', snippet['text']) +@@ -489,12 +493,19 @@ class Document: + + return False + +- def apply_snippet(self, snippet, start = None, end = None): ++ def apply_snippet(self, snippet, start = None, end = None, environ = {}): + if not snippet.valid: + return False + ++ # Set environmental variables ++ env = self.get_environment() ++ ++ if environ: ++ for k in environ: ++ env[k] = environ[k] ++ + buf = self.view.get_buffer() +- s = Snippet(snippet) ++ s = Snippet(snippet, env) + + if not start: + start = buf.get_iter_at_mark(buf.get_insert()) +@@ -513,9 +524,6 @@ class Document: + # it will be removed + start, end = buffer_line_boundary(buf) + +- # Set environmental variables +- self.update_environment() +- + # You know, we could be in an end placeholder + (current, next) = self.next_placeholder() + if current and current.__class__ == PlaceholderEnd: +@@ -527,8 +535,6 @@ class Document: + buf.delete(start, end) + + # Insert the snippet +- holders = len(self.placeholders) +- + if len(self.active_snippets) == 0: + self.first_snippet_inserted() + +@@ -536,7 +542,7 @@ class Document: + self.active_snippets.append(sn) + + # Put cursor at first tab placeholder +- keys = filter(lambda x: x > 0, sn.placeholders.keys()) ++ keys = [x for x in sn.placeholders.keys() if x > 0] + + if len(keys) == 0: + if 0 in sn.placeholders: +@@ -637,7 +643,6 @@ class Document: + return True + + def deactivate_snippet(self, snippet, force = False): +- buf = self.view.get_buffer() + remove = [] + ordered_remove = [] + +@@ -792,10 +797,11 @@ class Document: + library = Library() + + state = event.get_state() ++ keyname = Gdk.keyval_name(event.keyval) + + if not (state & Gdk.ModifierType.CONTROL_MASK) and \ + not (state & Gdk.ModifierType.MOD1_MASK) and \ +- event.keyval in self.TAB_KEY_VAL: ++ keyname in self.TAB_KEY_VAL: + if not state & Gdk.ModifierType.SHIFT_MASK: + return self.run_snippet() + else: +@@ -868,20 +874,9 @@ class Document: + pathname = '' + dirname = '' + ruri = '' ++ environ = {'PLUMA_DROP_DOCUMENT_TYPE': mime} + +- if Pluma.utils_uri_has_file_scheme(uri): +- pathname = gfile.get_path() +- dirname = gfile.get_parent().get_path() +- +- name = os.path.basename(uri) +- scheme = gfile.get_uri_scheme() +- +- os.environ['PLUMA_DROP_DOCUMENT_URI'] = uri +- os.environ['PLUMA_DROP_DOCUMENT_NAME'] = name +- os.environ['PLUMA_DROP_DOCUMENT_SCHEME'] = scheme +- os.environ['PLUMA_DROP_DOCUMENT_PATH'] = pathname +- os.environ['PLUMA_DROP_DOCUMENT_DIR'] = dirname +- os.environ['PLUMA_DROP_DOCUMENT_TYPE'] = mime ++ self.env_add_for_location(environ, gfile, 'PLUMA_DROP_DOCUMENT') + + buf = self.view.get_buffer() + location = buf.get_location() +@@ -890,7 +885,7 @@ class Document: + + relpath = self.relative_path(ruri, uri, mime) + +- os.environ['PLUMA_DROP_DOCUMENT_RELATIVE_PATH'] = relpath ++ environ['PLUMA_DROP_DOCUMENT_RELATIVE_PATH'] = relpath + + mark = buf.get_mark('gtk_drag_target') + +@@ -898,7 +893,7 @@ class Document: + mark = buf.get_insert() + + piter = buf.get_iter_at_mark(mark) +- self.apply_snippet(snippet, piter, piter) ++ self.apply_snippet(snippet, piter, piter, environ) + + def in_bounds(self, x, y): + rect = self.view.get_visible_rect() +@@ -907,6 +902,9 @@ class Document: + return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height) + + def on_drag_data_received(self, view, context, x, y, data, info, timestamp): ++ if not self.view.get_editable(): ++ return ++ + uris = drop_get_uris(data) + if not uris: + return +@@ -944,6 +942,9 @@ class Document: + return self.view.drag_dest_find_target(context, lst) + + def on_proposal_activated(self, proposal, piter): ++ if not self.view.get_editable(): ++ return False ++ + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + +@@ -1048,8 +1049,6 @@ class Document: + if isinstance(placeholder, PlaceholderEnd): + return + +- buf = self.view.get_buffer() +- + col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE) + col.alpha = 0.5 + Gdk.cairo_set_source_rgba(ctx, col) +diff --git a/plugins/snippets/snippets/Exporter.py b/plugins/snippets/snippets/Exporter.py +index 850c3a4..713077f 100644 +--- a/plugins/snippets/snippets/Exporter.py ++++ b/plugins/snippets/snippets/Exporter.py +@@ -3,9 +3,9 @@ import tempfile + import sys + import shutil + +-from Library import * ++from .Library import * + import xml.etree.ElementTree as et +-from Helper import * ++from .Helper import * + + class Exporter: + def __init__(self, filename, snippets): +diff --git a/plugins/snippets/snippets/Helper.py b/plugins/snippets/snippets/Helper.py +index 6d440d0..c1f1c35 100644 +--- a/plugins/snippets/snippets/Helper.py ++++ b/plugins/snippets/snippets/Helper.py +@@ -15,10 +15,10 @@ + # along with this program; if not, write to the Free Software + # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +-import string + from xml.sax import saxutils +-from xml.etree.ElementTree import * ++import xml.etree.ElementTree as et + import re ++import codecs + + from gi.repository import Gtk + +@@ -93,7 +93,7 @@ def write_xml(node, f, cdata_nodes=()): + assert node is not None + + if not hasattr(f, "write"): +- f = open(f, "wb") ++ f = codecs.open(f, "wb", encoding="utf-8") + + # Encoding + f.write("\n") +@@ -107,27 +107,27 @@ def _write_node(node, file, cdata_nodes=(), indent=0): + # write XML to file + tag = node.tag + +- if node is Comment: +- _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) +- elif node is ProcessingInstruction: +- _write_indent(file, "\n" % saxutils.escape(node.text.encode('utf-8')), indent) ++ if node is et.Comment: ++ _write_indent(file, "\n" % saxutils.escape(node.text), indent) ++ elif node is et.ProcessingInstruction: ++ _write_indent(file, "\n" % saxutils.escape(node.text), indent) + else: + items = node.items() + + if items or node.text or len(node): +- _write_indent(file, "<" + tag.encode('utf-8'), indent) ++ _write_indent(file, "<" + tag, indent) + + if items: + items.sort() # lexical order + for k, v in items: +- file.write(" %s=%s" % (k.encode('utf-8'), saxutils.quoteattr(v.encode('utf-8')))) ++ file.write(" %s=%s" % (k, saxutils.quoteattr(v))) + if node.text or len(node): + file.write(">") + if node.text and node.text.strip() != "": + if tag in cdata_nodes: + file.write(_cdata(node.text)) + else: +- file.write(saxutils.escape(node.text.encode('utf-8'))) ++ file.write(saxutils.escape(node.text)) + else: + file.write("\n") + +@@ -135,19 +135,17 @@ def _write_node(node, file, cdata_nodes=(), indent=0): + _write_node(n, file, cdata_nodes, indent + 1) + + if not len(node): +- file.write("\n") ++ file.write("\n") + else: +- _write_indent(file, "\n", \ +- indent) ++ _write_indent(file, "\n", indent) + else: + file.write(" />\n") + + if node.tail and node.tail.strip() != "": +- file.write(saxutils.escape(node.tail.encode('utf-8'))) ++ file.write(saxutils.escape(node.tail)) + +-def _cdata(text, replace=string.replace): +- text = text.encode('utf-8') +- return '', ']]]]>') + ']]>' ++def _cdata(text): ++ return '', ']]]]>') + ']]>' + + def buffer_word_boundary(buf): + iter = buf.get_iter_at_mark(buf.get_insert()) +diff --git a/plugins/snippets/snippets/Importer.py b/plugins/snippets/snippets/Importer.py +index c1d211e..83c520c 100644 +--- a/plugins/snippets/snippets/Importer.py ++++ b/plugins/snippets/snippets/Importer.py +@@ -3,7 +3,7 @@ import tempfile + import sys + import shutil + +-from Library import * ++from .Library import * + + class Importer: + def __init__(self, filename): +diff --git a/plugins/snippets/snippets/LanguageManager.py b/plugins/snippets/snippets/LanguageManager.py +index e738333..d962dcf 100644 +--- a/plugins/snippets/snippets/LanguageManager.py ++++ b/plugins/snippets/snippets/LanguageManager.py +@@ -1,7 +1,7 @@ + import os + from gi.repository import GtkSource + +-from Library import Library ++from .Library import Library + + global manager + manager = None +@@ -19,4 +19,5 @@ def get_language_manager(): + manager.set_search_path(dirs + manager.get_search_path()) + + return manager ++ + # ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Library.py b/plugins/snippets/snippets/Library.py +index f152082..d8ae219 100644 +--- a/plugins/snippets/snippets/Library.py ++++ b/plugins/snippets/snippets/Library.py +@@ -20,11 +20,12 @@ import weakref + import sys + import tempfile + import re ++import codecs + + from gi.repository import Gdk, Gtk + + import xml.etree.ElementTree as et +-from Helper import * ++from .Helper import * + + class NamespacedId: + def __init__(self, namespace, id): +@@ -453,28 +454,38 @@ class SnippetsSystemFile: + lambda node: elements.append((node, True)), \ + lambda node: elements.append((node, False))) + +- parser = et.XMLTreeBuilder(target=builder) ++ self.ok = True ++ parser = et.XMLParser(target=builder) + self.insnippet = False + + try: +- f = open(self.path, "r") ++ f = codecs.open(self.path, "r", encoding='utf-8') ++ except IOError: ++ self.ok = False ++ return + +- while True: ++ while self.ok: ++ try: + data = f.read(readsize) ++ except IOError: ++ self.ok = False ++ break + +- if not data: +- break ++ if not data: ++ break + ++ try: + parser.feed(data) ++ except Exception: ++ self.ok = False ++ break + +- for element in elements: +- yield element ++ for element in elements: ++ yield element + +- del elements[:] ++ del elements[:] + +- f.close() +- except IOError: +- self.ok = False ++ f.close() + + def load(self): + if not self.ok: +@@ -531,6 +542,8 @@ class SnippetsUserFile(SnippetsSystemFile): + SnippetsSystemFile.__init__(self, path) + self.tainted = False + self.need_id = False ++ self.modifier = False ++ self.root = None + + def _set_root(self, element): + SnippetsSystemFile._set_root(self, element) +@@ -611,7 +624,7 @@ class SnippetsUserFile(SnippetsSystemFile): + + try: + if not os.path.isdir(path): +- os.makedirs(path, 0755) ++ os.makedirs(path, 0o755) + except OSError: + # TODO: this is bad... + sys.stderr.write("Error in making dirs\n") +@@ -929,8 +942,8 @@ class Library(Singleton): + def valid_accelerator(self, keyval, mod): + mod &= Gtk.accelerator_get_default_mod_mask() + +- return (mod and (Gdk.keyval_to_unicode(keyval) or \ +- keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1))) ++ return mod and (Gdk.keyval_to_unicode(keyval) or \ ++ re.match('^F(?:1[012]?|[2-9])$', Gdk.keyval_name(keyval))) + + def valid_tab_trigger(self, trigger): + if not trigger: +diff --git a/plugins/snippets/snippets/Manager.py b/plugins/snippets/snippets/Manager.py +index 9760fa7..71ada38 100644 +--- a/plugins/snippets/snippets/Manager.py ++++ b/plugins/snippets/snippets/Manager.py +@@ -21,13 +21,13 @@ import shutil + + from gi.repository import GObject, Gio, Gdk, Gtk, GtkSource, Pluma + +-from Snippet import Snippet +-from Helper import * +-from Library import * +-from Importer import * +-from Exporter import * +-from Document import Document +-from LanguageManager import get_language_manager ++from .Snippet import Snippet ++from .Helper import * ++from .Library import * ++from .Importer import * ++from .Exporter import * ++from .Document import Document ++from .LanguageManager import get_language_manager + + class Manager: + NAME_COLUMN = 0 +@@ -42,7 +42,7 @@ class Manager: + dragging = False + dnd_target_list = [Gtk.TargetEntry.new('text/uri-list', 0, TARGET_URI)] + +- def __init__(self, datadir): ++ def __init__(self, datadir, window=None): + self.datadir = datadir + self.snippet = None + self.dlg = None +@@ -52,7 +52,7 @@ class Manager: + self.default_size = None + + self.key_press_id = 0 +- self.run() ++ self.run(window) + + def get_language_snippets(self, path, name = None): + library = Library() +@@ -159,9 +159,9 @@ class Manager: + snippet = model.get_value(iter, self.SNIPPET_COLUMN) + + if snippet and not snippet.valid: +- cell.set_property('stock-id', Gtk.STOCK_DIALOG_ERROR) ++ cell.set_property('icon-name', 'dialog-error') + else: +- cell.set_property('stock-id', None) ++ cell.set_property('icon-name', None) + + cell.set_property('xalign', 1.0) + +@@ -300,9 +300,6 @@ class Manager: + self.build_tree_view() + self.build_model() + +- image = self['image_remove'] +- image.set_from_stock(Gtk.STOCK_REMOVE, Gtk.IconSize.SMALL_TOOLBAR) +- + source_view = self['source_view_snippet'] + manager = get_language_manager() + lang = manager.get_language('snippets') +@@ -391,15 +388,15 @@ class Manager: + + if not (override ^ remove) or system: + button_remove.set_sensitive(False) +- image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) ++ image_remove.set_from_icon_name("edit-delete", Gtk.IconSize.BUTTON) + else: + button_remove.set_sensitive(True) + + if override: +- image_remove.set_from_stock(Gtk.STOCK_UNDO, Gtk.IconSize.BUTTON) ++ image_remove.set_from_icon_name("edit-undo", Gtk.IconSize.BUTTON) + tooltip = _('Revert selected snippet') + else: +- image_remove.set_from_stock(Gtk.STOCK_DELETE, Gtk.IconSize.BUTTON) ++ image_remove.set_from_icon_name("edit-delete", Gtk.IconSize.BUTTON) + tooltip = _('Delete selected snippet') + + button_remove.set_tooltip_text(tooltip) +@@ -427,12 +424,14 @@ class Manager: + + return self.snippet_changed(piter) + +- def run(self): ++ def run(self, window=None): + if not self.dlg: + self.build() ++ self.dlg.set_transient_for(window) + self.dlg.show() + else: + self.build_model() ++ self.dlg.set_transient_for(window) + self.dlg.present() + + def snippet_from_iter(self, model, piter): +@@ -611,7 +610,7 @@ class Manager: + self.default_size = [alloc.width, alloc.height] + + if resp == Gtk.ResponseType.HELP: +- Pluma.help_display(self, 'pluma', 'pluma-snippets-plugin') ++ Pluma.help_display(self.dlg, 'pluma', 'pluma-snippets-plugin') + return + + self.dlg.destroy() +@@ -668,7 +667,7 @@ class Manager: + + if text and not Library().valid_tab_trigger(text): + img = self['image_tab_trigger'] +- img.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.BUTTON) ++ img.set_from_icon_name("dialog-error", Gtk.IconSize.BUTTON) + img.show() + + #self['hbox_tab_trigger'].set_spacing(3) +@@ -790,10 +789,11 @@ class Manager: + self.import_snippets(f) + + def on_button_import_snippets_clicked(self, button): +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_("Import snippets"), +- action=Gtk.FileChooserAction.OPEN, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) ++ dlg = Gtk.FileChooserDialog(title=_("Import snippets"), ++ parent=self.dlg, ++ action=Gtk.FileChooserAction.OPEN) ++ self._add_button(dlg, _('_Cancel'), Gtk.ResponseType.CANCEL, "process-stop") ++ self._add_button(dlg, _("_Open"), Gtk.ResponseType.OK, "document-open") + + dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml'))) + dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',))) +@@ -875,10 +875,11 @@ class Manager: + return False + + if not filename: +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), +- action=Gtk.FileChooserAction.SAVE, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) ++ dlg = Gtk.FileChooserDialog(title=_('Export snippets'), ++ parent=self.dlg, ++ action=Gtk.FileChooserAction.SAVE) ++ self._add_button(dlg, _('_Cancel'), Gtk.ResponseType.CANCEL, "process-stop") ++ self._add_button(dlg, _("_Save"), Gtk.ResponseType.OK, "document-save") + + dlg._export_snippets = export_snippets + dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar'))) +@@ -913,10 +914,11 @@ class Manager: + else: + systemsnippets.append(snippet) + +- dlg = Gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'), +- action=Gtk.FileChooserAction.SAVE, +- buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, +- Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) ++ dlg = Gtk.FileChooserDialog(title=_('Export snippets'), ++ parent=self.dlg, ++ action=Gtk.FileChooserAction.SAVE) ++ self._add_button(dlg, _('_Cancel'), Gtk.ResponseType.CANCEL, "process-stop") ++ self._add_button(dlg, _("_Save"), Gtk.ResponseType.OK, "document-save") + + dlg._export_snippets = snippets + +@@ -1145,4 +1147,14 @@ class Manager: + context.finish(True, False, timestamp) + + entry.stop_emission('drag_data_received') ++ ++ @staticmethod ++ def _add_button(dialog, label, response, icon=None): ++ button = dialog.add_button(label, response) ++ if icon: ++ image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.BUTTON) ++ button.set_image(image) ++ button.set_property("always-show-image", True) ++ return button ++ + # ex:ts=4:et: +diff --git a/plugins/snippets/snippets/Parser.py b/plugins/snippets/snippets/Parser.py +index 280ce0c..f200043 100644 +--- a/plugins/snippets/snippets/Parser.py ++++ b/plugins/snippets/snippets/Parser.py +@@ -18,7 +18,7 @@ + import os + import re + import sys +-from SubstitutionParser import SubstitutionParser ++from .SubstitutionParser import SubstitutionParser + + class Token: + def __init__(self, klass, data): +diff --git a/plugins/snippets/snippets/Placeholder.py b/plugins/snippets/snippets/Placeholder.py +index 9edf099..050fda6 100644 +--- a/plugins/snippets/snippets/Placeholder.py ++++ b/plugins/snippets/snippets/Placeholder.py +@@ -24,12 +24,12 @@ import locale + import subprocess + from gi.repository import GObject, GLib, Gtk + +-from SubstitutionParser import SubstitutionParser +-from Helper import * ++from .SubstitutionParser import SubstitutionParser ++from .Helper import * + + # These are places in a view where the cursor can go and do things + class Placeholder: +- def __init__(self, view, tabstop, defaults, begin): ++ def __init__(self, view, tabstop, environ, defaults, begin): + self.ok = True + self.done = False + self.buf = view.get_buffer() +@@ -38,6 +38,7 @@ class Placeholder: + self.mirrors = [] + self.leave_mirrors = [] + self.tabstop = tabstop ++ self.environ = environ + self.set_default(defaults) + self.prev_contents = self.default + self.set_mark_gravity() +@@ -49,6 +50,9 @@ class Placeholder: + + self.end = None + ++ def get_environ(self): ++ return self.environ ++ + def __str__(self): + return '%s (%s)' % (str(self.__class__), str(self.default)) + +@@ -81,10 +85,12 @@ class Placeholder: + return s + + def re_environment(self, m): +- if m.group(1) or not m.group(2) in os.environ: ++ env = self.get_environ() ++ ++ if m.group(1) or not m.group(2) in env: + return '$' + m.group(2) + else: +- return self.format_environment(os.environ[m.group(2)]) ++ return self.format_environment(env[m.group(2)]) + + def expand_environment(self, text): + if not text: +@@ -214,8 +220,8 @@ class Placeholder: + + # This is an placeholder which inserts a mirror of another Placeholder + class PlaceholderMirror(Placeholder): +- def __init__(self, view, tabstop, begin): +- Placeholder.__init__(self, view, -1, None, begin) ++ def __init__(self, view, tabstop, environ, begin): ++ Placeholder.__init__(self, view, -1, environ, None, begin) + self.mirror_stop = tabstop + + def update(self, mirror): +@@ -237,8 +243,8 @@ class PlaceholderMirror(Placeholder): + + # This placeholder indicates the end of a snippet + class PlaceholderEnd(Placeholder): +- def __init__(self, view, begin, default): +- Placeholder.__init__(self, view, 0, default, begin) ++ def __init__(self, view, environ, begin, default): ++ Placeholder.__init__(self, view, 0, environ, default, begin) + + def run_last(self, placeholders): + Placeholder.run_last(self, placeholders) +@@ -264,8 +270,8 @@ class PlaceholderEnd(Placeholder): + + # This placeholder is used to expand a command with embedded mirrors + class PlaceholderExpand(Placeholder): +- def __init__(self, view, tabstop, begin, s): +- Placeholder.__init__(self, view, tabstop, None, begin) ++ def __init__(self, view, tabstop, environ, begin, s): ++ Placeholder.__init__(self, view, tabstop, environ, None, begin) + + self.mirror_text = {0: ''} + self.timeout_id = None +@@ -359,8 +365,6 @@ class PlaceholderExpand(Placeholder): + return ret + + def update(self, mirror): +- text = None +- + if mirror: + self.mirror_text[mirror.tabstop] = mirror.get_text() + +@@ -379,8 +383,8 @@ class PlaceholderExpand(Placeholder): + + # The shell placeholder executes commands in a subshell + class PlaceholderShell(PlaceholderExpand): +- def __init__(self, view, tabstop, begin, s): +- PlaceholderExpand.__init__(self, view, tabstop, begin, s) ++ def __init__(self, view, tabstop, environ, begin, s): ++ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, s) + + self.shell = None + self.remove_me = False +@@ -412,7 +416,7 @@ class PlaceholderShell(PlaceholderExpand): + self.close_shell() + self.remove_timeout() + +- self.set_text(str.join('', self.shell_output).rstrip('\n')) ++ self.set_text(''.join(self.shell_output).rstrip('\n')) + + if self.default == None: + self.default = self.get_text() +@@ -423,19 +427,24 @@ class PlaceholderShell(PlaceholderExpand): + + def process_cb(self, source, condition): + if condition & GObject.IO_IN: +- line = source.readline() ++ while True: ++ line = source.readline() + +- if len(line) > 0: +- try: +- line = unicode(line, 'utf-8') +- except: +- line = unicode(line, locale.getdefaultlocale()[1], +- 'replace') ++ if len(line) <= 0: ++ break + +- self.shell_output += line +- self.install_timeout() ++ if isinstance(line, bytes): ++ try: ++ line = line.decode('utf-8') ++ except UnicodeDecodeError: ++ line = line.decode(locale.getdefaultlocale()[1], ++ errors='replace') + +- return True ++ self.shell_output += line ++ self.install_timeout() ++ ++ if not (condition & GObject.IO_HUP): ++ return True + + self.process_close() + return False +@@ -456,7 +465,7 @@ class PlaceholderShell(PlaceholderExpand): + popen_args = { + 'cwd' : None, + 'shell': True, +- 'env' : os.environ, ++ 'env': self.get_environ(), + 'stdout': subprocess.PIPE + } + +@@ -491,8 +500,8 @@ class TimeoutError(Exception): + + # The python placeholder evaluates commands in python + class PlaceholderEval(PlaceholderExpand): +- def __init__(self, view, tabstop, refs, begin, s, namespace): +- PlaceholderExpand.__init__(self, view, tabstop, begin, s) ++ def __init__(self, view, tabstop, environ, refs, begin, s, namespace): ++ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, s) + + self.fdread = 0 + self.remove_me = False +@@ -529,7 +538,7 @@ class PlaceholderEval(PlaceholderExpand): + return hasattr(signal, 'SIGALRM') + + def timeout_cb(self, signum = 0, frame = 0): +- raise TimeoutError, "Operation timed out (>2 seconds)" ++ raise TimeoutError("Operation timed out (>2 seconds)") + + def install_timeout(self): + if not self.timeout_supported(): +@@ -568,7 +577,7 @@ class PlaceholderEval(PlaceholderExpand): + del self.namespace['process_snippet'] + + try: +- exec text in self.namespace ++ exec(text, self.namespace) + except: + traceback.print_exc() + +@@ -593,7 +602,7 @@ class PlaceholderEval(PlaceholderExpand): + 'time, execution aborted.') % self.command) + + return False +- except Exception, detail: ++ except Exception as detail: + self.remove_timeout() + + message_dialog(None, Gtk.MessageType.ERROR, +@@ -612,8 +621,8 @@ class PlaceholderEval(PlaceholderExpand): + + # Regular expression placeholder + class PlaceholderRegex(PlaceholderExpand): +- def __init__(self, view, tabstop, begin, inp, pattern, substitution, modifiers): +- PlaceholderExpand.__init__(self, view, tabstop, begin, '') ++ def __init__(self, view, tabstop, environ, begin, inp, pattern, substitution, modifiers): ++ PlaceholderExpand.__init__(self, view, tabstop, environ, begin, '') + + self.instant_update = True + self.inp = inp +@@ -652,10 +661,12 @@ class PlaceholderRegex(PlaceholderExpand): + return re.escape(s) + + def get_input(self): ++ env = self.get_environ() ++ + if isinstance(self.inp, int): + return self.mirror_text[self.inp] +- elif self.inp in os.environ: +- return os.environ[self.inp] ++ elif self.inp in env: ++ return env[self.inp] + else: + return '' + +@@ -672,7 +683,7 @@ class PlaceholderRegex(PlaceholderExpand): + # Try to compile pattern + try: + regex = re.compile(pattern, self.modifiers) +- except re.error, message: ++ except re.error as message: + sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message)) + return False + +diff --git a/plugins/snippets/snippets/Snippet.py b/plugins/snippets/snippets/Snippet.py +index 192b036..91e6380 100644 +--- a/plugins/snippets/snippets/Snippet.py ++++ b/plugins/snippets/snippets/Snippet.py +@@ -16,11 +16,12 @@ + # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + import os ++import six + from gi.repository import Gio, Gtk + +-from Placeholder import * +-from Parser import Parser, Token +-from Helper import * ++from .Placeholder import * ++from .Parser import Parser, Token ++from .Helper import * + + class EvalUtilities: + def __init__(self, view=None): +@@ -87,8 +88,8 @@ class EvalUtilities: + for col in range(0, len(items[row]) - 1): + item = items[row][col] + +- result += item + ("\t" * ((maxlen[col] - \ +- self._real_len(item, tablen)) / tablen)) ++ result += item + ("\t" * int(((maxlen[col] - \ ++ self._real_len(item, tablen)) / tablen))) + + result += items[row][len(items[row]) - 1] + +@@ -98,8 +99,9 @@ class EvalUtilities: + return result + + class Snippet: +- def __init__(self, data): ++ def __init__(self, data, environ = {}): + self.data = data ++ self.environ = environ + + def __getitem__(self, prop): + return self.data[prop] +@@ -132,7 +134,7 @@ class Snippet: + if not detail: + return nm + else: +- return nm + ' (' + markup_escape(str.join(', ', detail)) + \ ++ return nm + ' (' + markup_escape(", ".join(detail)) + \ + ')' + + def _add_placeholder(self, placeholder): +@@ -149,14 +151,17 @@ class Snippet: + + def _insert_text(self, text): + # Insert text keeping indentation in mind +- indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n')) ++ indented = (six.u('\n') + self._indent).join(spaces_instead_of_tabs(self._view, text).split('\n')) + self._view.get_buffer().insert(self._insert_iter(), indented) + + def _insert_iter(self): + return self._view.get_buffer().get_iter_at_mark(self._insert_mark) + + def _create_environment(self, data): +- val = ((data in os.environ) and os.environ[data]) or '' ++ if data in self.environ: ++ val = self.environ[data] ++ else: ++ val = '' + + # Get all the current indentation + all_indent = compute_indentation(self._view, self._insert_iter()) +@@ -165,7 +170,7 @@ class Snippet: + indent = all_indent[len(self._indent):] + + # Keep indentation +- return unicode.join('\n' + unicode(indent), val.split('\n')) ++ return (six.u('\n') + indent).join(val.split('\n')) + + def _create_placeholder(self, data): + tabstop = data['tabstop'] +@@ -173,25 +178,25 @@ class Snippet: + + if tabstop == 0: + # End placeholder +- return PlaceholderEnd(self._view, begin, data['default']) ++ return PlaceholderEnd(self._view, self.environ, begin, data['default']) + elif tabstop in self.placeholders: + # Mirror placeholder +- return PlaceholderMirror(self._view, tabstop, begin) ++ return PlaceholderMirror(self._view, tabstop, self.environ, begin) + else: + # Default placeholder +- return Placeholder(self._view, tabstop, data['default'], begin) ++ return Placeholder(self._view, tabstop, self.environ, data['default'], begin) + + def _create_shell(self, data): + begin = self._insert_iter() +- return PlaceholderShell(self._view, data['tabstop'], begin, data['contents']) ++ return PlaceholderShell(self._view, data['tabstop'], self.environ, begin, data['contents']) + + def _create_eval(self, data): + begin = self._insert_iter() +- return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace) ++ return PlaceholderEval(self._view, data['tabstop'], self.environ, data['dependencies'], begin, data['contents'], self._utils.namespace) + + def _create_regex(self, data): + begin = self._insert_iter() +- return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers']) ++ return PlaceholderRegex(self._view, data['tabstop'], self.environ, begin, data['input'], data['pattern'], data['substitution'], data['modifiers']) + + def _create_text(self, data): + return data +@@ -239,11 +244,11 @@ class Snippet: + 'eval': self._create_eval, + 'regex': self._create_regex, + 'text': self._create_text}[token.klass](token.data) +- except: ++ except KeyError: + sys.stderr.write('Token class not supported: %s\n' % token.klass) + continue + +- if isinstance(val, basestring): ++ if isinstance(val, six.string_types): + # Insert text + self._insert_text(val) + else: +@@ -252,7 +257,7 @@ class Snippet: + + # Create end placeholder if there isn't one yet + if 0 not in self.placeholders: +- self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None) ++ self.placeholders[0] = PlaceholderEnd(self._view, self.environ, self.end_iter(), None) + self.plugin_data.ordered_placeholders.append(self.placeholders[0]) + + # Make sure run_last is ran for all placeholders and remove any +@@ -317,8 +322,7 @@ class Snippet: + # So now all of the snippet is in the buffer, we have all our + # placeholders right here, what's next, put all marks in the + # plugin_data.marks +- k = self.placeholders.keys() +- k.sort(reverse=True) ++ k = sorted(self.placeholders.keys(), reverse=True) + + plugin_data.placeholders.insert(last_index, self.placeholders[0]) + last_iter = self.placeholders[0].end_iter() +diff --git a/plugins/snippets/snippets/WindowHelper.py b/plugins/snippets/snippets/WindowHelper.py +index 296ff03..44ac558 100644 +--- a/plugins/snippets/snippets/WindowHelper.py ++++ b/plugins/snippets/snippets/WindowHelper.py +@@ -21,8 +21,8 @@ import gettext + + from gi.repository import GObject, Gtk, Pluma + +-from Document import Document +-from Library import Library ++from .Document import Document ++from .Library import Library + + class WindowHelper: + def __init__(self, plugin): +diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py +index 8642406..ada586c 100644 +--- a/plugins/snippets/snippets/__init__.py ++++ b/plugins/snippets/snippets/__init__.py +@@ -18,9 +18,9 @@ + import os + from gi.repository import GObject, GLib, Gtk, Peas, Pluma + +-from WindowHelper import WindowHelper +-from Library import Library +-from Manager import Manager ++from .WindowHelper import WindowHelper ++from .Library import Library ++from .Manager import Manager + + class SnippetsPlugin(GObject.Object, Peas.Activatable): + __gtype_name__ = "SnippetsPlugin" +@@ -53,7 +53,7 @@ class SnippetsPlugin(GObject.Object, Peas.Activatable): + library = Library() + library.add_accelerator_callback(self.accelerator_activated) + +- snippetsdir = os.path.join(GLib.get_user_config_dir(), '/pluma/snippets') ++ snippetsdir = os.path.join(GLib.get_user_config_dir(), 'pluma/snippets') + library.set_dirs(snippetsdir, self.system_dirs()) + + self._helper = WindowHelper(self) +@@ -72,15 +72,12 @@ class SnippetsPlugin(GObject.Object, Peas.Activatable): + self._helper.update() + + def create_configure_dialog(self): +- if not self.dlg: +- self.dlg = Manager(self.plugin_info.get_data_dir()) +- else: +- self.dlg.run() +- + window = Pluma.App.get_default().get_active_window() + +- if window: +- self.dlg.dlg.set_transient_for(window) ++ if not self.dlg: ++ self.dlg = Manager(self.plugin_info.get_data_dir(), window) ++ else: ++ self.dlg.run(window) + + return self.dlg.dlg + +diff --git a/plugins/snippets/snippets/snippets.ui b/plugins/snippets/snippets/snippets.ui +index 6fcaf85..833aa02 100644 +--- a/plugins/snippets/snippets/snippets.ui ++++ b/plugins/snippets/snippets/snippets.ui +@@ -1,8 +1,11 @@ +- ++ ++ + + ++ + + ++ + + + +@@ -32,303 +35,249 @@ + + + +- +- True +- + ++ False + Snippets Manager +- GTK_WINDOW_TOPLEVEL +- GTK_WIN_POS_NONE +- False + 750 + 500 +- True + True +- True ++ dialog + True +- False +- GDK_WINDOW_TYPE_HINT_DIALOG +- GDK_GRAVITY_NORTH_WEST +- True +- False +- +- ++ ++ ++ ++ ++ + +- ++ + True +- False +- 0 ++ False ++ vertical + +- ++ + True +- GTK_BUTTONBOX_END ++ False ++ True ++ end + +- ++ ++ gtk-help + True +- True + True +- gtk-close ++ True ++ False + True +- GTK_RELIEF_NORMAL +- True + ++ ++ True ++ True ++ 0 ++ + + +- ++ ++ gtk-close + True +- True + True +- gtk-help ++ True ++ False + True +- GTK_RELIEF_NORMAL +- True + ++ ++ True ++ True ++ end ++ 1 ++ + + + +- 0 + False + True +- GTK_PACK_END ++ end ++ 0 + + + +- +- 6 ++ + True + True + 275 + +- +- 230 ++ + True +- False +- 6 +- +- +- True +- _Snippets: +- True +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- tree_view_snippets +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 +- +- +- 0 +- False +- False +- +- ++ False ++ True ++ True ++ vertical ++ 6 + + + True + True +- GTK_POLICY_AUTOMATIC +- GTK_POLICY_AUTOMATIC +- GTK_SHADOW_IN +- GTK_CORNER_TOP_LEFT ++ True ++ True ++ in + + + True + True + False +- False +- False +- True +- False +- False +- False +- +- ++ ++ ++ ++ ++ + + + + +- 0 +- True +- True ++ 0 ++ 1 + + + +- ++ + True +- False +- 6 ++ False ++ start ++ _Snippets: ++ True ++ tree_view_snippets ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ 6 + + + True +- Create new snippet +- True + True +- GTK_RELIEF_NORMAL +- True +- ++ True ++ False ++ Create new snippet ++ + + + True +- gtk-new +- 4 +- 0.5 +- 0.5 +- 0 +- 0 ++ False ++ document-new + + + + +- 0 +- False +- False ++ 0 ++ 0 + + + + + True +- Import snippets +- True + True +- GTK_RELIEF_NORMAL +- True +- ++ True ++ False ++ Import snippets ++ + + + True +- gtk-open +- 4 +- 0.5 +- 0.5 +- 0 +- 0 ++ False ++ document-open + + + + +- 0 +- False +- False ++ 1 ++ 0 + + + + + True +- Export selected snippets +- True + True +- GTK_RELIEF_NORMAL +- True +- ++ True ++ False ++ Export selected snippets ++ + + + True +- gtk-save +- 4 +- 0.5 +- 0.5 +- 0 +- 0 ++ False ++ document-save + + + + +- 0 +- False +- False ++ 2 ++ 0 + + + + + True + False +- Delete selected snippet +- True + True +- GTK_RELIEF_NORMAL +- True +- ++ True ++ False ++ Delete selected snippet ++ end ++ True ++ + + + True +- gtk-delete +- 4 +- 0.5 +- 0.5 +- 0 +- 0 ++ False ++ edit-delete + + + + +- 0 +- False +- False +- GTK_PACK_END ++ 3 ++ 0 + + + + +- 0 +- False +- False ++ 0 ++ 2 + + + + +- False + False ++ True + + + +- ++ + True +- False +- 12 ++ False ++ True ++ True ++ vertical ++ 12 + +- ++ + True +- False +- 6 +- +- +- True +- _Edit: +- True +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 +- +- +- 0 +- False +- False +- +- ++ False ++ True ++ True ++ vertical ++ 6 + + + True + True +- GTK_POLICY_AUTOMATIC +- GTK_POLICY_AUTOMATIC +- GTK_SHADOW_IN +- GTK_CORNER_TOP_LEFT ++ True ++ True ++ in + + + source_buffer +@@ -346,302 +295,198 @@ + + + +- 0 +- True +- True ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ start ++ True ++ _Edit: ++ True ++ ++ ++ 0 ++ 0 + + + + +- 0 +- True +- True ++ 0 ++ 0 + + + +- ++ + True +- False +- 6 ++ False ++ True ++ vertical ++ 6 + + + True ++ False ++ start ++ True + Activation +- False + True +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 + +- ++ + + + +- 0 +- False +- False ++ 0 ++ 0 + + + +- ++ + True +- False +- 0 ++ False ++ 12 ++ True ++ 6 ++ 6 + +- ++ + True +- +- False +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0.5 +- 0.5 +- 0 +- 0 +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 ++ False ++ True ++ model1 ++ True ++ 0 ++ ++ ++ True ++ ++ + + +- 0 +- False +- False ++ 1 ++ 2 ++ 2 + + + +- ++ + True +- 3 +- 2 +- False +- 6 +- 6 +- +- +- True +- _Tab trigger: +- True +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- entry_tab_trigger +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 +- +- +- 0 +- 1 +- 0 +- 1 +- fill +- +- +- +- +- +- True +- +- +- True +- False +- Single word the snippet is activated with after pressing Tab +- True +- True +- True +- 0 +- +- True +- * +- False +- +- +- +- +- True +- 0 +- +- +- +- +- False +- +- +- False +- 1 +- 3 +- +- +- +- +- 1 +- 2 +- 0 +- 1 +- +- +- +- +- +- True +- False +- Shortcut key with which the snippet is activated +- True +- False +- True +- 0 +- +- True +- * +- False +- +- +- +- +- +- 1 +- 2 +- 1 +- 2 +- +- +- +- +- +- True +- S_hortcut key: +- True +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- entry_accelerator +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 +- +- +- 0 +- 1 +- 1 +- 2 +- fill +- +- +- +- +- +- True +- _Drop targets: +- True +- False +- GTK_JUSTIFY_LEFT +- False +- False +- 0 +- 0.5 +- 0 +- 0 +- entry_accelerator +- PANGO_ELLIPSIZE_NONE +- -1 +- False +- 0 +- +- +- 0 +- 1 +- 2 +- 3 +- fill +- +- +- +- +- +- True +- False +- True +- True +- True +- model1 +- +- +- +- 0 +- +- +- +- +- 1 +- 2 +- 2 +- 3 +- fill +- fill +- +- ++ False ++ start ++ _Drop targets: ++ True ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shortcut key with which the snippet is activated ++ True ++ False ++ * ++ ++ ++ + + +- 0 +- True +- True ++ 1 ++ 1 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ start ++ S_hortcut key: ++ True ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ start ++ _Tab trigger: ++ True ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ ++ ++ 2 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Single word the snippet is activated with after pressing Tab ++ True ++ * ++ ++ ++ ++ ++ 1 ++ 0 + + + + +- 0 +- True +- True ++ 0 ++ 1 + + + + +- 0 +- False +- False ++ 0 ++ 1 + + + + +- True + True ++ True + + + + +- 0 + True + True ++ 1 + + + + + +- closebutton1 + button1 ++ closebutton1 + + ++ ++ True ++ + +-- +2.21.0 +