1
# Gedit snippets plugin
2
# Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl>
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
import ElementTree as et
23
from functions import *
26
def __init__(self, namespace, id):
31
self.id = namespace + '-'
38
PROPS = {'tag': '', 'text': '', 'description': 'New snippet',
41
def __init__(self, node, library):
42
self.priv_id = node.attrib.get('id')
44
self.set_library(library)
49
return (self.library and (isinstance(self.library(), SnippetsUserFile)))
51
def set_library(self, library):
53
self.library = weakref.ref(library)
57
self.id = NamespacedId(self.language(), self.priv_id).id
59
def set_node(self, node):
65
self.init_snippet_data(node)
67
def init_snippet_data(self, node):
71
self.override = node.attrib.get('override')
74
props = SnippetData.PROPS.copy()
76
# Store all properties present
78
if props.has_key(child.tag):
81
# Normalize accelerator
82
if child.tag == 'accelerator' and child.text != None:
83
keyval, mod = gtk.accelerator_parse(child.text)
85
if not gtk.accelerator_valid(keyval, mod):
88
child.text = gtk.accelerator_name(keyval, mod)
91
self.properties[child.tag] = child
93
self.properties[child.tag] = child.text or ''
95
# Create all the props that were not found so we stay consistent
98
child = et.SubElement(node, prop)
100
child.text = props[prop]
101
self.properties[prop] = child
103
self.properties[prop] = props[prop]
105
self.check_validation()
107
def check_validation(self):
108
if not self['tag'] and not self['accelerator']:
111
library = SnippetsLibrary()
112
keyval, mod = gtk.accelerator_parse(self['accelerator'])
114
self.valid = library.valid_tab_trigger(self['tag']) and \
115
(not self['accelerator'] or library.valid_accelerator(keyval, mod))
117
def __getitem__(self, prop):
118
if prop in self.properties:
119
if self.can_modify():
120
return self.properties[prop].text or ''
122
return self.properties[prop] or ''
126
def __setitem__(self, prop, value):
127
if not prop in self.properties:
130
if not self.can_modify() and self.properties[prop] != value:
131
# ohoh, this is not can_modify, but it needs to be changed...
132
# make sure it is transfered to the changes file and set all the
134
# This snippet data container will effectively become the container
135
# for the newly created node, but transparently to whoever uses
140
if self.can_modify() and self.properties[prop].text != value:
142
self.library().tainted = True
144
oldvalue = self.properties[prop].text
145
self.properties[prop].text = value
147
if prop == 'tag' or prop == 'accelerator':
148
container = SnippetsLibrary().container(self.language())
149
container.prop_changed(self, prop, oldvalue)
151
self.check_validation()
154
if self.library and self.library():
155
return self.library().language
159
def is_override(self):
160
return self.override and SnippetsLibrary().overridden[self.override]
164
target = SnippetsLibrary().get_user_library(self.language())
166
# Create a new node there with override
167
element = et.SubElement(target.root, 'snippet', \
168
{'override': self.id})
170
# Create all the properties
171
for p in self.properties:
172
prop = et.SubElement(element, p)
173
prop.text = self.properties[p]
174
self.properties[p] = prop
176
# Create an override snippet data, feed it element so that it stores
177
# all the values and then set the node to None so that it only contains
178
# the values in .properties
179
override = SnippetData(element, self.library())
180
override.set_node(None)
181
override.id = self.id
183
# Set our node to the new element
186
# Set the override to our id
187
self.override = self.id
190
# Set the new library
191
self.set_library(target)
193
# The library is tainted because we added this snippet
194
target.tainted = True
197
SnippetsLibrary().overridden[self.override] = override
199
def revert(self, snippet):
200
userlib = self.library()
201
self.set_library(snippet.library())
203
userlib.remove(self.node)
207
# Copy the properties
208
self.properties = snippet.properties
213
# Reset the override flag
216
class SnippetsTreeBuilder(et.TreeBuilder):
217
def __init__(self, start=None, end=None):
218
et.TreeBuilder.__init__(self)
219
self.set_start(start)
222
def set_start(self, start):
223
self._start_cb = start
225
def set_end(self, end):
228
def start(self, tag, attrs):
229
result = et.TreeBuilder.start(self, tag, attrs)
232
self._start_cb(result)
237
result = et.TreeBuilder.end(self, tag)
244
class LanguageContainer:
245
def __init__(self, language):
246
self.language = language
248
self.snippets_by_prop = {'tag': {}, 'accelerator': {}}
249
self.accel_group = gtk.AccelGroup()
252
def _add_prop(self, snippet, prop, value=0):
254
value = snippet[prop]
256
if not value or value == '':
259
snippets_debug('Added ' + prop + ' ' + value + ' to ' + \
262
if prop == 'accelerator':
263
keyval, mod = gtk.accelerator_parse(value)
264
self.accel_group.connect_group(keyval, mod, 0, \
265
SnippetsLibrary().accelerator_activated)
267
snippets = self.snippets_by_prop[prop]
269
if value in snippets:
270
snippets[value].append(snippet)
272
snippets[value] = [snippet]
274
def _remove_prop(self, snippet, prop, value=0):
276
value = snippet[prop]
278
if not value or value == '':
281
snippets_debug('Removed ' + prop + ' ' + value + ' from ' + \
284
if prop == 'accelerator':
285
keyval, mod = gtk.accelerator_parse(value)
286
self.accel_group.disconnect_key(keyval, mod)
288
snippets = self.snippets_by_prop[prop]
291
snippets[value].remove(snippet)
295
def append(self, snippet):
297
accelerator = snippet['accelerator']
299
self.snippets.append(snippet)
301
self._add_prop(snippet, 'tag')
302
self._add_prop(snippet, 'accelerator')
306
def remove(self, snippet):
308
self.snippets.remove(snippet)
312
self._remove_prop(snippet, 'tag')
313
self._remove_prop(snippet, 'accelerator')
315
def prop_changed(self, snippet, prop, oldvalue):
316
snippets_debug('PROP CHANGED (', prop, ')', oldvalue)
318
self._remove_prop(snippet, prop, oldvalue)
319
self._add_prop(snippet, prop)
321
def from_prop(self, prop, value):
322
snippets = self.snippets_by_prop[prop]
324
if value in snippets:
325
return snippets[value]
338
return self._refs != 0
340
class SnippetsSystemFile:
341
def __init__(self, path=None):
348
def load_error(self, message):
349
sys.stderr.write("An error occurred loading " + self.path + ":\n")
350
sys.stderr.write(message + "\nSnippets in this file will not be " \
351
"available, please correct or remove the file.\n")
353
def _add_snippet(self, element):
354
if not self.need_id or element.attrib.get('id'):
355
self.loading_elements.append(element)
357
def set_language(self, element):
358
self.language = element.attrib.get('language')
361
self.language = self.language.lower()
363
def _set_root(self, element):
364
self.set_language(element)
366
def _preprocess_element(self, element):
368
if not element.tag == "snippets":
369
self.load_error("Root element should be `snippets' instead " \
370
"of `%s'" % element.tag)
373
self._set_root(element)
375
elif element.tag != 'snippet' and not self.insnippet:
376
self.load_error("Element should be `snippet' instead of `%s'" \
380
self.insnippet = True
384
def _process_element(self, element):
385
if element.tag == 'snippet':
386
self._add_snippet(element)
387
self.insnippet = False
392
if not self.ok or self.loaded:
397
def parse_xml(self, readsize=16384):
403
builder = SnippetsTreeBuilder( \
404
lambda node: elements.append((node, True)), \
405
lambda node: elements.append((node, False)))
407
parser = et.XMLTreeBuilder(target=builder)
408
self.insnippet = False
411
f = open(self.path, "r")
414
data = f.read(readsize)
421
for element in elements:
434
snippets_debug("Loading library (" + str(self.language) + "): " + \
439
self.loading_elements = []
441
for element in self.parse_xml():
443
if not self._preprocess_element(element[0]):
444
del self.loading_elements[:]
447
if not self._process_element(element[0]):
448
del self.loading_elements[:]
451
for element in self.loading_elements:
452
snippet = SnippetsLibrary().add_snippet(self, element)
454
del self.loading_elements[:]
457
# This function will get the language for a file by just inspecting the
458
# root element of the file. This is provided so that a cache can be built
459
# for which file contains which language.
460
# It returns the name of the language
461
def ensure_language(self):
465
for element in self.parse_xml(256):
467
if element[0].tag == 'snippets':
468
self.set_language(element[0])
474
snippets_debug("Unloading library (" + str(self.language) + "): " + \
480
class SnippetsUserFile(SnippetsSystemFile):
481
def __init__(self, path=None):
482
SnippetsSystemFile.__init__(self, path)
486
def _set_root(self, element):
487
SnippetsSystemFile._set_root(self, element)
490
def add_prop(self, node, tag, data):
492
prop = et.SubElement(node, tag)
493
prop.text = data[tag]
499
def new_snippet(self, properties=None):
500
if (not self.ok) or self.root == None:
503
element = et.SubElement(self.root, 'snippet')
506
for prop in properties:
507
sub = et.SubElement(element, prop)
508
sub.text = properties[prop]
512
return SnippetsLibrary().add_snippet(self, element)
514
def set_language(self, element):
515
SnippetsSystemFile.set_language(self, element)
517
filename = os.path.basename(self.path).lower()
519
if not self.language and filename == "global.xml":
521
elif self.language and filename == self.language + ".xml":
524
self.modifier = False
526
def create_root(self, language):
528
snippets_debug('Not creating root, already loaded')
532
root = et.Element('snippets', {'language': language})
533
self.path = os.path.join(SnippetsLibrary().userdir, language.lower() + '.xml')
535
root = et.Element('snippets')
536
self.path = os.path.join(SnippetsLibrary().userdir, 'global.xml')
544
def remove(self, element):
546
self.root.remove(element)
554
# No more elements, this library is useless now
555
SnippetsLibrary().remove_library(self)
558
if not self.ok or self.root == None or not self.tainted:
561
path = os.path.dirname(self.path)
564
if not os.path.isdir(path):
565
os.makedirs(path, 0755)
567
# TODO: this is bad...
568
sys.stderr.write("Error in making dirs\n")
571
write_xml(self.root, self.path, ('text', 'accelerator'))
574
# Couldn't save, what to do
575
sys.stderr.write("Could not save user snippets file to " + \
579
SnippetsSystemFile.unload(self)
582
class SnippetsLibraryImpl:
584
self._accelerator_activated_cb = None
586
self.check_buffer = gtk.TextBuffer()
588
def set_dirs(self, userdir, systemdirs):
589
self.userdir = userdir
590
self.systemdirs = systemdirs
599
def set_accelerator_callback(self, cb):
600
self._accelerator_activated_cb = cb
602
def accelerator_activated(self, group, obj, keyval, mod):
603
if self._accelerator_activated_cb:
604
self._accelerator_activated_cb(group, obj, keyval, mod)
606
def add_snippet(self, library, element):
607
container = self.container(library.language)
608
overrided = self.overrided(library, element)
611
overrided.set_library(library)
612
snippets_debug('Snippet is overriden: ' + overrided['description'])
615
snippet = SnippetData(element, library)
617
if snippet.id in self.loaded_ids:
618
snippets_debug('Not added snippet ' + str(library.language) + \
619
'::' + snippet['description'] + ' (duplicate)')
622
snippet = container.append(snippet)
623
snippets_debug('Added snippet ' + str(library.language) + '::' + \
624
snippet['description'])
626
if snippet and snippet.override:
627
self.add_override(snippet)
630
self.loaded_ids.append(snippet.id)
634
def container(self, language):
635
language = self.normalize_language(language)
637
if not language in self.containers:
638
self.containers[language] = LanguageContainer(language)
640
return self.containers[language]
642
def get_user_library(self, language):
645
if language in self.libraries:
646
for library in self.libraries[language]:
647
if isinstance(library, SnippetsUserFile) and library.modifier:
649
elif not isinstance(library, SnippetsUserFile):
653
# Create a new user file then
654
snippets_debug('Creating a new user file for language ' + \
656
target = SnippetsUserFile()
657
target.create_root(language)
658
self.add_library(target)
662
def new_snippet(self, language, properties=None):
663
language = self.normalize_language(language)
664
library = self.get_user_library(language)
666
return library.new_snippet(properties)
668
def revert_snippet(self, snippet):
669
# This will revert the snippet to the one it overrides
670
if not snippet.can_modify() or not snippet.override in self.overridden:
671
# It can't be reverted, shouldn't happen, but oh..
674
# The snippet in self.overriden only contains the property contents and
675
# the library it belongs to
676
revertto = self.overridden[snippet.override]
677
del self.overridden[snippet.override]
680
snippet.revert(revertto)
683
self.loaded_ids.append(revertto.id)
685
def remove_snippet(self, snippet):
686
if not snippet.can_modify() or snippet.is_override():
689
# Remove from the library
690
userlib = snippet.library()
691
userlib.remove(snippet.node)
693
# Remove from the container
694
container = self.containers[userlib.language]
695
container.remove(snippet)
697
def overrided(self, library, element):
698
id = NamespacedId(library.language, element.attrib.get('id')).id
700
if id in self.overridden:
701
snippet = SnippetData(element, None)
702
snippet.set_node(None)
704
self.overridden[id] = snippet
709
def add_override(self, snippet):
710
snippets_debug('Add override:', snippet.override)
712
if not snippet.override in self.overridden:
713
self.overridden[snippet.override] = None
715
def add_library(self, library):
716
library.ensure_language()
719
snippets_debug('Library in wrong format, ignoring')
722
snippets_debug('Adding library (' + str(library.language) + '): ' + \
725
if library.language in self.libraries:
726
# Make sure all the user files are before the system files
727
if isinstance(library, SnippetsUserFile):
728
self.libraries[library.language].insert(0, library)
730
self.libraries[library.language].append(library)
732
self.libraries[library.language] = [library]
734
def remove_library(self, library):
738
if library.path and os.path.isfile(library.path):
739
os.unlink(library.path)
742
self.libraries[library.language].remove(library)
746
container = self.containers[library.language]
748
for snippet in list(container.snippets):
749
if snippet.library() == library:
750
container.remove(snippet)
752
def _add_user_library(self, path):
753
library = SnippetsUserFile(path)
754
self.add_library(library)
756
def _add_system_library(self, path):
757
library = SnippetsSystemFile(path)
758
self.add_library(library)
760
def find_libraries(self, path, searched, addcb):
761
snippets_debug("Finding in: " + path)
763
if not os.path.isdir(path):
766
files = os.listdir(path)
767
searched.append(path)
770
f = os.path.realpath(os.path.join(path, f))
772
# Determine what language this file provides snippets for
773
if os.path.isfile(f):
775
elif os.path.isdir(f) and not f in searched:
777
searched = self.find_libraries(self, f, searched, addcb)
781
def normalize_language(self, language):
783
return language.lower()
787
def remove_container(self, language):
788
for snippet in self.containers[language].snippets:
789
if snippet.id in self.loaded_ids:
790
self.loaded_ids.remove(snippet.id)
792
if snippet.override in self.overridden:
793
del self.overridden[snippet.override]
795
del self.containers[language]
797
def get_accel_group(self, language):
798
language = self.normalize_language(language)
800
container = self.container(language)
801
return container.accel_group
803
def save(self, language):
804
language = self.normalize_language(language)
806
if language in self.libraries:
807
for library in self.libraries[language]:
808
if isinstance(library, SnippetsUserFile):
813
def ref(self, language):
814
language = self.normalize_language(language)
816
snippets_debug('Ref:', language)
817
self.container(language).ref()
819
def unref(self, language):
820
language = self.normalize_language(language)
822
snippets_debug('Unref:', language)
824
if language in self.containers:
825
if not self.containers[language].unref() and \
826
language in self.libraries:
828
for library in self.libraries[language]:
831
self.remove_container(language)
833
def ensure(self, language):
834
language = self.normalize_language(language)
836
# Ensure language as well as the global snippets (None)
837
for lang in (None, language):
838
if lang in self.libraries:
839
# Ensure the container exists
842
for library in self.libraries[lang]:
845
def ensure_files(self):
850
searched = self.find_libraries(self.userdir, searched, \
851
self._add_user_library)
853
for d in self.systemdirs:
854
searched = self.find_libraries(d, searched, \
855
self._add_system_library)
859
def valid_accelerator(self, keyval, mod):
860
mod &= gtk.accelerator_get_default_mod_mask()
862
return (mod and (gdk.keyval_to_unicode(keyval) or \
863
keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1)))
865
def valid_tab_trigger(self, trigger):
869
if trigger.isdigit():
872
self.check_buffer.set_text(trigger)
874
start, end = self.check_buffer.get_bounds()
875
text = self.check_buffer.get_text(start, end)
880
end.backward_word_start()
881
start.forward_word_end()
883
return (s.equal(end) and e.equal(start)) or (len(text) == 1 and not (text.isalnum() or text.isspace()))
888
# Get snippets for a given language
889
def get_snippets(self, language=None):
891
language = self.normalize_language(language)
893
if not language in self.libraries:
897
self.ensure(language)
899
return list(self.containers[language].snippets)
901
# Get snippets for a given accelerator
902
def from_accelerator(self, accelerator, language=None):
906
language = self.normalize_language(language)
908
if not language in self.containers:
911
self.ensure(language)
912
result = self.containers[language].from_prop('accelerator', accelerator)
914
if len(result) == 0 and language and None in self.containers:
915
result = self.containers[None].from_prop('accelerator', accelerator)
919
# Get snippets for a given tag
920
def from_tag(self, tag, language=None):
924
language = self.normalize_language(language)
926
if not language in self.containers:
929
self.ensure(language)
930
result = self.containers[language].from_prop('tag', tag)
932
if len(result) == 0 and language and None in self.containers:
933
result = self.containers[None].from_prop('tag', tag)
937
class SnippetsLibrary:
941
if not SnippetsLibrary.__instance:
942
SnippetsLibrary.__instance = SnippetsLibraryImpl()
944
self.__dict__['_SnippetsLibrary__instance'] = SnippetsLibrary.__instance
946
def __getattr__(self, attr):
947
return getattr(self.__instance, attr)
949
def __setattr__(self, attr, value):
950
return setattr(self.__instance, attr, value)