# completion.py
#
# Copyright (C) 2006 - Guillaume Chazarain
#
# 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# http://guichaz.free.fr/gedit-completion
import gedit, gobject, gtk, re
class CompletionPlugin(gedit.Plugin):
"A gedit plugin to implement word completion"
# Avoid word completion on text bigger than 10M
MAX_TEXT_SIZE = 10 * 1024 * 1024
# List of characters to separate words
SEPARATOR_CHARS = re.escape("&\"'{([-|`)]} .,;:!?/^$\n\r*+#=<> ")
def __init__(self):
gedit.Plugin.__init__(self)
def activate(self, window):
"gedit callback: install the completion entry point"
ui_manager = window.get_ui_manager()
action_group = gtk.ActionGroup("GeditCompleteWordPluginActions")
complete_action = gtk.Action(name="CompleteWordAction",
label="Complete word...",
tooltip="Complete current word",
stock_id=gtk.STOCK_GO_FORWARD)
complete_action.connect("activate",
lambda a: self.complete_word_cb(window))
action_group.add_action_with_accel(complete_action,
"<Alt>slash")
ui_manager.insert_action_group(action_group, 0)
ui_merge_id = ui_manager.new_merge_id()
ui_manager.add_ui(ui_merge_id,
"/MenuBar/EditMenu/EditOps_5",
"CompleteWord",
"CompleteWordAction",
gtk.UI_MANAGER_MENUITEM, False)
ui_manager.__ui_data__ = (action_group, ui_merge_id)
def deactivate(self, window):
"gedit callback: get rid of the completion feature"
ui_manager = window.get_ui_manager()
(action_group, ui_merge_id) = ui_manager.__ui_data__
del ui_manager.__ui_data__
ui_manager.remove_action_group(action_group)
ui_manager.remove_ui(ui_merge_id)
def get_completions(self, buffer):
"""The completion code itself,
returns (prefix, [full1, full2, full3...])"""
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
line_iter = cursor_iter.copy()
line_iter.set_line_offset(0)
# The text from the start of the line to the cursor
line = buffer.get_text(line_iter, cursor_iter)
# Find the last word in it
match = re.search("[^%s]+$" %
(CompletionPlugin.SEPARATOR_CHARS), line)
if not match:
return (None, None)
word = match.group()
if not word:
return (None, None)
# Find the prefix in the whole text, and keep all the words
text = buffer.get_text(buffer.get_start_iter(),
buffer.get_end_iter())
words = re.findall("[%(sep)s]?(%(word)s[^%(sep)s]+)" %
{"sep": CompletionPlugin.SEPARATOR_CHARS,
"word": re.escape(word)},
text)
# Remove dupes and sort
words[:] = frozenset(words)
words.sort()
return (word, words)
def get_coordinates(self, view):
"Return a tuple containing the (x, y) position of the cursor"
buffer = view.get_buffer()
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
rect = view.get_iter_location(cursor_iter)
win = view.get_window(gtk.TEXT_WINDOW_TEXT)
(x, y) = view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,
rect.x + rect.width,
rect.y)
(xor, yor) = win.get_origin()
return (x + xor, y + yor)
def make_entry_completion(self, view):
"Prepare the completion dialog widget"
buffer = view.get_buffer()
(word, words) = self.get_completions(buffer)
if (not words):
return
entry = gtk.Entry()
completion = gtk.EntryCompletion()
entry.set_completion(completion)
list_store = gtk.ListStore(gobject.TYPE_STRING)
completion.set_model(list_store)
completion.set_text_column(0)
max_word_length = entry.get_width_chars()
for compl in words:
list_store.append((compl,))
compl_len = len(compl.decode("utf-8"))
max_word_length = max(max_word_length, compl_len)
entry.set_text(words[0])
entry.__original_text__ = word
entry.set_width_chars(min(max_word_length, 100))
def select_on_map(self, event):
self.select_region(len(word.decode("utf-8")), -1)
entry.connect_after("map-event", select_on_map)
entry.modify_style(view.get_modifier_style())
entry.show_all()
return entry
def get_word_replacement(self, window):
"Present the completion in a dialog and return (before, after)"
view = window.get_active_view()
entry = self.make_entry_completion(view)
if (not entry):
return
coords = self.get_coordinates(view)
completion_dialog = gtk.Dialog(
title="gedit completion",
parent=window,
flags=gtk.DIALOG_MODAL |
gtk.DIALOG_DESTROY_WITH_PARENT |
gtk.DIALOG_NO_SEPARATOR,
buttons=None)
completion_dialog.set_decorated(False)
completion_dialog.move(x=coords[0], y=coords[1])
completion_dialog.vbox.add(entry)
completion_dialog.set_default_response(gtk.RESPONSE_OK)
def idle_destroy():
completion_dialog.destroy()
return False
def activate_entry(self):
completion_dialog.response(gtk.RESPONSE_OK)
gobject.idle_add(idle_destroy)
def deactivate_entry(self, a1, a2, a3):
completion_dialog.response(gtk.RESPONSE_CANCEL)
gobject.idle_add(idle_destroy)
accel_group = gtk.AccelGroup()
key, modifier = gtk.accelerator_parse("Escape")
accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE,
deactivate_entry)
completion_dialog.add_accel_group(accel_group)
entry.connect("activate", activate_entry)
response = completion_dialog.run()
if (response == gtk.RESPONSE_OK):
return (entry.__original_text__, entry.get_text())
def complete_word_cb(self, window):
"The entry point to the word completion"
view = window.get_active_view()
buffer_length = view.get_buffer().get_char_count()
if (buffer_length >= CompletionPlugin.MAX_TEXT_SIZE):
print "Not using word completion on too big buffer"
return
replacement = self.get_word_replacement(window)
if (not replacement):
return
(before, after) = replacement
buffer = view.get_buffer()
cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
word_iter = cursor_iter.copy()
position_in_line = cursor_iter.get_line_index() - len(before)
word_iter.set_line_index(position_in_line)
buffer.delete(word_iter, cursor_iter)
buffer.insert_at_cursor(after)