-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Pair Character Completion plugin for gedit2
- Loading branch information
1 parent
f8ffd28
commit 7190604
Showing
4 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
plugins/gedit2/pair_char_completion/pair_char_completion.gedit-plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[Gedit Plugin] | ||
Loader=python | ||
Module=pair_char_completion | ||
IAge=2 | ||
Name=Pair Character Completion | ||
Description=Automatically insert closing quotes and parenthesis | ||
Authors=Kevin McGuinness <kevin.mcguinness@gmail.com> | ||
Copyright=Copyright © 2010 Kevin McGuinness | ||
Icon=pair_char_completion | ||
Website=http://code.google.com/p/gedit-pair-char-autocomplete/ |
288 changes: 288 additions & 0 deletions
288
plugins/gedit2/pair_char_completion/pair_char_completion.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,288 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Gedit plugin that does automatic pair character completion. | ||
# | ||
# Copyright © 2010, Kevin McGuinness <kevin.mcguinness@gmail.com> | ||
# | ||
# 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
# | ||
|
||
__version__ = '1.0.5' | ||
__author__ = 'Kevin McGuinness' | ||
|
||
import gedit | ||
import gtk | ||
import sys | ||
import os | ||
|
||
# Defaults | ||
DEFAULT_STMT_TERMINATOR = ';' | ||
LANG_META_STMT_TERMINATOR_KEY = 'statement-terminator' | ||
NEWLINE_CHAR = '\n' | ||
|
||
# Map from language identifiers to (opening parens, closing parens) pairs | ||
language_parens = {} | ||
|
||
def add_language_parenthesis(name, spec): | ||
"""Add parenthesis for the given language. The spec should be a string in | ||
which each pair of characters represents a pair of parenthesis for the | ||
language, eg. "(){}[]". | ||
""" | ||
parens = [], [] | ||
for i in range(0, len(spec), 2): | ||
parens[0].append(spec[i+0]) | ||
parens[1].append(spec[i+1]) | ||
language_parens[name] = parens | ||
|
||
def to_char(keyval_or_char): | ||
"""Convert a event keyval or character to a character""" | ||
if isinstance(keyval_or_char, str): | ||
return keyval_or_char | ||
return chr(keyval_or_char) if 0 < keyval_or_char < 128 else None | ||
|
||
class PairCompletionPlugin(gedit.Plugin): | ||
"""Automatic pair character completion for gedit""" | ||
|
||
ViewHandlerName = 'pair_char_completion_handler' | ||
|
||
def __init__(self): | ||
gedit.Plugin.__init__(self) | ||
self.ctrl_enter_enabled = True | ||
self.language_id = 'plain' | ||
self.opening_parens = language_parens['default'][0] | ||
self.closing_parens = language_parens['default'][1] | ||
|
||
def activate(self, window): | ||
self.update_ui(window) | ||
|
||
def deactivate(self, window): | ||
for view in window.get_views(): | ||
handler_id = getattr(view, self.ViewHandlerName, None) | ||
if handler_id is not None: | ||
view.disconnect(handler_id) | ||
setattr(view, self.ViewHandlerName, None) | ||
|
||
def update_ui(self, window): | ||
view = window.get_active_view() | ||
doc = window.get_active_document() | ||
if isinstance(view, gedit.View) and doc: | ||
if getattr(view, self.ViewHandlerName, None) is None: | ||
handler_id = view.connect('key-press-event', self.on_key_press, doc) | ||
setattr(view, self.ViewHandlerName, handler_id) | ||
|
||
def is_opening_paren(self,char): | ||
return char in self.opening_parens | ||
|
||
def is_closing_paren(self,char): | ||
return char in self.closing_parens | ||
|
||
def get_matching_opening_paren(self,closer): | ||
try: | ||
return self.opening_parens[self.closing_parens.index(closer)] | ||
except ValueError: | ||
return None | ||
|
||
def get_matching_closing_paren(self,opener): | ||
try: | ||
return self.closing_parens[self.opening_parens.index(opener)] | ||
except ValueError: | ||
return None | ||
|
||
def would_balance_parens(self, doc, closing_paren): | ||
iter1 = doc.get_iter_at_mark(doc.get_insert()) | ||
opening_paren = self.get_matching_opening_paren(closing_paren) | ||
balance = 1 | ||
while balance != 0 and not iter1.is_start(): | ||
iter1.backward_char() | ||
if iter1.get_char() == opening_paren: | ||
balance -= 1 | ||
elif iter1.get_char() == closing_paren: | ||
balance += 1 | ||
return balance == 0 | ||
|
||
def compare_marks(self, doc, mark1, mark2): | ||
return doc.get_iter_at_mark(mark1).compare(doc.get_iter_at_mark(mark2)) | ||
|
||
def enclose_selection(self, doc, opening_paren): | ||
closing_paren = self.get_matching_closing_paren(opening_paren) | ||
doc.begin_user_action() | ||
mark1 = doc.get_insert() | ||
mark2 = doc.get_selection_bound() | ||
if self.compare_marks(doc, mark1, mark2) > 0: | ||
mark1, mark2 = mark2, mark1 | ||
doc.insert(doc.get_iter_at_mark(mark1), opening_paren) | ||
doc.insert(doc.get_iter_at_mark(mark2), closing_paren) | ||
iter1 = doc.get_iter_at_mark(mark2) | ||
doc.place_cursor(iter1) | ||
doc.end_user_action() | ||
return True | ||
|
||
def auto_close_paren(self, doc, opening_paren): | ||
closing_paren = self.get_matching_closing_paren(opening_paren) | ||
doc.begin_user_action() | ||
doc.insert_at_cursor(opening_paren+closing_paren) | ||
iter1 = doc.get_iter_at_mark(doc.get_insert()) | ||
iter1.backward_char() | ||
doc.place_cursor(iter1) | ||
doc.end_user_action() | ||
return True | ||
|
||
def move_cursor_forward(self, doc): | ||
doc.begin_user_action() | ||
iter1 = doc.get_iter_at_mark(doc.get_insert()) | ||
iter1.forward_char() | ||
doc.place_cursor(iter1) | ||
doc.end_user_action() | ||
return True | ||
|
||
def move_to_end_of_line_and_insert(self, doc, text): | ||
doc.begin_user_action() | ||
mark = doc.get_insert() | ||
iter1 = doc.get_iter_at_mark(mark) | ||
iter1.set_line_offset(0) | ||
iter1.forward_to_line_end() | ||
doc.place_cursor(iter1) | ||
doc.insert_at_cursor(text) | ||
doc.end_user_action() | ||
return True | ||
|
||
def insert_two_lines(self, doc, text): | ||
doc.begin_user_action() | ||
mark = doc.get_insert() | ||
iter1 = doc.get_iter_at_mark(mark) | ||
doc.place_cursor(iter1) | ||
doc.insert_at_cursor(text) | ||
doc.insert_at_cursor(text) | ||
mark = doc.get_insert() | ||
iter2 = doc.get_iter_at_mark(mark) | ||
iter2.backward_chars(len(text)) | ||
doc.place_cursor(iter2) | ||
doc.end_user_action() | ||
return True | ||
|
||
def delete_both_parens(self, doc): | ||
doc.begin_user_action() | ||
start_iter = doc.get_iter_at_mark(doc.get_insert()) | ||
end_iter = start_iter.copy() | ||
start_iter.backward_char() | ||
end_iter.forward_char() | ||
doc.delete(start_iter, end_iter) | ||
doc.end_user_action() | ||
return True | ||
|
||
def get_char_under_cursor(self, doc): | ||
return doc.get_iter_at_mark(doc.get_insert()).get_char() | ||
|
||
def get_stmt_terminator(self, doc): | ||
terminator = DEFAULT_STMT_TERMINATOR | ||
lang = doc.get_language() | ||
if lang is not None: | ||
# Allow this to be changed by the language definition | ||
lang_terminator = lang.get_metadata(LANG_META_STMT_TERMINATOR_KEY) | ||
if lang_terminator is not None: | ||
terminator = lang_terminator | ||
return terminator | ||
|
||
def get_current_line_indent(self, doc): | ||
it_start = doc.get_iter_at_mark(doc.get_insert()) | ||
it_start.set_line_offset(0) | ||
it_end = it_start.copy() | ||
it_end.forward_to_line_end() | ||
indentation = [] | ||
while it_start.compare(it_end) < 0: | ||
char = it_start.get_char() | ||
if char == ' ' or char == '\t': | ||
indentation.append(char) | ||
else: | ||
break | ||
it_start.forward_char() | ||
return ''.join(indentation) | ||
|
||
def is_ctrl_enter(self, event): | ||
return (self.ctrl_enter_enabled and | ||
event.keyval == gtk.keysyms.Return and | ||
event.state & gtk.gdk.CONTROL_MASK) | ||
|
||
def should_auto_close_paren(self, doc): | ||
iter1 = doc.get_iter_at_mark(doc.get_insert()) | ||
if iter1.is_end() or iter1.ends_line(): | ||
return True | ||
char = iter1.get_char() | ||
return not (char.isalnum() or char == '_') | ||
|
||
def update_language(self, doc): | ||
lang = doc.get_language() | ||
lang_id = lang.get_id() if lang is not None else 'plain' | ||
if lang_id != self.language_id: | ||
parens = language_parens.get(lang_id, language_parens['default']) | ||
self.opening_parens = parens[0] | ||
self.closing_parens = parens[1] | ||
self.language_id = lang_id | ||
|
||
def should_delete_both_parens(self, doc, event): | ||
if event.keyval == gtk.keysyms.BackSpace: | ||
it = doc.get_iter_at_mark(doc.get_insert()) | ||
current_char = it.get_char() | ||
if self.is_closing_paren(current_char): | ||
it.backward_char() | ||
previous_char = it.get_char() | ||
matching_paren = self.get_matching_opening_paren(current_char) | ||
return previous_char == matching_paren | ||
return False | ||
|
||
def on_key_press(self, view, event, doc): | ||
handled = False | ||
self.update_language(doc) | ||
ch = to_char(event.keyval) | ||
if self.is_closing_paren(ch): | ||
# Skip over closing parenthesis if doing so would mean that the | ||
# preceeding parenthesis are correctly balanced | ||
if (self.get_char_under_cursor(doc) == ch and | ||
self.would_balance_parens(doc, ch)): | ||
handled = self.move_cursor_forward(doc) | ||
if not handled and self.is_opening_paren(ch): | ||
if doc.get_has_selection(): | ||
# Enclose selection in parenthesis or quotes | ||
handled = self.enclose_selection(doc, ch) | ||
elif self.should_auto_close_paren(doc): | ||
# Insert matching closing parenthesis and move cursor back one | ||
handled = self.auto_close_paren(doc, ch) | ||
if not handled and self.is_ctrl_enter(event): | ||
# Handle Ctrl+Return and Ctrl+Shift+Return | ||
text_to_insert = NEWLINE_CHAR + self.get_current_line_indent(doc) | ||
if event.state & gtk.gdk.SHIFT_MASK: | ||
text_to_insert = self.get_stmt_terminator(doc) + text_to_insert | ||
self.move_to_end_of_line_and_insert(doc, text_to_insert) | ||
view.scroll_mark_onscreen(doc.get_insert()) | ||
handled = True | ||
if not handled and event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter): | ||
# Enter was just pressed | ||
char_under_cursor = self.get_char_under_cursor(doc) | ||
if (self.is_closing_paren(char_under_cursor) and | ||
self.would_balance_parens(doc, char_under_cursor)): | ||
# If the character under the cursor would balance parenthesis | ||
text_to_insert = NEWLINE_CHAR + self.get_current_line_indent(doc) | ||
handled = self.insert_two_lines(doc, text_to_insert) | ||
if not handled and self.should_delete_both_parens(doc, event): | ||
# Delete parenthesis in front of cursor when one behind is deleted | ||
handled = self.delete_both_parens(doc) | ||
return handled | ||
|
||
# Load language parenthesis | ||
for path in sys.path: | ||
fn = os.path.join(path, 'pair_char_lang.py') | ||
if os.path.isfile(fn): | ||
execfile(fn, {'lang': add_language_parenthesis}) | ||
break |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# -*- coding: utf-8 -*- | ||
# | ||
# Programming language pair char support | ||
# | ||
# The default set is used if the language is not specified below. The plain | ||
# set is used for plain text, or when the document has no specified language. | ||
# | ||
lang('default', '(){}[]""\'\'``') | ||
lang('changelog', '(){}[]""<>') | ||
lang('html', '(){}[]""<>') | ||
lang('ruby', '(){}[]""\'\'``||') | ||
lang('xml', '(){}[]""<>') | ||
lang('php', '(){}[]""<>') | ||
lang('plain', '(){}[]""') | ||
lang('latex', '(){}[]""$$`\'') |