Skip to content

Commit

Permalink
1. A more user-friendly tool for constructing prompts. (openpilot-hub#29
Browse files Browse the repository at this point in the history
)

2. Generating test cases that suit various programming languages better.
  • Loading branch information
MrYangxf authored Jan 19, 2024
1 parent ff5dff9 commit 452f26a
Show file tree
Hide file tree
Showing 14 changed files with 515 additions and 60 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ intellij {
version.set("2022.1.4")
type.set("IC") // Target IDE Platform

plugins.set(listOf(/* Plugin Dependencies */))
plugins.set(listOf("com.intellij.java"))
}

dependencies {
Expand All @@ -26,6 +26,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:okhttp-sse:4.10.0")
implementation("com.vladsch.flexmark:flexmark-all:0.64.8")
implementation("org.apache.commons:commons-text:1.10.0")
compileOnly("com.puppycrawl.tools:checkstyle:10.9.1")
testImplementation("org.mockito:mockito-core:5.7.0")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
Expand All @@ -21,18 +22,28 @@
import com.zhongan.devpilot.settings.state.LanguageSettingsState;
import com.zhongan.devpilot.util.DevPilotMessageBundle;
import com.zhongan.devpilot.util.DocumentUtil;
import com.zhongan.devpilot.util.LanguageUtil;
import com.zhongan.devpilot.util.PerformanceCheckUtils;
import com.zhongan.devpilot.util.PromptTemplate;
import com.zhongan.devpilot.util.PsiFileUtil;
import com.zhongan.devpilot.webview.model.CodeReferenceModel;
import com.zhongan.devpilot.webview.model.MessageModel;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;

import javax.swing.Icon;

import static com.zhongan.devpilot.constant.PlaceholderConst.ADDITIONAL_MOCK_PROMPT;
import static com.zhongan.devpilot.constant.PlaceholderConst.LANGUAGE;
import static com.zhongan.devpilot.constant.PlaceholderConst.MOCK_FRAMEWORK;
import static com.zhongan.devpilot.constant.PlaceholderConst.SELECTED_CODE;
import static com.zhongan.devpilot.constant.PlaceholderConst.TEST_FRAMEWORK;

public class PopupMenuEditorActionGroupUtil {

private static final Map<String, Icon> ICONS = new LinkedHashMap<>(Map.of(
Expand Down Expand Up @@ -88,10 +99,22 @@ protected void actionPerformed(Project project, Editor editor, String selectedTe
};

EditorInfo editorInfo = new EditorInfo(editor);

String newPrompt = prompt.replace("{{selectedCode}}", selectedText);
PromptTemplate promptTemplate = PromptTemplate.of(prompt);
promptTemplate.setVariable(SELECTED_CODE, selectedText);
if (editorActionEnum == EditorActionEnum.GENERATE_TESTS) {
Optional.ofNullable(FileDocumentManager.getInstance().getFile(editor.getDocument()))
.map(vFile -> LanguageUtil.getLanguageByExtension(vFile.getExtension()))
.ifPresent(language -> {
promptTemplate.setVariable(LANGUAGE, language.getLanguageName());
promptTemplate.setVariable(TEST_FRAMEWORK, language.getDefaultTestFramework());
promptTemplate.setVariable(MOCK_FRAMEWORK, language.getDefaultMockFramework());
if (language.isJvmPlatform() && PsiFileUtil.isCaretInWebClass(project, editor)) {
promptTemplate.setVariable(ADDITIONAL_MOCK_PROMPT, PromptConst.MOCK_WEB_MVC);
}
});
}
if (LanguageSettingsState.getInstance().getLanguageIndex() == 1) {
newPrompt = newPrompt + PromptConst.ANSWER_IN_CHINESE;
promptTemplate.appendLast(PromptConst.ANSWER_IN_CHINESE);
}

var service = project.getService(DevPilotChatToolWindowService.class);
Expand All @@ -105,7 +128,7 @@ protected void actionPerformed(Project project, Editor editor, String selectedTe
var codeMessage = MessageModel.buildCodeMessage(
UUID.randomUUID().toString(), System.currentTimeMillis(), showText, username, codeReference);

service.sendMessage(SessionTypeEnum.MULTI_TURN.getCode(), newPrompt, callback, codeMessage);
service.sendMessage(SessionTypeEnum.MULTI_TURN.getCode(), promptTemplate.getPrompt(), callback, codeMessage);
}
};
group.add(action);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/zhongan/devpilot/constant/PlaceholderConst.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.zhongan.devpilot.constant;

public class PlaceholderConst {

public final static String SELECTED_CODE = "selectedCode";

public final static String LANGUAGE = "language";

public final static String TEST_FRAMEWORK = "testFramework";

public final static String MOCK_FRAMEWORK = "mockFramework";

public final static String ADDITIONAL_MOCK_PROMPT = "additionalMockPrompt";

}
2 changes: 2 additions & 0 deletions src/main/java/com/zhongan/devpilot/constant/PromptConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ private PromptConst() {
public static final String ANSWER_IN_CHINESE = "\nPlease answer in Chinese.";

public static final String GENERATE_COMMIT = "Summarize the git diff with a concise and descriptive commit message. Adopt the imperative mood, present tense, active voice, and include relevant verbs. Remember that your entire response will be directly used as the git commit message.";

public final static String MOCK_WEB_MVC = "please use MockMvc to mock web requests, ";

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ public enum EditorActionEnum {
"{{selectedCode}}\nGiving the code above, please generate code comments, return code with comments."),

GENERATE_TESTS("devpilot.action.generate.tests", "Generate Tests in the following code",
"{{selectedCode}}\nGiving the code above, " +
"please help to generate JUnit test cases for it, be aware that if the code is untestable, " +
"{{selectedCode}}\nGiving the {{language:unknown}} code above, " +
"please help to generate {{testFramework:suitable}} test cases for it, " +
"mocking test data with {{mockFramework:suitable mock framework}} if necessary, " +
"{{additionalMockPrompt:}}" +
"be aware that if the code is untestable, " +
"please state it and give suggestions instead."),

FIX_THIS("devpilot.action.fix", "Fix This in the following code",
Expand Down
166 changes: 166 additions & 0 deletions src/main/java/com/zhongan/devpilot/util/LanguageUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.zhongan.devpilot.util;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableSet;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class LanguageUtil {

public static final Set<String> JVM_PLATFORM_LANGUAGES = ImmutableSet.of(
"Java", "Scala", "Groovy", "Kotlin"
);

public static final Language JAVA = new Language().setLanguageName("Java")
.setFileExtensions(Collections.singletonList("java"))
.setTestFrameworks(Collections.singletonList("JUnit4"))
.setMockFrameworks(Collections.singletonList("Mockito"));

@NotNull
public static String getFileSuffixByLanguage(@NotNull String languageName) {
Language language = ObjectUtils.defaultIfNull(getLanguageByName(languageName), JAVA);
return DOT + language.getDefaultFileExtension();
}

@Nullable
public static Language getLanguageByName(@Nullable String languageName) {
if (languageName == null) {
return null;
}
return LANG_MAPPINGS.get(languageName.toLowerCase());
}

@Nullable
public static Language getLanguageByExtension(@Nullable String extensionName) {
if (extensionName == null) {
return null;
}
extensionName = StringUtils.removeStart(extensionName, DOT);
return EXT_MAPPINGS.get(extensionName);
}

private static final String DOT = ".";

private static final Map<String, Language> LANG_MAPPINGS;

private static final Map<String, Language> EXT_MAPPINGS;

static {
try {
Map<String, Language> langMappings = new HashMap<>();
Map<String, Language> extMappings = new HashMap<>();
ObjectMapper objectMapper = new ObjectMapper();
URL resource = LanguageUtil.class.getResource("/languageMappings.json");
List<Language> languages = objectMapper.readValue(resource, new TypeReference<>() { });
for (Language language : languages) {
langMappings.put(language.getLanguageName().toLowerCase(), language);
List<String> fileExtensions = new ArrayList<>();
for (String fileExtension : language.getFileExtensions()) {
String extensionWithoutDot = StringUtils.removeStart(fileExtension, DOT);
fileExtensions.add(extensionWithoutDot);
extMappings.putIfAbsent(extensionWithoutDot, language);
}
language.setFileExtensions(Collections.unmodifiableList(fileExtensions));
}
LANG_MAPPINGS = Collections.unmodifiableMap(langMappings);
EXT_MAPPINGS = Collections.unmodifiableMap(extMappings);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static class Language {

private String languageName;

private List<String> fileExtensions;

private List<String> testFrameworks;

private List<String> mockFrameworks;

@JsonIgnore
public boolean isJvmPlatform() {
return JVM_PLATFORM_LANGUAGES.contains(languageName);
}

@Nullable
@JsonIgnore
public String getDefaultFileExtension() {
if (CollectionUtils.isEmpty(fileExtensions)) {
return null;
}
return fileExtensions.get(0);
}

@Nullable
@JsonIgnore
public String getDefaultTestFramework() {
if (CollectionUtils.isEmpty(testFrameworks)) {
return null;
}
return testFrameworks.get(0);
}

@Nullable
@JsonIgnore
public String getDefaultMockFramework() {
if (CollectionUtils.isEmpty(mockFrameworks)) {
return null;
}
return mockFrameworks.get(0);
}

public String getLanguageName() {
return languageName;
}

public Language setLanguageName(String languageName) {
this.languageName = languageName;
return this;
}

public List<String> getFileExtensions() {
return fileExtensions;
}

public Language setFileExtensions(List<String> fileExtensions) {
this.fileExtensions = fileExtensions;
return this;
}

public List<String> getTestFrameworks() {
return testFrameworks;
}

public Language setTestFrameworks(List<String> testFrameworks) {
this.testFrameworks = testFrameworks;
return this;
}

public List<String> getMockFrameworks() {
return mockFrameworks;
}

public Language setMockFrameworks(List<String> mockFrameworks) {
this.mockFrameworks = mockFrameworks;
return this;
}

}

}
53 changes: 1 addition & 52 deletions src/main/java/com/zhongan/devpilot/util/MarkdownUtil.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package com.zhongan.devpilot.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.parser.Parser;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -19,10 +14,8 @@

public class MarkdownUtil {

private static final String DEFAULT_CODE_FILE_EXTENSION = ".java";

public static String getFileExtensionFromLanguage(String language) {
return languageFileExtMap.getOrDefault(language.toLowerCase(), DEFAULT_CODE_FILE_EXTENSION);
return LanguageUtil.getFileSuffixByLanguage(language);
}

/**
Expand Down Expand Up @@ -63,48 +56,4 @@ public static String extractContents(String codeBlock) {
return StringUtils.join(contents, "\n\n");
}

private static final Map<String, String> languageFileExtMap = buildLanguageFileExtMap();

private static Map<String, String> buildLanguageFileExtMap() {
Map<String, String> languageFileExtMap = new HashMap<>();
ObjectMapper objectMapper = new ObjectMapper();
List<LanguageFileExtInfo> languageFileExtInfos;
try {
URL resource = MarkdownUtil.class.getResource("/languageMappings.json");
languageFileExtInfos = objectMapper.readValue(resource, new TypeReference<>() {
});
for (LanguageFileExtInfo languageFileExtInfo : languageFileExtInfos) {
languageFileExtMap.put(languageFileExtInfo.getLanguageName().toLowerCase(),
languageFileExtInfo.getFileExtensions().get(0));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return languageFileExtMap;
}

public static class LanguageFileExtInfo {

private String languageName;

private List<String> fileExtensions;

public String getLanguageName() {
return languageName;
}

public void setLanguageName(String languageName) {
this.languageName = languageName;
}

public List<String> getFileExtensions() {
return fileExtensions;
}

public void setFileExtensions(List<String> fileExtensions) {
this.fileExtensions = fileExtensions;
}

}

}
Loading

0 comments on commit 452f26a

Please sign in to comment.