Hi Paolo et al., Following our quick discussion on IRC, I've been up hacking and fighting[1] with gedit and gedit plugins. Suffice it to say, that I am grateful I can write plugins in python, and don't have to go the gnome-shell javascript route ;) Thanks for getting me started! To make a long story short, I've put together a fairly functional new plugin: "SmarterSpaces". This is a fork of Steve FrÃcinaux's excellent "SmartSpaces" plugin. I am extending the code significantly past his original concept, however I call it a fork since I have renamed it so as not to conflict with the existing "SmartSpaces" plugin. I am more than happy to submit a patch to merge my code back in if everyone likes this. What my plugin does: I *really* can't stand using spaces for code indentation, it's insane. However, a lot of people out there do this, so I'd like to get my spaces to pretend that they are really tabs for when I hack on other peoples code. This means that when I use the left and right arrow keys, I expect the cursor to "jump" over the tabwidth of spaces until the next tabstop. If for some reason you used the mouse to put the cursor in the "middle" of a tab, moving left or right should take you to its boundary. This needs to work at the start of a line (where indentation usually is) but also at the end of lines where comments hide, and in the middle where more than one spaces are hanging out together. (The last two use cases were harder.) All of this should still work during "selection", when used with shift, or control+shift. (I had to add this in, but it was a surprisingly easy hack.) At the moment, this seems to be working for me. I'd really appreciate if you would test this, and let me know if you find any bugs, corner cases, or have suggestions. This caused me a lot of "off-by-one" hell, but I think it was worth it. There are one or two places in the code where I put some XXX, because I would need your advice. For some reason, I'm not 100% sure the boilerplate is perfect. Maybe someone can confirm that loading/unloading works properly, as I'm not sure. Thank you in advance. I've tested this with gedit 3.6.2 Thanks to pbor for telling me this might be hackish and impossible, and to shaddyz who helped me find the "doc" and "view" objects :) Happy hacking, James [1] good fighting :P
[Plugin] Loader=python Module=smarterspaces IAge=3 Name=Smarter Spaces Name[ar]=ÙØØÙØØ ØÙÙØ Name[be]=ÐÐÐÑÐÐÑÑ ÐÑÐÐÐÐÑ Name[cs]=Inteligentnà mezery Name[da]=Smarte mellemrum Name[de]=Intelligente Leerzeichen Name[el]=ÎÎÏÏÎÎ ÎÎÎÏÏÎÎÎÏÎ Name[en_GB]=Smart Spaces Name[es]=Espacios inteligentes Name[eu]=Tarte azkarrak Name[fr]=Espaces intelligents Name[gl]=Espazos intelixentes Name[he]=××××××× ××××× Name[hu]=Intelligens szÃkÃzÃk Name[id]=Spasi Cerdas Name[it]=Spazi intelligenti Name[ja]=ãããããããã Name[lt]=IÅmanieji tarpai Name[lv]=GudrÄs atstarpes Name[pl]=Inteligentne spacje Name[pt]=EspaÃos Inteligentes Name[pt_BR]=EspaÃos inteligentes Name[ro]=SpaÈii inteligente Name[ru]=ÂÐÐÐÑРÐÑÐÐÐÐÑ Name[sk]=Inteligentnà medzery Name[sl]=Pametni presledki Name[sr]=ÐÐÐÐÑÐÐ ÑÐÐÐÐÑÐ Name[sr latin]=Pametni razmaci Name[zh_CN]=æèçæ Description=Really forget you're not using tabulations. Description[ar]=ØÙØ ØÙÙ ÙØ ØØØØØÙ ØÙØØÙÙØ. Description[as]=ààààà àààààààà ààààààà àààààà ààààà àààà à Description[be]=ÐÐÐÑÐÐÑÑÐ ÐÑÐ ÑÐÐ, ÑÑÐ ÐÑ ÐÐ ÑÐÑÐÐÐÑÐ ÑÐÐÑÐÑÑÑÑ. Description[be latin]=ZabudÅsia, Åto ja nie vykarystoÅvaju tabulacyju. Description[bg]=ÐÐÐÑÐÐÑÐÐ, ÑÐ ÐÐ ÑÐ ÐÐÐÐÐÐÑ ÑÐÐÑÐÐÑÐÐ. Description[bn_IN]=àààààààààààà àààààà ààààà àààààààààà àààà ààààààà ààà ààà ààà Description[ca]=Oblideu-vos que no utilitzeu les tabulacions. Description[ca valencia]=Oblideu-vos que no utilitzeu les tabulacions. Description[cs]=ZapomeÅte, Åe nepouÅÃvÃte tabulace. Description[da]=Glem ikke at du bruger tabulatorer. Description[de]=Vergessen Sie, dass Sie keine EinzÃge benutzen Description[dz]=àààààààààà ààààààààààààààààààààààààààààààà àààààààà Description[el]=ÎÎÎÎÏÎÏÎÎ ÎÎ-ÏÏÎÏÎÏ ÏÏÎÎÎÎÎÏÏÎ. Description[en_CA]=Forget you're not using tabulations. Description[en_GB]=Forget you're not using tabulations. Description[es]=Olvidar que no està usando tabulaciones. Description[eu]=Ahaztu tabulazioak ez zarela erabiltzen ari. Description[fi]=Unohda, ettà et kÃytà sarkaimia. Description[fr]=Oubliez que vous n'utilisez pas les tabulations. Description[gl]=Esqueceu que vostede non està empregando tabulaciÃns. Description[gu]=àààà ààà àà ààà ààààààààà ààààà ààààà ààà. Description[he]=××××× ×××× ×× ××××× ××××××××. Description[hu]=Felejtse el, hogy nem hasznÃl tabokat Description[id]=Lupa bahwa Anda tidak menggunakan tabulasi. Description[it]=Dimentica che non stai usando le tabulazioni. Description[ja]=äèãäããããããããåããäããã Description[kn]=àààà ààààààààààà àààààààààààààààà ààààààààààààà. Description[lt]=PamirÅti, kad nenaudojate tabuliacijÅ. Description[lv]=Aizmirstiet, ka neizmantojat tabulatorus. Description[ml]=àààààààâ àààààààààâ ààààààààààààààààà ààààààà ààààààààààà. Description[mr]=àààààà ààààà àààà ààà àààà àà àààààà àààà. Description[nl]=Vergeet dat u geen tabs gebruikt. Description[or]=ààààààààààà ààà ààààà ààààààà àààààààààààà Description[pa]=àààà ààà àà ààààà ààààà àààà ààà ààà ààà Description[pl]=Tabulacja nie jest juÅ potrzebna. Description[pt]=Esquecer que nÃo està a utilizar tabuladores. Description[pt_BR]=EsqueÃa que nÃo està usando tabulaÃÃes. Description[ro]=UitÄ cÄ nu foloseÈti tabulatoare. Description[ru]=ÐÐÐÑÐÑÑÐ Ð ÑÐÐ, ÑÑÐ ÐÑ ÐÐ ÐÐÐÑÐÑÐÑÐÑÑ ÑÐÐÑÐÑÑÐÐÐ. Description[sk]=Zabudnite, Åe nepouÅÃvate tabulÃtory. Description[sl]=Pozabi, da tabulatorji niso v uporabi. Description[sr]=ÐÐÐÐÑÐÐÐÑÐ ÐÐ ÐÐ ÐÐÑÐÑÑÐÑÐ ÑÐÐÑÐÐÑÐÑÐ. Description[sr latin]=Zaboravite da ne koristite tabulatore. Description[sv]=GlÃm att du inte anvÃnder tabulatorer. Description[ta]=ààààààà àààààà àààààààààà ààààààààààààààààà. Description[te]=àààà àààààààààààààà àààààààààààà àààààààà ààààààà. Description[th]=ààààààààààààààààààààààààààà Description[vi]=QuÃn bán khÃng sá dáng cát Tab. Description[zh_CN]=åèäçåèççåäã Icon=gtk-unindent Authors=Steve FrÃcinaux <steve istique net>, James Shubin <james shubin ca> Copyright=Copyright  2006 Steve FrÃcinaux, Copyright  2013 James Shubin Website=https://ttboj.wordpress.com/smarter-spaces/ Version=3.6.2
# -*- coding: utf-8 -*- # smarterspaces.py # # Copyright (C) 2006 - Steve FrÃcinaux # Copyright (C) 2013 - James Shubin # # 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 3 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. from gi.repository import GObject, Gtk, Gdk, GtkSource, Gedit DEBUG = False # very useful # TODO: check that the boilerplate and the loading/unloading details work right class SmarterSpacesPlugin(GObject.Object, Gedit.ViewActivatable): __gtype_name__ = "SmarterSpacesPlugin" view = GObject.property(type=Gedit.View) def __init__(self): GObject.Object.__init__(self) def do_activate(self): self._handlers = [ None, self.view.connect('notify::editable', self.on_notify), self.view.connect('notify::insert-spaces-instead-of-tabs', self.on_notify) ] def do_deactivate(self): for handler in self._handlers: if handler is not None: self.view.disconnect(handler) def update_active(self): # Don't activate the feature if the buffer isn't editable or if # we're using tabs active = self.view.get_editable() and \ self.view.get_insert_spaces_instead_of_tabs() if active and self._handlers[0] is None: self._handlers[0] = self.view.connect('key-press-event', self.on_key_press_event) elif not active and self._handlers[0] is not None: self.view.disconnect(self._handlers[0]) self._handlers[0] = None def on_notify(self, view, pspec): self.update_active() def get_real_indent_width(self): indent_width = self.view.get_indent_width() if indent_width < 0: indent_width = self.view.get_tab_width() return indent_width def on_key_press_event(self, view, event): # Only take care of backspace, shift+backspace, left, right mods = Gtk.accelerator_get_default_mod_mask() if DEBUG: print 'keyval: %s' % str(event.keyval) # FIXME: this condition is confusing as ! bs_bool = event.keyval != Gdk.KEY_BackSpace or \ event.state & mods != 0 and event.state & mods != Gdk.ModifierType.SHIFT_MASK # TODO: are there other variations of Left and Right that should be added here ? if event.keyval in [Gdk.KEY_Left, Gdk.KEY_Right]: return self.do_key_press_leftright(view, event, debug=DEBUG) elif not(bs_bool): return self.do_key_press_backspace(view, event) else: return False def do_key_press_backspace(self, view, event): mods = Gtk.accelerator_get_default_mod_mask() if event.keyval != Gdk.KEY_BackSpace or \ event.state & mods != 0 and event.state & mods != Gdk.ModifierType.SHIFT_MASK: return False doc = view.get_buffer() if doc.get_has_selection(): return False cur = doc.get_iter_at_mark(doc.get_insert()) offset = cur.get_line_offset() if offset == 0: # We're at the begining of the line, so we can't obviously # unindent in this case return False start = cur.copy() prev = cur.copy() prev.backward_char() # If the previous chars are spaces, try to remove # them until the previous tab stop max_move = offset % self.get_real_indent_width() if max_move == 0: max_move = self.get_real_indent_width() moved = 0 while moved < max_move and prev.get_char() == ' ': start.backward_char() moved += 1 if not prev.backward_char(): # we reached the start of the buffer break if moved == 0: # The iterator hasn't moved, it was not a space return False # Actually delete the spaces doc.begin_user_action() doc.delete(start, cur) doc.end_user_action() return True def do_key_press_leftright(self, view, event, debug=False): """Handle moving left and right over spaces as if they were tabs! Handle selection (shift) and control-selection (shift+control) too. Writing this function was off-by-one hell. Patch carefully and test extensively.""" mods = Gtk.accelerator_get_default_mod_mask() if debug: print 'FUNCTION: do_key_press_leftright(keyval:%d)' % event.keyval both = bool(event.state & mods == Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK) if debug: print 'BOTH: %s' % both if both: shift = True else: shift = bool(event.state & mods == Gdk.ModifierType.SHIFT_MASK) if debug: print 'SHIFT: %s' % shift if both: control = True else: control = bool(event.state & mods == Gdk.ModifierType.CONTROL_MASK) if debug: print 'CONTROL: %s' % control # TODO: are there other variations of Left and Right that should be added here ? if event.keyval != Gdk.KEY_Left and \ event.keyval != Gdk.KEY_Right: return False doc = view.get_buffer() # NOTE: we don't do this because we want to work while under selection! #if doc.get_has_selection(): # return False tabwidth = self.get_real_indent_width() if debug: print 'TABWIDTH: %d' % tabwidth iterobj = doc.get_iter_at_mark(doc.get_insert()) # get cursor mark selecto = doc.get_iter_at_mark(doc.get_selection_bound()) length = iterobj.get_chars_in_line() # length of line if debug: print 'LENGTH: %d' % length offset = iterobj.get_line_offset() # an int if debug: print 'OFFSET: %d' % offset iter_a = doc.get_iter_at_mark(doc.get_insert()) iter_a.set_line_offset(0) # move to start iter_b = doc.get_iter_at_mark(doc.get_insert()) iter_b.forward_line() # move to end line_list = doc.get_slice(iter_a, iter_b, True) # line as an array if length != len(line_list): # sanity check import inspect print '** (gedit:%s:%d): CRITICAL **: do_key_press_leftright: assertion `length == len(line_list)\' failed' % (__file__, inspect.currentframe().f_back.f_lineno) # if in the 'middle' of a tab, jump to edge lalign = ((tabwidth-offset) % tabwidth) ralign = (offset % tabwidth) if debug: print 'LALIGN: %d' % lalign if debug: print 'RALIGN: %d' % ralign space = True until = 0 while until < len(line_list) and space: # find continuous if line_list[until] != ' ': space = False break until = until + 1 if debug: print 'UNTIL: %d' % until # XXX: does any of this need to be enclosed in "doc.begin_user_action()" and "doc.end_user_action()" ? motion = 1 # cursor moves itself by this (1) if event.keyval == Gdk.KEY_Left: if offset == 0: # start of line return False if offset > until and line_list[offset-1] != ' ': # not within continuous initial indentation return False if line_list[offset-1] == ' ': space = True luntil = offset while luntil > 0 and space: # find continuous if line_list[luntil-1] != ' ': space = False break luntil = luntil - 1 if debug: print 'LUNTIL: %d' % luntil iterobj.set_line_offset( max(offset - (tabwidth-lalign) + motion, luntil+1) ) else: iterobj.set_line_offset( offset - (tabwidth-lalign) + motion ) if event.keyval == Gdk.KEY_Right: if offset == length-1: # end of line return False if offset > until-1 and line_list[offset+0] != ' ': # not within continuous initial indentation return False if line_list[offset+0] == ' ': space = True runtil = offset while runtil < length and space: # find continuous if line_list[runtil+0] != ' ': space = False break runtil = runtil + 1 if debug: print 'RUNTIL: %d' % runtil iterobj.set_line_offset( min(offset + (tabwidth-ralign) - motion, runtil-1) ) else: iterobj.set_line_offset( offset + (tabwidth-ralign) - motion ) # do the placement doc.begin_user_action() if shift or both: # as long as shift is being used... doc.select_range(iterobj, selecto) # don't break the selection! else: doc.place_cursor(iterobj) doc.end_user_action() #return True # XXX: why doesn't this work ? return False # XXX: when this does! # ex:ts=4:et:
Attachment:
signature.asc
Description: This is a digitally signed message part