Viewing file: SnippetController.py (17.53 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# Gedit snippets plugin # Copyright (C) 2005-2006 Jesse van den Kieboom <jesse@icecrew.nl> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import gtk from SnippetsLibrary import SnippetsLibrary from gtk import gdk from Snippet import Snippet import os from SnippetPlaceholders import * from SnippetComplete import SnippetComplete
class SnippetController: TAB_KEY_VAL = (gtk.keysyms.Tab, \ gtk.keysyms.ISO_Left_Tab) SPACE_KEY_VAL = (gtk.keysyms.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.update_placeholders = [] self.jump_placeholders = [] self.language_id = 0 self.timeout_update_id = 0 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: gobject.source_remove(self.timeout_update_id) self.timeout_update_id = 0 del self.update_placeholders[:] del self.jump_placeholders[:]
SnippetsLibrary().unref(None) self.set_view(None) self.instance = None self.active_placeholder = None
def disconnect_signal(self, obj, signal): if signal in self.signal_ids: obj.disconnect(self.signal_ids[signal]) del self.signal_ids[signal]
# 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() self.disconnect_signal(self.view, 'key-press-event') self.disconnect_signal(self.view, 'destroy') self.disconnect_signal(buf, 'notify::language') self.disconnect_signal(self.view, 'notify::editable') self.disconnect_signal(buf, 'changed') self.disconnect_signal(buf, 'cursor-moved') # Remove all active snippets for snippet in list(self.active_snippets): self.deactivate_snippet(snippet, True)
self.view = view if view != None: buf = view.get_buffer() self.signal_ids['destroy'] = view.connect('destroy', \ self.on_view_destroy)
if view.get_editable(): self.signal_ids['key-press-event'] = view.connect( \ 'key_press_event', self.on_view_key_press)
self.signal_ids['notify::language'] = buf.connect( \ 'notify::language', self.on_notify_language) self.signal_ids['notify::editable'] = view.connect( \ 'notify::editable', self.on_notify_editable) self.update_language() elif self.language_id != 0: SnippetsLibrary().unref(self.language_id) 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
if self.language_id != 0: SnippetsLibrary().unref(self.language_id)
if lang: self.language_id = lang.get_id() else: self.language_id = None
self.instance.language_changed(self) SnippetsLibrary().ref(self.language_id)
def accelerator_activate(self, keyval, mod): if not self.view or not self.view.get_editable(): return
accelerator = gtk.accelerator_name(keyval, mod) snippets = SnippetsLibrary().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 return self.show_completion(snippets)
return True
def first_snippet_inserted(self): buf = self.view.get_buffer() self.signal_ids['changed'] = buf.connect('changed', \ self.on_buffer_changed) self.signal_ids['cursor-moved'] = buf.connect('cursor_moved', \ self.on_buffer_cursor_moved) def last_snippet_removed(self): buf = self.view.get_buffer() self.disconnect_signal(buf, 'changed') self.disconnect_signal(buf, 'cursor-moved')
def current_placeholder(self): buf = self.view.get_buffer() piter = buf.get_iter_at_mark(buf.get_insert()) current = None
for placeholder in self.placeholders: begin = placeholder.begin_iter() end = placeholder.end_iter()
if piter.compare(begin) >= 0 and \ piter.compare(end) <= 0: current = placeholder
return current
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()) prev = current = next = None length = len(self.placeholders)
if direction == 1: nearest = lambda w, x, y, z: (w.compare(y) >= 0 and (not z or \ y.compare(z.end_iter()) >= 0)) indexer = lambda x: x < length - 1 else: nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \ x.compare(z.end_iter()) <= 0)) indexer = lambda x: x > 0
for index in range(0, length): placeholder = self.placeholders[index] begin = placeholder.begin_iter() end = placeholder.end_iter() # Find the nearest placeholder if nearest(piter, begin, end, prev): prevIndex = index prev = placeholder # Find the current placeholder if piter.compare(begin) >= 0 and \ piter.compare(end) <= 0: currentIndex = index current = placeholder if current: if indexer(currentIndex): next = self.placeholders[currentIndex + direction] elif prev: if indexer(prevIndex): next = self.placeholders[prevIndex + direction] 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 goto_placeholder(self, current, next): last = None
if current: # Signal this placeholder to end action current.leave() if current.__class__ == SnippetPlaceholderEnd: last = current self.active_placeholder = next if next: next.enter() if next.__class__ == SnippetPlaceholderEnd: last = next
if last: # This is the end of the placeholder, remove the snippet etc for snippet in list(self.active_snippets): if snippet[3][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]) else: return ''
def env_get_current_word(self, buf): start, end = buffer_word_boundary(buf) return buf.get_text(start, end) def env_get_filename(self, buf): uri = buf.get_uri() if uri: return buf.get_uri_for_display() else: return '' def env_get_basename(self, buf): uri = buf.get_uri() if uri: return os.path.basename(uri) else: return ''
def update_environment(self): buf = self.view.get_buffer() variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text, \ 'GEDIT_CURRENT_WORD': self.env_get_current_word, \ 'GEDIT_FILENAME': self.env_get_filename, \ 'GEDIT_BASENAME': self.env_get_basename} for var in variables: os.environ[var] = variables[var](buf) def uses_current_word(self, snippet): matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', 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)
# 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__ == SnippetPlaceholderEnd: 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) active_info = s.insert_into(self) self.active_snippets.append(active_info)
# Put cursor back to beginning of the snippet piter = buf.get_iter_at_mark(active_info[0]) buf.place_cursor(piter)
# Jump to first placeholder (current, next) = self.next_placeholder() if current and current != self.active_placeholder: self.goto_placeholder(None, current) elif next: self.goto_placeholder(None, next) buf.end_user_action() if len(self.active_snippets) == 1: self.first_snippet_inserted()
return True
def get_tab_tag(self, buf): end = buf.get_iter_at_mark(buf.get_insert()) 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) else: start = end.copy() else: start = end.copy() 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 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() snippets = SnippetsLibrary().from_tag(word, self.language_id) if snippets: if len(snippets) == 1: return self.apply_snippet(snippets[0], start, end) else: # Do the fancy completion dialog return self.show_completion(snippets)
return self.skip_to_next_placeholder() def deactivate_snippet(self, snippet, force = False): buf = self.view.get_buffer() remove = [] for tabstop in snippet[3]: if tabstop == -1: placeholders = snippet[3][-1] else: placeholders = [snippet[3][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) for placeholder in remove: if placeholder == self.active_placeholder: self.active_placeholder = None
self.placeholders.remove(placeholder) placeholder.remove(force)
buf.delete_mark(snippet[0]) buf.delete_mark(snippet[1]) buf.delete_mark(snippet[2]) self.active_snippets.remove(snippet) if len(self.active_snippets) == 0: self.last_snippet_removed()
# Moves the completion window to a suitable place honoring the hint given # by x and y. It tries to position the window so it's always visible on the # screen. def move_completion_window(self, complete, x, y): MARGIN = 15 screen = self.view.get_screen() width = screen.get_width() height = screen.get_height() cw, ch = complete.get_size() if x + cw > width: x = width - cw - MARGIN elif x < MARGIN: x = MARGIN if y + ch > height: y = height - ch - MARGIN elif y < MARGIN: y = MARGIN
complete.move(x, y)
# Show completion, shows a completion dialog in the view. # If preset is not None then a completion dialog is shown with the snippets # in the preset list. Otherwise it will try to find the word preceding the # current cursor position. If such a word is found, it is taken as a # tab trigger prefix so that only snippets with a tab trigger prefixed with # the word are in the list. If no such word can be found than all snippets # are shown. def show_completion(self, preset = None): buf = self.view.get_buffer() bounds = buf.get_selection_bounds() prefix = None if not bounds and not preset: # When there is no text selected and no preset present, find the # prefix (prefix, start, end) = self.get_tab_tag(buf) if not prefix: # If there is no prefix, than take the insertion point as the end end = buf.get_iter_at_mark(buf.get_insert()) if not preset or len(preset) == 0: # There is no preset, find all the global snippets and the language # specific snippets nodes = SnippetsLibrary().get_snippets(None) if self.language_id: nodes += SnippetsLibrary().get_snippets(self.language_id) if prefix and len(prefix) == 1 and not prefix.isalnum(): hasnodes = False for node in nodes: if node['tag'] and node['tag'].startswith(prefix): hasnodes = True break if not hasnodes: prefix = None complete = SnippetComplete(nodes, prefix, False) else: # There is a preset, so show that preset complete = SnippetComplete(preset, None, True) complete.connect('snippet-activated', self.on_complete_row_activated) rect = self.view.get_iter_location(end) win = self.view.get_window(gtk.TEXT_WINDOW_TEXT) (x, y) = self.view.buffer_to_window_coords( \ gtk.TEXT_WINDOW_TEXT, rect.x + rect.width, rect.y) (xor, yor) = win.get_origin() self.move_completion_window(complete, x + xor, y + yor) return complete.run()
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_complete_row_activated(self, complete, snippet): buf = self.view.get_buffer() bounds = buf.get_selection_bounds() if bounds: self.apply_snippet(snippet.data, None, None) else: (word, start, end) = self.get_tab_tag(buf) self.apply_snippet(snippet.data, start, end)
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[0].get_deleted() or snippet[1].get_deleted(): self.deactivate(snippet) else: begin = buf.get_iter_at_mark(snippet[0]) end = buf.get_iter_at_mark(snippet[1]) 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: if self.active_placeholder: self.jump_placeholders.append((self.active_placeholder, current)) if self.timeout_update_id != 0: gobject.source_remove(self.timeout_update_id) self.timeout_update_id = gobject.timeout_add(0, self.update_snippet_contents)
self.active_placeholder = current def on_buffer_changed(self, buf): current = self.current_placeholder() if current: self.update_placeholders.append(current) if self.timeout_update_id != 0: gobject.source_remove(self.timeout_update_id)
self.timeout_update_id = gobject.timeout_add(0, \ self.update_snippet_contents) 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 = SnippetsLibrary()
if not (event.state & gdk.CONTROL_MASK) and \ not (event.state & gdk.MOD1_MASK) and \ event.keyval in self.TAB_KEY_VAL: if not event.state & gdk.SHIFT_MASK: return self.run_snippet() else: return self.skip_to_previous_placeholder() elif (event.state & gdk.CONTROL_MASK) and \ not (event.state & gdk.MOD1_MASK) and \ not (event.state & gdk.SHIFT_MASK) and \ event.keyval in self.SPACE_KEY_VAL: return self.show_completion() elif not library.loaded and \ library.valid_accelerator(event.keyval, event.state): library.ensure_files() library.ensure(self.language_id) self.accelerator_activate(event.keyval, \ event.state & gtk.accelerator_get_default_mod_mask())
return False
|