root/org.axdt.as3/src/org/axdt/as3/imp/services/AS3ContentProposer.java @ 49cbb509af4876ed4a2ccd7942c17372373a5ae2

Revision 49cbb509af4876ed4a2ccd7942c17372373a5ae2, 13.1 KB (checked in by mb0 <mb0@…>, 15 months ago)

moved imp related language services to org.axdt.as3.imp.services

  • Property mode set to 100644
Line 
1package org.axdt.as3.imp.services;
2
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.Collections;
6import java.util.Comparator;
7
8import lpg.runtime.IAst;
9import lpg.runtime.IPrsStream;
10import lpg.runtime.IToken;
11
12import org.axdt.as3.AS3Plugin;
13import org.axdt.as3.imp.parser.AS3ASTNodeLocator;
14import org.axdt.as3.imp.parser.AS3ParseController;
15import org.axdt.as3.imp.parser.AS3Parsersym;
16import org.axdt.as3.imp.parser.SymbolTable;
17import org.axdt.as3.imp.parser.Ast.ASTNode;
18import org.axdt.as3.imp.parser.Ast.IAnnotatableDirective_full;
19import org.axdt.as3.imp.parser.Ast.IName;
20import org.axdt.as3.imp.parser.Ast.ImportDirective;
21import org.axdt.as3.imp.parser.Ast.PackageDefinition;
22import org.axdt.as3.templates.AS3TemplateCompletionProcessor;
23import org.axdt.as3.util.AS3Util;
24import org.axdt.axdoc.model.AXEntry;
25import org.axdt.axdoc.model.AXIndex;
26import org.axdt.axdoc.util.Index0r;
27import org.eclipse.core.resources.IResource;
28import org.eclipse.imp.editor.SourceProposal;
29import org.eclipse.imp.parser.IParseController;
30import org.eclipse.imp.services.IContentProposer;
31import org.eclipse.jface.text.BadLocationException;
32import org.eclipse.jface.text.ITextViewer;
33import org.eclipse.jface.text.contentassist.ICompletionProposal;
34
35public class AS3ContentProposer implements IContentProposer {
36       
37        private static AS3ASTNodeLocator locator = new AS3ASTNodeLocator();
38       
39        private boolean offsetWithinToken;
40        private boolean usingBackup;
41        private int offset;
42        private AS3ParseController control;
43        private IToken token;
44        private String prefix;
45        private ASTNode ast;
46        private ASTNode node;
47
48        private ArrayList<IAst> ancestors;
49
50        private IToken previous;
51
52        private IToken next;
53
54        private boolean offsetAfterToken;
55
56        private ITextViewer viewer;
57
58        private boolean dontTrustTokens;
59
60        private int originalOffset;
61
62        private boolean offsetBeforeToken;
63
64        private int offsetDiff;
65
66        private boolean nextIsSemi;
67       
68        private void clear() {
69                offsetWithinToken = offsetAfterToken = offsetBeforeToken = nextIsSemi = false;
70                usingBackup = dontTrustTokens = false;
71                prefix = null; 
72                ancestors = null;
73                token = previous = next = null;
74                ast = node = null;
75                originalOffset = offset;
76        }
77
78        public AS3ContentProposer() {
79        }
80        private boolean tokenIsValid(IToken t) {
81                String text = t.toString();
82                try {
83                        String string = viewer.getDocument().get(t.getStartOffset(), text.length());
84                        return text.equals(string);
85                } catch (BadLocationException e) {
86                        AS3Plugin.getDefault().debug("error validating token", e);
87                }
88                return false;
89        }
90        private String tokenIsValid(IToken t, int diff, int lendiff) {
91                try {
92                        String text = t.toString();
93                        int offset = t.getStartOffset()+diff;
94                        int length = text.length()+lendiff;
95                        String string = viewer.getDocument().get(offset, length);
96                        if (lendiff>0) {
97                                if (string.startsWith(text)) {
98                                        return string;
99                                }
100                        } else if (diff != 0) {
101                                if (text.equals(string))
102                                        return string;
103                        }
104                } catch (Exception e) {
105                        AS3Plugin.getDefault().debug("error validating token", e);
106                }
107                return null;
108        }
109        protected boolean collectInfo(IParseController controller, int offset, ITextViewer viewer) {
110                this.viewer = viewer;
111                this.control = (AS3ParseController) controller;
112                this.offset = offset;
113                offsetDiff = offset - originalOffset;
114                ASTNode newast = getAst();
115                if (newast != null && newast == ast) {
116                        AS3Plugin.getDefault().debug("using old ast nothing changed");
117                        return true;
118                }
119                if (newast == null) {
120                        AS3Plugin.getDefault().debug("no ast available try to recover");
121                        Object backupAst = control.getBackupAst();
122                        if (! (backupAst instanceof ASTNode)) {
123                                AS3Plugin.getDefault().debug("no backup ast. recover failed.");
124                                return false;
125                        }
126                        newast = (ASTNode) backupAst;
127                        String validPrefix = tokenIsValid(token,0,offsetDiff);
128                        if (validPrefix != null) {
129                                if (offsetDiff>0) {
130                                        boolean previousIsValid = tokenIsValid(previous);
131                                        boolean nextIsValid = null != tokenIsValid(next,offsetDiff,0);
132                                        if (previousIsValid && nextIsValid) {
133                                                prefix = validPrefix;
134                                                dontTrustTokens = true;
135                                                this.offset = offset; 
136                                                AS3Plugin.getDefault().debug("recovery ok.");
137                                                return true;
138                                        }
139                                }
140                        }
141                        return false;
142                }
143                clear();
144                try {
145                        ast = newast;
146                        IPrsStream stream = control.getParser().getIPrsStream();
147                        int index = stream.getTokenIndexAtCharacter(offset);
148                        token = getToken(stream, index < 0 ? -index + 1: index);
149                        previous = getToken(stream, token.getTokenIndex()-1);
150                        if (previous.getEndOffset() == offset - 1) {
151                                offsetAfterToken = true;
152                                next = token;
153                                token = previous;
154                                previous = stream.getIToken(token.getTokenIndex()-1);
155                        } else {
156                                next = stream.getIToken(stream.getNext(token.getTokenIndex()));
157                        }
158                        nextIsSemi = next.getKind() == Sym.TK_SEMI;
159                        offsetWithinToken = Sym.isWithinToken(offset, token);
160                        offsetBeforeToken = offset < token.getStartOffset();
161                        if ((offsetAfterToken||offsetWithinToken) && Sym.isKeyword(token)) 
162                                return false;
163                        this.prefix = offsetWithinToken ? Sym.getPrefix(offset, token) 
164                                        : ( offsetAfterToken ? token.toString() : "");
165                        node = getNode(token);
166                        collectAncestorInfo(node);
167                } catch (Exception e) {
168                        AS3Plugin.getDefault().debug("error collecting context info for completion proposals", e);
169                }
170                return true;
171        }
172        private IToken getToken(IPrsStream s, int index) {
173                IToken result = s.getIToken(index);
174                if (result.getKind() == Sym.TK_VirtualSemicolon)
175                        result = s.getIToken(index-1);
176                return result;
177        }
178        private void collectAncestorInfo(ASTNode node) {
179                ancestors = new ArrayList<IAst>();
180                for (IAst n = node; n != null; n = n.getParent()) {
181                        if (n instanceof IAnnotatableDirective_full
182                         || n instanceof ImportDirective
183                         || n instanceof PackageDefinition
184                         ) {
185                                ancestors.add(n);
186                        }
187                }
188        }
189        private ASTNode getNode(IToken t) {
190                Object object = locator.findNode(ast, t.getStartOffset(), t.getEndOffset());
191                return (object instanceof ASTNode) ? (ASTNode) object : null;
192        }
193        private ASTNode getAst() {
194                Object object = control.parse(viewer.getDocument().get(), null);
195                return (object instanceof ASTNode) ? (ASTNode) object : null;
196        }
197        /**
198         * Returns an array of content proposals applicable relative to the AST of
199         * the given parse controller at the given position. (The provided
200         * ITextViewer is not used in the default implementation provided here but
201         * but is stipulated by the IContentProposer interface for purposes such as
202         * accessing the IDocument for which content proposals are sought.)
203         *
204         * @param controller
205         *            A parse controller from which the AST of the document being
206         *            edited can be obtained
207         * @param int
208         *            The offset for which content proposals are sought
209         * @param viewer
210         *            The viewer in which the document represented by the AST in the
211         *            given parse controller is being displayed (may be null for
212         *            some implementations)
213         * @return An array of completion proposals applicable relative to the AST
214         *         of the given parse controller at the given position
215         */
216        public ICompletionProposal[] getContentProposals(IParseController controller, int offset,
217                        ITextViewer viewer) {
218                boolean precede = collectInfo(controller, offset, viewer);
219                if (!precede) {
220                        return new ICompletionProposal[0];
221                }
222                ProposalHelper proposals = new ProposalHelper();
223                if (ast != null) {
224                        if (ancestors != null && ancestors.size() > 0) {
225                                IAst ancestor = ancestors.get(0);
226                                if (ancestor instanceof PackageDefinition) {
227                                        PackageDefinition packdef = (PackageDefinition)ancestor;
228                                        if (packdef.getBody() == null || offset < packdef.getBody().getLeftIToken().getStartOffset()+offsetDiff) {
229                                                // in package header
230                                                IName nameNode = packdef.getName();
231                                                prefix = fetchName((IAst) nameNode);
232                                                IResource project = control.getProject().getResource();
233                                                String expectedName = AS3Util.getExpectedPackageName(project,project.getFullPath().append(control.getPath()));
234                                                if (!prefix.equals(expectedName))
235                                                        proposals.addSourceProposal(expectedName, prefix,PackageDefinition.class);
236                                        } else {
237                                                proposals.addUnitScopeProposals(node);
238                                                if (prefix.startsWith("import")) {
239                                                        for (String packname:Index0r.getInstance().getPackageNames()) {
240                                                                proposals.addSourceProposal("import "+packname+".*", prefix, ImportDirective.class, !nextIsSemi);
241                                                        }
242                                                }
243                                        }
244                                } else if (ancestor instanceof ImportDirective) {
245                                        IName nameNode = ((ImportDirective)ancestor).getName();
246                                        prefix = fetchName((IAst) nameNode);
247                                        int dotIndex = prefix.lastIndexOf('.');
248                                        Collection<String> packageNames = Index0r.getInstance().getPackageNames();
249                                        if (dotIndex > 0) {
250                                                String packname = prefix.substring(0,dotIndex);
251                                                String rest = prefix.substring(dotIndex+1);
252                                                if (packageNames.contains(packname)) {
253                                                        if ("*".equals(rest)) {
254                                                                return proposals.getResult();
255                                                        }
256                                                        for (AXIndex index:Index0r.getInstance().getPackages(packname)) {
257                                                                for (AXEntry entry:index.getEntries()) {
258                                                                        String entryname = entry.getName();
259                                                                        if (entryname != null && (rest.length() == 0 || entryname.startsWith(rest))) {
260                                                                                proposals.addSourceProposal(packname+"."+entryname, prefix, ImportDirective.class, !nextIsSemi);
261                                                                        }
262                                                                }
263                                                                if (rest.length() == 0) {
264                                                                        proposals.addSourceProposal(packname+".*", prefix, ImportDirective.class, !nextIsSemi);
265                                                                }
266                                                        }
267                                                }
268                                        }
269                                        for (String packname:packageNames) {
270                                                if (packname.startsWith(prefix)) {
271                                                        proposals.addSourceProposal(packname+".*", prefix, ImportDirective.class, !nextIsSemi);
272                                                }
273                                        }
274                                } else {
275                                        proposals.addUnitScopeProposals(node);
276                                }
277                        } else {
278                                // parent is ast
279                        }
280                }
281                if (prefix != null && prefix.length() > 0) {
282                        proposals.addKeywordProposals();
283                        proposals.addTemplateProposals(viewer);
284                }
285                return proposals.getResult();
286        }
287        protected String fetchName(IAst nameNode) {
288                String name = nameNode == null ? "" : nameNode.toString();
289                int startOffset = nameNode == null ? offset : nameNode.getLeftIToken().getStartOffset();
290                if (offset > startOffset + name.length()) {
291                        try {
292                                name = viewer.getDocument().get(startOffset, offset - startOffset);
293                        } catch (Exception e) {
294                                AS3Plugin.getDefault().debug("error fetching real name",e);
295                        }
296                }
297                return name;
298        }
299        public static final Comparator<ICompletionProposal> ProposalComparator = new Comparator<ICompletionProposal>() {
300                public int compare(ICompletionProposal p1, ICompletionProposal p2) {
301                        return p1.getDisplayString().compareTo(p2.getDisplayString());
302                }
303        };
304        private class ProposalHelper implements AS3Parsersym {
305                ArrayList<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
306               
307                void addUnitScopeProposals(ASTNode node) {
308                        SymbolTable table = SymbolTable.getEnclosing(node);
309                        if (table == null) return;
310                        for (String key:table.getAllKeys(null)) {
311                                String name = table.cleanName(key);
312                                if (name.startsWith(prefix)) {
313                                        IAst ast = table.findDeclaration(key);
314                                        result.add(new AS3SourceProposal(name, prefix, offset, ast.getClass()));
315                                }
316                        }
317                }
318                void addSourceProposal(String packageName, String usePrefix, ASTNode node) {
319                        result.add(new AS3SourceProposal(packageName, usePrefix, offset, node.getClass()));
320                }
321                void addSourceProposal(String packageName, String usePrefix, Class<?> nodeClass) {
322                        result.add(new AS3SourceProposal(packageName, usePrefix, offset, nodeClass));
323                }
324                void addSourceProposal(String packageName, String usePrefix, Class<?> nodeClass, boolean addSemi) {
325                        if (addSemi) packageName += ";";
326                        result.add(new AS3SourceProposal(packageName, usePrefix, offset, nodeClass));
327                }
328                void addKeywordProposals() {
329                        for (String key:Sym.getKeyWords()) {
330                                if (!key.equals(prefix) && key.startsWith(prefix)) {
331                                        SourceProposal proposal = new SourceProposal(key, prefix, offset);
332                                        result.add(proposal);
333                                }
334                        }
335                }
336                void addTemplateProposals(ITextViewer viewer) {
337                        AS3TemplateCompletionProcessor processor = new AS3TemplateCompletionProcessor();
338                        result.addAll(processor.computeCompletionProposalList(viewer, offset, prefix));
339                }
340                ICompletionProposal[] getResult() {
341                        Collections.sort(result, ProposalComparator);
342                        return result.toArray(new ICompletionProposal[result.size()]);
343                }
344        }
345        public static class Sym implements AS3Parsersym {
346                private static String[] keywords = null;
347               
348                public static String[] getKeyWords() {
349                        if (keywords == null)
350                                keywords = createKeywords();
351                        return keywords;
352                }
353               
354                public static boolean isKeyword(IToken t) {
355                        int kind = t.getKind();
356                        if (kind > 1 && kind < orderedTerminalSymbols.length) {
357                                return orderedTerminalSymbols[kind].charAt(0) > 'Z';
358                        }
359                        return false;
360                }
361
362                public static String getPrefix(int o, IToken t) {
363                        return t.getKind() == Sym.TK_IDENTIFIER ?
364                                        t.toString().substring(0, o-t.getStartOffset()) : "";
365                }
366
367                public static boolean isWithinToken(int o, IToken t) {
368                        return o >= t.getStartOffset() && o <= t.getEndOffset();
369                }
370               
371                private static String[] createKeywords() {
372                        ArrayList<String> result = new ArrayList<String>();
373                        for (int i = 1; i < orderedTerminalSymbols.length; i++) {
374                                if (orderedTerminalSymbols[i].charAt(0) > 'Z')
375                                        result.add(orderedTerminalSymbols[i]);
376                        }
377                        return result.toArray(new String[result.size()]);
378                }
379        }
380}
Note: See TracBrowser for help on using the browser.