Skip to content
This repository has been archived by the owner on May 4, 2023. It is now read-only.

Commit

Permalink
Merge pull request #80 from codiga/juli1/add-markdown-description
Browse files Browse the repository at this point in the history
Add markdown + support shortcuts
  • Loading branch information
juli1 authored Dec 1, 2021
2 parents ca4e407 + 2da94e0 commit 7af575f
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 32 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ repositories {
dependencies {
implementation("com.apollographql.apollo:apollo-runtime:2.5.11")
implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("com.github.rjeschke:txtmark:0.13")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testCompile("org.mockito:mockito-core:4.1.0")
Expand Down
2 changes: 2 additions & 0 deletions src/main/graphql/io/codiga/api/GetRecipesForClient.graphql
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
query GetRecipesForClient($fingerprint: String, $filename: String, $keywords: [String!]!, $dependencies: [String!]!, $parameters: String, $language: LanguageEnumeration!){
getRecipesForClient(fingerprint: $fingerprint, keywords: $keywords, filename: $filename, dependencies:$dependencies, parameters:$parameters,language:$language){
id
name
code
keywords
imports
language
description
shortcut
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/codiga/plugins/jetbrains/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public class Constants {
public static final String LINE_SEPARATOR = "\n";
public static final char CHARACTER_SPACE = ' ';

public static final int NUMBER_OF_RECIPES_TO_KEEP_FOR_COMPLETION = 3;
public static final int MINIMUM_LINE_LENGTH_TO_TRIGGER_AUTOCOMPLETION = 5;
public static final int NUMBER_OF_RECIPES_TO_KEEP_FOR_COMPLETION = 5;
public static final int MINIMUM_LINE_LENGTH_TO_TRIGGER_AUTOCOMPLETION = 3;


// Python-specific constants
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.codiga.plugins.jetbrains.actions;

import com.github.rjeschke.txtmark.Processor;
import com.intellij.ui.components.JBScrollPane;
import io.codiga.api.GetRecipesForClientQuery;
import io.codiga.api.type.LanguageEnumeration;
import io.codiga.plugins.jetbrains.dependencies.DependencyManagement;
Expand Down Expand Up @@ -27,8 +29,11 @@
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.*;
import java.math.BigDecimal;
Expand All @@ -49,7 +54,7 @@ public class AssistantUseRecipeAction extends AnAction {
public static final Logger LOGGER = Logger.getInstance(LOGGER_NAME);

public static final String ENTER_SEARCH_TERM_TEXT = "(enter search terms)";

private final CodigaMarkdownDecorator codigaMarkdownDecorator = new CodigaMarkdownDecorator();
private final CodigaApi codigaApi = ApplicationManager.getApplication().getService(CodigaApi.class);

// UI elements
Expand All @@ -59,6 +64,8 @@ public class AssistantUseRecipeAction extends AnAction {
private JButton nextButton = null;
private JButton previousButton = null;
private JButton okButton = null;
private final JEditorPane jEditorPane = new JEditorPane();
private final JBScrollPane scrollPane = new JBScrollPane(jEditorPane);

// status of the action: is code inserted, what are the recipes, etc.
private boolean codeInserted = false;
Expand Down Expand Up @@ -148,11 +155,18 @@ public void showCurrentRecipe(AnActionEvent anActionEvent) {

// reindent the code based on the indentation of the current line.
String indentedCode = indentOtherLines(code, indentationCurrentLine);
String finalDescription = recipe.description().length() == 0 ? "no description" : recipe.description();
String finalDescriptionWithLink = finalDescription + String.format("\n\n[%s](https://app.codiga.io/marketplace/recipe/%s/view)", "View Recipe on Codiga", recipe.id());


String html = Processor.process(finalDescriptionWithLink, codigaMarkdownDecorator);
jEditorPane.setContentType("text/html");
jEditorPane.setText(html);


// Update the label in the box with the description.
String finalDescription = recipe.description().length() == 0 ? "no description" : recipe.description();
String descriptionLabelText = String.format("result %s/%s: %s", currentRecipeIndex + 1, currentRecipes.size(), finalDescription);

String descriptionLabelText = String.format("result %s/%s: %s", currentRecipeIndex + 1, currentRecipes.size(), recipe.name());
jLabelResults.setText(descriptionLabelText);

// add the code and update global variables to indicate code has been inserted.
Expand Down Expand Up @@ -385,10 +399,34 @@ public void actionPerformed(@NotNull AnActionEvent event) {

JPanel jPanelMiddle = new JPanel(new FlowLayout());
JPanel jPanelBottom = new JPanel(new FlowLayout(FlowLayout.LEFT));
JPanel jPanelDescription = new JPanel(new FlowLayout(FlowLayout.LEFT));

// bottom panel
jPanelBottom.add(jLabelResults);

// description panel
jEditorPane.setContentType("text/html");
jEditorPane.setText(Processor.process("Recipe description will appear here", codigaMarkdownDecorator));
jEditorPane.setEditable(false);

jEditorPane.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent hle) {
if (HyperlinkEvent.EventType.ACTIVATED.equals(hle.getEventType())) {
Desktop desktop = Desktop.getDesktop();
try {
desktop.browse(hle.getURL().toURI());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});
jPanelDescription.setBorder(BorderFactory.createEmptyBorder(0, CodigaIcons.Codiga_default_icon.getIconWidth() + 10, 0, 0));
scrollPane.setPreferredSize(new Dimension(800 - (CodigaIcons.Codiga_default_icon.getIconWidth() + 10) * 2 , 200));
scrollPane.setMinimumSize(new Dimension(800 - (CodigaIcons.Codiga_default_icon.getIconWidth() + 10) * 2, 200));
jPanelDescription.add(scrollPane);

// middle panel
JLabel codigaLabel = new JLabel(CodigaIcons.Codiga_default_icon);
codigaLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
Expand Down Expand Up @@ -493,6 +531,7 @@ public void keyPressed(KeyEvent ke) {
// build the main panel
jPanelMain.add(jPanelMiddle);
jPanelMain.add(jPanelBottom);
jPanelMain.add(jPanelDescription);

// Build the main window to keep it with an IntelliJ style
windowWrapper = new WindowWrapperBuilder(WindowWrapper.Mode.FRAME, jPanelMain)
Expand All @@ -506,8 +545,9 @@ public void keyPressed(KeyEvent ke) {
return true;
})
.build();
windowWrapper.getWindow().setPreferredSize(new Dimension(800, 100));
windowWrapper.getWindow().setSize(new Dimension(800, 100));

windowWrapper.getWindow().setPreferredSize(new Dimension(800, 300));
windowWrapper.getWindow().setSize(new Dimension(800, 300));

windowWrapper.show();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.codiga.plugins.jetbrains.actions;

import com.github.rjeschke.txtmark.DefaultDecorator;

public class CodigaMarkdownDecorator extends DefaultDecorator {

private static final String style = " style=\"font-family: Arial;\" ";

@Override
public void openHeadline(final StringBuilder out, final int level)
{
out.append("<h");
out.append(level);
out.append(style);
}

@Override
public void openParagraph(final StringBuilder out)
{

out.append("<p");
out.append(style);
out.append(">");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.tree.IElementType;

import static com.intellij.patterns.PlatformPatterns.psiElement;
import static io.codiga.plugins.jetbrains.Constants.LOGGER_NAME;
Expand All @@ -12,7 +13,7 @@ public class CodigaCompletion extends CompletionContributor {
public static final Logger LOGGER = Logger.getInstance(LOGGER_NAME);
public CodigaCompletion() {
extend(CompletionType.BASIC,
PlatformPatterns.psiElement(),
PlatformPatterns.not(PlatformPatterns.alwaysFalse()),
new CodigaCompletionProvider());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,42 @@
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.util.ProcessingContext;
import com.intellij.util.ThrowableRunnable;
import icons.CodigaIcons;
import io.codiga.api.GetRecipesForClientQuery;
import io.codiga.api.type.LanguageEnumeration;
import io.codiga.plugins.jetbrains.dependencies.DependencyManagement;
import io.codiga.plugins.jetbrains.graphql.CodigaApi;
import io.codiga.plugins.jetbrains.graphql.LanguageUtils;
import io.codiga.plugins.jetbrains.model.CodeInsertion;
import io.codiga.plugins.jetbrains.settings.application.AppSettingsState;
import org.jetbrains.annotations.NotNull;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;

import static io.codiga.plugins.jetbrains.Constants.*;
import static io.codiga.plugins.jetbrains.utils.CodeImportUtils.hasImport;
import static io.codiga.plugins.jetbrains.utils.CodePositionUtils.*;

/**
* Provide completion when the user type some code on one line.
Expand All @@ -44,6 +55,68 @@ public class CodigaCompletionProvider extends CompletionProvider<CompletionParam
CodigaCompletionProvider() {
}

/**
* Add the recipe into the editor.
* @param recipe
* @param indentationCurrentLine
* @param parameters
* @param insertionContext
*/
private void addRecipeInEditor(GetRecipesForClientQuery.GetRecipesForClient recipe,
int indentationCurrentLine,
@NotNull CompletionParameters parameters,
@NotNull InsertionContext insertionContext) {
insertionContext.setAddCompletionChar(false);
final Editor editor = parameters.getEditor();
final Document document = editor.getDocument();
final String currentCode = document.getText();
final Project project = parameters.getEditor().getProject();

// remove the code on the line
int startOffetToRemove = insertionContext.getEditor().getCaretModel().getVisualLineStart();
final int endOffetToRemove = insertionContext.getEditor().getCaretModel().getVisualLineEnd();
insertionContext.getEditor().getDocument().deleteString(startOffetToRemove + indentationCurrentLine, endOffetToRemove );

// add the code and update the document.
String code = new String(Base64.getDecoder().decode(recipe.code())).replaceAll("\r\n", LINE_SEPARATOR);
String indentedCode = indentOtherLines(code, indentationCurrentLine) + "\n";

/**
* Insert the code
*/
EditorModificationUtil.insertStringAtCaret(insertionContext.getEditor(), indentedCode);
insertionContext.commitDocument();

/**
* Insert all imports
*/
List<String> imports = recipe.imports();
try {

WriteCommandAction.writeCommandAction(project).run(
(ThrowableRunnable<Throwable>) () -> {
int firstInsertion = firstPositionToInsert(currentCode, recipe.language());

for(String importStatement: imports) {
if(!hasImport(currentCode, importStatement, recipe.language())) {

String dependencyStatement = importStatement + LINE_SEPARATOR;
document.insertString(firstInsertion, dependencyStatement);
}
}
}
);
} catch (Throwable e) {
e.printStackTrace();
LOGGER.error("showCurrentRecipe - impossible to update the code from the recipe");
LOGGER.error(e);
}

// sent a callback that the recipe has been used.
long recipeId = ((BigDecimal) recipe.id()).longValue();
codigaApi.recordRecipeUse(recipeId);
}

/**
* Add the completion: call the API to get all completions and surface them
* @param parameters
Expand All @@ -69,6 +142,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters,
if(lineEnd > lineStart + 1){
currentLine = editor.getDocument().getText(new TextRange(lineStart, lineEnd - 1));
}
int indentationCurrentLine = getIndentation(currentLine);


if (currentLine.length() < MINIMUM_LINE_LENGTH_TO_TRIGGER_AUTOCOMPLETION){
Expand All @@ -82,12 +156,11 @@ protected void addCompletions(@NotNull CompletionParameters parameters,
}

// Get all recipes parameters.
final List<String> keywords = Arrays.asList(currentLine.split(" "));
final List<String> keywords = Arrays.asList(currentLine.split(" ")).stream().filter(p -> !p.isEmpty()).collect(Collectors.toList());
final VirtualFile virtualFile = parameters.getOriginalFile().getVirtualFile();
LanguageEnumeration language = LanguageUtils.getLanguageFromFilename(virtualFile.getCanonicalPath());
List<String> dependenciesName = dependencyManagement.getDependencies(parameters.getOriginalFile()).stream().map(d -> d.getName()).collect(Collectors.toList());
final String filename = virtualFile.getName();

// Get the recipes from the API.
List<GetRecipesForClientQuery.GetRecipesForClient> recipes = codigaApi.getRecipesForClient(
keywords,
Expand All @@ -104,31 +177,25 @@ protected void addCompletions(@NotNull CompletionParameters parameters,
* For each of them, add a completion item and add a routine to insert the code.
*/
for(GetRecipesForClientQuery.GetRecipesForClient recipe: recipes.stream().limit(NUMBER_OF_RECIPES_TO_KEEP_FOR_COMPLETION).collect(Collectors.toList())){
String lookup = String.join(" ", recipe.keywords());
List<String> recipeKeywords = new ArrayList<>(recipe.keywords());
if (recipe.shortcut() != null) {
recipeKeywords.add(recipe.shortcut());
}


String lookup = String.join(" ", recipeKeywords);

LookupElementBuilder element = LookupElementBuilder
.create(lookup)
.withTypeText(recipe.description())
.create(recipe.name())
.withTypeText(String.join(",", recipeKeywords))
.withLookupString(lookup)
.withInsertHandler((insertionContext, lookupElement) -> {
insertionContext.setAddCompletionChar(false);

// remove the code on the line
final int startOffetToRemove = insertionContext.getEditor().getCaretModel().getVisualLineStart();
final int endOffetToRemove = insertionContext.getEditor().getCaretModel().getVisualLineEnd();
insertionContext.getEditor().getDocument().deleteString(startOffetToRemove, endOffetToRemove );

// add the code and update the document.
String code = new String(Base64.getDecoder().decode(recipe.code())).replaceAll("\r\n", LINE_SEPARATOR);
EditorModificationUtil.insertStringAtCaret(insertionContext.getEditor(), code);
insertionContext.commitDocument();

// sent a callback that the recipe has been used.
long recipeId = ((BigDecimal) recipe.id()).longValue();
codigaApi.recordRecipeUse(recipeId);
addRecipeInEditor(recipe, indentationCurrentLine, parameters, insertionContext);
})
.withIcon(CodigaIcons.Codiga_default_icon);



result.addElement(element);
}
}
Expand Down

0 comments on commit 7af575f

Please sign in to comment.