netbeanstypescript.TSCodeCompletion.java Source code

Java tutorial

Introduction

Here is the source code for netbeanstypescript.TSCodeCompletion.java

Source

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2015 Everlaw. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
package netbeanstypescript;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import netbeanstypescript.api.lexer.JsTokenId;
import netbeanstypescript.api.lexer.LexUtilities;
import netbeanstypescript.options.OptionsUtils;
import org.json.simple.JSONObject;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
import org.netbeans.modules.csl.api.*;
import org.netbeans.modules.csl.spi.DefaultCompletionResult;
import org.netbeans.modules.csl.spi.ParserResult;
import org.openide.filesystems.FileObject;

/**
 *
 * @author jeffrey
 */
public class TSCodeCompletion implements CodeCompletionHandler {

    public static class TSCompletionProposal extends TSElementHandle implements CompletionProposal {
        FileObject fileObj;
        int caretOffset;
        int anchorOffset;

        String type;

        TSCompletionProposal(FileObject fileObj, int caretOffset, int anchorOffset, JSONObject m) {
            super(OffsetRange.NONE, m);
            this.fileObj = fileObj;
            this.caretOffset = caretOffset;
            this.anchorOffset = anchorOffset;
            type = (String) m.get("type"); // may be null
        }

        @Override
        public int getAnchorOffset() {
            return anchorOffset;
        }

        @Override
        public ElementHandle getElement() {
            // Keywords don't have documentation or location info. Calling getCompletionEntryDetails
            // may show irrelevant info about a symbol with the same name.
            // https://github.com/Microsoft/TypeScript/issues/3921
            return kind == ElementKind.KEYWORD ? null : this;
        }

        @Override
        public String document() {
            Object info = TSService.call("getCompletionEntryDetails", fileObj, caretOffset, name);
            return info == null ? null : new TSElementHandle(OffsetRange.NONE, (JSONObject) info).document();
        }

        @Override
        public String getInsertPrefix() {
            return name;
        }

        @Override
        public String getSortText() {
            return null;
        }

        @Override
        public String getLhsHtml(HtmlFormatter hf) {
            if (modifiers.contains(Modifier.DEPRECATED)) {
                hf.deprecated(true);
                hf.appendText(name);
                hf.deprecated(false);
            } else {
                hf.appendText(name);
            }
            return hf.getText();
        }

        @Override
        public String getRhsHtml(HtmlFormatter hf) {
            hf.setMaxLength(
                    OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).getCodeCompletionItemSignatureWidth());
            if (type == null) {
                return null;
            }
            hf.type(true);
            hf.appendText(type);
            hf.type(false);
            return hf.getText();
        }

        @Override
        public boolean isSmart() {
            return false;
        }

        @Override
        public int getSortPrioOverride() {
            return 0;
        }

        @Override
        public String getCustomInsertTemplate() {
            String suffix = "";
            switch (getKind()) {
            case METHOD:
                suffix = "(${cursor})";
                break;
            case PACKAGE:
                suffix = "/";
                break;
            }
            return getInsertPrefix() + suffix;
        }

        private JSONObject location;

        @Override
        public FileObject getFileObject() {
            location = (JSONObject) TSService.call("getCompletionEntryLocation", fileObj, caretOffset, name);
            if (location == null)
                return null;
            return TSService.findAnyFileObject((String) location.get("fileName"));
        }

        @Override
        public String getMimeType() {
            return "text/typescript";
        }

        @Override
        public OffsetRange getOffsetRange(ParserResult pr) {
            // This method only gets called when the location is in the same file that the
            // completion was done in. Otherwise, csl.api's UiUtils.open just opens the file
            // and doesn't set the offset, even though we have it. :(
            if (location == null)
                return OffsetRange.NONE;
            return new OffsetRange(((Number) location.get("start")).intValue(),
                    ((Number) location.get("end")).intValue());
        }
    }

    static long lastCompletionTime;
    static boolean lastCompletionWasGlobal;

    @Override
    public CodeCompletionResult complete(CodeCompletionContext ccc) {
        FileObject fileObj = ccc.getParserResult().getSnapshot().getSource().getFileObject();
        int caretOffset = ccc.getCaretOffset();
        String prefix = ccc.getPrefix();
        if (!ccc.isCaseSensitive())
            prefix = prefix.toLowerCase();
        JSONObject info;
        synchronized (TSCodeCompletion.class) {
            info = (JSONObject) TSService.call("getCompletions", fileObj, caretOffset);
            lastCompletionTime = System.currentTimeMillis();
            lastCompletionWasGlobal = info != null && Boolean.TRUE.equals(info.get("isGlobalCompletion"));
            TSCodeCompletion.class.notify();
        }
        if (info == null) {
            return CodeCompletionResult.NONE;
        }

        List<CompletionProposal> lst = new ArrayList<>();
        for (JSONObject entry : (List<JSONObject>) info.get("entries")) {
            String name = (String) entry.get("name");
            if (!ccc.isCaseSensitive())
                name = name.toLowerCase();
            if (ccc.isPrefixMatch() ? name.startsWith(prefix) : name.equals(prefix)) {
                lst.add(new TSCodeCompletion.TSCompletionProposal(fileObj, caretOffset,
                        caretOffset - prefix.length(), entry));
            }
        }
        return new DefaultCompletionResult(lst, false);
    }

    @Override
    public String document(ParserResult pr, ElementHandle eh) {
        return ((TSElementHandle) eh).document();
    }

    @Override
    public ElementHandle resolveLink(String string, ElementHandle eh) {
        return null;
    }

    @Override
    public String getPrefix(ParserResult info, int caretOffset, boolean upToOffset) {
        CharSequence seq = info.getSnapshot().getText();
        int i = caretOffset, j = i;
        while (i > 0 && Character.isJavaIdentifierPart(seq.charAt(i - 1))) {
            i--;
        }
        while (!upToOffset && j < seq.length() && Character.isJavaIdentifierPart(seq.charAt(j))) {
            j++;
        }
        return seq.subSequence(i, j).toString();
    }

    // CHARS_NO_AUTO_COMPLETE and getAutoQuery from javascript2.editor JsCodeCompletion
    private static final String CHARS_NO_AUTO_COMPLETE = ";,/+-\\:={}[]()"; //NOI18N

    @Override
    public QueryType getAutoQuery(JTextComponent component, String typedText) {
        if (typedText.length() == 0) {
            return QueryType.NONE;
        }

        int offset = component.getCaretPosition();
        TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(component.getDocument(), offset);
        if (ts != null) {
            int diff = ts.move(offset);
            TokenId currentTokenId = null;
            if (diff == 0 && ts.movePrevious() || ts.moveNext()) {
                currentTokenId = ts.token().id();
            }

            char lastChar = typedText.charAt(typedText.length() - 1);
            if (currentTokenId == JsTokenId.BLOCK_COMMENT || currentTokenId == JsTokenId.DOC_COMMENT
                    || currentTokenId == JsTokenId.LINE_COMMENT) {
                if (lastChar == '@') { //NOI18N
                    return QueryType.COMPLETION;
                }
            } else if (currentTokenId == JsTokenId.STRING && lastChar == '/') {
                return QueryType.COMPLETION;
            } else {
                switch (lastChar) {
                case '.': //NOI18N
                    if (OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).autoCompletionAfterDot()) {
                        return QueryType.COMPLETION;
                    }
                    break;
                default:
                    if (OptionsUtils.forLanguage(JsTokenId.javascriptLanguage()).autoCompletionFull()) {
                        if (!Character.isWhitespace(lastChar) && CHARS_NO_AUTO_COMPLETE.indexOf(lastChar) == -1) {
                            return QueryType.COMPLETION;
                        }
                    }
                    return QueryType.NONE;
                }
            }
        }
        return QueryType.NONE;
    }

    @Override
    public String resolveTemplateVariable(String string, ParserResult pr, int i, String string1, Map map) {
        return null;
    }

    @Override
    public Set<String> getApplicableTemplates(Document dcmnt, int i, int i1) {
        return null;
    }

    @Override
    public ParameterInfo parameters(ParserResult pr, int i, CompletionProposal cp) {
        return ParameterInfo.NONE;
    }

    public static class TemplateFilterFactory implements CodeTemplateFilter.ContextBasedFactory {
        @Override
        public CodeTemplateFilter createFilter(JTextComponent component, int offset) {
            if (!Thread.currentThread().getName().equals("Code Completion")) {
                // Called from AbbrevDetection or SurroundWithFix - just allow it
                return createFilter(true);
            }
            // This is a code completion (called from CodeTemplateCompletionProvider). To determine
            // whether code templates should show up, we need the result from .complete(), but it's
            // running in a different thread. To make matters even worse, typing or backspacing
            // characters during a completion may re-run this method without re-running .complete().
            synchronized (TSCodeCompletion.class) {
                // Assume that if .complete() is called, the two synchronized blocks are entered
                // within 50ms of each other.
                if (lastCompletionTime < System.currentTimeMillis() - 50) {
                    try {
                        // .complete() has not yet entered its sync block; wait for it.
                        TSCodeCompletion.class.wait(50);
                        // If it was called, it'll have finished its sync block now. If not, we're
                        // still in the same completion and lastCompletionWasGlobal is still valid.
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                return createFilter(lastCompletionWasGlobal);
            }
        }

        private CodeTemplateFilter createFilter(final boolean accept) {
            return new CodeTemplateFilter() {
                @Override
                public boolean accept(CodeTemplate template) {
                    return accept;
                }
            };
        }

        @Override
        public List<String> getSupportedContexts() {
            return Collections.singletonList("JavaScript-Code");
        }
    }
}