gitg-diff-view.vala 26.7 KB
Newer Older
1
2
3
/*
 * This file is part of gitg
 *
4
 * Copyright (C) 2015 - Jesse van den Kieboom
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * gitg 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 2 of the License, or
 * (at your option) any later version.
 *
 * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
 */

20
[GtkTemplate( ui = "/org/gnome/gitg/ui/gitg-diff-view.ui" )]
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
21
public class Gitg.DiffView : Gtk.Grid
22
{
23
	[GtkChild( name = "commit_details" )]
24
	private unowned Gitg.DiffViewCommitDetails d_commit_details;
25

26
	[GtkChild( name = "scrolledwindow" )]
27
	private unowned Gtk.ScrolledWindow d_scrolledwindow;
28

29
	[GtkChild( name = "grid_files" )]
30
	private unowned Gtk.Grid d_grid_files;
31

32
	[GtkChild( name = "event_box" )]
33
	private unowned Gtk.EventBox d_event_box;
34
35

	[GtkChild( name = "revealer_options" )]
36
	private unowned Gtk.Revealer d_revealer_options;
37
38

	[GtkChild( name = "diff_view_options" )]
39
	private unowned DiffViewOptions d_diff_view_options;
40

41
	[GtkChild( name = "text_view_message" )]
42
	private unowned Gtk.TextView d_text_view_message;
43

44
45
46
	private Ggit.Diff? d_diff;
	private Commit? d_commit;
	private Ggit.DiffOptions? d_options;
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
47
48
49
50
	private Cancellable d_cancellable;
	private ulong d_expanded_notify;
	private ulong d_parent_commit_notify;
	private bool d_changes_inline;
51

52
53
54
55
56
	Gdk.RGBA d_color_link;
	Gdk.RGBA color_hovered_link;
	bool hovering_over_link = false;
	Gtk.TextTag hover_tag = null;

57
58
59
	private uint d_reveal_options_timeout;
	private uint d_unreveal_options_timeout;

60
61
	private static Gee.HashSet<string> s_image_mime_types;

62
63
64
	public Ggit.DiffOptions options
	{
		get
65
		{
66
			if (d_options == null)
67
			{
68
				d_options = new Ggit.DiffOptions();
69
			}
70

71
			return d_options;
72
		}
73
	}
74

75
76
	public bool has_selection
	{
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
77
		get; private set;
78
	}
79

80
81
82
83
	public Ggit.Diff? diff
	{
		get { return d_diff; }
		set
84
		{
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
85
86
87
88
89
			if (d_diff != value)
			{
				d_diff = value;
				d_commit = null;
			}
90

91
			update(false);
92
		}
93
	}
94

95
96
97
98
	public Commit? commit
	{
		get { return d_commit; }
		set
99
		{
100
			if (d_commit != value)
101
			{
102
103
				d_commit = value;
				d_diff = null;
104
			}
105

106
			update(false);
107
		}
108
	}
109

110
111
112
	public virtual signal void options_changed()
	{
		if (d_commit != null)
113
		{
114
			update(true);
115
		}
116
	}
117

118
	public bool wrap_lines { get; construct set; default = true; }
119
120
121
	public bool staged { get; set; default = false; }
	public bool unstaged { get; set; default = false; }
	public bool show_parents { get; set; default = false; }
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
122
123
124
125
	public bool default_collapse_all { get; construct set; default = true; }
	public bool use_gravatar { get; construct set; default = true; }
	public int tab_width { get; construct set; default = 4; }
	public bool handle_selection { get; construct set; default = false; }
126
	public bool highlight { get; construct set; default = true; }
127
128
129

	private Repository? d_repository;

130
131
	private GLib.Regex regex_custom_links = /gitg\.custom-link\.(.+)\.regex/;

132
133
134
135
	public Repository? repository {
		get { return d_repository; }
		set {
			d_repository = value;
Gaurav Agrawal's avatar
Gaurav Agrawal committed
136
137
			if (d_repository!=null)
			{
138
				d_commit_details.repository = d_repository;
Gaurav Agrawal's avatar
Gaurav Agrawal committed
139
			}
140
141
		}
	}
142
	public bool new_is_workdir { get; set; }
143
144

	private GLib.Regex regex_url = /\w+:(\/?\/?)[^\s]+/;
145

146
147
148
149
	private bool flag_get(Ggit.DiffOption f)
	{
		return (options.flags & f) != 0;
	}
150

151
152
153
	private void flag_set(Ggit.DiffOption f, bool val)
	{
		var flags = options.flags;
154

155
		if (val)
156
		{
157
			flags |= f;
158
		}
159
		else
160
		{
161
			flags &= ~f;
162
163
		}

164
		if (flags != options.flags)
165
		{
166
167
			options.flags = flags;
			options_changed();
168
		}
169
	}
170

171
172
173
174
175
	public bool ignore_whitespace
	{
		get { return flag_get(Ggit.DiffOption.IGNORE_WHITESPACE); }
		set { flag_set(Ggit.DiffOption.IGNORE_WHITESPACE, value); }
	}
176

177
178
179
180
	public bool changes_inline
	{
		get { return d_changes_inline; }
		set
181
		{
182
			if (d_changes_inline != value)
183
			{
184
				d_changes_inline = value;
Sindhu S's avatar
Sindhu S committed
185

186
187
				// TODO
				//options_changed();
188
			}
189
		}
190
	}
191

192
193
194
	public int context_lines
	{
		get { return options.n_context_lines; }
195

196
		construct set
197
		{
198
			if (options.n_context_lines != value)
199
			{
200
201
				options.n_context_lines = value;
				options.n_interhunk_lines = value;
202

203
				options_changed();
204
205
			}
		}
206
	}
207

208
209
210
	protected override void constructed()
	{
		d_expanded_notify = d_commit_details.notify["expanded"].connect(update_expanded_files);
211
		d_parent_commit_notify = d_commit_details.notify["parent-commit"].connect(parent_commit_changed);
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
212
213

		bind_property("use-gravatar", d_commit_details, "use-gravatar", BindingFlags.SYNC_CREATE);
214
215
216
217
218
219
220
221
		d_text_view_message.event_after.connect (on_event_after);
		d_text_view_message.key_press_event.connect (on_key_press);
		d_text_view_message.motion_notify_event.connect (on_motion_notify_event);
		d_text_view_message.has_tooltip = true;
		d_text_view_message.query_tooltip.connect (on_query_tooltip_event);
		d_text_view_message.style_updated.connect (load_colors_from_theme);

		load_colors_from_theme(d_text_view_message);
222
223
224

		d_event_box.motion_notify_event.connect(motion_notify_event_on_event_box);
		d_diff_view_options.view = this;
225
226
	}

227
228
229
230
231
232
233
234
235
236
	public override void dispose()
	{
		if (d_cancellable != null)
		{
			d_cancellable.cancel();
		}

		base.dispose();
	}

237
238
	private void parent_commit_changed()
	{
239
		update(false);
240
241
242
243
244
245
246
247
	}

	private void update_expanded_files()
	{
		var expanded = d_commit_details.expanded;

		foreach (var file in d_grid_files.get_children())
		{
248
			((Gitg.DiffViewFile) file).expanded = expanded;
249
250
251
		}
	}

252
253
254
255
	private static Regex s_message_regexp;

	static construct
	{
256
257
258
259
260
261
262
263
264
265
		s_image_mime_types = new Gee.HashSet<string>();

		foreach (var format in Gdk.Pixbuf.get_formats())
		{
			foreach (var mime_type in format.get_mime_types())
			{
				s_image_mime_types.add(mime_type);
			}
		}

266
267
		try
		{
268
			s_message_regexp = new Regex(".*(\\R|\\s)*(?P<message>(?:.|\\R)*?)\\s*$");
269
270
271
		} catch (Error e) { stderr.printf(@"Failed to compile regex: $(e.message)\n"); }
	}

272
273
274
275
276
	construct
	{
		context_lines = 3;
	}

277
278
279
280
281
282
283
284
285
286
287
288
289
	private string message_without_subject(Commit commit)
	{
		var message = commit.get_message();
		MatchInfo minfo;

		if (s_message_regexp.match(message, 0, out minfo))
		{
			return minfo.fetch_named("message");
		}

		return "";
	}

290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
	private bool on_query_tooltip_event(int x, int y, bool keyboard_tooltip, Gtk.Tooltip tooltip)
	{
		Gtk.TextIter iter;

		if (d_text_view_message.get_iter_at_location (out iter, x, y))
		{
			var tags = iter.get_tags ();
			foreach (Gtk.TextTag tag in tags)
			{
				if (tag.get_data<string>("type") == "url" && tag.get_data<bool>("is_custom_link"))
				{
					string url = tag.get_data<string>("url");
					tooltip.set_text (url);
					return true;
				}
			}
		}
		return false;
	}

	public void apply_link_tags(Gtk.TextBuffer buffer, Regex regex, string? replacement, Gdk.RGBA custom_color_link, bool is_custom_color, bool is_custom_link)
	{
		try
		{
			GLib.MatchInfo matchInfo;

			var buffer_text = buffer.text;
			regex.match (buffer_text, 0, out matchInfo);

			while (matchInfo.matches ())
			{
				Gtk.TextIter start, end;
				int start_pos, end_pos;
				string text = matchInfo.fetch(0);
				matchInfo.fetch_pos (0, out start_pos, out end_pos);
Alberto Fanjul's avatar
Alberto Fanjul committed
325
326
				buffer.get_iter_at_offset(out start, start_pos -1);
				buffer.get_iter_at_offset(out end, end_pos -1);
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524

				var tag = buffer.create_tag(null, "underline", Pango.Underline.SINGLE);
				tag.foreground_rgba = custom_color_link;
				tag.set_data("type", "url");
				tag.set_data<Gdk.RGBA?>("color_link", custom_color_link);
				if (replacement != null)
				{
					text = regex.replace(text, text.length, 0, replacement);
				}
				tag.set_data("url", text);
				tag.set_data("is_custom_color_link", is_custom_color);
				tag.set_data("is_custom_link", is_custom_link);
				buffer.apply_tag(tag, start, end);

				matchInfo.next();
			}
		}
		catch(Error e)
		{
		}
	}

	private void load_colors_from_theme(Gtk.Widget widget)
	{
		Gtk.TextView textview = (Gtk.TextView)widget;
		Gtk.StyleContext context = textview.get_style_context ();

		context.save ();
		context.set_state (Gtk.StateFlags.LINK);
		d_color_link = context.get_color (context.get_state ());

		context.set_state (Gtk.StateFlags.LINK | Gtk.StateFlags.PRELIGHT);
		color_hovered_link = context.get_color (context.get_state ());
		context.restore ();

		textview.buffer.tag_table.foreach ((tag) =>
		{
			if (!tag.get_data<bool>("is_custom_color_link"))
			{
				tag.set_data<Gdk.RGBA?>("color_link", d_color_link);
				tag.foreground_rgba = d_color_link;
			}
		});
	}

	private bool on_key_press (Gtk.Widget widget, Gdk.EventKey evt)
	{
		if (evt.keyval == Gdk.Key.Return || evt.keyval == Gdk.Key.KP_Enter)
		{

			Gtk.TextIter iter;
			Gtk.TextView textview = (Gtk.TextView) widget;
			textview.buffer.get_iter_at_mark(out iter, textview.buffer.get_insert());

			follow_if_link (widget, iter);
		}
		return false;
	}

	public void follow_if_link(Gtk.Widget texview, Gtk.TextIter iter)
	{
		var tags = iter.get_tags ();
		foreach (Gtk.TextTag tag in tags)
		{
			if (tag.get_data<string>("type") == "url")
			{
				string url = tag.get_data<string>("url");
				try
				{
					GLib.AppInfo.launch_default_for_uri(url, null);
				}
				catch(Error e)
				{
					warning ("Cannot open %s: %s", url, e.message);
				}
			}
		}

	}

	private void on_event_after (Gtk.Widget widget, Gdk.Event evt)
	{

		Gtk.TextIter start, end, iter;
		Gtk.TextBuffer buffer;
		double ex, ey;
		int x, y;

		if (evt.type == Gdk.EventType.BUTTON_RELEASE)
		{
			Gdk.EventButton event;

			event = (Gdk.EventButton)evt;
			if (event.button != Gdk.BUTTON_PRIMARY)
			return;

			ex = event.x;
			ey = event.y;
		}
		else if (evt.type == Gdk.EventType.TOUCH_END)
		{
			Gdk.EventTouch event;

			event = (Gdk.EventTouch)evt;

			ex = event.x;
			ey = event.y;
		}
		else
		{
			return;
		}

		Gtk.TextView textview = (Gtk.TextView)widget;
		buffer = textview.buffer;

		/* we shouldn't follow a link if the user has selected something */
		buffer.get_selection_bounds (out start, out end);
		if (start.get_offset () != end.get_offset ())
		{
			return;
		}

		textview.window_to_buffer_coords (Gtk.TextWindowType.WIDGET,(int)ex, (int)ey, out x, out y);

		if (textview.get_iter_at_location (out iter, x, y))
		{
			follow_if_link (textview, iter);
		}
	}

	private bool on_motion_notify_event (Gtk.Widget widget, Gdk.EventMotion evt)
	{
		int x, y;

		Gtk.TextView textview = ((Gtk.TextView)widget);

		textview.window_to_buffer_coords (Gtk.TextWindowType.WIDGET,(int)evt.x, (int)evt.y, out x, out y);

		Gtk.TextIter iter;
		bool hovering = false;

		if (textview.get_iter_at_location (out iter, x, y))
		{
			var tags = iter.get_tags ();
			foreach (Gtk.TextTag tag in tags)
			{
				if (tag.get_data<string>("type") == "url")
				{
					hovering = true;
					if (hover_tag != null && hover_tag != tag)
					{
						restore_tag_color_link (hover_tag);
						hovering_over_link = false;
					}
					hover_tag = tag;
					break;
				}
			}
		}

		if (hovering != hovering_over_link)
		{
			hovering_over_link = hovering;

			Gdk.Display display = textview.get_display();
			Gdk.Cursor hand_cursor = new Gdk.Cursor.from_name (display, "pointer");
			Gdk.Cursor regular_cursor = new Gdk.Cursor.from_name (display, "text");

			Gdk.Window window = textview.get_window (Gtk.TextWindowType.TEXT);
			if (hovering_over_link)
			{
				window.set_cursor (hand_cursor);
				if (hover_tag != null)
				{
					hover_tag.foreground_rgba = color_hovered_link;
				}
			}
			else
			{
				window.set_cursor (regular_cursor);
				if (hover_tag != null)
				{
					restore_tag_color_link (hover_tag);
					hover_tag = null;
				}
			}
		}

		return true;
	}

	private void restore_tag_color_link (Gtk.TextTag tag)
	{
		Gdk.RGBA? color = tag.get_data<Gdk.RGBA?>("color_link");
		tag.foreground_rgba = color;
	}

525
	private void update(bool preserve_expanded)
526
	{
527

528
529
530
		// If both `d_diff` and `d_commit` are null, clear
		// the diff content
		if (d_diff == null && d_commit == null)
531
		{
532
533
			d_commit_details.hide();
			d_scrolledwindow.hide();
534
			return;
535
536
		}

537
538
		d_commit_details.show();
		d_scrolledwindow.show();
539

540
541
542
		// Cancel running operations
		d_cancellable.cancel();
		d_cancellable = new Cancellable();
543

544
		if (d_commit != null)
545
		{
546
547
548
549
			SignalHandler.block(d_commit_details, d_parent_commit_notify);
			d_commit_details.commit = d_commit;
			SignalHandler.unblock(d_commit_details, d_parent_commit_notify);

550
551
			int parent = 0;
			var parents = d_commit.get_parents();
552

553
554
555
			var parent_commit = d_commit_details.parent_commit;

			if (parent_commit != null)
556
			{
557
				for (var i = 0; i < parents.size; i++)
558
				{
559
					var id = parents.get_id(i);
560

561
					if (id.equal(parent_commit.get_id()))
562
563
564
					{
						parent = i;
						break;
565
566
					}
				}
567
568
			}

569
570
			d_diff = d_commit.get_diff(options, parent);
			d_commit_details.show();
571
572
573
574

			var message = message_without_subject(d_commit);

			d_text_view_message.buffer.set_text(message);
575
576
577
578
			var buffer = d_text_view_message.get_buffer();

			apply_link_tags(buffer, regex_url, null, d_color_link, false, false);

579
			parse_smart_text(buffer);
580

581
			d_text_view_message.visible = (message != "");
582
		}
583
		else
584
		{
585
586
			d_commit_details.commit = null;
			d_commit_details.hide();
587
588

			d_text_view_message.hide();
589
590
		}

591
		if (d_diff != null)
592
		{
593
			update_diff(d_diff, preserve_expanded, d_cancellable);
594
		}
595
	}
596

597
	private void parse_smart_text(Gtk.TextBuffer buffer)
598
	{
599
		if (repository != null)
600
		{
Gaurav Agrawal's avatar
Gaurav Agrawal committed
601
			try
602
			{
Alberto Fanjul's avatar
Alberto Fanjul committed
603
				var conf = repository.get_config().snapshot();
604
605
606
607
608
609
				conf.match_foreach(regex_custom_links, (match_info, value) => {
					string group = match_info.fetch(1);
					debug ("found custom-link group: %s", group);
					string custom_link_regexp = value;
					string replacement_key = "gitg.custom-link.%s.replacement".printf(group);
					try
610
					{
611
612
613
614
615
616
						string custom_link_replacement = conf.get_string(replacement_key);
						string color_key = "gitg.custom-link.%s.color".printf(group);
						string custom_color = conf.get_string(color_key);
						Gdk.RGBA color = d_color_link;
						bool is_custom_color = custom_color != null;
						if (is_custom_color)
617
						{
618
619
							color = Gdk.RGBA();
							color.parse(custom_color);
620
						}
621
622
623
624
						apply_link_tags(buffer, new Regex (custom_link_regexp), custom_link_replacement, color, is_custom_color, true);
					} catch (Error e)
					{
						warning ("Cannot read git config: %s", e.message);
625
					}
626
627
					return 0;
				});
Gaurav Agrawal's avatar
Gaurav Agrawal committed
628
629
			} catch (Error e)
			{
630
				warning ("Cannot read git config: %s", e.message);
631
632
633
634
			}
		}
	}

635
	private void auto_change_expanded(bool expanded)
636
	{
637
638
639
640
		SignalHandler.block(d_commit_details, d_expanded_notify);
		d_commit_details.expanded = expanded;
		SignalHandler.unblock(d_commit_details, d_expanded_notify);
	}
641

642
643
644
645
646
647
	private void on_selection_changed()
	{
		bool something_selected = false;

		foreach (var file in d_grid_files.get_children())
		{
648
			if (((Gitg.DiffViewFile) file).has_selection())
649
650
651
652
653
654
			{
				something_selected = true;
				break;
			}
		}

Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
655
		if (has_selection != something_selected)
656
		{
Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
657
			has_selection = something_selected;
658
659
660
		}
	}

661
	private string? primary_path(Ggit.DiffDelta delta)
662
	{
663
		var path = delta.get_old_file().get_path();
664
665
666

		if (path == null)
		{
667
			path = delta.get_new_file().get_path();
668
669
670
671
672
		}

		return path;
	}

673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
	private delegate void Anon();

	private string key_for_delta(Ggit.DiffDelta delta)
	{
		var new_file = delta.get_new_file();
		var new_path = new_file.get_path();

		if (new_path != null)
		{
			return @"path:$(new_path)";
		}

		var old_file = delta.get_old_file();
		var old_path = old_file.get_path();

		if (old_path != null)
		{
			return @"path:$(old_path)";
		}

		return "";
	}

696
	private void update_diff(Ggit.Diff diff, bool preserve_expanded, Cancellable? cancellable)
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
	{
		var nqueries = 0;
		var finished = false;
		var infomap = new Gee.HashMap<string, DiffViewFileInfo>();

		Anon check_finish = () => {
			if (nqueries == 0 && finished && (cancellable == null || !cancellable.is_cancelled()))
			{
				finished = false;
				update_diff_hunks(diff, preserve_expanded, infomap, cancellable);
			}
		};

		// Collect file info asynchronously first
		for (var i = 0; i < diff.get_num_deltas(); i++)
		{
			var delta = diff.get_delta(i);
			var info = new DiffViewFileInfo(repository, delta, new_is_workdir);

			nqueries++;

			info.query.begin(cancellable, (obj, res) => {
				info.query.end(res);

				infomap[key_for_delta(delta)] = info;

				nqueries--;

				check_finish();
			});
		}

		finished = true;
		check_finish();
	}

	private void update_diff_hunks(Ggit.Diff diff, bool preserve_expanded, Gee.HashMap<string, DiffViewFileInfo> infomap, Cancellable? cancellable)
734
735
	{
		var files = new Gee.ArrayList<Gitg.DiffViewFile>();
736

737
738
739
		Gitg.DiffViewFile? current_file = null;
		Ggit.DiffHunk? current_hunk = null;
		Gee.ArrayList<Ggit.DiffLine>? current_lines = null;
740
		var current_is_binary = false;
741

742
		var maxlines = 0;
743

744
745
746
747
		Anon add_hunk = () => {
			if (current_hunk != null)
			{
				current_file.add_hunk(current_hunk, current_lines);
748

749
750
751
752
				current_lines = null;
				current_hunk = null;
			}
		};
753

754
755
		Anon add_file = () => {
			add_hunk();
756

757
			if (current_file != null)
758
			{
759
				current_file.show();
760

761
762
				files.add(current_file);

763
764
765
				current_file = null;
			}
		};
766

767
		try
768
		{
769
770
771
772
773
774
			diff.foreach(
				(delta, progress) => {
					if (cancellable != null && cancellable.is_cancelled())
					{
						return 1;
					}
775

776
					add_file();
777

778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
					DiffViewFileInfo? info = null;
					var deltakey = key_for_delta(delta);

					if (infomap.has_key(deltakey))
					{
						info = infomap[deltakey];
					}
					else
					{
						info = new DiffViewFileInfo(repository, delta, new_is_workdir);
					}

					current_is_binary = ((delta.get_flags() & Ggit.DiffFlag.BINARY) != 0);

					// List of known binary file types that may be wrongly classified by
					// libgit2 because it does not contain any null bytes in the first N
					// bytes. E.g. PDF
					var known_binary_files_types = new string[] {"application/pdf"};

					// Ignore binary based on content type
					if (info != null && info.new_file_content_type in known_binary_files_types)
					{
						current_is_binary = true;
					}

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
					string? mime_type_for_image = null;

					if (info == null || info.new_file_content_type == null)
					{
						// Guess mime type from old file name in the case of a deleted file
						var oldpath = delta.get_old_file().get_path();

						if (oldpath != null)
						{
							bool uncertain;
							var ctype = ContentType.guess(Path.get_basename(oldpath), null, out uncertain);

							if (ctype != null)
							{
								mime_type_for_image = ContentType.get_mime_type(ctype);
							}
						}
					}
					else
					{
						mime_type_for_image = ContentType.get_mime_type(info.new_file_content_type);
					}

826
					bool can_diff_as_image = mime_type_for_image != null && s_image_mime_types.contains(mime_type_for_image);
827
828
829
830
					bool can_diff_as_text = ContentType.is_mime_type(mime_type_for_image, "text/plain");

					current_file = new Gitg.DiffViewFile(info);

831
					if (can_diff_as_image)
832
833
834
					{
						current_file.add_image_renderer();
					}
835
836
837
838
839
					if (!can_diff_as_image && !current_is_binary && !can_diff_as_text)
					{
							//force diff as text if no other diff is possible
							can_diff_as_text = true;
					}
840
					if (can_diff_as_text)
841
					{
842
						current_file.add_text_renderer(handle_selection);
843
844
845
846
847
848
849
850
851
852
853
854
855
						var renderer_list = current_file.renderer_list;
						foreach (DiffViewFileRenderer renderer in renderer_list)
						{
							var renderer_text = renderer as DiffViewFileRendererTextable;
							if (renderer_text != null)
							{
								bind_property("highlight", renderer_text, "highlight", BindingFlags.SYNC_CREATE);
								bind_property("wrap-lines", renderer_text, "wrap-lines", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
								bind_property("tab-width", renderer_text, "tab-width", BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
								renderer_text.maxlines = maxlines;
								renderer_text.notify["has-selection"].connect(on_selection_changed);
							}
						}
856
					}
857
					if (current_is_binary)
858
					{
859
860
861
						try {
							var new_file = delta.get_new_file();
							var old_file = delta.get_old_file();
862

863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
							if (TextConv.has_textconv_command(repository, old_file) || TextConv.has_textconv_command(repository, new_file))
							{
								uint8[] n_textconv = TextConv.get_textconv_content(repository, new_file);
								uint8[] o_textconv = TextConv.get_textconv_content(repository, old_file);

								current_is_binary = false;
								var opts = new Ggit.DiffOptions();
								opts.flags = Ggit.DiffOption.INCLUDE_UNTRACKED |
											Ggit.DiffOption.IGNORE_WHITESPACE |
											Ggit.DiffOption.DISABLE_PATHSPEC_MATCH |
											Ggit.DiffOption.RECURSE_UNTRACKED_DIRS;
								opts.n_context_lines = 3;
								opts.n_interhunk_lines = 3;


								var bdiff = new Ggit.Diff.buffers(o_textconv, old_file.get_path(), n_textconv, new_file.get_path(), opts);
								bdiff.foreach(
									(delta, progress) => {
											if (cancellable != null && cancellable.is_cancelled())
											{
												return 1;
											}
											deltakey = key_for_delta(delta);

											if (infomap.has_key(deltakey))
											{
												info = infomap[deltakey];
											}
											else
											{
												info = new DiffViewFileInfo(repository, delta, new_is_workdir);
											}
												current_file = new Gitg.DiffViewFile(info);
												current_file.add_text_renderer(handle_selection);
												return 0;
											},
									(delta, binary) => {
											if (cancellable != null && cancellable.is_cancelled())
											{
												return 1;
											}
											return 0;
									},
									(delta, hunk) => {
											if (cancellable != null && cancellable.is_cancelled())
											{
												return 1;
											}
											if (!current_is_binary)
											{
												maxlines = int.max(maxlines, hunk.get_old_start() + hunk.get_old_lines());
												maxlines = int.max(maxlines, hunk.get_new_start() + hunk.get_new_lines());

												add_hunk();

												current_hunk = hunk;
												current_lines = new Gee.ArrayList<Ggit.DiffLine>();
											}

											return 0;
									},
									(delta, hunk, line) => {
											if (cancellable != null && cancellable.is_cancelled())
											{
												return 1;
											}
											if (!current_is_binary)
											{
												current_lines.add(line);
											}
											return 0;
									}
								);
								add_hunk();
								add_file();
							}
						} catch (Error error) {
							stderr.printf (@"Error: $(error.message)\n");
						}
						if (current_is_binary)
							current_file.add_binary_renderer();
					}
945
946
					return 0;
				},
947

948
949
950
951
952
953
				(delta, binary) => {
					// FIXME: do we want to handle binary data?
					if (cancellable != null && cancellable.is_cancelled())
					{
						return 1;
					}
954

955
956
					return 0;
				},
957

958
959
960
961
962
				(delta, hunk) => {
					if (cancellable != null && cancellable.is_cancelled())
					{
						return 1;
					}
963

964
965
966
967
					if (!current_is_binary)
					{
						maxlines = int.max(maxlines, hunk.get_old_start() + hunk.get_old_lines());
						maxlines = int.max(maxlines, hunk.get_new_start() + hunk.get_new_lines());
968

969
						add_hunk();
970

971
972
973
						current_hunk = hunk;
						current_lines = new Gee.ArrayList<Ggit.DiffLine>();
					}
974

975
976
					return 0;
				},
977

978
979
980
981
982
				(delta, hunk, line) => {
					if (cancellable != null && cancellable.is_cancelled())
					{
						return 1;
					}
983

984
					if (!current_is_binary)
985
986
987
					{
						current_lines.add(line);
					}
988

989
990
991
992
					return 0;
				}
			);
		} catch {}
993

994
995
		add_hunk();
		add_file();
996

997
998
999
1000
1001
		var file_widgets = d_grid_files.get_children();
		var was_expanded = new Gee.HashSet<string>();

		foreach (var file in file_widgets)
		{
1002
			unowned DiffViewFile f = (DiffViewFile) file;
1003
1004
1005

			if (preserve_expanded && f.expanded)
			{
1006
				var path = primary_path(f.info.delta);
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016

				if (path != null)
				{
					was_expanded.add(path);
				}
			}

			f.destroy();
		}

Jesse van den Kieboom's avatar
Jesse van den Kieboom committed
1017
1018
		d_commit_details.expanded = (files.size <= 1 || !default_collapse_all);
		d_commit_details.expander_visible = (files.size > 1);
1019

1020
		for (var i = 0; i < files.size; i++)
1021
		{
1022
			var file = files[i];
1023
			var path = primary_path(file.info.delta);
1024

1025
			file.expanded = d_commit_details.expanded || (path != null && was_expanded.contains(path));
1026

1027
1028
1029
1030
			if (i == files.size - 1)
			{
				file.vexpand = true;
			}
1031
1032
1033
1034

			d_grid_files.add(file);

			file.notify["expanded"].connect(auto_update_expanded);
1035
1036
1037
1038
1039
1040
1041
		}
	}

	private void auto_update_expanded()
	{
		foreach (var file in d_grid_files.get_children())
		{
1042
			if (!((Gitg.DiffViewFile) file).expanded)
1043
1044
1045
1046
			{
				auto_change_expanded(false);
				return;
			}
1047
		}
1048
1049

		auto_change_expanded(true);
1050
	}
1051

1052
	public PatchSet[] get_selection()
1053
	{
1054
1055
1056
		var ret = new PatchSet[0];

		foreach (var file in d_grid_files.get_children())
1057
		{
1058
			ret += ((Gitg.DiffViewFile)file).get_selection();
1059
		}
1060
1061

		return ret;
1062
	}
1063

1064
	private void update_hide_show_options(Gdk.Window window, int ex, int ey)
1065
	{
1066
1067
		void *data;
		window.get_user_data(out data);
1068

1069
		var w = data as Gtk.Widget;
1070

1071
		if (w == null)
1072
		{
1073
			return;
1074
1075
		}

1076
1077
1078
1079
1080
1081
1082
		int x, y;
		w.translate_coordinates(d_event_box, ex, ey, out x, out y);

		Gtk.Allocation alloc, revealer_alloc;

		d_event_box.get_allocation(out alloc);
		d_revealer_options.get_allocation(out revealer_alloc);
1083

1084
		if (!d_revealer_options.reveal_child && y >= alloc.height - 18 && x >= alloc.width - 150 && d_reveal_options_timeout == 0)
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
		{
			if (d_unreveal_options_timeout != 0)
			{
				Source.remove(d_unreveal_options_timeout);
				d_unreveal_options_timeout = 0;
			}

			d_reveal_options_timeout = Timeout.add(300, () => {
				d_reveal_options_timeout = 0;
				d_revealer_options.reveal_child = true;
				return false;
			});
		}
		else if (d_revealer_options.reveal_child)
		{
			var above = (y <= alloc.height - 6 - revealer_alloc.height);

			if (above && d_unreveal_options_timeout == 0)
			{
				if (d_reveal_options_timeout != 0)
				{
					Source.remove(d_reveal_options_timeout);
					d_reveal_options_timeout = 0;
				}

				d_unreveal_options_timeout = Timeout.add(1000, () => {
					d_unreveal_options_timeout = 0;
					d_revealer_options.reveal_child = false;
					return false;
				});
			}
			else if (!above && d_unreveal_options_timeout != 0)
			{
				Source.remove(d_unreveal_options_timeout);
				d_unreveal_options_timeout = 0;
			}
		}
1122
1123
1124
	}

	[GtkCallback]
1125
	private bool leave_notify_event_on_event_box(Gtk.Widget widget, Gdk.EventCrossing event)
1126
	{
1127
1128
1129
		update_hide_show_options(event.window, (int)event.x, (int)event.y);
		return false;
	}
1130

1131
1132
1133
1134
	[GtkCallback]
	private bool enter_notify_event_on_event_box(Gtk.Widget widget, Gdk.EventCrossing event)
	{
		update_hide_show_options(event.window, (int)event.x, (int)event.y);
1135
1136
1137
		return false;
	}

1138
1139
	[GtkCallback]
	private bool motion_notify_event_on_event_box(Gtk.Widget widget, Gdk.EventMotion event)
1140
	{
1141
1142
		update_hide_show_options(event.window, (int)event.x, (int)event.y);
		return false;
1143
	}
1144
1145
}

Paolo Borelli's avatar
Paolo Borelli committed
1146
// ex:ts=4 noet