Merge lp:~davidmhewitt/scratch/fix-1303562-draw-selected-spaces into lp:~elementary-apps/scratch/scratch

Proposed by David Hewitt
Status: Merged
Approved by: David Hewitt
Approved revision: 1845
Merged at revision: 1841
Proposed branch: lp:~davidmhewitt/scratch/fix-1303562-draw-selected-spaces
Merge into: lp:~elementary-apps/scratch/scratch
Diff against target: 338 lines (+235/-7)
6 files modified
schemas/org.pantheon.scratch.gschema.xml (+7/-2)
src/CMakeLists.txt (+1/-0)
src/Dialogs/PreferencesDialog.vala (+6/-3)
src/DrawSpacesUtils.vala (+202/-0)
src/Services/Settings.vala (+7/-1)
src/Widgets/SourceView.vala (+12/-1)
To merge this branch: bzr merge lp:~davidmhewitt/scratch/fix-1303562-draw-selected-spaces
Reviewer Review Type Date Requested Status
Jeremy Wootten code, function Approve
Review via email: mp+319370@code.launchpad.net

Commit message

Add setting and functionality to draw spaces only for selected text.

Description of the change

Currently, in the preferences, you have the option to turn "draw spaces" on or off. This branch adds a third option where spaces are drawn only within the selected region. This is useful for seeing if there is a discrepancy between tabs or spaces in a particular part of a file.

The code is ported from Gtksourceview to Vala so spaces are drawn in exactly the same way, using the same algorithm, just in a more controlled manner between the selection start and end points. It looks as though it may be possible to do this more easily without porting too much code after Gtksourceview-3.20, but this solution will continue to work too.

To post a comment you must log in.
1840. By David Hewitt

Remove un-needed dep on libm

Revision history for this message
Corentin Noël (tintou) wrote :

I think it's better to use an enum in the gsettings instead of comparing strings

1841. By David Hewitt

Switch to enum instead of string for gsettings

Revision history for this message
David Hewitt (davidmhewitt) wrote :

Agreed, thanks Corentin. That's done now. Did you get chance to test the functionality at all?

1842. By David Hewitt

Merge trunk

Revision history for this message
Danielle Foré (danrabbit) wrote :

I can confirm that it works as expected :)

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Only very minor niggles:

1) When drawing spaces in selected text only, the spaces are drawn pale as expected so they are visible. However, if the setting is then changed to "Always" the spaces disappear in the selected region (because they are drawn dark on a dark background) which is unexpected. Admittedly, spaces disappear in selected text in trunk as well, but in this branch they can be drawn pale but are not.

2) If the setting is changed while text is selected the behaviour inside the selection is inconsistent. Going from "Never" to "Selected text only" does not cause spaces to appear in the selection. Going from "Always" to "Selected text only" causes them to appear. Going from "Selected text" to "Always" causes them to disappear. Going from "Selected text" to "Never" does not cause them to disappear. This may be related to point 1).

Also, the diff below does not seem to show the removal of code that was replaced by DrawSpaceUtils, not sure why.

1843. By David Hewitt

Prefix gsettings enum with io.elementary.scratch

1844. By David Hewitt

Trigger redraw when setting changed

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Looks good now.

review: Approve (code, function)
1845. By David Hewitt

Merged trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'schemas/org.pantheon.scratch.gschema.xml'
2--- schemas/org.pantheon.scratch.gschema.xml 2016-12-13 02:06:39 +0000
3+++ schemas/org.pantheon.scratch.gschema.xml 2017-03-26 16:25:47 +0000
4@@ -5,6 +5,11 @@
5 <value nick="Maximized" value="1" />
6 <value nick="Fullscreen" value="2" />
7 </enum>
8+ <enum id="io.elementary.scratch.draw-spaces-states">
9+ <value nick="Never" value="0" />
10+ <value nick="For Selection" value="1" />
11+ <value nick="Always" value="2" />
12+ </enum>
13 <schema path="/org/pantheon/scratch/saved-state/" id="org.pantheon.scratch.saved-state" gettext-domain="scratch">
14 <key name="window-state" enum="scratch-window-states">
15 <default>"Normal"</default>
16@@ -104,8 +109,8 @@
17 <summary>Highlight Matching Brackets</summary>
18 <description>Whether Scratch should highlight matching brackets.</description>
19 </key>
20- <key name="draw-spaces" type="b">
21- <default>false</default>
22+ <key name="draw-spaces" enum="io.elementary.scratch.draw-spaces-states">
23+ <default>"Never"</default>
24 <summary>Draw spaces and tabs with symbols</summary>
25 <description>Draw spaces and tabs with symbols.</description>
26 </key>
27
28=== modified file 'src/CMakeLists.txt'
29--- src/CMakeLists.txt 2016-04-13 05:03:04 +0000
30+++ src/CMakeLists.txt 2017-03-26 16:25:47 +0000
31@@ -2,6 +2,7 @@
32 MainWindow.vala
33 Scratch.vala
34 Utils.vala
35+ DrawSpacesUtils.vala
36 Dialogs/PreferencesDialog.vala
37 Widgets/DocumentView.vala
38 Widgets/ToolBar.vala
39
40=== modified file 'src/Dialogs/PreferencesDialog.vala'
41--- src/Dialogs/PreferencesDialog.vala 2016-05-09 22:14:07 +0000
42+++ src/Dialogs/PreferencesDialog.vala 2017-03-26 16:25:47 +0000
43@@ -31,7 +31,6 @@
44 Gtk.Switch highlight_current_line;
45 Gtk.Switch highlight_matching_brackets;
46 Gtk.Switch line_break;
47- Gtk.Switch draw_spaces;
48 Gtk.Switch spaces_instead_of_tabs_switch;
49 Gtk.Switch autoindent_switch;
50
51@@ -163,7 +162,11 @@
52 line_break = new SettingsSwitch ("line-break");
53
54 var draw_spaces_label = new SettingsLabel (_("Draw Spaces:"));
55- draw_spaces = new SettingsSwitch ("draw-spaces");
56+ var draw_spaces_combo = new Gtk.ComboBoxText ();
57+ draw_spaces_combo.append ("Never", _("Never"));
58+ draw_spaces_combo.append ("For Selection", _("For selected text"));
59+ draw_spaces_combo.append ("Always", _("Always"));
60+ Scratch.settings.schema.bind ("draw-spaces", draw_spaces_combo, "active-id", SettingsBindFlags.DEFAULT);
61
62 var line_numbers_label = new SettingsLabel (_("Show line numbers:"));
63 var line_numbers = new SettingsSwitch ("show-line-numbers");
64@@ -206,7 +209,7 @@
65 content.attach (line_break_label, 0, 3, 1, 1);
66 content.attach (line_break, 1, 3, 1, 1);
67 content.attach (draw_spaces_label, 0, 4, 1, 1);
68- content.attach (draw_spaces, 1, 4, 1, 1);
69+ content.attach (draw_spaces_combo, 1, 4, 1, 1);
70 content.attach (line_numbers_label, 0, 5, 1, 1);
71 content.attach (line_numbers, 1, 5, 1, 1);
72 #if GTKSOURCEVIEW_3_18
73
74=== added file 'src/DrawSpacesUtils.vala'
75--- src/DrawSpacesUtils.vala 1970-01-01 00:00:00 +0000
76+++ src/DrawSpacesUtils.vala 2017-03-26 16:25:47 +0000
77@@ -0,0 +1,202 @@
78+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
79+/***
80+ BEGIN LICENSE
81+
82+ Copyright (C) 2017 elementary LLC
83+ This program is free software: you can redistribute it and/or modify it
84+ under the terms of the GNU Lesser General Public License version 3, as published
85+ by the Free Software Foundation.
86+
87+ This program is distributed in the hope that it will be useful, but
88+ WITHOUT ANY WARRANTY; without even the implied warranties of
89+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
90+ PURPOSE. See the GNU General Public License for more details.
91+
92+ You should have received a copy of the GNU General Public License along
93+ with this program. If not, see <http://www.gnu.org/licenses/>
94+
95+ END LICENSE
96+***/
97+
98+namespace Scratch.Utils {
99+ static void get_end_iter (Gtk.TextView text_view, Gtk.TextIter start_iter, out Gtk.TextIter end_iter, int x, int y, bool is_wrapping) {
100+ int min, max, i;
101+ Gdk.Rectangle rect;
102+
103+ end_iter = start_iter;
104+
105+ if (!end_iter.ends_line ()) {
106+ end_iter.forward_to_line_end ();
107+ }
108+
109+ text_view.get_iter_location (end_iter, out rect);
110+
111+ if ((is_wrapping && rect.y < y) || (!is_wrapping && rect.x < x)) {
112+ return;
113+ }
114+
115+ min = start_iter.get_line_offset ();
116+ max = end_iter.get_line_offset ();
117+
118+ while (max >= min) {
119+ i = (min + max) >> 1;
120+ end_iter.set_line_offset (i);
121+ text_view.get_iter_location (end_iter, out rect);
122+
123+ if ((is_wrapping && rect.y < y) || (!is_wrapping && rect.x < x)) {
124+ min = i + 1;
125+ } else if ((is_wrapping && rect.y > y) || (!is_wrapping && rect.x > x)) {
126+ max = i - 1;
127+ } else {
128+ break;
129+ }
130+ }
131+ }
132+
133+ static void draw_space_at_iter (Cairo.Context cr, Gtk.TextView view, Gtk.TextIter iter, Gdk.Rectangle rect) {
134+ int x, y;
135+ double w;
136+
137+ view.buffer_to_window_coords (Gtk.TextWindowType.TEXT, rect.x, rect.y + rect.height * 2 / 3, out x, out y);
138+
139+ w = rect.width != 0 ? rect.width : rect.height;
140+
141+ cr.save ();
142+ cr.move_to (x + w * 0.5, y);
143+ cr.arc (x + w * 0.5, y, 0.8, 0, 2 * Math.PI);
144+ cr.restore ();
145+ }
146+
147+ static void draw_tab_at_iter (Cairo.Context cr, Gtk.TextView view, Gtk.TextIter iter, Gdk.Rectangle rect) {
148+ int x, y;
149+ double w, h;
150+
151+ view.buffer_to_window_coords (Gtk.TextWindowType.TEXT, rect.x, rect.y + rect.height * 2 / 3, out x, out y);
152+
153+ w = rect.width != 0 ? rect.width : rect.height;
154+ h = rect.height;
155+
156+ cr.save ();
157+ cr.move_to (x + w * 1 / 8, y);
158+ cr.rel_line_to (w * 6 / 8, 0);
159+ cr.rel_line_to (-h * 1 / 4, -h * 1 / 4);
160+ cr.rel_move_to (+h * 1 / 4, +h * 1 / 4);
161+ cr.rel_line_to (-h * 1 / 4, +h * 1 / 4);
162+ cr.restore ();
163+ }
164+
165+ static void draw_nbsp_at_iter (Cairo.Context cr, Gtk.TextView view, Gtk.TextIter iter, Gdk.Rectangle rect, bool narrowed) {
166+ int x, y;
167+ double w, h;
168+
169+ view.buffer_to_window_coords (Gtk.TextWindowType.TEXT, rect.x, rect.y + rect.height / 2, out x, out y);
170+
171+ w = rect.width != 0 ? rect.width : rect.height;
172+ h = rect.height;
173+
174+ cr.save ();
175+ cr.move_to (x + w * 1 / 6, y);
176+ cr.rel_line_to (w * 4 / 6, 0);
177+ cr.rel_line_to (-w * 2 / 6, +h * 1 / 4);
178+ cr.rel_line_to (-w * 2 / 6, -h * 1 / 4);
179+
180+ if (narrowed) {
181+ cr.fill ();
182+ } else {
183+ cr.stroke ();
184+ }
185+
186+ cr.restore ();
187+ }
188+
189+ static void draw_spaces_at_iter (Cairo.Context cr, Gtk.TextView text_view, Gtk.TextIter iter) {
190+ unichar c;
191+ Gdk.Rectangle rect;
192+
193+ text_view.get_iter_location (iter, out rect);
194+
195+ c = iter.get_char ();
196+
197+ if (c == '\t') {
198+ draw_tab_at_iter (cr, text_view, iter, rect);
199+ } else if (c.break_type () == UnicodeBreakType.NON_BREAKING_GLUE) {
200+ draw_nbsp_at_iter (cr, text_view, iter, rect, c == 0x202F);
201+ } else if (c.type () == UnicodeType.SPACE_SEPARATOR) {
202+ draw_space_at_iter (cr, text_view, iter, rect);
203+ } else if (c.type () == UnicodeType.SPACE_SEPARATOR) {
204+ draw_space_at_iter (cr, text_view, iter, rect);
205+ }
206+ }
207+
208+ static void draw_tabs_and_spaces (Gtk.SourceView view, Cairo.Context cr) {
209+ Gtk.TextIter selection_start, selection_end;
210+ view.buffer.get_selection_bounds (out selection_start, out selection_end);
211+
212+ Gtk.TextView text_view;
213+ Gdk.Rectangle clip;
214+ int x1, y1, x2, y2;
215+ Gtk.TextIter s, e;
216+ Gtk.TextIter lineend;
217+ bool is_wrapping;
218+
219+ if (!Gdk.cairo_get_clip_rectangle (cr, out clip)) {
220+ return;
221+ }
222+
223+ text_view = view;
224+
225+ is_wrapping = text_view.get_wrap_mode () != Gtk.WrapMode.NONE;
226+
227+ x1 = clip.x;
228+ y1 = clip.y;
229+ x2 = x1 + clip.width;
230+ y2 = y1 + clip.height;
231+
232+ text_view.window_to_buffer_coords (Gtk.TextWindowType.TEXT, x1, y1, out x1, out y1);
233+ text_view.window_to_buffer_coords (Gtk.TextWindowType.TEXT, x2, y2, out x2, out y2);
234+
235+ text_view.get_iter_at_location (out s, x1, y1);
236+ text_view.get_iter_at_location (out e, x2, y2);
237+
238+ cr.set_source_rgba (1.0, 1.0, 1.0, 1.0);
239+
240+ cr.set_line_width (0.8);
241+ cr.translate (-0.5, -0.5);
242+
243+ get_end_iter (text_view, s, out lineend, x2, y2, is_wrapping);
244+
245+ while (true) {
246+ unichar c = s.get_char ();
247+ int ly;
248+
249+ if (c.isspace () && s.compare (selection_start) >= 0 && s.compare (selection_end) < 0) {
250+ draw_spaces_at_iter (cr, text_view, s);
251+ }
252+
253+ if (!s.forward_char ()) {
254+ break;
255+ }
256+
257+ if (s.compare (lineend) > 0) {
258+ if (s.compare (e) > 0) {
259+ break;
260+ }
261+
262+ if (!s.starts_line () && !s.forward_line ()) {
263+ break;
264+ }
265+
266+ text_view.get_line_yrange (s, out ly, null);
267+ text_view.get_iter_at_location (out s, x1, ly);
268+
269+ if (!s.starts_line ()) {
270+ s.backward_char ();
271+ }
272+
273+ get_end_iter (text_view, s, out lineend, x2, y2, is_wrapping);
274+ }
275+ }
276+
277+ cr.stroke ();
278+ }
279+}
280
281=== modified file 'src/Services/Settings.vala'
282--- src/Services/Settings.vala 2016-12-13 02:06:39 +0000
283+++ src/Services/Settings.vala 2017-03-26 16:25:47 +0000
284@@ -27,6 +27,12 @@
285 FULLSCREEN = 2
286 }
287
288+ public enum ScratchDrawSpacesState {
289+ NEVER = 0,
290+ FOR_SELECTION = 1,
291+ ALWAYS = 2
292+ }
293+
294 public class SavedState : Granite.Services.Settings {
295
296 public int window_width { get; set; }
297@@ -51,7 +57,7 @@
298 public bool line_break { get; set; }
299 public bool highlight_current_line { get; set; }
300 public bool highlight_matching_brackets { get; set; }
301- public bool draw_spaces { get; set; }
302+ public ScratchDrawSpacesState draw_spaces { get; set; }
303 public bool spaces_instead_of_tabs { get; set; }
304 public bool auto_indent { get; set; }
305 public int indent_width { get; set; }
306
307=== modified file 'src/Widgets/SourceView.vala'
308--- src/Widgets/SourceView.vala 2015-10-17 21:52:34 +0000
309+++ src/Widgets/SourceView.vala 2017-03-26 16:25:47 +0000
310@@ -190,10 +190,12 @@
311 show_line_numbers = Scratch.settings.show_line_numbers;
312 highlight_current_line = Scratch.settings.highlight_current_line;
313 buffer.highlight_matching_brackets = Scratch.settings.highlight_matching_brackets;
314- if (settings.draw_spaces) {
315+ if (settings.draw_spaces == ScratchDrawSpacesState.ALWAYS) {
316 draw_spaces = Gtk.SourceDrawSpacesFlags.TAB;
317 draw_spaces |= Gtk.SourceDrawSpacesFlags.SPACE;
318 } else {
319+ // Ensure we change the draw_spaces variable at least once to trigger redraw
320+ draw_spaces = Gtk.SourceDrawSpacesFlags.TAB;
321 draw_spaces = Gtk.SourceDrawSpacesFlags.NBSP;
322 }
323
324@@ -326,5 +328,14 @@
325
326 return false;
327 }
328+
329+ public override void draw_layer (Gtk.TextViewLayer layer, Cairo.Context context) {
330+ if (layer == Gtk.TextViewLayer.ABOVE && this.buffer.get_has_selection () && settings.draw_spaces == ScratchDrawSpacesState.FOR_SELECTION) {
331+ context.save ();
332+ Utils.draw_tabs_and_spaces (this, context);
333+ context.restore ();
334+ }
335+ base.draw_layer (layer, context);
336+ }
337 }
338 }

Subscribers

People subscribed via source and target branches