1/*
2 * Copyright (C) 2008-2010 Abderrahim Kitouni
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18using GLib;
19using Gtk;
20using Anjuta;
21
22public class ValaPlugin : Plugin, IAnjuta.Preferences {
23	internal const string PREF_WIDGET_SPACE = "preferences:completion-space-after-func";
24	internal const string PREF_WIDGET_BRACE = "preferences:completion-brace-after-func";
25	internal const string PREF_WIDGET_AUTO = "preferences:completion-enable";
26	internal const string ICON_FILE = "anjuta-vala.png";
27	internal static string PREFS_BUILDER = Config.PACKAGE_DATA_DIR + "/glade/anjuta-vala.ui";
28
29	internal weak IAnjuta.Editor current_editor;
30	internal GLib.Settings settings = new GLib.Settings ("org.gnome.anjuta.plugins.vala");
31	uint editor_watch_id;
32	ulong project_loaded_id;
33
34	Vala.CodeContext context;
35	Cancellable cancel;
36	BlockLocator locator = new BlockLocator ();
37
38	AnjutaReport report;
39	ValaProvider provider;
40
41	Vala.Parser parser;
42	Vala.Genie.Parser genie_parser;
43
44	public static Gtk.Builder bxml;
45
46	Vala.Set<string> current_sources = new Vala.HashSet<string> (str_hash, str_equal);
47	ValaPlugin () {
48		Object ();
49	}
50	public override bool activate () {
51		debug("Activating ValaPlugin");
52		report = new AnjutaReport();
53		report.docman = (IAnjuta.DocumentManager) shell.get_object("IAnjutaDocumentManager");
54		parser = new Vala.Parser ();
55		genie_parser = new Vala.Genie.Parser ();
56
57		init_context ();
58
59		provider = new ValaProvider(this);
60		editor_watch_id = add_watch("document_manager_current_document",
61		                            editor_value_added,
62		                            editor_value_removed);
63
64		return true;
65	}
66
67	public override bool deactivate () {
68		debug("Deactivating ValaPlugin");
69		remove_watch(editor_watch_id, true);
70
71		cancel.cancel ();
72		lock (context) {
73			context = null;
74		}
75
76		return true;
77	}
78
79	void init_context () {
80		context = new Vala.CodeContext();
81		context.profile = Vala.Profile.GOBJECT;
82		context.report = report;
83		report.clear_error_indicators ();
84
85		cancel = new Cancellable ();
86
87		/* This doesn't actually parse anything as there are no files yet,
88		   it's just to set the context in the parsers */
89		parser.parse (context);
90		genie_parser.parse (context);
91
92		current_sources = new Vala.HashSet<string> (str_hash, str_equal);
93
94	}
95
96	void parse () {
97		try {
98			Thread.create<void>(() => {
99				lock (context) {
100					Vala.CodeContext.push(context);
101					var report = context.report as AnjutaReport;
102
103					foreach (var src in context.get_source_files ()) {
104						if (src.get_nodes ().size == 0) {
105							debug ("parsing file %s", src.filename);
106							genie_parser.visit_source_file (src);
107							parser.visit_source_file (src);
108						}
109
110						if (cancel.is_cancelled ()) {
111							Vala.CodeContext.pop();
112							return;
113						}
114					}
115
116					if (report.get_errors () > 0 || cancel.is_cancelled ()) {
117						Vala.CodeContext.pop();
118						return;
119					}
120
121					context.check ();
122					Vala.CodeContext.pop();
123				}
124			}, false);
125		} catch (ThreadError err) {
126			warning ("cannot create thread : %s", err.message);
127		}
128	}
129
130	void add_project_files () {
131		var pm = (IAnjuta.ProjectManager) shell.get_object("IAnjutaProjectManager");
132		var project = pm.get_current_project ();
133		var current_file = (current_editor as IAnjuta.File).get_file ();
134		if (project == null)
135			return;
136
137		Vala.CodeContext.push (context);
138
139		var current_src = project.get_root ().get_source_from_file (current_file);
140		if (current_src == null)
141			return;
142
143		var current_target = current_src.parent_type (Anjuta.ProjectNodeType.TARGET);
144		if (current_target == null)
145			return;
146
147		current_target.foreach (TraverseType.PRE_ORDER, (node) => {
148			if (!(Anjuta.ProjectNodeType.SOURCE in node.get_node_type ()))
149				return;
150
151			if (node.get_file () == null)
152				return;
153
154			var path = node.get_file ().get_path ();
155			if (path == null)
156				return;
157
158			if (path.has_suffix (".vala") || path.has_suffix (".vapi") || path.has_suffix (".gs")) {
159				if (path in current_sources) {
160					debug ("file %s already added", path);
161				} else {
162					context.add_source_filename (path);
163					current_sources.add (path);
164					debug ("file %s added", path);
165				}
166			} else {
167				debug ("file %s skipped", path);
168			}
169		});
170
171		if (!context.has_package ("gobject-2.0")) {
172			context.add_external_package("glib-2.0");
173			context.add_external_package("gobject-2.0");
174			debug ("standard packages added");
175		} else {
176			debug ("standard packages already added");
177		}
178
179		string[] flags = {};
180		unowned Anjuta.ProjectProperty prop = current_target.get_property ("VALAFLAGS");
181		if (prop != null && prop != prop.info.default_value) {
182			GLib.Shell.parse_argv (prop.value, out flags);
183		} else {
184			/* Fall back to AM_VALAFLAGS */
185			var current_group = current_target.parent_type (Anjuta.ProjectNodeType.GROUP);
186			prop = current_group.get_property ("VALAFLAGS");
187			if (prop != null && prop != prop.info.default_value)
188				GLib.Shell.parse_argv (prop.value, out flags);
189		}
190
191		string[] packages = {};
192		string[] vapidirs = {};
193
194		for (int i = 0; i < flags.length; i++) {
195			if (flags[i] == "--vapidir")
196				vapidirs += flags[++i];
197			else if (flags[i].has_prefix ("--vapidir="))
198				vapidirs += flags[i].substring ("--vapidir=".length);
199			else if (flags[i] == "--pkg")
200				packages += flags[++i];
201			else if (flags[i].has_prefix ("--pkg="))
202				packages += flags[i].substring ("--pkg=".length);
203			else
204				debug ("Unknown valac flag %s", flags[i]);
205		}
206
207		var srcdir = current_target.parent_type (Anjuta.ProjectNodeType.GROUP).get_file ().get_path ();
208		var top_srcdir = project.get_root ().get_file ().get_path ();
209		for (int i = 0; i < vapidirs.length; i++) {
210			vapidirs[i] = vapidirs[i].replace ("$(srcdir)", srcdir)
211			                         .replace ("$(top_srcdir)", top_srcdir);
212		}
213
214		context.vapi_directories = vapidirs;
215		foreach (var pkg in packages) {
216			if (context.has_package (pkg)) {
217				debug ("package %s skipped", pkg);
218			} else if (context.add_external_package(pkg)) {
219				debug ("package %s added", pkg);
220			} else {
221				debug ("package %s not found", pkg);
222			}
223		}
224		Vala.CodeContext.pop();
225	}
226
227	public void on_project_loaded (IAnjuta.ProjectManager pm, Error? e) {
228		if (context == null)
229			return;
230		add_project_files ();
231		parse ();
232		pm.disconnect (project_loaded_id);
233		project_loaded_id = 0;
234	}
235
236	/* "document_manager_current_document" watch */
237	public void editor_value_added (Anjuta.Plugin plugin, string name, Value value) {
238		debug("editor value added");
239		assert (current_editor == null);
240		if (!(value.get_object() is IAnjuta.Editor)) {
241			/* a glade document, for example, isn't an editor */
242			return;
243		}
244
245		current_editor = value.get_object() as IAnjuta.Editor;
246		var current_file = value.get_object() as IAnjuta.File;
247
248		var pm = (IAnjuta.ProjectManager) shell.get_object("IAnjutaProjectManager");
249		var project = pm.get_current_project ();
250
251		if (!project.is_loaded()) {
252			if (project_loaded_id == 0)
253				project_loaded_id = pm.project_loaded.connect (on_project_loaded);
254		} else {
255			var cur_gfile = current_file.get_file ();
256			if (cur_gfile == null) {
257				// File hasn't been saved yet
258				return;
259			}
260
261			if (!(cur_gfile.get_path () in current_sources)) {
262				cancel.cancel ();
263				lock (context) {
264					init_context ();
265					add_project_files ();
266				}
267
268				parse ();
269			}
270		}
271		if (current_editor != null) {
272			if (current_editor is IAnjuta.EditorAssist)
273				(current_editor as IAnjuta.EditorAssist).add(provider);
274			if (current_editor is IAnjuta.EditorTip)
275				current_editor.char_added.connect (on_char_added);
276			if (current_editor is IAnjuta.FileSavable) {
277				var file_savable = (IAnjuta.FileSavable) current_editor;
278				file_savable.saved.connect (on_file_saved);
279			}
280			if (current_editor is IAnjuta.EditorGladeSignal) {
281				var gladesig = current_editor as IAnjuta.EditorGladeSignal;
282				gladesig.drop_possible.connect (on_drop_possible);
283				gladesig.drop.connect (on_drop);
284			}
285			current_editor.glade_member_add.connect (insert_member_decl_and_init);
286		}
287		report.update_errors (current_editor);
288	}
289	public void editor_value_removed (Anjuta.Plugin plugin, string name) {
290		debug("editor value removed");
291		if (current_editor is IAnjuta.EditorAssist)
292			(current_editor as IAnjuta.EditorAssist).remove(provider);
293		if (current_editor is IAnjuta.EditorTip)
294			current_editor.char_added.disconnect (on_char_added);
295		if (current_editor is IAnjuta.FileSavable) {
296			var file_savable = (IAnjuta.FileSavable) current_editor;
297			file_savable.saved.disconnect (on_file_saved);
298		}
299		if (current_editor is IAnjuta.EditorGladeSignal) {
300			var gladesig = current_editor as IAnjuta.EditorGladeSignal;
301			gladesig.drop_possible.disconnect (on_drop_possible);
302			gladesig.drop.disconnect (on_drop);
303		}
304		current_editor.glade_member_add.disconnect (insert_member_decl_and_init);
305		current_editor = null;
306	}
307
308	public void on_file_saved (IAnjuta.FileSavable savable, File file) {
309		foreach (var source_file in context.get_source_files ()) {
310			if (source_file.filename != file.get_path())
311				continue;
312
313			uint8[] contents;
314			try {
315				file.load_contents (null, out contents, null);
316				source_file.content = (string) contents;
317				update_file (source_file);
318			} catch (Error e) {
319				// ignore
320			}
321			return;
322		}
323	}
324
325	public void on_char_added (IAnjuta.Editor editor, IAnjuta.Iterable position, char ch) {
326		if (!settings.get_boolean (ValaProvider.PREF_CALLTIP_ENABLE))
327			return;
328
329		var editortip = editor as IAnjuta.EditorTip;
330		if (ch == '(') {
331			provider.show_call_tip (editortip);
332		} else if (ch == ')') {
333			editortip.cancel ();
334		}
335	}
336
337	/* tries to find the opening brace of the scope the current position before calling
338	 * get_current_context since the source_reference of a class or namespace only
339	 * contain the declaration not the entire "content" */
340	Vala.Symbol? get_scope (IAnjuta.Editor editor, IAnjuta.Iterable position) {
341		var depth = 0;
342		do {
343			var current_char = (position as IAnjuta.EditorCell).get_character ();
344			if (current_char == "}") {
345				depth++;
346			} else if (current_char == "{") {
347				if (depth > 0) {
348					depth--;
349				} else {
350					// a scope which contains the current position
351					do {
352						position.previous ();
353						current_char = (position as IAnjuta.EditorCell).get_character ();
354					} while (! current_char.get_char ().isalnum ());
355					return get_current_context (editor, position);
356				}
357			}
358		} while (position.previous ());
359		return null;
360	}
361
362	public bool on_drop_possible (IAnjuta.EditorGladeSignal editor, IAnjuta.Iterable position) {
363		var line = editor.get_line_from_position (position);
364		var column = editor.get_line_begin_position (line).diff (position);
365		debug ("line %d, column %d", line, column);
366
367		var scope = get_scope (editor, position.clone ());
368		if (scope != null)
369			debug ("drag is inside %s", scope.get_full_name ());
370		if (scope == null || scope is Vala.Namespace || scope is Vala.Class)
371			return true;
372
373		return false;
374	}
375
376	public void on_drop (IAnjuta.EditorGladeSignal editor, IAnjuta.Iterable position, string signal_data) {
377		var data = signal_data.split (":");
378		var widget_name = data[0];
379		var signal_name = data[1].replace ("-", "_");
380		var handler_name = data[2];
381		var swapped = (data[4] == "1");
382		var scope = get_scope (editor, position.clone ());
383		var builder = new StringBuilder ();
384
385#if VALA_0_38
386		var handler_cname = "";
387#else
388		var scope_prefix = "";
389		if (scope != null) {
390			scope_prefix = Vala.CCodeBaseModule.get_ccode_lower_case_prefix (scope);
391			if (handler_name.has_prefix (scope_prefix))
392				handler_name = handler_name.substring (scope_prefix.length);
393		}
394		var handler_cname = scope_prefix + handler_name;
395#endif
396
397		if (data[2] != handler_cname && !swapped) {
398			builder.append_printf ("[CCode (cname=\"%s\", instance_pos=-1)]\n", data[2]);
399		} else if (data[2] != handler_cname) {
400			builder.append_printf ("[CCode (cname=\"%s\")]\n", data[2]);
401		} else if (!swapped) {
402			builder.append ("[CCode (instance_pos=-1)]\n");
403		}
404
405		var widget = lookup_symbol_by_cname (widget_name);
406		var sigs = symbol_lookup_inherited (widget, signal_name, false);
407		if (sigs == null || !(sigs.data is Vala.Signal))
408			return;
409		Vala.Signal sig = (Vala.Signal) sigs.data;
410
411		builder.append_printf ("public void %s (", handler_name);
412
413		if (swapped) {
414			builder.append_printf ("%s sender", widget.get_full_name ());
415
416			foreach (var param in sig.get_parameters ()) {
417				builder.append_printf (", %s %s", param.variable_type.data_type.get_full_name (), param.name);
418			}
419		} else {
420			foreach (var param in sig.get_parameters ()) {
421				builder.append_printf ("%s %s, ", param.variable_type.data_type.get_full_name (), param.name);
422			}
423
424			builder.append_printf ("%s sender", widget.get_full_name ());
425		}
426
427		builder.append_printf (") {\n\n}\n");
428
429		editor.insert (position, builder.str, -1);
430
431		var indenter = shell.get_object ("IAnjutaIndenter") as IAnjuta.Indenter;
432		if (indenter != null) {
433			var end = position.clone ();
434			/* -1 so we don't count the last newline (as that would indent the line after) */
435			end.set_position (end.get_position () + builder.str.char_count () - 1);
436			indenter.indent (position, end);
437		}
438
439		var inside = editor.get_line_end_position (editor.get_line_from_position (position) + 2);
440		editor.goto_position (inside);
441		if (indenter != null)
442			indenter.indent (inside, inside);
443	}
444
445	const string DECL_MARK = "/* ANJUTA: Widgets declaration for %s - DO NOT REMOVE */\n";
446	const string INIT_MARK = "/* ANJUTA: Widgets initialization for %s - DO NOT REMOVE */\n";
447
448	void insert_member_decl_and_init (IAnjuta.Editor editor, string widget_ctype, string widget_name, string filename) {
449		var widget_type = lookup_symbol_by_cname (widget_ctype).get_full_name ();
450		var basename = Path.get_basename (filename);
451
452		string member_decl = "%s %s;\n".printf (widget_type, widget_name);
453		string member_init = "%s = builder.get_object(\"%s\") as %s;\n".printf (widget_name, widget_name, widget_type);
454
455		insert_after_mark (editor, DECL_MARK.printf (basename), member_decl)
456		  && insert_after_mark (editor, INIT_MARK.printf (basename), member_init);
457	}
458
459	bool insert_after_mark (IAnjuta.Editor editor, string mark, string code_to_add) {
460		var search_start = editor.get_start_position () as IAnjuta.EditorCell;
461		var search_end = editor.get_end_position () as IAnjuta.EditorCell;
462
463		IAnjuta.EditorCell result_end;
464		(editor as IAnjuta.EditorSearch).forward (mark, false, search_start, search_end, null, out result_end);
465
466		var mark_position = result_end as IAnjuta.Iterable;
467		if (mark_position == null)
468			return false;
469
470		editor.insert (mark_position, code_to_add, -1);
471
472		var indenter = shell.get_object ("IAnjutaIndenter") as IAnjuta.Indenter;
473		if (indenter != null) {
474			var end = mark_position.clone ();
475			/* -1 so we don't count the last newline (as that would indent the line after) */
476			end.set_position (end.get_position () + code_to_add.char_count () - 1);
477			indenter.indent (mark_position, end);
478		}
479
480		/* Emit code-added signal, so symbols will be updated */
481		editor.code_added (mark_position, code_to_add);
482
483		return true;
484	}
485
486	Vala.Symbol? lookup_symbol_by_cname (string cname, Vala.Symbol parent=context.root) {
487		var sym = parent.scope.lookup (cname);
488		if (sym != null)
489			return sym;
490
491		var symtab = parent.scope.get_symbol_table ();
492		foreach (var name in symtab.get_keys ()) {
493			if (cname.has_prefix (name)) {
494				return lookup_symbol_by_cname (cname.substring (name.length), parent.scope.lookup (name));
495			}
496		}
497		return null;
498	}
499
500	internal Vala.Symbol get_current_context (IAnjuta.Editor editor, IAnjuta.Iterable? position=null) requires (editor is IAnjuta.File) {
501		var file = editor as IAnjuta.File;
502
503		var path = file.get_file().get_path();
504		lock (context) {
505			Vala.SourceFile source = null;
506			foreach (var src in context.get_source_files()) {
507				if (src.filename == path) {
508					source = src;
509					break;
510				}
511			}
512			if (source == null) {
513				source = new Vala.SourceFile (context,
514				                              path.has_suffix("vapi") ? Vala.SourceFileType.PACKAGE:
515					                                                    Vala.SourceFileType.SOURCE,
516				                              path);
517				context.add_source_file(source);
518				update_file(source);
519			}
520			int line; int column;
521			if (position == null) {
522				line = editor.get_lineno ();
523				column = editor.get_column ();
524			} else {
525				line = editor.get_line_from_position (position);
526				column = editor.get_line_begin_position (line).diff (position);
527			}
528			return locator.locate(source, line, column);
529		}
530	}
531
532	internal List<Vala.Symbol> lookup_symbol (Vala.Expression? inner, string name, bool prefix_match,
533	                                          Vala.Block? block) {
534		var matching_symbols = new List<Vala.Symbol> ();
535
536		if (block == null) return matching_symbols;
537
538		lock (context) {
539			if (inner == null) {
540				for (var sym = (Vala.Symbol) block; sym != null; sym = sym.parent_symbol) {
541					matching_symbols.concat (symbol_lookup_inherited (sym, name, prefix_match));
542				}
543
544				foreach (var ns in block.source_reference.file.current_using_directives) {
545					matching_symbols.concat (symbol_lookup_inherited (ns.namespace_symbol, name, prefix_match));
546				}
547			} else if (inner.symbol_reference != null) {
548					matching_symbols.concat (symbol_lookup_inherited (inner.symbol_reference, name, prefix_match));
549			} else if (inner is Vala.MemberAccess) {
550				var inner_ma = (Vala.MemberAccess) inner;
551				var matching = lookup_symbol (inner_ma.inner, inner_ma.member_name, false, block);
552				if (matching != null)
553					matching_symbols.concat (symbol_lookup_inherited (matching.data, name, prefix_match));
554			} else if (inner is Vala.MethodCall) {
555				var inner_inv = (Vala.MethodCall) inner;
556				var inner_ma = inner_inv.call as Vala.MemberAccess;
557				if (inner_ma != null) {
558					var matching = lookup_symbol (inner_ma.inner, inner_ma.member_name, false, block);
559					if (matching != null)
560						matching_symbols.concat (symbol_lookup_inherited (matching.data, name, prefix_match, true));
561				}
562			}
563		}
564		return matching_symbols;
565	}
566	List<Vala.Symbol> symbol_lookup_inherited (Vala.Symbol? sym, string name, bool prefix_match, bool invocation = false) {
567		List<Vala.Symbol> result = null;
568
569		// This may happen if we cannot find all the needed packages
570		if (sym == null)
571			return result;
572
573		var symbol_table = sym.scope.get_symbol_table ();
574		if (symbol_table != null) {
575			foreach (string key in symbol_table.get_keys()) {
576				if (((prefix_match && key.has_prefix (name)) || key == name)) {
577					result.append (symbol_table[key]);
578				}
579			}
580		}
581		if (invocation && sym is Vala.Method) {
582			var func = (Vala.Method) sym;
583			result.concat (symbol_lookup_inherited (func.return_type.data_type, name, prefix_match));
584		} else if (sym is Vala.Class) {
585			var cl = (Vala.Class) sym;
586			foreach (var base_type in cl.get_base_types ()) {
587				result.concat (symbol_lookup_inherited (base_type.data_type, name, prefix_match));
588			}
589		} else if (sym is Vala.Struct) {
590			var st = (Vala.Struct) sym;
591			result.concat (symbol_lookup_inherited (st.base_type.data_type, name, prefix_match));
592		} else if (sym is Vala.Interface) {
593			var iface = (Vala.Interface) sym;
594			foreach (var prerequisite in iface.get_prerequisites ()) {
595				result.concat (symbol_lookup_inherited (prerequisite.data_type, name, prefix_match));
596			}
597		} else if (sym is Vala.LocalVariable) {
598			var variable = (Vala.LocalVariable) sym;
599			result.concat (symbol_lookup_inherited (variable.variable_type.data_type, name, prefix_match));
600		} else if (sym is Vala.Field) {
601			var field = (Vala.Field) sym;
602			result.concat (symbol_lookup_inherited (field.variable_type.data_type, name, prefix_match));
603		} else if (sym is Vala.Property) {
604			var prop = (Vala.Property) sym;
605			result.concat (symbol_lookup_inherited (prop.property_type.data_type, name, prefix_match));
606		} else if (sym is Vala.Parameter) {
607			var fp = (Vala.Parameter) sym;
608			result.concat (symbol_lookup_inherited (fp.variable_type.data_type, name, prefix_match));
609		}
610
611		return result;
612	}
613	void update_file (Vala.SourceFile file) {
614		lock (context) {
615			/* Removing nodes in the same loop causes problems (probably due to ReadOnlyList)*/
616			var nodes = new Vala.ArrayList<Vala.CodeNode> ();
617			foreach (var node in file.get_nodes()) {
618				nodes.add(node);
619			}
620			foreach (var node in nodes) {
621				file.remove_node (node);
622				if (node is Vala.Symbol) {
623					var sym = (Vala.Symbol) node;
624					if (sym.owner != null)
625						/* we need to remove it from the scope*/
626						sym.owner.remove(sym.name);
627					if (context.entry_point == sym)
628						context.entry_point = null;
629				}
630			}
631			file.current_using_directives = new Vala.ArrayList<Vala.UsingDirective>();
632			var ns_ref = new Vala.UsingDirective (new Vala.UnresolvedSymbol (null, "GLib"));
633			file.add_using_directive (ns_ref);
634			context.root.add_using_directive (ns_ref);
635
636			report.clear_error_indicators (file);
637
638			parse ();
639
640			report.update_errors(current_editor);
641		}
642	}
643
644	private void on_autocompletion_toggled (ToggleButton button) {
645		var sensitive = button.get_active();
646		Gtk.Widget widget = bxml.get_object (PREF_WIDGET_SPACE) as Widget;
647		widget.set_sensitive (sensitive);
648		widget = bxml.get_object (PREF_WIDGET_BRACE) as  Widget;
649		widget.set_sensitive (sensitive);
650	}
651
652	public void merge (Anjuta.Preferences prefs) throws GLib.Error {
653		bxml = new Builder();
654
655		/* Add preferences */
656		try {
657			bxml.add_from_file (PREFS_BUILDER);
658		} catch (Error err) {
659			warning ("Couldn't load builder file: %s", err.message);
660		}
661		prefs.add_from_builder (bxml, settings, "preferences", _("Auto-complete"),
662		                        ICON_FILE);
663		var toggle = bxml.get_object (PREF_WIDGET_AUTO) as ToggleButton;
664		toggle.toggled.connect (on_autocompletion_toggled);
665		on_autocompletion_toggled (toggle);
666	}
667
668	public void unmerge (Anjuta.Preferences prefs) throws GLib.Error {
669		prefs.remove_page (_("Auto-complete"));
670	}
671}
672
673[ModuleInit]
674public Type anjuta_glue_register_components (TypeModule module) {
675    return typeof (ValaPlugin);
676}
677