Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
191 lines (161 sloc) 6.85 KB
#!/usr/bin/env python
# -*- coding: utf8 -*-
# Reflow plugin for Gedit
# Copyright (C) 2011-2015 Guillaume Chereau
# 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, see <>.
import gettext
import re
import textwrap
from gi.repository import GObject, Gedit, Gio
# - Replace cursor at the correct position after a reflow.
# - Support double space characters (like chinese chars).
# - Fix bug when there is no newline at the end of the document.
ACCELERATOR = ['<Alt>q']
# Simple to start.
FILL_REGEX = r'^([#\*"/\-\+]*\s*)+'
class ReflowPluginAppActivatable(GObject.Object, Gedit.AppActivatable):
__gtype_name__ = "ReflowPlugin"
app = GObject.Property(type=Gedit.App)
def __init__(self):
def do_activate(self):"win.reflow", ACCELERATOR)
# Translate actions below, hardcoding domain here to avoid
# complications now
_ = lambda s: gettext.dgettext('devhelp', s)
self.menu_ext = self.extend_menu("tools-section")
item ="Reflow"), "win.reflow")
def do_deactivate(self):"win.reflow", [])
self.menu_ext = None
def do_update_state(self):
class ReflowPluginWindowActivatable(GObject.Object, Gedit.WindowActivatable):
window =
def __init__(self):
self.settings ="org.gnome.gedit.preferences.editor")
def do_activate(self):
action = Gio.SimpleAction(name="reflow")
action.connect('activate', self.on_reflow_text_activate)
def on_reflow_text_activate(self, action, parameter, user_data=None):
begin, end = self._get_paragraph()
if begin == end:
# We first reflow up to the cursor location, just to get the number of
# characters from the beginning till the cursor.
document = self.window.get_active_document()
insert_mark = document.get_insert()
insert_iter = document.get_iter_at_mark(insert_mark)
before_text = document.get_iter_at_line(begin).get_text(insert_iter)
text = self._fill(before_text)
insert_pos = len(text) # We use it later to restore the cursor pos.
# Fill all the lines in the paragraph
lines = [self._get_line(i) for i in range(begin, end)]
text = self._fill("\n".join(lines))
self._replace(begin, end, text)
# Restore the cursor pos.
# XXX: there is a bug if the cursor was after a space.
cursor_iter = document.get_iter_at_line(begin)
def _fill(self, text):
lines = text.splitlines()
splits = [self._split(x) for x in lines]
lines = [x[1].strip() for x in splits]
text = '\n'.join(lines)
# for the indentation to work we need at least two lines
# paragraph
if len(splits) > 1:
first_prefix = splits[0][0]
prefix = splits[-1][0]
first_prefix = ''
prefix = ''
text = textwrap.fill(text,
return text
def _get_line(self, index):
document = self.window.get_active_document()
begin = document.get_iter_at_line(index)
if begin.ends_line():
return ""
end = begin.copy()
return begin.get_text(end)
def _split(self, line, prefix=None):
if prefix is None:
m = re.match(FILL_REGEX, line) # XXX: too slow I guess
if not m:
return (None, line)
return (, line[m.end():])
if not line.startswith(prefix):
return (None, line)
return (prefix, line[len(prefix):])
def _get_paragraph(self):
"""return begin and end line of the current paragraph"""
document = self.window.get_active_document()
insert_mark = document.get_insert()
start = document.get_iter_at_mark(insert_mark).get_line()
end = start
prefix, line = self._split(self._get_line(start))
if prefix is None or line.strip() == "":
return start, end
while start > 0:
other_prefix, line = self._split(self._get_line(start - 1))
if line.strip() == "" or prefix and not other_prefix:
if other_prefix != prefix:
# Check if we should consider the line as part of the block or
# not. This is a quite empirical formula that works fine in
# most of the tested cases.
if len(other_prefix) <= len(prefix) and \
len(other_prefix.strip()) >= len(prefix.strip()):
start -= 1
start -= 1
# When we run the command on the firt line, then we consider all the
# lines that starts with the first line prefix.
search_prefix = prefix if start == end else None
while end < document.get_line_count():
end += 1
other_prefix, line = \
self._split(self._get_line(end), search_prefix)
if other_prefix != prefix or line.strip() == "":
return start, end
def _replace(self, begin, end, text):
document = self.window.get_active_document()
begin_iter = document.get_iter_at_line(begin)
if end >= document.get_line_count():
end_iter = document.get_end_iter()
end_iter = document.get_iter_at_line(end)
document.delete(begin_iter, end_iter)
document.insert(begin_iter, text)
def get_gedit_margin(self):
return self.settings.get_uint("right-margin-position")