Initial German translation of the build tutorial
[anjuta.git] / plugins / language-support-vala / plugin.vala
blob13d35fec4f50981f6d3cf50e2b56addfa48d05ab
1 /*
2 * Copyright (C) 2008-2010 Abderrahim Kitouni
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.
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.
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/>.
18 using GLib;
19 using Gtk;
20 using Anjuta;
22 public 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";
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;
34 Vala.CodeContext context;
35 Cancellable cancel;
36 BlockLocator locator = new BlockLocator ();
38 AnjutaReport report;
39 ValaProvider provider;
41 Vala.Parser parser;
42 Vala.Genie.Parser genie_parser;
44 public static Gtk.Builder bxml;
46 Vala.Set<string> current_sources = new Vala.HashSet<string> (str_hash, str_equal);
47 ValaPlugin () {
48 Object ();
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 ();
57 init_context ();
59 provider = new ValaProvider(this);
60 editor_watch_id = add_watch("document_manager_current_document",
61 editor_value_added,
62 editor_value_removed);
64 return true;
67 public override bool deactivate () {
68 debug("Deactivating ValaPlugin");
69 remove_watch(editor_watch_id, true);
71 cancel.cancel ();
72 lock (context) {
73 context = null;
76 return true;
79 void init_context () {
80 context = new Vala.CodeContext();
81 context.profile = Vala.Profile.GOBJECT;
82 context.report = report;
83 report.clear_error_indicators ();
85 cancel = new Cancellable ();
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);
92 current_sources = new Vala.HashSet<string> (str_hash, str_equal);
96 void parse () {
97 try {
98 Thread.create<void>(() => {
99 lock (context) {
100 Vala.CodeContext.push(context);
101 var report = context.report as AnjutaReport;
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);
110 if (cancel.is_cancelled ()) {
111 Vala.CodeContext.pop();
112 return;
116 if (report.get_errors () > 0 || cancel.is_cancelled ()) {
117 Vala.CodeContext.pop();
118 return;
121 context.check ();
122 Vala.CodeContext.pop();
124 }, false);
125 } catch (ThreadError err) {
126 warning ("cannot create thread : %s", err.message);
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;
137 Vala.CodeContext.push (context);
139 var current_src = project.get_root ().get_source_from_file (current_file);
140 if (current_src == null)
141 return;
143 var current_target = current_src.parent_type (Anjuta.ProjectNodeType.TARGET);
144 if (current_target == null)
145 return;
147 current_target.foreach (TraverseType.PRE_ORDER, (node) => {
148 if (!(Anjuta.ProjectNodeType.SOURCE in node.get_node_type ()))
149 return;
151 if (node.get_file () == null)
152 return;
154 var path = node.get_file ().get_path ();
155 if (path == null)
156 return;
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);
166 } else {
167 debug ("file %s skipped", path);
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");
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);
191 string[] packages = {};
192 string[] vapidirs = {};
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]);
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);
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);
224 Vala.CodeContext.pop();
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;
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;
245 current_editor = value.get_object() as IAnjuta.Editor;
246 var current_file = value.get_object() as IAnjuta.File;
248 var pm = (IAnjuta.ProjectManager) shell.get_object("IAnjutaProjectManager");
249 var project = pm.get_current_project ();
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;
261 if (!(cur_gfile.get_path () in current_sources)) {
262 cancel.cancel ();
263 lock (context) {
264 init_context ();
265 add_project_files ();
268 parse ();
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);
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);
285 current_editor.glade_member_add.connect (insert_member_decl_and_init);
287 report.update_errors (current_editor);
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);
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);
304 current_editor.glade_member_add.disconnect (insert_member_decl_and_init);
305 current_editor = null;
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;
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
321 return;
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;
329 var editortip = editor as IAnjuta.EditorTip;
330 if (ch == '(') {
331 provider.show_call_tip (editortip);
332 } else if (ch == ')') {
333 editortip.cancel ();
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);
358 } while (position.previous ());
359 return null;
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);
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;
373 return false;
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 ();
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);
394 var handler_cname = scope_prefix + handler_name;
395 #endif
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");
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;
411 builder.append_printf ("public void %s (", handler_name);
413 if (swapped) {
414 builder.append_printf ("%s sender", widget.get_full_name ());
416 foreach (var param in sig.get_parameters ()) {
417 builder.append_printf (", %s %s", param.variable_type.data_type.get_full_name (), param.name);
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);
424 builder.append_printf ("%s sender", widget.get_full_name ());
427 builder.append_printf (") {\n\n}\n");
429 editor.insert (position, builder.str, -1);
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);
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);
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";
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);
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);
455 insert_after_mark (editor, DECL_MARK.printf (basename), member_decl)
456 && insert_after_mark (editor, INIT_MARK.printf (basename), member_init);
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;
463 IAnjuta.EditorCell result_end;
464 (editor as IAnjuta.EditorSearch).forward (mark, false, search_start, search_end, null, out result_end);
466 var mark_position = result_end as IAnjuta.Iterable;
467 if (mark_position == null)
468 return false;
470 editor.insert (mark_position, code_to_add, -1);
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);
480 /* Emit code-added signal, so symbols will be updated */
481 editor.code_added (mark_position, code_to_add);
483 return true;
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;
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));
497 return null;
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;
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;
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);
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);
528 return locator.locate(source, line, column);
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> ();
536 if (block == null) return matching_symbols;
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));
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));
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));
564 return matching_symbols;
566 List<Vala.Symbol> symbol_lookup_inherited (Vala.Symbol? sym, string name, bool prefix_match, bool invocation = false) {
567 List<Vala.Symbol> result = null;
569 // This may happen if we cannot find all the needed packages
570 if (sym == null)
571 return result;
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]);
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));
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));
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));
611 return result;
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);
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;
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);
636 report.clear_error_indicators (file);
638 parse ();
640 report.update_errors(current_editor);
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);
652 public void merge (Anjuta.Preferences prefs) throws GLib.Error {
653 bxml = new Builder();
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);
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);
668 public void unmerge (Anjuta.Preferences prefs) throws GLib.Error {
669 prefs.remove_page (_("Auto-complete"));
673 [ModuleInit]
674 public Type anjuta_glue_register_components (TypeModule module) {
675 return typeof (ValaPlugin);