• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521635

20 Jul 2025 03:45PM UTC coverage: 82.266% (-0.09%) from 82.355%
#525521635

push

github

hurricup
Build 252.23892.248

30936 of 37605 relevant lines covered (82.27%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.53
/plugin/backend/src/main/java/com/perl5/lang/pod/idea/completion/PodLinkCompletionProvider.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.pod.idea.completion;
18

19
import com.intellij.codeInsight.completion.CompletionParameters;
20
import com.intellij.codeInsight.completion.CompletionProvider;
21
import com.intellij.codeInsight.completion.CompletionResultSet;
22
import com.intellij.codeInsight.lookup.LookupElementBuilder;
23
import com.intellij.openapi.diagnostic.Logger;
24
import com.intellij.openapi.util.text.StringUtil;
25
import com.intellij.psi.PsiElement;
26
import com.intellij.psi.PsiFile;
27
import com.intellij.psi.tree.IElementType;
28
import com.intellij.psi.util.PsiTreeUtil;
29
import com.intellij.psi.util.PsiUtilCore;
30
import com.intellij.usageView.UsageViewUtil;
31
import com.intellij.util.ProcessingContext;
32
import com.perl5.PerlIcons;
33
import com.perl5.lang.perl.idea.completion.providers.processors.PerlCompletionProcessor;
34
import com.perl5.lang.perl.idea.completion.providers.processors.PerlSimpleCompletionProcessor;
35
import com.perl5.lang.perl.idea.completion.util.PerlPackageCompletionUtil;
36
import com.perl5.lang.perl.util.PerlPackageUtil;
37
import com.perl5.lang.pod.filetypes.PodFileType;
38
import com.perl5.lang.pod.lexer.PodElementTypes;
39
import com.perl5.lang.pod.parser.psi.PodCompositeElement;
40
import com.perl5.lang.pod.parser.psi.PodFile;
41
import com.perl5.lang.pod.parser.psi.PodStubsAwareRecursiveVisitor;
42
import com.perl5.lang.pod.parser.psi.PodTitledSection;
43
import com.perl5.lang.pod.parser.psi.mixin.PodFormatterL;
44
import com.perl5.lang.pod.parser.psi.mixin.PodFormatterX;
45
import com.perl5.lang.pod.parser.psi.mixin.PodSectionItem;
46
import com.perl5.lang.pod.parser.psi.util.PodFileUtil;
47
import com.perl5.lang.pod.parser.psi.util.PodRenderUtil;
48
import com.perl5.lang.pod.psi.PsiItemSection;
49
import com.perl5.lang.pod.psi.PsiPodFormatIndex;
50
import org.jetbrains.annotations.Contract;
51
import org.jetbrains.annotations.NotNull;
52
import org.jetbrains.annotations.Nullable;
53

54
import java.util.HashSet;
55
import java.util.Set;
56

57
public class PodLinkCompletionProvider extends CompletionProvider<CompletionParameters> implements PodElementTypes {
1✔
58
  private static final Logger LOG = Logger.getInstance(PodLinkCompletionProvider.class);
1✔
59

60
  @Override
61
  protected void addCompletions(@NotNull CompletionParameters parameters,
62
                                @NotNull ProcessingContext context,
63
                                @NotNull CompletionResultSet result) {
64
    PsiElement element = parameters.getPosition();
1✔
65
    PsiElement linkPart = element.getParent();
1✔
66
    PodFormatterL linkElement = PsiTreeUtil.getParentOfType(element, PodFormatterL.class);
1✔
67
    if (linkElement == null) {
1✔
68
      return;
×
69
    }
70
    IElementType parentType = PsiUtilCore.getElementType(linkPart);
1✔
71
    PerlSimpleCompletionProcessor completionProcessor = new PerlSimpleCompletionProcessor(parameters, result, linkElement);
1✔
72

73
    if (parentType == LINK_TEXT && element.getPrevSibling() == null) {
1✔
74
      processFilesCompletions(completionProcessor);
×
75
    }
76
    if (parentType == LINK_NAME) {
1✔
77
      processFilesCompletions(completionProcessor);
1✔
78
    }
79
    if (parentType == LINK_SECTION) {
1✔
80
      addSectionsCompletions(PodFileUtil.getTargetFile(linkElement), completionProcessor);
1✔
81
    }
82
    completionProcessor.logStatus(getClass());
1✔
83
  }
1✔
84

85
  @SuppressWarnings("UnusedReturnValue")
86
  protected boolean processFilesCompletions(@NotNull PerlCompletionProcessor completionProcessor) {
87
    PodFormatterL linkElement = (PodFormatterL)completionProcessor.getLeafElement();
1✔
88
    final Set<String> foundPods = new HashSet<>();
1✔
89
    PerlPackageUtil.processIncFilesForPsiElement(linkElement, (file, classRoot) -> {
1✔
90
      String className = PodFileUtil.getPackageNameFromVirtualFile(file, classRoot);
1✔
91
      if (StringUtil.isNotEmpty(className)) {
1✔
92
        boolean isBuiltIn = false;
1✔
93
        if (StringUtil.startsWith(className, "pod::")) {
1✔
94
          isBuiltIn = true;
1✔
95
          className = className.substring(5);
1✔
96
        }
97
        if (completionProcessor.matches(className) && foundPods.add(className)) {
1✔
98
          if (!completionProcessor
1✔
99
            .process(LookupElementBuilder.create(file, className).withIcon(PerlIcons.POD_FILE).withBoldness(isBuiltIn))) {
1✔
100
            return false;
×
101
          }
102
        }
103
      }
104
      return completionProcessor.result();
1✔
105
    }, PodFileType.INSTANCE);
106

107
    return PerlPackageUtil.processPackageFilesForPsiElement(linkElement, (packageName, file) -> {
1✔
108
      if (StringUtil.isNotEmpty(packageName) && completionProcessor.matches(packageName) && foundPods.add(packageName)) {
1✔
109
        return PerlPackageCompletionUtil.processPackageLookupElement(file, packageName, null, completionProcessor, false);
1✔
110
      }
111
      return completionProcessor.result();
1✔
112
    });
113
  }
114

115
  protected static void addSectionsCompletions(@Nullable PsiFile targetFile,
116
                                               @NotNull PerlCompletionProcessor completionProcessor) {
117
    if (targetFile == null) {
1✔
118
      return;
×
119
    }
120
    Set<String> distinctString = new HashSet<>();
1✔
121

122
    targetFile.accept(new PodStubsAwareRecursiveVisitor() {
1✔
123
      @Override
124
      public void visitElement(@NotNull PsiElement element) {
125
        if (completionProcessor.result()) {
1✔
126
          super.visitElement(element);
1✔
127
        }
128
      }
1✔
129

130
      @Override
131
      public void visitTargetableSection(PodTitledSection o) {
132
        String title = cleanItemText(o.getTitleText());
1✔
133
        if (completionProcessor.matches(title) && distinctString.add(title)) {
1✔
134
          completionProcessor.process(LookupElementBuilder.create(o, PodRenderUtil.escapeTitle(title))
1✔
135
                                        .withLookupString(title)
1✔
136
                                        .withPresentableText(title)
1✔
137
                                        .withIcon(PerlIcons.POD_FILE)
1✔
138
                                        .withTypeText(UsageViewUtil.getType(o)));
1✔
139
        }
140
        super.visitTargetableSection(o);
1✔
141
      }
1✔
142

143
      @Override
144
      public void visitItemSection(@NotNull PsiItemSection o) {
145
        if (((PodSectionItem)o).isTargetable()) {
1✔
146
          super.visitItemSection(o);
1✔
147
        }
148
        else {
149
          visitElement(o);
1✔
150
        }
151
      }
1✔
152

153
      @Contract("null->null")
154
      private @Nullable String cleanItemText(@Nullable String itemText) {
155
        if (itemText == null) {
1✔
156
          return null;
×
157
        }
158
        String trimmed = StringUtil.trimLeading(itemText.trim(), '*');
1✔
159
        if (isNumber(trimmed)) {
1✔
160
          return null;
1✔
161
        }
162
        return PodRenderUtil.trimItemText(trimmed);
1✔
163
      }
164

165
      private static boolean isNumber(@NotNull String text){
166
        if( text.isEmpty()){
1✔
167
          return false;
×
168
        }
169

170
        for( int i = 0; i < text.length(); i++){
1✔
171
          if( !Character.isDigit(text.charAt(i))){
1✔
172
            return false;
1✔
173
          }
174
        }
175

176
        return true;
1✔
177
      }
178

179
      @Override
180
      public void visitPodFormatIndex(@NotNull PsiPodFormatIndex o) {
181
        assert o instanceof PodFormatterX;
1✔
182
        if (!((PodFormatterX)o).isMeaningful()) {
1✔
183
          return;
1✔
184
        }
185
        String indexTitle = ((PodFormatterX)o).getTitleText();
1✔
186
        if (StringUtil.isEmpty(indexTitle) || !distinctString.add(indexTitle)) {
1✔
187
          return;
1✔
188
        }
189

190
        String escapedIndexTitle = PodRenderUtil.escapeTitle(indexTitle);
1✔
191
        if (!completionProcessor.matches(indexTitle) && !completionProcessor.matches(escapedIndexTitle)) {
1✔
192
          return;
1✔
193
        }
194

195
        PsiElement indexTarget = ((PodFormatterX)o).getIndexTarget();
1✔
196
        String targetPresentableText;
197
        if (indexTarget instanceof PodFile podFile) {
1✔
198
          targetPresentableText = cleanItemText(podFile.getPodLinkText());
×
199
        }
200
        else if (indexTarget instanceof PodCompositeElement podCompositeElement) {
1✔
201
          targetPresentableText = cleanItemText(podCompositeElement.getPresentableText());
1✔
202
        }
203
        else {
204
          LOG.warn("Unhandled index target: " + indexTarget);
×
205
          return;
×
206
        }
207
        String tailText = null;
1✔
208
        if (targetPresentableText != null) {
1✔
209
          tailText = "(" + targetPresentableText + ")";
1✔
210
        }
211
        completionProcessor.process(
1✔
212
          LookupElementBuilder.create(o, escapedIndexTitle)
1✔
213
            .withLookupString(indexTitle)
1✔
214
            .withPresentableText(indexTitle)
1✔
215
            .withTailText(tailText)
1✔
216
            .withTypeText(UsageViewUtil.getType(o))
1✔
217
            .withIcon(PerlIcons.POD_FILE)
1✔
218
        );
219
      }
1✔
220
    });
221
  }
1✔
222
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc