diff options
Diffstat (limited to 'plugins/snippets')
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Completion.py | 304 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Document.py | 1994 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Exporter.py | 176 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Helper.py | 244 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Importer.py | 184 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/LanguageManager.py | 25 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Library.py | 1892 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Manager.py | 2176 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Parser.py | 466 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Placeholder.py | 1278 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/Snippet.py | 657 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/SubstitutionParser.py | 356 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/WindowHelper.py | 248 | ||||
-rw-r--r--[-rwxr-xr-x] | plugins/snippets/snippets/__init__.py | 94 |
14 files changed, 5048 insertions, 5046 deletions
diff --git a/plugins/snippets/snippets/Completion.py b/plugins/snippets/snippets/Completion.py index 1788ecdf..a860d210 100755..100644 --- 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 index 07faf2a8..f1cc0654 100755..100644 --- 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 index 18369a68..850c3a4a 100755..100644 --- 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 index d8a19673..6d440d03 100755..100644 --- 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("<?xml version='1.0' encoding='utf-8'?>\n") + # Encoding + f.write("<?xml version='1.0' encoding='utf-8'?>\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, "<!-- %s -->\n" % saxutils.escape(node.text.encode('utf-8')), indent) - elif node is ProcessingInstruction: - _write_indent(file, "<?%s?>\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("</" + tag.encode('utf-8') + ">\n") - else: - _write_indent(file, "</" + tag.encode('utf-8') + ">\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, "<!-- %s -->\n" % saxutils.escape(node.text.encode('utf-8')), indent) + elif node is ProcessingInstruction: + _write_indent(file, "<?%s?>\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("</" + tag.encode('utf-8') + ">\n") + else: + _write_indent(file, "</" + tag.encode('utf-8') + ">\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 '<![CDATA[' + replace(text, ']]>', ']]]]><![CDATA[>') + ']]>' + text = text.encode('utf-8') + return '<![CDATA[' + replace(text, ']]>', ']]]]><![CDATA[>') + ']]>' 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 index b2e8723a..c1d211e2 100755..100644 --- 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 index 1fb4347c..e738333e 100755..100644 --- 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 index 5b3773ae..f1520822 100755..100644 --- 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 index f496b1c3..9760fa7c 100755..100644 --- 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, ('<i>' + _('Add a new snippet...') + \ - '</i>', '', 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, ('<i>' + _('Add a new snippet...') + \ + '</i>', '', 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 <b>system</b> 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 <b>system</b> 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 <b>system</b> 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 <b>system</b> 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 index 0c638df7..280ce0c1 100755..100644 --- 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 index 5fa6e559..9edf099e 100755..100644 --- 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 index 2d7f67de..192b036a 100755..100644 --- 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 + ' (<b>' + markup_escape(str.join(', ', detail)) + \ - '</b>)' - - 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 + ' (<b>' + markup_escape(str.join(', ', detail)) + \ + '</b>)' + + 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 index a41f5a6a..246f4da7 100755..100644 --- 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 index 3b08d12f..296ff039 100755..100644 --- 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 index d005e894..86424069 100755..100644 --- 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: |