home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / gedit-2 / plugins / snippets / SnippetController.py < prev    next >
Encoding:
Python Source  |  2006-08-27  |  17.5 KB  |  648 lines

  1. #    Gedit snippets plugin
  2. #    Copyright (C) 2005-2006  Jesse van den Kieboom <jesse@icecrew.nl>
  3. #
  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.
  8. #
  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.
  13. #
  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
  17.  
  18. import gtk
  19. from SnippetsLibrary import SnippetsLibrary
  20. from gtk import gdk
  21. from Snippet import Snippet
  22. import os
  23. from SnippetPlaceholders import *
  24. from SnippetComplete import SnippetComplete
  25.  
  26. class SnippetController:
  27.     TAB_KEY_VAL = (gtk.keysyms.Tab, \
  28.             gtk.keysyms.ISO_Left_Tab)
  29.     SPACE_KEY_VAL = (gtk.keysyms.space,)
  30.     
  31.     def __init__(self, instance, view):
  32.         self.view = None
  33.         self.instance = instance
  34.         
  35.         self.placeholders = []
  36.         self.active_snippets = []
  37.         self.active_placeholder = None
  38.         self.signal_ids = {}
  39.         
  40.         self.update_placeholders = []
  41.         self.jump_placeholders = []
  42.         self.language_id = 0
  43.         self.timeout_update_id = 0
  44.         
  45.         self.set_view(view)
  46.     
  47.     # Stop controlling the view. Remove all active snippets, remove references
  48.     # to the view and the plugin instance, disconnect all signal handlers
  49.     def stop(self):
  50.         if self.timeout_update_id != 0:
  51.             gobject.source_remove(self.timeout_update_id)
  52.             self.timeout_update_id = 0
  53.             del self.update_placeholders[:]
  54.             del self.jump_placeholders[:]
  55.  
  56.         SnippetsLibrary().unref(None)
  57.         self.set_view(None)
  58.         self.instance = None
  59.         self.active_placeholder = None
  60.  
  61.     def disconnect_signal(self, obj, signal):
  62.         if signal in self.signal_ids:
  63.             obj.disconnect(self.signal_ids[signal])
  64.             del self.signal_ids[signal]
  65.  
  66.     # Set the view to be controlled. Installs signal handlers and sets current
  67.     # language. If there is already a view set this function will first remove
  68.     # all currently active snippets and disconnect all current signals. So
  69.     # self.set_view(None) will effectively remove all the control from the
  70.     # current view
  71.     def _set_view(self, view):
  72.         if self.view:
  73.             buf = self.view.get_buffer()
  74.             
  75.             self.disconnect_signal(self.view, 'key-press-event')
  76.             self.disconnect_signal(self.view, 'destroy')
  77.             self.disconnect_signal(buf, 'notify::language')
  78.             self.disconnect_signal(self.view, 'notify::editable')
  79.             self.disconnect_signal(buf, 'changed')
  80.             self.disconnect_signal(buf, 'cursor-moved')
  81.             
  82.             # Remove all active snippets
  83.             for snippet in list(self.active_snippets):
  84.                 self.deactivate_snippet(snippet, True)
  85.  
  86.         self.view = view
  87.         
  88.         if view != None:
  89.             buf = view.get_buffer()
  90.             
  91.             self.signal_ids['destroy'] = view.connect('destroy', \
  92.                     self.on_view_destroy)
  93.  
  94.             if view.get_editable():
  95.                 self.signal_ids['key-press-event'] = view.connect( \
  96.                         'key_press_event', self.on_view_key_press)
  97.  
  98.             self.signal_ids['notify::language'] = buf.connect( \
  99.                     'notify::language', self.on_notify_language)
  100.             self.signal_ids['notify::editable'] = view.connect( \
  101.                     'notify::editable', self.on_notify_editable)
  102.             
  103.             self.update_language()
  104.         elif self.language_id != 0:
  105.             SnippetsLibrary().unref(self.language_id)
  106.     
  107.     def set_view(self, view):
  108.         if view == self.view:
  109.             return
  110.         
  111.         self._set_view(view)
  112.  
  113.     # Call this whenever the language in the view changes. This makes sure that
  114.     # the correct language is used when finding snippets
  115.     def update_language(self):
  116.         lang = self.view.get_buffer().get_language()
  117.  
  118.         if lang == None and self.language_id == None:
  119.             return
  120.         elif lang and lang.get_id() == self.language_id:
  121.             return
  122.  
  123.         if self.language_id != 0:
  124.             SnippetsLibrary().unref(self.language_id)
  125.  
  126.         if lang:
  127.             self.language_id = lang.get_id()
  128.         else:
  129.             self.language_id = None
  130.  
  131.         self.instance.language_changed(self)
  132.         SnippetsLibrary().ref(self.language_id)
  133.  
  134.     def accelerator_activate(self, keyval, mod):
  135.         if not self.view or not self.view.get_editable():
  136.             return
  137.  
  138.         accelerator = gtk.accelerator_name(keyval, mod)
  139.         snippets = SnippetsLibrary().from_accelerator(accelerator, \
  140.                 self.language_id)
  141.  
  142.         snippets_debug('Accel!')
  143.  
  144.         if len(snippets) == 0:
  145.             return False
  146.         elif len(snippets) == 1:
  147.             self.apply_snippet(snippets[0])
  148.         else:
  149.             # Do the fancy completion dialog
  150.             return self.show_completion(snippets)
  151.  
  152.         return True
  153.  
  154.     def first_snippet_inserted(self):
  155.         buf = self.view.get_buffer()
  156.         
  157.         self.signal_ids['changed'] = buf.connect('changed', \
  158.                 self.on_buffer_changed)
  159.         self.signal_ids['cursor-moved'] = buf.connect('cursor_moved', \
  160.                     self.on_buffer_cursor_moved)
  161.     
  162.     def last_snippet_removed(self):
  163.         buf = self.view.get_buffer()
  164.         self.disconnect_signal(buf, 'changed')
  165.         self.disconnect_signal(buf, 'cursor-moved')
  166.  
  167.     def current_placeholder(self):
  168.         buf = self.view.get_buffer()
  169.         
  170.         piter = buf.get_iter_at_mark(buf.get_insert())    
  171.         current = None
  172.  
  173.         for placeholder in self.placeholders:
  174.             begin = placeholder.begin_iter()
  175.             end = placeholder.end_iter()
  176.  
  177.             if piter.compare(begin) >= 0 and \
  178.                     piter.compare(end) <= 0:
  179.                 current = placeholder
  180.  
  181.         return current
  182.  
  183.     def advance_placeholder(self, direction):
  184.         # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction
  185.         buf = self.view.get_buffer()
  186.         
  187.         piter = buf.get_iter_at_mark(buf.get_insert())
  188.         prev = current = next = None
  189.         length = len(self.placeholders)        
  190.  
  191.         if direction == 1:
  192.             nearest = lambda w, x, y, z: (w.compare(y) >= 0 and (not z or \
  193.                     y.compare(z.end_iter()) >= 0))
  194.             indexer = lambda x: x < length - 1
  195.         else:
  196.             nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \
  197.                     x.compare(z.end_iter()) <= 0))
  198.             indexer = lambda x: x > 0
  199.  
  200.         for index in range(0, length):
  201.             placeholder = self.placeholders[index]
  202.             begin = placeholder.begin_iter()
  203.             end = placeholder.end_iter()
  204.             
  205.             # Find the nearest placeholder
  206.             if nearest(piter, begin, end, prev):
  207.                 prevIndex = index
  208.                 prev = placeholder
  209.             
  210.             # Find the current placeholder
  211.             if piter.compare(begin) >= 0 and \
  212.                     piter.compare(end) <= 0:
  213.                 currentIndex = index
  214.                 current = placeholder
  215.         
  216.         if current:
  217.             if indexer(currentIndex):
  218.                 next = self.placeholders[currentIndex + direction]
  219.         elif prev:
  220.             if indexer(prevIndex):
  221.                 next = self.placeholders[prevIndex + direction]
  222.         elif length > 0:
  223.             next = self.placeholders[0]
  224.         
  225.         return current, next
  226.     
  227.     def next_placeholder(self):
  228.         return self.advance_placeholder(1)
  229.     
  230.     def previous_placeholder(self):
  231.         return self.advance_placeholder(-1)
  232.  
  233.     def cursor_on_screen(self):
  234.         buf = self.view.get_buffer()
  235.         self.view.scroll_mark_onscreen(buf.get_insert())
  236.     
  237.     def goto_placeholder(self, current, next):
  238.         last = None
  239.  
  240.         if current:
  241.             # Signal this placeholder to end action
  242.             current.leave()
  243.             
  244.             if current.__class__ == SnippetPlaceholderEnd:
  245.                 last = current
  246.         
  247.         self.active_placeholder = next
  248.         
  249.         if next:
  250.             next.enter()
  251.             
  252.             if next.__class__ == SnippetPlaceholderEnd:
  253.                 last = next
  254.  
  255.         if last:
  256.             # This is the end of the placeholder, remove the snippet etc
  257.             for snippet in list(self.active_snippets):
  258.                 if snippet[3][0] == last:
  259.                     self.deactivate_snippet(snippet)
  260.                     break
  261.         
  262.         self.cursor_on_screen()
  263.         
  264.         return next != None
  265.     
  266.     def skip_to_next_placeholder(self):
  267.         (current, next) = self.next_placeholder()
  268.         return self.goto_placeholder(current, next)
  269.     
  270.     def skip_to_previous_placeholder(self):
  271.         (current, prev) = self.previous_placeholder()
  272.         return self.goto_placeholder(current, prev)
  273.  
  274.     def env_get_selected_text(self, buf):
  275.         bounds = buf.get_selection_bounds()
  276.  
  277.         if bounds:
  278.             return buf.get_text(bounds[0], bounds[1])
  279.         else:
  280.             return ''
  281.  
  282.     def env_get_current_word(self, buf):
  283.         start, end = buffer_word_boundary(buf)
  284.         
  285.         return buf.get_text(start, end)
  286.         
  287.     def env_get_filename(self, buf):
  288.         uri = buf.get_uri()
  289.         
  290.         if uri:
  291.             return buf.get_uri_for_display()
  292.         else:
  293.             return ''
  294.     
  295.     def env_get_basename(self, buf):
  296.         uri = buf.get_uri()
  297.         
  298.         if uri:
  299.             return os.path.basename(uri)
  300.         else:
  301.             return ''
  302.  
  303.     def update_environment(self):
  304.         buf = self.view.get_buffer()
  305.         
  306.         variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text, \
  307.                 'GEDIT_CURRENT_WORD': self.env_get_current_word, \
  308.                 'GEDIT_FILENAME': self.env_get_filename, \
  309.                 'GEDIT_BASENAME': self.env_get_basename}
  310.         
  311.         for var in variables:
  312.             os.environ[var] = variables[var](buf)
  313.     
  314.     def uses_current_word(self, snippet):
  315.         matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', snippet['text'])
  316.         
  317.         for match in matches:
  318.             if len(match) % 2 == 0:
  319.                 return True
  320.         
  321.         return False
  322.  
  323.     def apply_snippet(self, snippet, start = None, end = None):
  324.         if not snippet.valid:
  325.             return False
  326.  
  327.         buf = self.view.get_buffer()
  328.         s = Snippet(snippet)
  329.         
  330.         if not start:
  331.             start = buf.get_iter_at_mark(buf.get_insert())
  332.         
  333.         if not end:
  334.             end = buf.get_iter_at_mark(buf.get_selection_bound())
  335.  
  336.         if start.equal(end) and self.uses_current_word(s):
  337.             # There is no tab trigger and no selection and the snippet uses
  338.             # the current word. Set start and end to the word boundary so that 
  339.             # it will be removed
  340.             start, end = buffer_word_boundary(buf)
  341.  
  342.         # Set environmental variables
  343.         self.update_environment()
  344.         
  345.         # You know, we could be in an end placeholder
  346.         (current, next) = self.next_placeholder()
  347.         if current and current.__class__ == SnippetPlaceholderEnd:
  348.             self.goto_placeholder(current, None)
  349.         
  350.         buf.begin_user_action()
  351.  
  352.         # Remove the tag, selection or current word
  353.         buf.delete(start, end)
  354.         
  355.         # Insert the snippet
  356.         holders = len(self.placeholders)
  357.         active_info = s.insert_into(self)
  358.         self.active_snippets.append(active_info)
  359.  
  360.         # Put cursor back to beginning of the snippet
  361.         piter = buf.get_iter_at_mark(active_info[0])
  362.         buf.place_cursor(piter)
  363.  
  364.         # Jump to first placeholder
  365.         (current, next) = self.next_placeholder()
  366.         
  367.         if current and current != self.active_placeholder:
  368.             self.goto_placeholder(None, current)
  369.         elif next:
  370.             self.goto_placeholder(None, next)        
  371.             
  372.         buf.end_user_action()
  373.         
  374.         if len(self.active_snippets) == 1:
  375.             self.first_snippet_inserted()
  376.  
  377.         return True
  378.  
  379.     def get_tab_tag(self, buf):
  380.         end = buf.get_iter_at_mark(buf.get_insert())
  381.         start = end.copy()
  382.         
  383.         word = None
  384.         
  385.         if start.backward_word_start():
  386.             # Check if we were at a word start ourselves
  387.             tmp = start.copy()
  388.             tmp.forward_word_end()
  389.             
  390.             if tmp.equal(end):
  391.                 word = buf.get_text(start, end)
  392.             else:
  393.                 start = end.copy()
  394.         else:
  395.             start = end.copy()
  396.         
  397.         if not word or word == '':
  398.             if start.backward_char():
  399.                 word = start.get_char()
  400.  
  401.                 if word.isalnum() or word.isspace():
  402.                     return (None, None, None)
  403.             else:
  404.                 return (None, None, None)
  405.         
  406.         return (word, start, end)
  407.  
  408.     def run_snippet(self):    
  409.         if not self.view:
  410.             return False
  411.         
  412.         buf = self.view.get_buffer()
  413.         
  414.         # get the word preceding the current insertion position
  415.         (word, start, end) = self.get_tab_tag(buf)
  416.         
  417.         if not word:
  418.             return self.skip_to_next_placeholder()
  419.         
  420.         snippets = SnippetsLibrary().from_tag(word, self.language_id)
  421.         
  422.         if snippets:
  423.             if len(snippets) == 1:
  424.                 return self.apply_snippet(snippets[0], start, end)
  425.             else:
  426.                 # Do the fancy completion dialog
  427.                 return self.show_completion(snippets)
  428.  
  429.         return self.skip_to_next_placeholder()
  430.     
  431.     def deactivate_snippet(self, snippet, force = False):
  432.         buf = self.view.get_buffer()
  433.         remove = []
  434.         
  435.         for tabstop in snippet[3]:
  436.             if tabstop == -1:
  437.                 placeholders = snippet[3][-1]
  438.             else:
  439.                 placeholders = [snippet[3][tabstop]]
  440.             
  441.             for placeholder in placeholders:
  442.                 if placeholder in self.placeholders:
  443.                     if placeholder in self.update_placeholders:
  444.                         placeholder.update_contents()
  445.                         
  446.                         self.update_placeholders.remove(placeholder)
  447.                     elif placeholder in self.jump_placeholders:
  448.                         placeholder[0].leave()
  449.                         
  450.                     remove.append(placeholder)
  451.         
  452.         for placeholder in remove:
  453.             if placeholder == self.active_placeholder:
  454.                 self.active_placeholder = None
  455.  
  456.             self.placeholders.remove(placeholder)
  457.             placeholder.remove(force)
  458.  
  459.         buf.delete_mark(snippet[0])
  460.         buf.delete_mark(snippet[1])
  461.         buf.delete_mark(snippet[2])
  462.         
  463.         self.active_snippets.remove(snippet)
  464.         
  465.         if len(self.active_snippets) == 0:
  466.             self.last_snippet_removed()
  467.  
  468.     # Moves the completion window to a suitable place honoring the hint given
  469.     # by x and y. It tries to position the window so it's always visible on the
  470.     # screen.
  471.     def move_completion_window(self, complete, x, y):
  472.         MARGIN = 15
  473.         screen = self.view.get_screen()
  474.         
  475.         width = screen.get_width()
  476.         height = screen.get_height()
  477.         
  478.         cw, ch = complete.get_size()
  479.         
  480.         if x + cw > width:
  481.             x = width - cw - MARGIN
  482.         elif x < MARGIN:
  483.             x = MARGIN
  484.         
  485.         if y + ch > height:
  486.             y = height - ch - MARGIN
  487.         elif y < MARGIN:
  488.             y = MARGIN
  489.  
  490.         complete.move(x, y)
  491.  
  492.     # Show completion, shows a completion dialog in the view.
  493.     # If preset is not None then a completion dialog is shown with the snippets
  494.     # in the preset list. Otherwise it will try to find the word preceding the
  495.     # current cursor position. If such a word is found, it is taken as a 
  496.     # tab trigger prefix so that only snippets with a tab trigger prefixed with
  497.     # the word are in the list. If no such word can be found than all snippets
  498.     # are shown.
  499.     def show_completion(self, preset = None):
  500.         buf = self.view.get_buffer()
  501.         bounds = buf.get_selection_bounds()
  502.         prefix = None
  503.         
  504.         if not bounds and not preset:
  505.             # When there is no text selected and no preset present, find the
  506.             # prefix
  507.             (prefix, start, end) = self.get_tab_tag(buf)
  508.         
  509.         if not prefix:
  510.             # If there is no prefix, than take the insertion point as the end
  511.             end = buf.get_iter_at_mark(buf.get_insert())
  512.         
  513.         if not preset or len(preset) == 0:
  514.             # There is no preset, find all the global snippets and the language
  515.             # specific snippets
  516.             
  517.             nodes = SnippetsLibrary().get_snippets(None)
  518.             
  519.             if self.language_id:
  520.                 nodes += SnippetsLibrary().get_snippets(self.language_id)
  521.             
  522.             if prefix and len(prefix) == 1 and not prefix.isalnum():
  523.                 hasnodes = False
  524.                 
  525.                 for node in nodes:
  526.                     if node['tag'] and node['tag'].startswith(prefix):
  527.                         hasnodes = True
  528.                         break
  529.                 
  530.                 if not hasnodes:
  531.                     prefix = None
  532.             
  533.             complete = SnippetComplete(nodes, prefix, False)    
  534.         else:
  535.             # There is a preset, so show that preset
  536.             complete = SnippetComplete(preset, None, True)
  537.         
  538.         complete.connect('snippet-activated', self.on_complete_row_activated)
  539.         
  540.         rect = self.view.get_iter_location(end)
  541.         win = self.view.get_window(gtk.TEXT_WINDOW_TEXT)
  542.         (x, y) = self.view.buffer_to_window_coords( \
  543.                 gtk.TEXT_WINDOW_TEXT, rect.x + rect.width, rect.y)
  544.         (xor, yor) = win.get_origin()
  545.         
  546.         self.move_completion_window(complete, x + xor, y + yor)        
  547.         return complete.run()
  548.  
  549.     def update_snippet_contents(self):
  550.         self.timeout_update_id = 0
  551.         
  552.         for placeholder in self.update_placeholders:
  553.             placeholder.update_contents()
  554.         
  555.         for placeholder in self.jump_placeholders:
  556.             self.goto_placeholder(placeholder[0], placeholder[1])
  557.         
  558.         del self.update_placeholders[:]
  559.         del self.jump_placeholders[:]
  560.         
  561.         return False
  562.  
  563.     # Callbacks
  564.     def on_view_destroy(self, view):
  565.         self.stop()
  566.         return
  567.  
  568.     def on_complete_row_activated(self, complete, snippet):
  569.         buf = self.view.get_buffer()
  570.         bounds = buf.get_selection_bounds()
  571.         
  572.         if bounds:
  573.             self.apply_snippet(snippet.data, None, None)
  574.         else:
  575.             (word, start, end) = self.get_tab_tag(buf)
  576.             self.apply_snippet(snippet.data, start, end)
  577.  
  578.     def on_buffer_cursor_moved(self, buf):
  579.         piter = buf.get_iter_at_mark(buf.get_insert())
  580.  
  581.         # Check for all snippets if the cursor is outside its scope
  582.         for snippet in list(self.active_snippets):
  583.             if snippet[0].get_deleted() or snippet[1].get_deleted():
  584.                 self.deactivate(snippet)
  585.             else:
  586.                 begin = buf.get_iter_at_mark(snippet[0])
  587.                 end = buf.get_iter_at_mark(snippet[1])
  588.             
  589.                 if piter.compare(begin) < 0 or piter.compare(end) > 0:
  590.                     # Oh no! Remove the snippet this instant!!
  591.                     self.deactivate_snippet(snippet)
  592.         
  593.         current = self.current_placeholder()
  594.         
  595.         if current != self.active_placeholder:
  596.             if self.active_placeholder:
  597.                 self.jump_placeholders.append((self.active_placeholder, current))
  598.                 
  599.                 if self.timeout_update_id != 0:
  600.                     gobject.source_remove(self.timeout_update_id)
  601.                 
  602.                 self.timeout_update_id = gobject.timeout_add(0, 
  603.                         self.update_snippet_contents)
  604.  
  605.             self.active_placeholder = current
  606.     
  607.     def on_buffer_changed(self, buf):
  608.         current = self.current_placeholder()
  609.         
  610.         if current:
  611.             self.update_placeholders.append(current)
  612.         
  613.             if self.timeout_update_id != 0:
  614.                 gobject.source_remove(self.timeout_update_id)
  615.  
  616.             self.timeout_update_id = gobject.timeout_add(0, \
  617.                     self.update_snippet_contents)
  618.     
  619.     def on_notify_language(self, buf, spec):
  620.         self.update_language()
  621.  
  622.     def on_notify_editable(self, view, spec):
  623.         self._set_view(view)
  624.     
  625.     def on_view_key_press(self, view, event):
  626.         library = SnippetsLibrary()
  627.  
  628.         if not (event.state & gdk.CONTROL_MASK) and \
  629.                 not (event.state & gdk.MOD1_MASK) and \
  630.                 event.keyval in self.TAB_KEY_VAL:
  631.             if not event.state & gdk.SHIFT_MASK:
  632.                 return self.run_snippet()
  633.             else:
  634.                 return self.skip_to_previous_placeholder()
  635.         elif (event.state & gdk.CONTROL_MASK) and \
  636.                 not (event.state & gdk.MOD1_MASK) and \
  637.                 not (event.state & gdk.SHIFT_MASK) and \
  638.                 event.keyval in self.SPACE_KEY_VAL:
  639.             return self.show_completion()
  640.         elif not library.loaded and \
  641.                 library.valid_accelerator(event.keyval, event.state):
  642.             library.ensure_files()
  643.             library.ensure(self.language_id)
  644.             self.accelerator_activate(event.keyval, \
  645.                     event.state & gtk.accelerator_get_default_mod_mask())
  646.  
  647.         return False
  648.