| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.gradle.editor.cli; |
| |
| import org.netbeans.modules.gradle.api.GradleBaseProject; |
| import org.netbeans.modules.gradle.api.GradleTask; |
| import org.netbeans.modules.gradle.api.execute.GradleCommandLine; |
| import java.awt.Color; |
| import java.awt.Font; |
| import java.awt.Graphics; |
| import java.awt.event.KeyEvent; |
| import java.awt.font.TextAttribute; |
| import java.net.URL; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.swing.Action; |
| import javax.swing.ImageIcon; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.Element; |
| import javax.swing.text.JTextComponent; |
| import javax.swing.text.StyledDocument; |
| import org.netbeans.api.annotations.common.StaticResource; |
| import org.netbeans.api.editor.completion.Completion; |
| import org.netbeans.api.editor.mimelookup.MimeRegistration; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.gradle.spi.actions.ReplaceTokenProvider; |
| import org.netbeans.spi.editor.completion.CompletionDocumentation; |
| import org.netbeans.spi.editor.completion.CompletionItem; |
| import org.netbeans.spi.editor.completion.CompletionProvider; |
| import org.netbeans.spi.editor.completion.CompletionResultSet; |
| import org.netbeans.spi.editor.completion.CompletionTask; |
| import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery; |
| import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; |
| import org.netbeans.spi.editor.completion.support.CompletionUtilities; |
| import org.openide.util.Exceptions; |
| import org.openide.util.ImageUtilities; |
| |
| /** |
| * |
| * @author lkishalmi |
| */ |
| @MimeRegistration(mimeType = GradleCliEditorKit.MIME_TYPE, service = CompletionProvider.class) |
| public class GradleCliCompletionProvider implements CompletionProvider { |
| private static final Pattern PROP_INPUT = Pattern.compile("\\$\\{([\\w.]*)$"); //NOI18N |
| private static final String INPUT_TOKEN = "input:"; //NOI18N |
| private static final Set<GradleCommandLine.GradleOptionItem> GRADLE_OPTIONS; |
| |
| static { |
| Set<GradleCommandLine.GradleOptionItem> all = new HashSet<>(); |
| GRADLE_OPTIONS = Collections.unmodifiableSet(all); |
| all.addAll(Arrays.asList(GradleCommandLine.Flag.values())); |
| all.addAll(Arrays.asList(GradleCommandLine.Parameter.values())); |
| all.addAll(Arrays.asList(GradleCommandLine.Property.values())); |
| } |
| |
| @Override |
| public CompletionTask createTask(int queryType, JTextComponent component) { |
| if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) { |
| return null; |
| } |
| |
| return new AsyncCompletionTask(new AsyncCompletionQuery() { |
| @Override |
| protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { |
| String filter = null; |
| int startOffset = caretOffset - 1; |
| |
| try { |
| final StyledDocument bDoc = (StyledDocument) doc; |
| final int lineStartOffset = getRowFirstNonWhite(bDoc, caretOffset); |
| final char[] line = bDoc.getText(lineStartOffset, caretOffset - lineStartOffset).toCharArray(); |
| final int whiteOffset = indexOfWhite(line); |
| filter = new String(line, whiteOffset + 1, line.length - whiteOffset - 1); |
| if (whiteOffset > 0) { |
| startOffset = lineStartOffset + whiteOffset + 1; |
| } else { |
| startOffset = lineStartOffset; |
| } |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| Project project = null; |
| Object prop = doc.getProperty(Document.StreamDescriptionProperty); |
| if (prop != null && prop instanceof Project) { |
| project = (Project) prop; |
| } |
| Matcher tokenMatcher = PROP_INPUT.matcher(filter); |
| boolean tokenInFilter = tokenMatcher.find(); |
| try { |
| GradleCommandLine cli = new GradleCommandLine(doc.getText(0, doc.getLength())); |
| if (!filter.startsWith("-") && !tokenInFilter) { |
| if (project != null) { |
| GradleBaseProject gbp = GradleBaseProject.get(project); |
| for (GradleTask task : gbp.getTasks()) { |
| if (!task.isPrivate() |
| && !cli.getTasks().contains(task.getName()) |
| && !cli.getExcludedTasks().contains(task.getName()) |
| && (task.getName().startsWith(filter) || task.matches(filter))) { |
| resultSet.addItem(new GradleTaskCompletionItem(task, startOffset, caretOffset)); |
| } |
| } |
| } |
| } |
| if (filter.isEmpty() || filter.startsWith("-")) { |
| for (GradleCommandLine.GradleOptionItem item : GRADLE_OPTIONS) { |
| if (cli.canAdd(item)) { |
| for (String f : item.getFlags()) { |
| if (f.startsWith(filter)) { |
| resultSet.addItem(new GradleOptionCompletionItem(item, f, startOffset, caretOffset)); |
| } |
| } |
| } |
| } |
| } |
| } catch (BadLocationException ex) { |
| // Nothing to do. |
| } |
| if (tokenInFilter && (project != null)) { |
| String propFilter = tokenMatcher.group(1); |
| ReplaceTokenProvider tokenProvider = project.getLookup().lookup(ReplaceTokenProvider.class); |
| for (String token : tokenProvider.getSupportedTokens()) { |
| if (token.startsWith(propFilter)) { |
| resultSet.addItem(new TokenCompletionItem(token, startOffset + tokenMatcher.start(1), caretOffset)); |
| } |
| } |
| if (INPUT_TOKEN.startsWith(propFilter)) { |
| resultSet.addItem(new TokenCompletionItem(INPUT_TOKEN, startOffset + tokenMatcher.start(1), caretOffset)); |
| } |
| } |
| |
| resultSet.finish(); |
| } |
| }, component); |
| } |
| |
| @Override |
| public int getAutoQueryTypes(JTextComponent component, String typedText) { |
| return 1; |
| } |
| |
| static int getRowFirstNonWhite(StyledDocument doc, int offset) throws BadLocationException { |
| Element lineElement = doc.getParagraphElement(offset); |
| int start = lineElement.getStartOffset(); |
| while (start + 1 < lineElement.getEndOffset()) { |
| try { |
| if (doc.getText(start, 1).charAt(0) != ' ') { |
| break; |
| } |
| } catch (BadLocationException ex) { |
| throw (BadLocationException) new BadLocationException( |
| "calling getText(" + start + ", " + (start + 1) |
| + ") on doc of length: " + doc.getLength(), start |
| ).initCause(ex); |
| } |
| start++; |
| } |
| return start; |
| } |
| |
| static int indexOfWhite(char[] line) { |
| int i = line.length; |
| while (--i > -1) { |
| final char c = line[i]; |
| if (Character.isWhitespace(c)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| private abstract static class AbstractGradleCompletionItem implements CompletionItem { |
| private final int startOffset; |
| private final int caretOffset; |
| |
| public AbstractGradleCompletionItem(int startOffset, int caretOffset) { |
| this.startOffset = startOffset; |
| this.caretOffset = caretOffset; |
| } |
| |
| protected abstract String getValue(); |
| @Override |
| public void defaultAction(JTextComponent jtc) { |
| try { |
| Document doc = jtc.getDocument(); |
| doc.remove(startOffset, caretOffset - startOffset); |
| doc.insertString(startOffset, getValue(), null); |
| //This statement will close the code completion box: |
| Completion.get().hideAll(); |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| @Override |
| public void processKeyEvent(KeyEvent evt) { |
| } |
| |
| @Override |
| public int getPreferredWidth(Graphics g, Font font) { |
| return CompletionUtilities.getPreferredWidth(getValue(), null, g, font); |
| } |
| |
| @Override |
| public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) { |
| CompletionUtilities.renderHtml(null, getValue(), null, g, defaultFont, defaultColor, width, height, selected); |
| } |
| |
| @Override |
| public CompletionTask createDocumentationTask() { |
| return null; |
| } |
| |
| @Override |
| public CompletionTask createToolTipTask() { |
| return null; |
| } |
| |
| @Override |
| public boolean instantSubstitution(JTextComponent component) { |
| return false; |
| } |
| |
| @Override |
| public int getSortPriority() { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public CharSequence getSortText() { |
| return getValue(); |
| } |
| |
| @Override |
| public CharSequence getInsertPrefix() { |
| return getValue(); |
| } |
| } |
| |
| private static class GradleTaskCompletionItem extends AbstractGradleCompletionItem { |
| |
| //This resource is from Gradle Projects module |
| private static final String TASK_ICON = "org/netbeans/modules/gradle/resources/gradle-task.gif"; //NOI18N |
| private static final ImageIcon TASK_IMAGEICON = ImageUtilities.loadImageIcon(TASK_ICON, false); |
| |
| private final GradleTask task; |
| |
| public GradleTaskCompletionItem(GradleTask task, int startOffset, int caretOffset) { |
| super(startOffset, caretOffset); |
| this.task = task; |
| } |
| |
| @Override |
| public int getSortPriority() { |
| switch (task.getGroup()) { |
| case "application": return 0; |
| case "build": return 1; |
| case "distribution": return 2; |
| default: return 3; |
| } |
| } |
| |
| @Override |
| public CompletionTask createDocumentationTask() { |
| return new AsyncCompletionTask(new AsyncCompletionQuery() { |
| @Override |
| protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { |
| resultSet.setDocumentation(new GradleTaskCompletionDocumentation()); |
| resultSet.finish(); |
| } |
| }); |
| } |
| |
| @Override |
| protected String getValue() { |
| return task.getName(); |
| } |
| |
| @Override |
| public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) { |
| CompletionUtilities.renderHtml(TASK_IMAGEICON, getValue(), null, g, defaultFont, defaultColor, width, height, selected); |
| } |
| |
| private class GradleTaskCompletionDocumentation implements CompletionDocumentation { |
| |
| @Override |
| public String getText() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<html>"); |
| sb.append("<b>Name:</b> ").append(task.getName()).append("<br/>"); |
| sb.append("<b>Group:</b> ").append(task.getGroup()).append("<br/>"); |
| sb.append("<b>Path:</b> ").append(task.getPath()).append("<br/>"); |
| sb.append("<b>Description:</b><p>").append(task.getDescription()); |
| return sb.toString(); |
| } |
| |
| @Override |
| public URL getURL() { |
| return null; |
| } |
| |
| @Override |
| public CompletionDocumentation resolveLink(String link) { |
| return null; |
| } |
| |
| @Override |
| public Action getGotoSourceAction() { |
| return null; |
| } |
| |
| } |
| } |
| |
| private static class GradleOptionCompletionItem extends AbstractGradleCompletionItem { |
| private final GradleCommandLine.GradleOptionItem item; |
| private final String value; |
| |
| public GradleOptionCompletionItem(GradleCommandLine.GradleOptionItem item, String value, int startOffset, int caretOffset) { |
| super(startOffset, caretOffset); |
| this.item = item; |
| this.value = value; |
| } |
| |
| @Override |
| public int getSortPriority() { |
| return value.startsWith("--") ? 5 : 4; |
| } |
| |
| |
| |
| @Override |
| protected String getValue() { |
| return value; |
| } |
| |
| @Override |
| public void render(Graphics g, Font defaultFont, Color defaultColor, Color backgroundColor, int width, int height, boolean selected) { |
| Map<TextAttribute, Object> attributes = new HashMap<>(defaultFont.getAttributes()); |
| if (!item.isSupported()) { |
| attributes.put(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); |
| } |
| Font font = new Font(attributes); |
| CompletionUtilities.renderHtml(null, getValue(), null, g, font, defaultColor, width, height, selected); |
| } |
| |
| @Override |
| public CompletionTask createDocumentationTask() { |
| return new AsyncCompletionTask(new AsyncCompletionQuery() { |
| @Override |
| protected void query(CompletionResultSet resultSet, Document doc, int caretOffset) { |
| resultSet.setDocumentation(new GradleItemCompletionDocumentation()); |
| resultSet.finish(); |
| } |
| }); |
| } |
| |
| private class GradleItemCompletionDocumentation implements CompletionDocumentation { |
| |
| @Override |
| public String getText() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("<html>"); |
| if (!item.isSupported()) { |
| sb.append("<b>Unsupported:</b> This argument will be ignored"); |
| } |
| sb.append(item.getDescription()); |
| return sb.toString(); |
| } |
| |
| @Override |
| public URL getURL() { |
| return null; |
| } |
| |
| @Override |
| public CompletionDocumentation resolveLink(String link) { |
| return null; |
| } |
| |
| @Override |
| public Action getGotoSourceAction() { |
| return null; |
| } |
| |
| } |
| |
| |
| } |
| |
| private static class TokenCompletionItem extends AbstractGradleCompletionItem { |
| |
| final String token; |
| |
| public TokenCompletionItem(String token, int startOffset, int caretOffset) { |
| super(startOffset, caretOffset); |
| this.token = token; |
| } |
| |
| @Override |
| protected String getValue() { |
| return token; |
| } |
| |
| } |
| } |