Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 9a33868f33
Fetching contributors…

Cannot retrieve contributors at this time

189 lines (165 sloc) 7.149 kB
#!/usr/bin/env python
# -*- coding: utf8 -*-
# Reflow plugin for Gedit
# Copyright (C) 2011 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 <>.
from gi.repository import GObject, Gedit, Gtk
import re
import textwrap
# - 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.
# Simple to start.
FILL_REGEX = r'^([#\*"/\-\+]*\s*)+'
class ReflowPlugin(GObject.Object, Gedit.WindowActivatable):
__gtype_name__ = "ReflowPlugin"
window =
def __init__(self):
def do_activate(self):
self._action_group = Gtk.ActionGroup("ReflowPluginActions")
self._action_group.add_actions([('Reflow', None, 'Reflow', ACCELERATOR,
'Reflow paragraph.', self._reflow)])
manager = self.window.get_ui_manager()
manager.insert_action_group(self._action_group, -1)
ui_str = """
<menubar name="MenuBar">
<menu name="EditMenu" action="Edit">
<placeholder name="EditOps_6">
<placeholder name="Rewrap">
<menuitem name="reflow" action="Reflow"/>
self._menu_ui_id = manager.add_ui_from_string(ui_str)
def do_deactivate(self):
manager = self.window.get_ui_manager()
self._action_group = None
self._menu_ui_id = None
def do_update_state(self):
self.window.get_active_document() != None)
def _reflow(self, action, 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)
first_prefix = splits[0][0]
prefix = splits[-1][0]
text = textwrap.fill(text,
width = get_gedit_margin(),
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) <= 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():
# I don't know how to access the settings in gedit 3...
# gconf_client = gconf.client_get_default()
# margin = gconf_client.get_int('/apps/gedit-3/preferences/editor/'
# 'right_margin/right_margin_position')
# return margin
return 79
Jump to Line
Something went wrong with that request. Please try again.