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

Camelcade / Perl5-IDEA / #525521553

27 May 2025 10:24AM UTC coverage: 82.286% (-0.03%) from 82.32%
#525521553

push

github

hurricup
Removed redundant implementations, moved up to the abstract class

1 of 1 new or added line in 1 file covered. (100.0%)

128 existing lines in 30 files now uncovered.

30886 of 37535 relevant lines covered (82.29%)

0.82 hits per line

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

90.91
/plugin/core/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.psi.PsiItemSection;
48
import com.perl5.lang.pod.psi.PsiPodFormatIndex;
49
import org.jetbrains.annotations.Contract;
50
import org.jetbrains.annotations.NotNull;
51
import org.jetbrains.annotations.Nullable;
52

53
import java.util.HashSet;
54
import java.util.List;
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
  private static final List<String> TO_ESCAPE = List.of("<", ">", "/", "|");
1✔
60
  private static final List<String> ESCAPE_TO = List.of("E<lt>", "E<gt>", "E<sol>", "E<verbar>");
1✔
61

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

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

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

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

117
  /**
118
   * @return section title with escaped {@code < > | /} chars
119
   */
120
  public static @NotNull String escapeTitle(@NotNull String title) {
121
    return StringUtil.replace(title, TO_ESCAPE, ESCAPE_TO);
1✔
122
  }
123

124
  protected static void addSectionsCompletions(@Nullable PsiFile targetFile,
125
                                               @NotNull PerlCompletionProcessor completionProcessor) {
126
    if (targetFile == null) {
1✔
UNCOV
127
      return;
×
128
    }
129
    Set<String> distinctString = new HashSet<>();
1✔
130

131
    targetFile.accept(new PodStubsAwareRecursiveVisitor() {
1✔
132
      @Override
133
      public void visitElement(@NotNull PsiElement element) {
134
        if (completionProcessor.result()) {
1✔
135
          super.visitElement(element);
1✔
136
        }
137
      }
1✔
138

139
      @Override
140
      public void visitTargetableSection(PodTitledSection o) {
141
        String title = cleanItemText(o.getTitleText());
1✔
142
        if (completionProcessor.matches(title) && distinctString.add(title)) {
1✔
143
          completionProcessor.process(LookupElementBuilder.create(o, escapeTitle(title))
1✔
144
                                        .withLookupString(title)
1✔
145
                                        .withPresentableText(title)
1✔
146
                                        .withIcon(PerlIcons.POD_FILE)
1✔
147
                                        .withTypeText(UsageViewUtil.getType(o)));
1✔
148
        }
149
        super.visitTargetableSection(o);
1✔
150
      }
1✔
151

152
      @Override
153
      public void visitItemSection(@NotNull PsiItemSection o) {
154
        if (((PodSectionItem)o).isTargetable()) {
1✔
155
          super.visitItemSection(o);
1✔
156
        }
157
        else {
158
          visitElement(o);
1✔
159
        }
160
      }
1✔
161

162
      @Contract("null->null")
163
      private @Nullable String cleanItemText(@Nullable String itemText) {
164
        if (itemText == null) {
1✔
UNCOV
165
          return null;
×
166
        }
167
        String trimmed = StringUtil.trimLeading(itemText.trim(), '*');
1✔
168
        if (isNumber(trimmed)) {
1✔
169
          return null;
1✔
170
        }
171
        return trimItemText(trimmed);
1✔
172
      }
173

174
      private static boolean isNumber(@NotNull String text){
175
        if( text.isEmpty()){
1✔
UNCOV
176
          return false;
×
177
        }
178

179
        for( int i = 0; i < text.length(); i++){
1✔
180
          if( !Character.isDigit(text.charAt(i))){
1✔
181
            return false;
1✔
182
          }
183
        }
184

185
        return true;
1✔
186
      }
187

188
      @Override
189
      public void visitPodFormatIndex(@NotNull PsiPodFormatIndex o) {
190
        assert o instanceof PodFormatterX;
1✔
191
        if (!((PodFormatterX)o).isMeaningful()) {
1✔
192
          return;
1✔
193
        }
194
        String indexTitle = ((PodFormatterX)o).getTitleText();
1✔
195
        if (StringUtil.isEmpty(indexTitle) || !distinctString.add(indexTitle)) {
1✔
196
          return;
1✔
197
        }
198

199
        String escapedIndexTitle = escapeTitle(indexTitle);
1✔
200
        if (!completionProcessor.matches(indexTitle) && !completionProcessor.matches(escapedIndexTitle)) {
1✔
201
          return;
1✔
202
        }
203

204
        PsiElement indexTarget = ((PodFormatterX)o).getIndexTarget();
1✔
205
        String targetPresentableText;
206
        if (indexTarget instanceof PodFile podFile) {
1✔
UNCOV
207
          targetPresentableText = cleanItemText(podFile.getPodLinkText());
×
208
        }
209
        else if (indexTarget instanceof PodCompositeElement podCompositeElement) {
1✔
210
          targetPresentableText = cleanItemText(podCompositeElement.getPresentableText());
1✔
211
        }
212
        else {
UNCOV
213
          LOG.warn("Unhandled index target: " + indexTarget);
×
UNCOV
214
          return;
×
215
        }
216
        String tailText = null;
1✔
217
        if (targetPresentableText != null) {
1✔
218
          tailText = "(" + targetPresentableText + ")";
1✔
219
        }
220
        completionProcessor.process(
1✔
221
          LookupElementBuilder.create(o, escapedIndexTitle)
1✔
222
            .withLookupString(indexTitle)
1✔
223
            .withPresentableText(indexTitle)
1✔
224
            .withTailText(tailText)
1✔
225
            .withTypeText(UsageViewUtil.getType(o))
1✔
226
            .withIcon(PerlIcons.POD_FILE)
1✔
227
        );
228
      }
1✔
229
    });
230
  }
1✔
231

232
  public static String trimItemText(String trimmed) {
233
    return StringUtil.nullize(StringUtil.shortenTextWithEllipsis(trimmed, 140, 0));
1✔
234
  }
235
}
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