~ricotz/valide/valide-svgicons

« back to all changes in this revision

Viewing changes to plugins/completion/afrodite-provider/afrodite-provider.vala

  • Committer: gege2061
  • Date: 2010-09-03 21:40:48 UTC
  • Revision ID: svn-v4:35bcdfa6-b98f-11dd-bba1-afcbec1a1e1f:trunk:687
Use Afrodite for completion

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* afrodite-provider.vala
 
2
 *
 
3
 * Copyright (C) 2010 Nicolas Joseph
 
4
 *
 
5
 * This program is free software: you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation, either version 3 of the License, or
 
8
 * (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
 *
 
18
 * Author:
 
19
 *  Nicolas Joseph <nicolas.joseph@valaide.org>
 
20
 */
 
21
 
 
22
using Valide;
 
23
 
 
24
internal class AfroditeProvider : Fix.SourceCompletionProvider, Object
 
25
{
 
26
  public signal void completion_lock_failed ();
 
27
 
 
28
  private Gdk.Pixbuf icon;
 
29
  private int priority = 1;
 
30
  private List<Gtk.SourceCompletionItem> proposals;
 
31
 
 
32
  private Afrodite.SourceItem sb = null;
 
33
 
 
34
  private uint timeout_id = 0;
 
35
  private uint idle_id = 0;
 
36
  private bool all_doc = false; //this is a hack!!!
 
37
 
 
38
  private int prealloc_index = 0;
 
39
 
 
40
  private bool cache_building = false;
 
41
  private bool filter = false;
 
42
  private uint sb_msg_id = 0;
 
43
  //private uint sb_context_id = 0;
 
44
 
 
45
  private Gtk.SourceCompletionInfo calltip_window = null;
 
46
  private Gtk.Label calltip_window_label = null;
 
47
  
 
48
  private int last_line = -1;
 
49
  private bool doc_changed = false;
 
50
 
 
51
  private Afrodite.CompletionEngine completion = null;
 
52
  
 
53
  public unowned Document document
 
54
  {
 
55
    get;
 
56
    construct;
 
57
  }
 
58
 
 
59
  public AfroditeProvider (Document document)
 
60
  {
 
61
    Object (document: document);
 
62
  }
 
63
 
 
64
  construct
 
65
  {
 
66
    this.icon = this.get_icon ();
 
67
 
 
68
    string name = Vtg.Utils.get_document_name (this.document.buffer);
 
69
 
 
70
    this.sb = new Afrodite.SourceItem ();
 
71
    this.sb.path = name;
 
72
    this.sb.content = this.document.buffer.get_buffer_contents ();
 
73
    
 
74
    this.document.view.key_press_event.connect (this.on_view_key_press);
 
75
    this.document.view.focus_out_event.connect (this.on_view_focus_out);
 
76
    this.document.view.get_completion ().show.connect (this.on_completion_window_hide);
 
77
 
 
78
    this.document.buffer.notify["text"] += this.on_text_changed;
 
79
    this.document.buffer.notify["cursor-position"] += this.on_cursor_position_changed;
 
80
    Signal.connect (this.document, "saved", (Callback)on_document_saved, this);
 
81
 
 
82
    //var status_bar = (Gedit.Statusbar) _symbol_completion.plugin_instance.window.get_statusbar ();
 
83
    //sb_context_id = status_bar.get_context_id ("symbol status");
 
84
    
 
85
    this.cache_building = true; 
 
86
    this.all_doc = true;
 
87
    //_symbol_completion.notify["completion-engine"].connect (this.on_completion_engine_changed);
 
88
    this.completion = new Afrodite.CompletionEngine ("Afrodite");
 
89
  }
 
90
 
 
91
  ~SymbolCompletionProvider ()
 
92
  {
 
93
    if (this.timeout_id != 0)
 
94
    {
 
95
      GLib.Source.remove (this.timeout_id);
 
96
    }
 
97
    if (this.idle_id != 0)
 
98
    {
 
99
      GLib.Source.remove (this.idle_id);
 
100
    }
 
101
    
 
102
    this.document.view.key_press_event.disconnect (this.on_view_key_press);
 
103
    this.document.view.focus_out_event.disconnect (this.on_view_focus_out);
 
104
 
 
105
    SourceBuffer doc = this.document.buffer;
 
106
    //_symbol_completion.notify["completion-engine"].disconnect (this.on_completion_engine_changed);
 
107
    doc.notify["text"] -= this.on_text_changed;
 
108
    doc.notify["cursor-position"] -= this.on_cursor_position_changed;
 
109
    SignalHandler.disconnect_by_func (doc, (void*)this.on_document_saved, this);
 
110
/*
 
111
    if (this.sb_msg_id != 0)
 
112
    {
 
113
      var status_bar = (Gedit.Statusbar) _symbol_completion.plugin_instance.window.get_statusbar ();
 
114
      status_bar.remove (_sb_context_id, _sb_msg_id);
 
115
    }
 
116
*/
 
117
  }
 
118
 
 
119
  public string get_name ()
 
120
  {
 
121
    return _("Afrodite");
 
122
  }
 
123
 
 
124
  public int get_priority ()
 
125
  {
 
126
    return this.priority;
 
127
  }
 
128
 
 
129
  public bool match (Gtk.SourceCompletionContext context)
 
130
  {
 
131
    SourceBuffer src = this.document.buffer;
 
132
    unowned Gtk.TextMark mark = src.get_insert ();
 
133
    Gtk.TextIter start;
 
134
 
 
135
    src.get_iter_at_mark (out start, mark);
 
136
    Gtk.TextIter pos = start;
 
137
    bool result = !Vtg.Utils.is_inside_comment_or_literal (src, pos);
 
138
 
 
139
    if (result)
 
140
    {
 
141
      pos = start;
 
142
      int line = pos.get_line ();
 
143
      unichar ch = pos.get_char ();
 
144
      if (pos.backward_char ())
 
145
      {
 
146
        if (pos.get_line () == line)
 
147
        {
 
148
          unichar prev_ch = pos.get_char ();
 
149
          if (prev_ch == '(' || ch == '('
 
150
              || prev_ch == '[' || ch == '['
 
151
              || prev_ch == ' '
 
152
              || prev_ch == ')'
 
153
              || prev_ch == ']'
 
154
              || prev_ch == ';'
 
155
              || prev_ch == '?'
 
156
              || prev_ch == '/' || ch == '/'
 
157
              || prev_ch == ',')
 
158
          {
 
159
            result = false;
 
160
            Vtg.Utils.trace ("not match current char: '%s', previous: '%s'", ch.to_string (), prev_ch.to_string ());
 
161
          }
 
162
          else
 
163
          {
 
164
            Vtg.Utils.trace ("match current char: '%s', previous: '%s'", ch.to_string (), prev_ch.to_string ());
 
165
          }
 
166
        }
 
167
      } 
 
168
    }
 
169
 
 
170
    return result;
 
171
  }
 
172
 
 
173
  private void on_completion_window_hide (Gtk.SourceCompletion sender)
 
174
  {
 
175
    this.filter = false;
 
176
  }
 
177
 
 
178
  public void populate (Gtk.SourceCompletionContext context)
 
179
  {
 
180
    Vtg.Utils.trace ("populate");
 
181
    unowned Gtk.TextMark mark = (Gtk.TextMark) context.completion.view.get_buffer ().get_insert ();
 
182
    Gtk.TextIter start;
 
183
    Gtk.TextIter end;
 
184
    context.completion.view.get_buffer ().get_iter_at_mark (out start, mark);
 
185
    context.completion.view.get_buffer ().get_iter_at_mark (out end, mark);
 
186
 
 
187
    if (!start.starts_line ())
 
188
    {
 
189
      start.set_line_offset (0);
 
190
    }
 
191
 
 
192
    string text = start.get_text (end);
 
193
    unichar prev_ch = 'a';
 
194
    if (end.backward_char ())
 
195
    {
 
196
      prev_ch = end.get_char ();
 
197
      end.forward_char ();
 
198
    }
 
199
    
 
200
    bool symbols_in_scope_mode = false;
 
201
    string word = "";
 
202
    this.filter = true;
 
203
    
 
204
    if (text.has_suffix (".") || (prev_ch != '_' && !prev_ch.isalnum()))
 
205
    {
 
206
      this.filter = false;
 
207
    }
 
208
    else
 
209
    {
 
210
      bool dummy, is_declaration;
 
211
      Vtg.ParserUtils.parse_line (text, out word, out dummy, out dummy, out is_declaration);
 
212
      
 
213
      if (!is_declaration && word.rstr(".") == null)
 
214
      {
 
215
        symbols_in_scope_mode = true;
 
216
        this.filter = false;
 
217
      }
 
218
    }
 
219
 
 
220
    if (!this.filter)
 
221
    {
 
222
      this.proposals = new List<Gtk.SourceCompletionItem> ();
 
223
      if (symbols_in_scope_mode)
 
224
      {
 
225
        this.lookup_visible_symbols_in_scope (word, Afrodite.CompareMode.START_WITH);
 
226
      }
 
227
      else
 
228
      {
 
229
        this.complete_current_word ();
 
230
      }
 
231
 
 
232
      context.add_proposals (this, this.proposals, true);
 
233
    }
 
234
    else
 
235
    {
 
236
      string[] tmp = word.split (".");
 
237
      string last_part = "";
 
238
      
 
239
      if (tmp.length > 0)
 
240
      {
 
241
        last_part = tmp[tmp.length-1];
 
242
      }
 
243
 
 
244
      Vtg.Utils.trace ("filtering with: '%s' - '%s'", word, last_part);
 
245
      if (!Vtg.StringUtils.is_null_or_empty (last_part))
 
246
      {
 
247
        List<Gtk.SourceCompletionItem> filtered_proposals = new List<Gtk.SourceCompletionItem> ();
 
248
        foreach (Gtk.SourceCompletionItem proposal in this.proposals)
 
249
        {
 
250
          if (proposal.get_label ().has_prefix (last_part))
 
251
          {
 
252
            filtered_proposals.append (proposal);
 
253
          }
 
254
        }
 
255
      
 
256
        if (this.proposals.length () > 0 && filtered_proposals.length () == 0) {
 
257
          // no matching add a dummy one to prevent proposal windows from closing
 
258
          Gtk.SourceCompletionItem dummy_proposal = new Gtk.SourceCompletionItem (_("No matching proposal"), "", null, null);
 
259
          filtered_proposals.append (dummy_proposal);
 
260
        }
 
261
        context.add_proposals (this, filtered_proposals, true);
 
262
      }
 
263
      else
 
264
      {
 
265
        // match all optimization
 
266
        context.add_proposals (this, this.proposals, true);
 
267
      }
 
268
    }
 
269
  }
 
270
 
 
271
  public Gdk.Pixbuf get_icon ()
 
272
  {
 
273
    if (this.icon == null)
 
274
    {
 
275
      try
 
276
      {
 
277
        Gtk.IconTheme theme = Gtk.IconTheme.get_default ();
 
278
        this.icon = theme.load_icon (Gtk.STOCK_DIALOG_INFO, 16, 0);
 
279
      }
 
280
      catch (Error err)
 
281
      {
 
282
        critical ("error: %s", err.message);
 
283
      }
 
284
    }
 
285
    return this.icon;
 
286
  }
 
287
 
 
288
  public bool activate_proposal (Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter)
 
289
  {
 
290
    this.filter = false;
 
291
    return false;
 
292
  }
 
293
 
 
294
  public Gtk.SourceCompletionActivation get_activation ()
 
295
  {
 
296
    return Gtk.SourceCompletionActivation.INTERACTIVE |
 
297
      Gtk.SourceCompletionActivation.USER_REQUESTED;
 
298
  }
 
299
 
 
300
  public Gtk.Widget get_info_widget (Gtk.SourceCompletionProposal proposal)
 
301
  {
 
302
    return null;
 
303
  }
 
304
 
 
305
  public int get_interactive_delay ()
 
306
  {
 
307
    return 10;
 
308
  }
 
309
 
 
310
  public bool get_start_iter (Gtk.SourceCompletionContext context, Gtk.SourceCompletionProposal proposal, Gtk.TextIter iter)
 
311
  {
 
312
    return false;
 
313
  }
 
314
 
 
315
  public void update_info (Gtk.SourceCompletionProposal proposal, Gtk.SourceCompletionInfo info)
 
316
  {
 
317
  }
 
318
 
 
319
  private bool on_view_focus_out (Gtk.Widget sender, Gdk.EventFocus event)
 
320
  {
 
321
    this.hide_calltip ();
 
322
    return false;
 
323
  }
 
324
  
 
325
  [CCode(instance_pos=-1)]
 
326
  private void on_document_saved (Document doc)
 
327
  {
 
328
    this.doc_changed = true;
 
329
    this.all_doc = true;
 
330
    this.schedule_reparse ();
 
331
  }
 
332
/*
 
333
  private void on_completion_engine_changed (Object sender, ParamSpec pspec)
 
334
  {
 
335
    this.completion = this.document.completion_engine;
 
336
  }
 
337
*/
 
338
  private int get_current_line_index (SourceBuffer? doc = null)
 
339
  {
 
340
    if (doc == null)
 
341
    {
 
342
      doc = this.document.view.buffer;
 
343
    }
 
344
    
 
345
    // get current line
 
346
    unowned Gtk.TextMark mark = doc.get_insert ();
 
347
    Gtk.TextIter start;
 
348
    doc.get_iter_at_mark (out start, mark);
 
349
    return start.get_line ();
 
350
  }
 
351
 
 
352
  private void schedule_reparse ()
 
353
  {
 
354
    if (this.timeout_id == 0 && this.doc_changed)
 
355
    {
 
356
      this.timeout_id = Timeout.add (250, this.on_timeout_parse);
 
357
    }
 
358
  }
 
359
 
 
360
  private void on_text_changed (Object sender, ParamSpec pspec)
 
361
  {
 
362
    this.doc_changed = true;
 
363
    // parse text only on init or line changes
 
364
    if (this.last_line == -1 || this.last_line != this.get_current_line_index ())
 
365
    {
 
366
      this.all_doc = true;
 
367
      this.schedule_reparse ();
 
368
    }
 
369
  }
 
370
 
 
371
  private void on_cursor_position_changed (Object sender, ParamSpec pspec)
 
372
  {
 
373
    // parse text only on init or line changes
 
374
    if (this.last_line == -1 || this.last_line != this.get_current_line_index ())
 
375
    {
 
376
      this.all_doc = true;
 
377
      this.schedule_reparse ();
 
378
    }
 
379
  }
 
380
 
 
381
  private bool on_timeout_parse ()
 
382
  {
 
383
    SourceBuffer doc = this.document.buffer;
 
384
    this.parse (this.document);
 
385
    this.timeout_id = 0;
 
386
    this.last_line = this.get_current_line_index (doc);
 
387
    return false;
 
388
  }
 
389
 
 
390
  private bool on_view_key_press (Gtk.Widget sender, Gdk.EventKey evt)
 
391
  {
 
392
    unichar ch = Gdk.keyval_to_unicode (evt.keyval);
 
393
    
 
394
    if (ch == '(')
 
395
    {
 
396
      this.show_calltip ();
 
397
    }
 
398
    else if (evt.keyval == Gdk.KeySyms.Escape || ch == ')' || ch == ';' || ch == '{' ||
 
399
        (evt.keyval == Gdk.KeySyms.Return && (evt.state & Gdk.ModifierType.SHIFT_MASK) != 0))
 
400
    {
 
401
      this.hide_calltip ();
 
402
    }
 
403
    if (evt.keyval == Gdk.KeySyms.Return || ch == ';')
 
404
    {
 
405
      this.all_doc = true; // new line or eol, reparse all source buffer
 
406
    }
 
407
    else if (ch.isprint () 
 
408
         || evt.keyval == Gdk.KeySyms.Delete
 
409
         || evt.keyval == Gdk.KeySyms.BackSpace)
 
410
    {
 
411
      this.all_doc = false; // a change so reparse the buffer minus the current line
 
412
      this.doc_changed = true;
 
413
    }
 
414
    return false;
 
415
  }
 
416
 
 
417
  private void show_calltip ()
 
418
  {
 
419
    Afrodite.Symbol? completion_result = this.get_current_symbol_item ();
 
420
    if (completion_result != null)
 
421
    {
 
422
      this.show_calltip_info (completion_result.info);
 
423
    }
 
424
  }
 
425
 
 
426
  private void show_calltip_info (string markup_text)
 
427
  {
 
428
    if (this.calltip_window == null)
 
429
    {
 
430
      this.initialize_calltip_window ();
 
431
    }
 
432
 
 
433
    if (markup_text != null)
 
434
    {
 
435
      this.calltip_window_label.set_markup (markup_text);
 
436
      this.calltip_window.move_to_iter (this.document.view);
 
437
      this.calltip_window.show_all ();
 
438
      this.calltip_window.show ();
 
439
    }
 
440
  }
 
441
 
 
442
  private void hide_calltip ()
 
443
  {
 
444
    if (this.calltip_window == null)
 
445
    {
 
446
      return;
 
447
    }
 
448
 
 
449
    this.calltip_window.hide ();
 
450
  }
 
451
 
 
452
  private void initialize_calltip_window ()
 
453
  {
 
454
    this.calltip_window = new Gtk.SourceCompletionInfo ();
 
455
    //this.calltip_window.set_transient_for (_symbol_completion.plugin_instance.window);
 
456
    this.calltip_window.set_sizing (800, 400, true, true);
 
457
    this.calltip_window_label = new Gtk.Label ("");
 
458
    this.calltip_window.set_widget (this.calltip_window_label);      
 
459
  }
 
460
 
 
461
  private void parse (Document doc)
 
462
  {
 
463
    // automatically add package if this buffer
 
464
    // belong to the default project
 
465
/*
 
466
    var current_project = _symbol_completion.plugin_instance.project_view.current_project; 
 
467
    if (current_project.is_default) {
 
468
      if (this.autoadd_packages (doc, current_project) > 0)
 
469
      {
 
470
        current_project.project.update ();
 
471
      }
 
472
    }
 
473
*/
 
474
    // schedule a parse
 
475
    var buffer = this.get_document_text (doc.buffer, this.all_doc);
 
476
    this.sb.content = buffer;
 
477
    this.completion.queue_source (this.sb);
 
478
    this.doc_changed = false;
 
479
  }
 
480
/*
 
481
  private int autoadd_packages (Gedit.Document doc, Vtg.ProjectManager project_manager)
 
482
  {
 
483
  
 
484
    int added_count = 0;
 
485
    
 
486
    try {
 
487
      var text = this.get_document_text (doc, true);
 
488
      GLib.Regex regex = new GLib.Regex ("""^\s*(using)\s+(\w\S*)\s*;.*$""");
 
489
    
 
490
      foreach (string line in text.split ("\n")) {
 
491
        GLib.MatchInfo match;
 
492
        regex.match (line, RegexMatchFlags.NEWLINE_ANY, out match);
 
493
        while (match.matches ()) {
 
494
          string using_name = null;
 
495
 
 
496
          if (match.fetch (2) == "GLib") {
 
497
            // standard GLib are already merged by the completion engine
 
498
            // I'll add gio for the default project
 
499
            if (project_manager.is_default) {
 
500
              using_name = "gio";
 
501
            }
 
502
          } else {
 
503
            using_name = match.fetch (2);
 
504
          }
 
505
          string package_name = null;
 
506
 
 
507
          if (using_name != null)
 
508
            package_name = Vbf.Vtg.Utils.guess_package_name (using_name);
 
509
 
 
510
          Vtg.Utils.trace ("guessing name of using clause %s for package %s: %s", match.fetch (2), using_name, package_name);
 
511
          if (package_name != null) {
 
512
            var group = project_manager.project.get_group("Sources");
 
513
            var target = group.get_target_for_id ("Default");
 
514
            if (!target.contains_package (package_name))
 
515
            {
 
516
              target.add_package (new Vbf.Package (package_name));
 
517
              added_count++;
 
518
            }
 
519
          }
 
520
          match.next ();
 
521
        }
 
522
      }
 
523
    } catch (Error err) {
 
524
      critical ("error: %s", err.message);
 
525
    }
 
526
 
 
527
    return added_count;
 
528
  }
 
529
*/
 
530
  private bool proposal_list_contains_name (string name)
 
531
  {
 
532
    foreach (Gtk.SourceCompletionItem proposal in this.proposals)
 
533
    {
 
534
      if (proposal.get_label () == name)
 
535
      {
 
536
        return true;
 
537
      }
 
538
    }
 
539
 
 
540
    return false;
 
541
  }
 
542
  
 
543
  private void append_symbols (Afrodite.QueryOptions? options, Vala.List<Afrodite.Symbol> symbols, bool include_private_symbols = true)
 
544
  {
 
545
    unowned Gtk.SourceCompletionItem[] proposals = Vtg.Utils.get_proposal_cache ();
 
546
 
 
547
    foreach (Afrodite.Symbol symbol in symbols)
 
548
    {
 
549
      if ((!include_private_symbols && symbol.access == Afrodite.SymbolAccessibility.PRIVATE)
 
550
        || symbol.name == "new"
 
551
        || (options != null && !symbol.check_options (options)))
 
552
      {
 
553
        //Vtg.Utils.trace ("not append symbols: %s", symbol.name);
 
554
        continue;
 
555
      }
 
556
 
 
557
      string name;
 
558
 
 
559
      if (symbol.type_name == "CreationMethod")
 
560
      {
 
561
        name = symbol.name;
 
562
      }
 
563
      else
 
564
      {
 
565
        name = (symbol.display_name != null ? symbol.display_name : "<null>");
 
566
      }
 
567
 
 
568
      if (!symbol.overrides || (symbol.overrides && !this.proposal_list_contains_name (name)))
 
569
      {
 
570
        Gtk.SourceCompletionItem proposal;
 
571
        string info = (symbol.info != null ? symbol.info : "");
 
572
        Gdk.Pixbuf icon = Vtg.Utils.get_icon_for_type_name (symbol.type_name);
 
573
 
 
574
        if (this.prealloc_index < Vtg.Utils.prealloc_count)
 
575
        {
 
576
          proposal = proposals [this.prealloc_index];
 
577
          this.prealloc_index++;
 
578
 
 
579
          proposal.label = name;
 
580
          proposal.text = name;
 
581
          proposal.info = info;
 
582
          proposal.icon = icon;
 
583
        }
 
584
        else
 
585
        {
 
586
          proposal = new Gtk.SourceCompletionItem (name, name, icon, info);
 
587
        }
 
588
        //Vtg.Utils.trace ("append symbols: %s", symbol.name);
 
589
        this.proposals.append (proposal);
 
590
      }
 
591
    }
 
592
    //sort list
 
593
    this.proposals.sort (this.proposal_sort);
 
594
  }
 
595
 
 
596
  private static int proposal_sort (void* a, void* b)
 
597
  {
 
598
    Gtk.SourceCompletionItem pa = (Gtk.SourceCompletionItem) a;
 
599
    Gtk.SourceCompletionItem pb = (Gtk.SourceCompletionItem) b;
 
600
 
 
601
    return strcmp (pa.get_label (), pb.get_label ());
 
602
  }
 
603
 
 
604
  private void transform_result (Afrodite.QueryOptions? options, Afrodite.QueryResult? result)
 
605
  {
 
606
    this.prealloc_index = 0;
 
607
    this.proposals = new List<Gtk.SourceCompletionItem> ();
 
608
    Vala.ArrayList<Afrodite.Symbol> visited_interfaces = new Vala.ArrayList<Afrodite.Symbol> ();
 
609
    
 
610
    if (result != null && !result.is_empty)
 
611
    {
 
612
      options.dump_settings ();
 
613
      
 
614
      foreach (Afrodite.ResultItem item in result.children)
 
615
      {
 
616
        var symbol = item.symbol;
 
617
 
 
618
        if (options == null || symbol.check_options (options))
 
619
        {
 
620
          if (symbol.has_children)
 
621
          {
 
622
            append_symbols (options, symbol.children);
 
623
          }
 
624
          
 
625
          append_base_type_symbols (options, symbol, visited_interfaces);
 
626
        }
 
627
      }
 
628
    }
 
629
  }
 
630
 
 
631
  private void append_base_type_symbols (Afrodite.QueryOptions? options, Afrodite.Symbol symbol, Vala.List<Afrodite.Symbol> visited_interfaces)
 
632
  {
 
633
    if (symbol.has_base_types 
 
634
        && (symbol.type_name == "Class" || symbol.type_name == "Interface" || symbol.type_name == "Struct"))
 
635
    {
 
636
      foreach (Afrodite.DataType type in symbol.base_types)
 
637
      {
 
638
        Vtg.Utils.trace ("visiting base type: %s", type.type_name);
 
639
        if (!type.unresolved 
 
640
            && type.symbol.has_children
 
641
            && (options == null || type.symbol.check_options (options))
 
642
            && (type.symbol.type_name == "Class" || type.symbol.type_name == "Interface" || type.symbol.type_name == "Struct"))
 
643
        {
 
644
          // symbols of base types (classes or interfaces)
 
645
          if (!visited_interfaces.contains (type.symbol))
 
646
          {
 
647
            visited_interfaces.add (type.symbol);
 
648
            append_symbols (options, type.symbol.children, false);
 
649
            append_base_type_symbols (options, type.symbol, visited_interfaces);
 
650
          }
 
651
        }
 
652
      }
 
653
    }
 
654
    else
 
655
    {
 
656
      Vtg.Utils.trace ("NO base type for %s-%s", symbol.name, symbol.type_name);
 
657
    }
 
658
  }
 
659
 
 
660
  private void get_current_line_and_column (out int line, out int column)
 
661
  {
 
662
    unowned SourceBuffer doc = this.document.buffer;
 
663
    unowned Gtk.TextMark mark = doc.get_insert ();
 
664
    Gtk.TextIter start;
 
665
 
 
666
    doc.get_iter_at_mark (out start, mark);
 
667
    line = start.get_line ();
 
668
    column = start.get_line_offset ();
 
669
  }
 
670
 
 
671
  private string get_current_line_text (bool align_to_right_word)
 
672
  {
 
673
    unowned SourceBuffer doc = this.document.buffer;
 
674
    unowned Gtk.TextMark mark = doc.get_insert ();
 
675
    Gtk.TextIter end;
 
676
    Gtk.TextIter start;
 
677
    unichar ch;
 
678
    
 
679
    doc.get_iter_at_mark (out start, mark);
 
680
    int line = start.get_line ();
 
681
    
 
682
    //go to the right word boundary
 
683
    ch = start.get_char ();
 
684
    while (ch.isalnum () || ch == '_')
 
685
    {
 
686
      start.forward_char ();
 
687
      int curr_line = start.get_line ();
 
688
      if (line != curr_line) //changed line?
 
689
      {
 
690
        start.backward_char ();
 
691
        break;
 
692
      }
 
693
      ch = start.get_char ();
 
694
    }
 
695
 
 
696
    end = start;
 
697
    start.set_line_offset (0);
 
698
    return start.get_text (end);
 
699
  }
 
700
 
 
701
  public Afrodite.Symbol? get_current_symbol_item (int retry_count = 0)
 
702
  {
 
703
    string text = this.get_current_line_text (true);
 
704
    string word;
 
705
    int line, col;
 
706
    bool is_assignment, is_creation, is_declaration;
 
707
 
 
708
    Vtg.ParserUtils.parse_line (text, out word, out is_assignment, out is_creation, out is_declaration);
 
709
 
 
710
    if (word == null || word == "")
 
711
    {
 
712
      return null;
 
713
    }
 
714
 
 
715
    this.get_current_line_and_column (out line, out col);
 
716
 
 
717
    string[] tmp = word.split (".");
 
718
    string last_part = tmp[tmp.length - 1];
 
719
    string symbol_name = last_part;
 
720
    
 
721
    //don't try to find method signature if is a: for, foreach, if, while etc...
 
722
    if (is_vala_keyword (symbol_name))
 
723
    {
 
724
      return null;
 
725
    }
 
726
 
 
727
    /* 
 
728
      strip last type part. 
 
729
      eg. for demos.demo.demo_method obtains
 
730
      demos.demo + demo_method
 
731
    */
 
732
    string first_part;
 
733
    if (word != last_part)
 
734
    {
 
735
      first_part = word.substring (0, word.length - last_part.length - 1);
 
736
    }
 
737
    else
 
738
    {
 
739
      first_part = word; // "this"; //HACK: this won't work for static methods
 
740
    }
 
741
 
 
742
    Afrodite.Ast ast;
 
743
    Afrodite.Symbol? symbol = null;
 
744
 
 
745
    if (this.completion.try_acquire_ast (out ast, retry_count))
 
746
    {
 
747
      Afrodite.QueryResult? result = null;
 
748
      Afrodite.QueryOptions options = this.get_options_for_line (text, is_assignment, is_creation);
 
749
      
 
750
      if (word == symbol_name)
 
751
      {
 
752
        result = this.get_symbol_for_name (options, ast, first_part, null,  line, col);
 
753
      }
 
754
      else
 
755
      {
 
756
        result = this.get_symbol_type_for_name (options, ast, first_part, null,  line, col);
 
757
      }
 
758
 
 
759
      if (result != null && !result.is_empty)
 
760
      {
 
761
        var first = result.children.get (0);
 
762
        if (word == symbol_name)
 
763
        {
 
764
          symbol = first.symbol;
 
765
        }
 
766
        else
 
767
        {
 
768
          symbol = this.get_symbol_for_name_in_children (symbol_name, first.symbol);
 
769
          if (symbol == null)
 
770
          {
 
771
            symbol =this.get_symbol_for_name_in_base_types (symbol_name, first.symbol);
 
772
          }
 
773
        }
 
774
      }
 
775
      this.completion.release_ast (ast);
 
776
    }
 
777
    return symbol;
 
778
  }
 
779
 
 
780
  private Afrodite.Symbol? get_symbol_for_name_in_children (string symbol_name, Afrodite.Symbol parent)
 
781
  {
 
782
    if (parent.has_children)
 
783
    {
 
784
      foreach (Afrodite.Symbol? symbol in parent.children)
 
785
      {
 
786
        if (symbol.name == symbol_name)
 
787
        {
 
788
          return symbol;
 
789
        }
 
790
      }
 
791
    }
 
792
    return null;
 
793
  }
 
794
  
 
795
  private Afrodite.Symbol? get_symbol_for_name_in_base_types (string symbol_name, Afrodite.Symbol parent) 
 
796
  {
 
797
    if (parent.has_base_types)
 
798
    {
 
799
      foreach  (Afrodite.DataType t in parent.base_types)
 
800
      {
 
801
        if (t.symbol != null)
 
802
        {
 
803
          var base_symbol = this.get_symbol_for_name_in_children (symbol_name, t.symbol);
 
804
          if (base_symbol == null)
 
805
          {
 
806
            base_symbol = this.get_symbol_for_name_in_base_types (symbol_name, t.symbol);
 
807
          }
 
808
 
 
809
          if (base_symbol != null)
 
810
          {
 
811
            return base_symbol;
 
812
          }
 
813
        }
 
814
      }
 
815
    }
 
816
    return null;
 
817
  }
 
818
  
 
819
  private Afrodite.QueryOptions get_options_for_line (string line, bool is_assignment, bool is_creation)
 
820
  {
 
821
    Afrodite.QueryOptions options = null;
 
822
    
 
823
    if (is_creation)
 
824
    {
 
825
      options = Afrodite.QueryOptions.creation_methods ();
 
826
    }
 
827
    else if (is_assignment || (line != null && line.rstr (":") != null))
 
828
    {
 
829
      options = Afrodite.QueryOptions.standard ();
 
830
      options.binding |= Afrodite.MemberBinding.STATIC;
 
831
    }
 
832
    else if (line != null && (line.str ("throws ") != null || line.str ("throw ") != null))
 
833
    {
 
834
      options = Afrodite.QueryOptions.error_domains ();
 
835
    }
 
836
    if (options == null)
 
837
    {
 
838
      options = Afrodite.QueryOptions.standard ();
 
839
    }
 
840
 
 
841
    options.access = Afrodite.SymbolAccessibility.INTERNAL | Afrodite.SymbolAccessibility.PROTECTED | Afrodite.SymbolAccessibility.PUBLIC;
 
842
    options.auto_member_binding_mode = true;
 
843
    options.compare_mode = Afrodite.CompareMode.EXACT;
 
844
    //options.dump_settings ();
 
845
    return options;
 
846
  }
 
847
 
 
848
  private void complete_current_word ()
 
849
  {
 
850
    //string whole_line, word, last_part;
 
851
    //int line, column;
 
852
 
 
853
    //parse_current_line (false, out word, out last_part, out whole_line, out line, out column);
 
854
    string text = this.get_current_line_text (false);
 
855
    string word;
 
856
    
 
857
    bool is_assignment, is_creation, is_declaration;
 
858
 
 
859
    Vtg.ParserUtils.parse_line (text, out word, out is_assignment, out is_creation, out is_declaration);
 
860
 
 
861
    Afrodite.Ast ast = null;
 
862
    Vtg.Utils.trace ("completing word: '%s'", word);
 
863
    if (!Vtg.StringUtils.is_null_or_empty (word) 
 
864
        && this.completion.try_acquire_ast (out ast))
 
865
    {
 
866
      Afrodite.QueryOptions options = this.get_options_for_line (text, is_assignment, is_creation);
 
867
      Afrodite.QueryResult result = null;
 
868
      int line, col;
 
869
 
 
870
      this.get_current_line_and_column (out line, out col);
 
871
 
 
872
      if (word.has_prefix ("\"") && word.has_suffix ("\""))
 
873
      {
 
874
        word = "string";
 
875
      }
 
876
      else if (word.has_prefix ("\'") && word.has_suffix ("\'"))
 
877
      {
 
878
        word = "unichar";
 
879
      }
 
880
      result = this.get_symbol_type_for_name (options, ast, word, text, line, col);
 
881
      this.transform_result (options, result);
 
882
      this.completion.release_ast (ast);
 
883
    }
 
884
    else
 
885
    {
 
886
      if (!Vtg.StringUtils.is_null_or_empty (word))
 
887
      {
 
888
        Vtg.Utils.trace ("build_proposal_item_list: couldn't acquire ast lock");
 
889
        this.show_calltip_info (_("<i>source symbol cache is still updating...</i>"));
 
890
        Timeout.add_seconds (2, this.on_hide_calltip_timeout);
 
891
        this.completion_lock_failed ();
 
892
      }
 
893
      this.transform_result (null, null);
 
894
    }
 
895
  }
 
896
 
 
897
  private void lookup_visible_symbols_in_scope (string word, Afrodite.CompareMode mode)
 
898
  {
 
899
    Afrodite.Ast ast = null;
 
900
    Vtg.Utils.trace ("lookup_all_symbols_in_scope: mode: %s word:'%s' ", 
 
901
      mode == Afrodite.CompareMode.EXACT ? "exact" : "start-with",
 
902
      word);
 
903
    if (!Vtg.StringUtils.is_null_or_empty (word) 
 
904
        && this.completion.try_acquire_ast (out ast, 0))
 
905
    {
 
906
      Vala.List<Afrodite.Symbol> results = new Vala.ArrayList<Afrodite.Symbol> ();
 
907
 
 
908
      weak SourceBuffer doc = this.document.buffer;
 
909
      var source = ast.lookup_source_file (Vtg.Utils.get_document_name (doc));
 
910
      if (source != null)
 
911
      {
 
912
        // get the source node at this position
 
913
        int line, column;
 
914
        get_current_line_and_column (out line, out column);
 
915
 
 
916
        var s = ast.get_symbol_for_source_and_position (source, line, column);
 
917
        if (s != null)
 
918
        {
 
919
          results = ast.lookup_visible_symbols_from_symbol (s, word, mode, Afrodite.CaseSensitiveness.CASE_SENSITIVE);
 
920
        }
 
921
      }
 
922
      
 
923
      if (results.size == 0)
 
924
      {
 
925
        Vtg.Utils.trace ("no symbol visible");
 
926
        this.transform_result (null, null);
 
927
      }
 
928
      else
 
929
      {
 
930
        this.proposals = new List<Gtk.SourceCompletionItem> ();
 
931
        append_symbols (null, results);
 
932
      }
 
933
      this.completion.release_ast (ast);
 
934
    }
 
935
    else
 
936
    {
 
937
      if (!Vtg.StringUtils.is_null_or_empty (word))
 
938
      {
 
939
        Vtg.Utils.trace ("build_proposal_item_list: couldn't acquire ast lock");
 
940
        this.completion_lock_failed ();
 
941
      }
 
942
      this.transform_result (null, null);
 
943
    }
 
944
  }
 
945
 
 
946
  private bool on_hide_calltip_timeout ()
 
947
  {
 
948
    this.hide_calltip ();
 
949
    return false;
 
950
  }
 
951
 
 
952
  private Afrodite.QueryResult? get_symbol_type_for_name (Afrodite.QueryOptions options, Afrodite.Ast ast, string word, string? whole_line, int line, int column)
 
953
  {
 
954
    Afrodite.QueryResult result = null;
 
955
    result = ast.get_symbol_type_for_name_and_path (options, word, this.sb.path, line, column);
 
956
    Vtg.Utils.trace ("symbol matched %d", result.children.size);
 
957
    return result;
 
958
  }
 
959
 
 
960
  private Afrodite.QueryResult? get_symbol_for_name (Afrodite.QueryOptions options, Afrodite.Ast ast,string word, string? whole_line, int line, int column)
 
961
  {
 
962
    Afrodite.QueryResult result = null;
 
963
    result = ast.get_symbol_for_name_and_path (options, word, this.sb.path, line, column);
 
964
 
 
965
    return result;
 
966
  }
 
967
 
 
968
  private bool is_vala_keyword (string keyword)
 
969
  {
 
970
    return (keyword == "if"
 
971
      || keyword == "for"
 
972
      || keyword == "foreach"
 
973
      || keyword == "while"
 
974
      || keyword == "switch");
 
975
  }
 
976
 
 
977
  private string get_document_text (SourceBuffer doc, bool all_doc = false)
 
978
  {
 
979
    weak Gtk.TextMark mark = doc.get_insert ();
 
980
    Gtk.TextIter end;
 
981
    Gtk.TextIter start;
 
982
 
 
983
    doc.get_iter_at_mark (out start, mark);
 
984
    string doc_text;
 
985
    if (all_doc || doc.is_untouched ())
 
986
    {
 
987
      end = start;
 
988
      start.set_line_offset (0);
 
989
      while (start.backward_line ())
 
990
      {
 
991
      }
 
992
 
 
993
      while (end.forward_line ())
 
994
      {
 
995
      }
 
996
      
 
997
      doc_text = start.get_text (end);
 
998
    }
 
999
    else
 
1000
    {
 
1001
      end = start;
 
1002
      end.set_line_offset (0);
 
1003
      while (start.backward_line ())
 
1004
      {
 
1005
      }
 
1006
 
 
1007
      string text1 = start.get_text (end);
 
1008
      string text2 = "";
 
1009
      //trick: jump the current edited row (there
 
1010
      //are a lot of probability that this row will
 
1011
      //cause a parser error)
 
1012
      if (end.forward_line ())
 
1013
      {
 
1014
        end.set_line_offset (0);
 
1015
        start = end;
 
1016
        while (end.forward_line ())
 
1017
        {
 
1018
        }
 
1019
 
 
1020
        text2 = start.get_text (end);
 
1021
      }
 
1022
      doc_text = "%s\n%s".printf (text1, text2);
 
1023
    }
 
1024
    
 
1025
    return doc_text;
 
1026
  }
 
1027
}
 
1028