沉淀、分享、成长,让自己和他人都能有所收获!?
你不知道这个已经有人做了吗?
哈哈哈,是不是你在做一些尝试、创新、落地的时候,都可能会被有些人问这样的话,好像这一句话还挺有力量的。你不知道说明你调研不够、有人做了你还搞证明浪费时间
,那你问他你知道那个已经做了的怎么设计的、用了什么技术、我们可以借鉴哪些能力呢,他又有7788的一堆理由,总之一张嘴,都是进口饮料。
但其实我们很多时候在学习补充自己的能力,是要通过大量实践验证的,在实践的过程中完善X产品的a功能,补充Y产品的b功能,而这些一个个小的点就像我们积累下来的乐高玩具,我们有实践支撑理论,那么在以后真的要开发产品所需能力的时候,就是把这些一个个的乐高技术技能,组合起来,搭建出我们的目标结果。但如果你没折腾过,那么手里的乐高肯定不多!
首先我想问问,你在编程开发中,有把类名称、属性名、方法名,写错的时候吗,比如;把data
写成date
、把main
写成mian
、把queryBatch
写成queryBitch
,闹了大笑话了,上线对外还没发修改了。
那能有什么办法在我写这样的单词属性名称的时候,给我来个提示,把那些关联到的正确的提醒出来,不要让我还得一个个敲,主要是还不受控制的敲错呢?
办法是有的,本章节我们就结合 IDEA Plugin 开发的能力,在启动插件后加载常用单词文件,生成一个单词树的结构。那么用户在 IDEA 开发时输入属性名称的时候,按照输入信息获取单词树中的匹配信息,并提醒成列表反馈到用户输入界面。
guide-idea-plugin-remind
├── .gradle
└── src
├── main
│ └── java
│ └── cn.bugstack.guide.idea.plugin
│ ├── action
│ │ └── RemindCompletionContributor.java
│ ├── application
│ │ └── IWordManageService.java
│ ├── domain
│ │ ├── model
│ │ │ └── Node.java
│ │ └── service
│ │ ├── AbstractWordManage.java
│ │ └── WordManageServiceImpl.java
│ └── infrastructure
│ └── Utils.java
├── resources
│ ├── dictionary
│ │ ├── word01.txt
│ │ ├── word02.txt
│ │ └── word03.txt
│ └── META-INF
│ └── plugin.xml
├── build.gradle
└── gradle.properties
在此 IDEA 插件工程中,主要分为2块核心功能:
IWordManageService
为主的用于实现对 dictionary
处理,生成单词树链表结构。RemindCompletionContributor
检测用户输入的熟悉单词信息,从单词树中索引到匹配的内容返回给用户。处理单词树,其实就是处理的一种数据结构,怎么让在用户一个个的输入字母的时候,找到对应匹配的单词。那么这里就需要把文件中的单词按照字母一个个存放到链表结构中,例如把 fustack 存放到单词树中,如下:
word.txt
fustack#
batch#adj.批量
bitch#adj.彪子 PS:你是要输入 batch 吧?
word tree
f->u
f->u->s
f->u->s->t
f->u->s->t->a
f->u->s->t->a->c
f->u->s->t->a->k
当用户输入f、u,那么就开始索引这棵单词树,找到匹配的字母以及当前字母位置后续链路上的整个内容,并记录到可以反馈给用户的单词列表中。
cn.bugstack.guide.idea.plugin.domain.service.AbstractWordManage
private void loadFile(String path) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(path), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
String[] wordInfo = line.split("#");
if (!compile.matcher(wordInfo[0]).matches()) {
continue;
}
wordInfo[0] = wordInfo[0].toLowerCase().trim();
StringBuilder sb = new StringBuilder();
if (wordInfo.length == 2) {
Matcher matcher = explainPattern.matcher(wordInfo[1]);
boolean hasMatch = matcher.find();
String singleExplain = wordInfo[1];
do {
if (hasMatch) {
singleExplain = matcher.group(2);
sb.append(matcher.group(1));
}
sb.append(Arrays.stream(singleExplain.split("[;;,,]")).min(Comparator.comparingInt(String::length)).get());
wordInfo[1] = sb.toString();
} while (hasMatch = matcher.find());
}
insert(wordsTree, wordInfo[0].toLowerCase().trim(), wordInfo.length == 2 ? wordInfo[1] : "");
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void insert(Node root, String words, String explain) {
char[] chars = words.toCharArray();
for (char aChar : chars) {
int charIndex = aChar - 'a';
if (root.slot[charIndex] == null) {
root.slot[charIndex] = new Node();
}
root = root.slot[charIndex];
root.c = aChar;
root.prefix++;
}
root.explain = explain;
root.count++;
}
接下来我们使用到继承 CompletionContributor
的实现类,来监听用户输入的属性字母信息,每一个输入都会调用到这个实现类中,之后在实现类里索引到匹配的单词列表反馈给用户。
cn.bugstack.guide.idea.plugin.action.RemindCompletionContributor
public RemindCompletionContributor() {
IWordManageService wordManageService = ApplicationManager.getApplication().getService(WordManageServiceImpl.class);
CompletionProvider<CompletionParameters> provider = new DefaultCompletionProvider(wordManageService);
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiLocalVariable.class), provider);
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiMethod.class), provider);
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiField.class), provider);
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiParameter.class), provider);
}
/** 定义 RemindCompletionContributor 类,该类继承 CompletionContributor 类,实现代码自动补全功能的扩展。 */
public class RemindCompletionContributor extends CompletionContributor {
public RemindCompletionContributor() {
/** 通过 ApplicationManager 获取 WordManageServiceImpl 实现类的实例,用于提供单词列表数据。 */
IWordManageService wordManageService = ApplicationManager.getApplication().getService(WordManageServiceImpl.class);
/**使用 DefaultCompletionProvider 类的实例作为自动补全建议提供者。 */
CompletionProvider<CompletionParameters> provider = new DefaultCompletionProvider(wordManageService);
/** 扩展代码自动补全功能,当在局部变量位置输入时,提供建议列表 */
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiLocalVariable.class), provider);
/** 扩展代码自动补全功能,当在方法位置输入时,提供建议列表。 */
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiMethod.class), provider);
/** 扩展代码自动补全功能,当在字段位置输入时,提供建议列表。*/
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiField.class), provider);
/** 扩展代码自动补全功能,当在方法参数位置输入时,提供建议列表。 */
extend(CompletionType.BASIC, psiElement(PsiIdentifier.class).withParent(PsiParameter.class), provider);
}
/** 定义 DefaultCompletionProvider 类,该类实现了根据用户输入的前缀查找单词列表的功能,并将这些单词作为建议填充到编辑器中。 */
static class DefaultCompletionProvider extends CompletionProvider<CompletionParameters> {
/** 定义 wordManageService 变量,用于提供单词列表数据 */
private final IWordManageService wordManageService;
/** 定义 DefaultCompletionProvider 的构造方法,初始化 wordManageService */
public DefaultCompletionProvider(IWordManageService wordManageService) {
this.wordManageService = wordManageService;
}
/** 实现自动补全建议的逻辑 */
@Override
protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull ProcessingContext context, @NotNull CompletionResultSet result) {
/** 获取用户输入的前缀匹配器。 */
PrefixMatcher prefixMatcher = result.getPrefixMatcher();
/** 获取用户输入的前缀 */
String prefix = prefixMatcher.getPrefix();
/** 如果用户输入的前缀是复合单词,获取最后一个单词,并转换为小写。 */
if (Utils.isCompositeName(prefix)) {
prefix = Utils.getLastWord(prefix).toLowerCase(Locale.ROOT);
}
/** 使用 wordManageService 提供的方法,根据用户输入的前缀查找单词 查找匹配的Node对象集合 */
List<Node> list = wordManageService.searchPrefix(prefix);
/** 表示任何前缀变化都会重新开始补全。 */
result.restartCompletionOnAnyPrefixChange();
/** 如果搜索结果为空,则重新开始补全,并退出方法。 */
if (list.isEmpty()) {
result.restartCompletionOnAnyPrefixChange();
return;
}
/** 将单词节点 list 转化为 LookupElementBuilder 类型的建议列表,并设置其展示文本、图标、类型文本和插入处理器 */
List<LookupElementBuilder> collect = list.stream().map(node -> LookupElementBuilder
.create(node.word)
.withPresentableText(node.word)
.withIcon(AllIcons.Actions.More)
.withBoldness(false)
.withTypeText(node.explain)
.bold()
.withInsertHandler((ctx, item) -> {
String text = ctx.getDocument().getText(new TextRange(ctx.getStartOffset() - 1, ctx.getStartOffset()));
if (StringUtil.isEmpty(text)) {
return;
}
char symbol = text.charAt(0);
String insertText = item.getLookupString();
if (Character.isLowerCase(symbol)) {
insertText = StringUtil.capitalize(item.getLookupString());
}
ctx.getDocument().replaceString(ctx.getStartOffset(), ctx.getTailOffset(), insertText);
}))
.collect(Collectors.toList());
/** 创建一个新的结果集 resultSet,并将其前缀匹配器设置为自定义的 MyPrefixMatcher 类型
* 。然后将建议列表添加到 resultSet 中,并添加一个查找广告(即补全列表底部的文字说明) */
CompletionResultSet resultSet = result.withPrefixMatcher(new MyPrefixMatcher(prefix));
resultSet.addAllElements(collect);
resultSet.addLookupAdvertisement("匹配数量" + list.size());
}
}
/** 定义一个 MyPrefixMatcher 类,继承自 PrefixMatcher 类,负责根据前缀匹配单词。该类使用 NameUtil.buildMatcher 方法
* 创建一个大小写不敏感的匹配器 matcher,并实现了 prefixMatches、matchingDegree 和 cloneWithPrefix 方法,用于匹配单词的前缀
* ,计算匹配度和生成新的前缀匹配器。 */
static class MyPrefixMatcher extends PrefixMatcher {
private final MinusculeMatcher matcher;
public MyPrefixMatcher(String prefix) {
super(prefix);
matcher = NameUtil.buildMatcher(CamelHumpMatcher.applyMiddleMatching(getPrefix())).typoTolerant().build();
}
@Override
public boolean prefixMatches(@NotNull String name) {
return matcher.isStartMatch(name);
}
@Override
public int matchingDegree(String string) {
return string.startsWith(getPrefix()) ? 1 : 0;
}
@Override
public @NotNull
PrefixMatcher cloneWithPrefix(@NotNull String prefix) {
return new MyPrefixMatcher(prefix);
}
}
}
这里的索引是一个继承 CompletionProvider
的实现类,通过对 addCompletions
的扩展,完成对单词的索引操作。
索引单词
cn.bugstack.guide.idea.plugin.domain.service.WordManageServiceImpl
@Override
public List<Node> searchPrefix(String prefix) {
Node root = wordsTree;
char[] chars = prefix.toCharArray();
StringBuilder sb = new StringBuilder();
for (char aChar : chars) {
int charIndex = aChar - 'a';
if (charIndex > root.slot.length || charIndex < 0 || root.slot[charIndex] == null) {
return Collections.emptyList();
}
sb.append(aChar);
root = root.slot[charIndex];
}
ArrayList<Node> list = new ArrayList<>();
if (root.prefix != 0) {
for (int i = 0; i < root.slot.length; i++) {
if (root.slot[i] != null) {
char c = (char) (i + 'a');
collect(root.slot[i], String.valueOf(sb) + c, list, RESULT_LIMIT);
if (list.size() >= RESULT_LIMIT) {
return list;
}
}
}
}
return list;
}
prefix
是用于输入的单词,在 searchPrefix 中提取单词中中的字母,例如:fus,则陆续提取三个字母,一直到最后一个字母 root = root.slot[charIndex];
s开头
的列表,循环获取后续的字母并返回最终的结果列表。<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<applicationService serviceImplementation="cn.bugstack.guide.idea.plugin.domain.service.WordManageServiceImpl"/>
<completion.contributor language="JAVA"
order="first"
implementationClass="cn.bugstack.guide.idea.plugin.action.RemindCompletionContributor"/>
</extensions>
启动插件
测试插件