-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathreflow.py
190 lines (161 loc) · 6.85 KB
/
reflow.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/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 <http://www.gnu.org/licenses/>.
import gettext
import re
import textwrap
from gi.repository import GObject, Gedit, Gio
# TODO:
# - 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):
GObject.Object.__init__(self)
def do_activate(self):
self.app.set_accels_for_action("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 = Gio.MenuItem.new(_("Reflow"), "win.reflow")
self.menu_ext.prepend_menu_item(item)
def do_deactivate(self):
self.app.set_accels_for_action("win.reflow", [])
self.menu_ext = None
def do_update_state(self):
pass
class ReflowPluginWindowActivatable(GObject.Object, Gedit.WindowActivatable):
window = GObject.property(type=Gedit.Window)
def __init__(self):
GObject.Object.__init__(self)
self.settings = Gio.Settings.new("org.gnome.gedit.preferences.editor")
def do_activate(self):
action = Gio.SimpleAction(name="reflow")
action.connect('activate', self.on_reflow_text_activate)
self.window.add_action(action)
def on_reflow_text_activate(self, action, parameter, user_data=None):
begin, end = self._get_paragraph()
if begin == end:
return
# 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)
cursor_iter.forward_chars(insert_pos)
document.place_cursor(cursor_iter)
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]
else:
first_prefix = ''
prefix = ''
text = textwrap.fill(text,
width=self.get_gedit_margin(),
initial_indent=first_prefix,
subsequent_indent=prefix,
break_on_hyphens=False,
drop_whitespace=True)
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()
end.forward_to_line_end()
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 (m.group(), line[m.end():])
else:
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:
break
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
break
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() == "":
break
return start, end
def _replace(self, begin, end, text):
document = self.window.get_active_document()
document.begin_user_action()
begin_iter = document.get_iter_at_line(begin)
if end >= document.get_line_count():
end_iter = document.get_end_iter()
else:
end_iter = document.get_iter_at_line(end)
end_iter.backward_char()
document.delete(begin_iter, end_iter)
document.insert(begin_iter, text)
document.end_user_action()
def get_gedit_margin(self):
return self.settings.get_uint("right-margin-position")