/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.tvl.goworks.editor.go.completion; import com.tvl.spi.editor.completion.CompletionProvider; import java.util.EnumSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.text.AbstractDocument; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.antlr.netbeans.editor.classification.TokenTag; import org.antlr.netbeans.editor.tagging.TaggedPositionRegion; import org.antlr.netbeans.editor.tagging.Tagger; import org.antlr.netbeans.editor.text.DocumentSnapshot; import org.antlr.netbeans.editor.text.NormalizedSnapshotPositionRegionCollection; import org.antlr.netbeans.editor.text.OffsetRegion; import org.antlr.netbeans.editor.text.SnapshotPositionRegion; import org.antlr.netbeans.editor.text.VersionedDocumentUtilities; import org.antlr.netbeans.parsing.spi.ParserData; import org.antlr.netbeans.parsing.spi.ParserDataOptions; import org.antlr.netbeans.parsing.spi.ParserTaskManager; import org.antlr.v4.runtime.Lexer; import org.antlr.v4.runtime.Token; import org.antlr.works.editor.antlr4.completion.AbstractCompletionProvider; import org.antlr.works.editor.antlr4.completion.AbstractCompletionQuery; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.Parameters; import org.tvl.goworks.editor.GoEditorKit; import org.tvl.goworks.editor.go.GoParserDataDefinitions; import org.tvl.goworks.editor.go.parser.GoLexer; /** * * @author Sam Harwell */ @MimeRegistration(mimeType=GoEditorKit.GO_MIME_TYPE, service=CompletionProvider.class) @NbBundle.Messages({ "GCP-imported-items=", "GCP-instance-members=" }) public class GoCompletionProvider extends AbstractCompletionProvider { // -J-Dorg.tvl.goworks.editor.go.completion.GoCompletionProvider.level=FINE private static final Logger LOGGER = Logger.getLogger(GoCompletionProvider.class.getName()); private static String goCompletionAutoPopupTriggers = "."; private static String goCompletionSelectors = "{}[]()<>.,:;+-*/%&|^!~=?@#'\"\\ "; public static void incompleteCompletionSupport() { assert false : "Editor code completion for Go is not yet complete."; } public static void incompleteCompletionSupport(String message) { assert false : message; } @Override public int getAutoQueryTypes(JTextComponent component, String typedText) { if (typedText == null || typedText.length() != 1) { return 0; } boolean triggered = getCompletionAutoPopupTriggers().indexOf(typedText.charAt(0)) >= 0; if (triggered || (autoPopupOnIdentifierPart() && GoCompletionQuery.isIdentifierPart(typedText))) { int offset = component.getSelectionStart() - 1; Token contextToken = getContext(component, offset); if (contextToken == null) { return 0; } if (!triggered) { // the caret must be at the end of the identifier. note that the // offset is already 1 position before the caret, so no need to // add 1 to contextToken.getStopIndex(). if (offset != contextToken.getStopIndex()) { return 0; } // only trigger for the first character of the identifier if (contextToken.getStopIndex() > contextToken.getStartIndex()) { return 0; } } boolean allowInStrings = false; if (isGoContext(contextToken, offset, allowInStrings)) { return COMPLETION_QUERY_TYPE | AUTO_QUERY_TYPE; } } return 0; } @Override protected AbstractCompletionQuery createCompletionQuery(int queryType, int caretOffset, boolean extend) { return new GoCompletionQuery(this, queryType, caretOffset, true, extend); } @Override public boolean autoPopupOnIdentifierPart() { return true; } @Override public String getCompletionAutoPopupTriggers() { return goCompletionAutoPopupTriggers; } @Override public String getCompletionSelectors() { return goCompletionSelectors; } @Override public Token getContext(JTextComponent component, int offset) { return getContext(component.getDocument(), offset); } @Override public Token getContext(Document document, int offset) { Parameters.notNull("document", document); if (document instanceof AbstractDocument) { ((AbstractDocument)document).readLock(); } try { // try { ParserTaskManager taskManager = Lookup.getDefault().lookup(ParserTaskManager.class); DocumentSnapshot snapshot = VersionedDocumentUtilities.getVersionedDocument(document).getCurrentSnapshot(); Future<ParserData<Tagger<TokenTag<Token>>>> futureTokensData = taskManager.getData(snapshot, GoParserDataDefinitions.LEXER_TOKENS, EnumSet.of(ParserDataOptions.SYNCHRONOUS)); if (futureTokensData == null) { return null; } Tagger<TokenTag<Token>> tagger; try { tagger = futureTokensData.get().getData(); if (tagger == null) { return null; } } catch (InterruptedException | ExecutionException ex) { LOGGER.log(Level.WARNING, "An exception occurred while getting tokens.", ex); return null; } // get the token(s) at the cursor position, with affinity both directions OffsetRegion region = OffsetRegion.fromBounds(Math.max(0, offset - 1), Math.min(snapshot.length(), offset + 1)); Iterable<TaggedPositionRegion<TokenTag<Token>>> tags = tagger.getTags(new NormalizedSnapshotPositionRegionCollection(new SnapshotPositionRegion(snapshot, region))); // TODO: cache tokens // ANTLRInputStream input = new ANTLRInputStream(document.getText(0, document.getLength())); // GoLexer lexer = new GoLexer(input); // CommonTokenStream tokenStream = new CommonTokenStream(lexer); Token token = null; // for (token = tokenStream.LT(1); token != null && token.getType() != Token.EOF; token = tokenStream.LT(1)) { // tokenStream.consume(); // if (token.getStartIndex() <= offset && token.getStopIndex() >= offset) { // break; // } // } for (TaggedPositionRegion<TokenTag<Token>> taggedRegion : tags) { if (taggedRegion.getTag().getToken().getChannel() != Lexer.DEFAULT_TOKEN_CHANNEL) { continue; } token = taggedRegion.getTag().getToken(); if (token.getStartIndex() <= offset && token.getStopIndex() >= offset) { break; } } if (token == null) { // try again without skipping off-channel tokens for (TaggedPositionRegion<TokenTag<Token>> taggedRegion : tags) { token = taggedRegion.getTag().getToken(); if (token.getStartIndex() <= offset && token.getStopIndex() >= offset) { break; } } } return token; //List<Token> tokens; // } catch (BadLocationException ex) { // Exceptions.printStackTrace(ex); // return null; // } } finally { if (document instanceof AbstractDocument) { ((AbstractDocument)document).readUnlock(); } } } @Override public boolean isContext(Token token, int offset, int queryType) { return isGoContext(token, offset, false); } /*package*/ boolean isGoContext(JTextComponent component, int offset, boolean allowInStrings) { return isGoContext(getContext(component, offset), offset, allowInStrings); } /*package*/ static boolean isGoContext(Token token, int offset, boolean allowInStrings) { if (token == null) { return false; } switch (token.getType()) { case GoLexer.COMMENT: return false; case GoLexer.CharLiteral: case GoLexer.StringLiteral: return allowInStrings; case GoLexer.WS: case GoLexer.NEWLINE: return true; default: return token.getChannel() == Lexer.DEFAULT_TOKEN_CHANNEL; } } }