2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.languages.features;
45
import java.util.ArrayList;
46
import java.util.HashSet;
49
import java.util.Iterator;
50
import java.util.List;
51
import javax.swing.text.JTextComponent;
52
import javax.swing.text.Document;
54
import org.netbeans.api.languages.ASTItem;
55
import org.netbeans.api.languages.CompletionItem.Type;
56
import org.netbeans.api.languages.ParseException;
57
import org.netbeans.api.languages.ASTPath;
58
import org.netbeans.api.languages.ParserManager;
59
import org.netbeans.api.languages.ParserManager.State;
60
import org.netbeans.api.languages.ParserManagerListener;
61
import org.netbeans.api.languages.ASTToken;
62
import org.netbeans.api.languages.SyntaxContext;
63
import org.netbeans.api.languages.ASTNode;
64
import org.netbeans.api.languages.ParseException;
65
import org.netbeans.api.languages.LanguageDefinitionNotFoundException;
66
import org.netbeans.api.lexer.Token;
67
import org.netbeans.api.lexer.TokenHierarchy;
68
import org.netbeans.api.lexer.TokenId;
69
import org.netbeans.api.lexer.TokenSequence;
70
import org.netbeans.api.languages.Context;
71
import org.netbeans.api.project.FileOwnerQuery;
72
import org.netbeans.api.project.Project;
73
import org.netbeans.modules.editor.NbEditorDocument;
74
import org.netbeans.modules.editor.NbEditorUtilities;
75
import org.netbeans.modules.languages.Feature;
76
import org.netbeans.modules.languages.Language;
77
import org.netbeans.modules.languages.LanguagesManager;
78
import org.netbeans.modules.languages.LanguagesManager;
79
import org.netbeans.modules.languages.ParserManagerImpl;
80
import org.netbeans.spi.editor.completion.CompletionItem;
81
import org.netbeans.spi.editor.completion.CompletionProvider;
82
import org.netbeans.spi.editor.completion.CompletionResultSet;
83
import org.netbeans.spi.editor.completion.CompletionTask;
84
import org.netbeans.spi.project.ActionProvider;
86
import org.openide.ErrorManager;
87
import org.openide.ErrorManager;
88
import org.openide.filesystems.FileObject;
95
public class CompletionProviderImpl implements CompletionProvider {
97
static final String COMPLETION = "COMPLETION";
100
* Append text after current end of token. This type of cc is used
103
static final String COMPLETION_APPEND = "append";
106
* Inserts text into current token, no prefix used. Used for whitespaces,
109
static final String COMPLETION_INSERT = "insert";
112
* Inserts text into current token, with current prefix. Used for keywords
115
static final String COMPLETION_COMPLETE = "complete";
118
public CompletionTask createTask (int queryType, JTextComponent component) {
119
return new CompletionTaskImpl (component);
122
public int getAutoQueryTypes (JTextComponent component, String typedText) {
123
if (".".equals(typedText)) { // NOI18N
124
Document doc = component.getDocument ();
125
TokenHierarchy<Document> tokenHierarchy = TokenHierarchy.get (doc);
126
if (doc instanceof NbEditorDocument)
127
((NbEditorDocument) doc).readLock ();
129
int offset = component.getCaret().getDot();
133
offset = offset - 2; //do Schlieman's magic
135
List<TokenSequence<?>> sequences = tokenHierarchy.embeddedTokenSequences(offset, true);
136
if(sequences.isEmpty()) {
137
return 0; //no token sequence
139
TokenSequence<?> tokenSequence = sequences.get(sequences.size() - 1); //get the most embedded
140
tokenSequence.move(offset);
141
if (!tokenSequence.moveNext() && !tokenSequence.movePrevious()) {
144
Token<?> token = tokenSequence.token ();
145
if (token.id().name().indexOf("identifier") > -1) { // NOI18N [PENDING]
146
return COMPLETION_QUERY_TYPE;
149
if (doc instanceof NbEditorDocument)
150
((NbEditorDocument) doc).readUnlock ();
156
List<CompletionItem> query (JTextComponent component) {
157
ListResult r = new ListResult ();
158
CompletionTaskImpl task = new CompletionTaskImpl (component);
164
private static TokenSequence getDeepestTokenSequence (
165
TokenHierarchy tokenHierarchy,
168
TokenSequence tokenSequence = tokenHierarchy.tokenSequence ();
169
while(tokenSequence != null) {
170
tokenSequence.move(offset - 1);
171
if(!tokenSequence.moveNext()) {
174
TokenSequence ts = tokenSequence.embedded();
176
return tokenSequence;
181
return tokenSequence;
185
// innerclasses ............................................................
187
private static class CompletionTaskImpl implements CompletionTask {
189
private JTextComponent component;
190
private Document doc;
191
private FileObject fileObject;
192
private boolean ignoreCase;
193
private List<CompletionItem> items = new ArrayList<CompletionItem> ();
196
CompletionTaskImpl (JTextComponent component) {
197
this.component = component;
200
public void query (CompletionResultSet resultSet) {
201
//S ystem.out.println("CodeCompletion: query " + resultSet);
202
compute (new CompletionResult (resultSet));
205
public void refresh (CompletionResultSet resultSet) {
206
if (resultSet == null) return;
207
doc = component.getDocument ();
208
fileObject = NbEditorUtilities.getFileObject (doc);
209
TokenHierarchy tokenHierarchy = TokenHierarchy.get (doc);
210
if (doc instanceof NbEditorDocument)
211
((NbEditorDocument) doc).readLock ();
213
TokenSequence tokenSequence = getDeepestTokenSequence (
215
component.getCaret ().getDot ()
217
Token token = tokenSequence.token ();
219
String start = token.text ().toString ();
221
Iterator<CompletionItem> it = items.iterator ();
222
while (it.hasNext ()) {
223
CompletionItem completionItem = it.next ();
224
String text = completionItem.getInsertPrefix ().toString ();
225
if (text.startsWith (start))
226
resultSet.addItem (completionItem);
230
if (doc instanceof NbEditorDocument)
231
((NbEditorDocument) doc).readUnlock ();
236
public void cancel () {
239
private void compute (Result resultSet) {
240
doc = component.getDocument ();
241
fileObject = NbEditorUtilities.getFileObject (doc);
242
TokenHierarchy tokenHierarchy = TokenHierarchy.get (doc);
243
boolean finishSet = true;
244
if (doc instanceof NbEditorDocument) {
245
((NbEditorDocument) doc).readLock ();
248
int offset = component.getCaret ().getDot ();
249
TokenSequence tokenSequence = getDeepestTokenSequence (
251
component.getCaret ().getDot ()
253
if (!tokenSequence.isEmpty() && tokenSequence.offset () > offset) {
254
// border of embedded language
255
// [HACK] borders should be represented by some tokens!!!
258
String mimeType = tokenSequence.language ().mimeType ();
259
Language language = LanguagesManager.getDefault ().
260
getLanguage (mimeType);
261
if (!tokenSequence.isEmpty()) {
262
compute (tokenSequence, offset, resultSet, doc, language);
264
finishSet = addParserTags (resultSet, language);
265
} catch (LanguageDefinitionNotFoundException ex) {
268
if (doc instanceof NbEditorDocument) {
269
((NbEditorDocument) doc).readUnlock ();
277
private String getCompletionType (Feature feature, String tokenType) {
278
String projectType = (String) feature.getValue("project_type");
279
if (projectType != null) {
280
if (fileObject == null) return null;
281
Project p = FileOwnerQuery.getOwner (fileObject);
282
if (p == null) return null;
283
Object o = p.getLookup ().lookup (ActionProvider.class);
284
if (o == null) return null;
285
if (o.getClass ().getName ().indexOf (projectType) < 0)
288
String completionType = (String) feature.getValue ("type");
289
if (completionType != null) return completionType;
290
if (tokenType.indexOf ("whitespace") >= 0 ||
291
tokenType.indexOf ("operator") >= 0 ||
292
tokenType.indexOf ("separator") >= 0
294
return COMPLETION_INSERT;
296
if (tokenType.indexOf ("comment") >= 0)
297
return COMPLETION_APPEND;
298
return COMPLETION_COMPLETE;
301
private void compute (
302
TokenSequence tokenSequence,
309
Token token = tokenSequence.token ();
310
start = token.text ().toString ();
311
List<Feature> features = language.getFeatures (COMPLETION, token.id ().ordinal ());
312
Iterator<Feature> it = features.iterator ();
313
while (it.hasNext ()) {
314
Feature feature = it.next ();
315
String completionType = getCompletionType (feature, token.id ().name ());
316
int tokenOffset = tokenSequence.offset();
317
if (completionType == null) continue;
318
if (COMPLETION_APPEND.equals (completionType) &&
319
offset < tokenOffset + token.length ()
322
start = COMPLETION_COMPLETE.equals (completionType) ?
323
start.substring (0, offset - tokenOffset).trim () :
326
Feature f = language.getFeature ("PROPERTIES");
328
ignoreCase = f.getBoolean ("ignoreCase", false);
329
if (ignoreCase) start = start.toLowerCase ();
330
addTags (feature, start, Context.create (doc, offset), resultSet);
334
private boolean addParserTags (final Result resultSet, final Language language) {
335
final ParserManager parserManager = ParserManager.get (doc);
336
if (parserManager.getState () == State.PARSING) {
337
//S ystem.out.println("CodeCompletion: parsing...");
338
parserManager.addListener (new ParserManagerListener () {
339
public void parsed (State state, ASTNode ast) {
340
//S ystem.out.println("CodeCompletion: parsed " + state);
341
if (resultSet.isFinished ()) return;
342
parserManager.removeListener (this);
343
addParserTags (ast, resultSet, language);
350
addParserTags (ParserManagerImpl.get (doc).getAST (), resultSet, language);
351
} catch (ParseException ex) {
352
ErrorManager.getDefault ().notify (ex);
358
private void addParserTags (ASTNode node, Result resultSet, Language language) {
360
//S ystem.out.println("CodeCompletion: No AST");
363
int offset = component.getCaret ().getDot ();
364
ASTPath path = node.findPath (offset - 1);
365
if (path == null) return;
366
ASTItem item = path.getLeaf ();
367
if (item instanceof ASTNode) return;
368
ASTToken token = (ASTToken) item;
369
if (token.getLength () != token.getIdentifier ().length ()) {
371
// something like token.getRealIndex () +
372
// add tokens for language borders...
375
int tokenOffset = token.getOffset ();
377
for (int i = path.size () - 1; i >= 0; i--) {
379
if (item.getLanguage () == language) break;
380
List<Feature> features = language.getFeatures (COMPLETION, path.subPath (i));
381
Iterator<Feature> it2 = features.iterator ();
382
while (it2.hasNext ()) {
383
Feature feature = it2.next ();
384
String completionType = getCompletionType (feature, token.getTypeName ());
385
if (completionType == null) continue;
386
if (COMPLETION_APPEND.equals (completionType) &&
387
offset < tokenOffset + token.getLength ()
389
String start = COMPLETION_COMPLETE.equals (completionType) ?
390
token.getIdentifier ().substring (0, offset - tokenOffset).trim () :
392
addTags (feature, start, SyntaxContext.create (doc, path.subPath (i)), resultSet);
396
DatabaseContext context = DatabaseManager.getRoot (node);
397
if (context == null) return;
398
List<DatabaseDefinition> definitions = context.getAllVisibleDefinitions (offset);
399
String start = token.getIdentifier ().substring (0, offset - tokenOffset).trim ();
400
Set<String> names = new HashSet<String> ();
401
Iterator<DatabaseDefinition> it = definitions.iterator ();
402
while (it.hasNext ()) {
403
DatabaseDefinition definition = it.next ();
404
names.add (definition.getName ());
405
CompletionSupport cs = createCompletionItem (definition, getFileName (), start);
407
if (definition.getName ().startsWith (start))
408
resultSet.addItem (cs);
411
if (fileObject != null) {
412
Map<FileObject,List<DatabaseDefinition>> globals = Index.getGlobalItems (fileObject, true);
413
Iterator<FileObject> it1 = globals.keySet ().iterator ();
414
while (it1.hasNext()) {
415
FileObject fileObject = it1.next();
416
List<DatabaseDefinition> l = globals.get (fileObject);
417
Iterator<DatabaseDefinition> it2 = l.iterator ();
418
while (it2.hasNext()) {
419
DatabaseDefinition definition = it2.next();
420
if (names.contains (definition.getName ())) continue;
421
CompletionSupport cs = createCompletionItem (definition, fileObject.getNameExt (), start);
423
if (definition.getName ().startsWith (start))
424
resultSet.addItem (cs);
429
} catch (FileNotParsedException ex) {
430
ex.printStackTrace ();
434
private String fileName;
436
private String getFileName () {
437
if (fileName == null) {
438
fileName = (String) doc.getProperty ("title");
439
if (fileName == null) return null;
440
int i = fileName.lastIndexOf (File.separatorChar);
442
fileName = fileName.substring (i + 1);
447
private void addTags (Feature feature, String start, Context context, Result resultSet) {
450
if (context instanceof SyntaxContext &&
451
feature.getType ("text" + j) == Feature.Type.STRING &&
452
((SyntaxContext) context).getASTPath ().getLeaf () instanceof ASTToken
457
Object o = feature.getValue ("text" + j, context);
458
if (o == null) break;
459
if (o instanceof String)
460
addTags ((String) o, feature, j, start, resultSet);
474
* Adds completion items obtained by method call to result.
476
private void addMethodCallTags (
482
Iterator it = keys.iterator ();
483
while (it.hasNext ()) {
484
Object o = it.next ();
485
if (o instanceof org.netbeans.api.languages.CompletionItem)
486
o = new CompletionSupport (
487
(org.netbeans.api.languages.CompletionItem) o,
490
CompletionItem item = (CompletionItem) o;
492
CharSequence chs = item.getInsertPrefix ();
493
String s = chs instanceof String ? (String) chs : chs.toString ();
495
s = s.toLowerCase ();
496
if (s.startsWith (start))
497
resultSet.addItem (item);
501
private void addTags (
509
text = text.toLowerCase ();
510
String description = (String) feature.getValue ("description" + j);
511
if (description == null)
513
String icon = (String) feature.getValue ("icon" + j);
514
CompletionItem item = new CompletionSupport (
515
text, start, description, null, icon, 2
518
if (!text.startsWith (start))
520
resultSet.addItem (item);
523
private static CompletionSupport createCompletionItem (DatabaseDefinition definition, String fileName,
526
if ("local".equals (definition.getType ()))
529
if ("parameter".equals (definition.getType ()))
530
type = Type.PARAMETER;
532
if ("field".equals (definition.getType ()))
535
if ("method".equals (definition.getType ()))
537
return new CompletionSupport (new org.netbeans.api.languages.CompletionItem (
538
definition.getName (),
547
private static interface Result {
548
void addItem (CompletionItem item);
550
boolean isFinished ();
553
private static class CompletionResult implements Result {
554
private CompletionResultSet resultSet;
556
CompletionResult (CompletionResultSet resultSet) {
557
this.resultSet = resultSet;
560
public void addItem (CompletionItem item) {
561
resultSet.addItem (item);
564
public void finish () {
568
public boolean isFinished () {
569
return resultSet.isFinished ();
573
private static class ListResult implements Result {
574
private List<CompletionItem> result = new ArrayList<CompletionItem> ();
575
private boolean finished = false;
576
private Object LOCK = new Object ();
578
public void addItem (CompletionItem item) {
582
public void finish () {
584
synchronized (LOCK) {
589
public boolean isFinished () {
593
void waitFinished () {
594
if (finished) return;
595
synchronized (LOCK) {
598
} catch (InterruptedException ex) {
602
public List<CompletionItem> getList () {