• 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

71.85
/plugin/backend/src/main/java/com/perl5/lang/perl/idea/completion/util/PerlStringCompletionUtil.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.perl.idea.completion.util;
18

19
import com.intellij.codeInsight.lookup.LookupElementBuilder;
20
import com.intellij.lang.Language;
21
import com.intellij.openapi.progress.ProgressManager;
22
import com.intellij.openapi.project.Project;
23
import com.intellij.openapi.util.text.StringUtil;
24
import com.intellij.psi.PsiElement;
25
import com.intellij.psi.PsiFile;
26
import com.intellij.psi.tree.IElementType;
27
import com.intellij.psi.util.PsiTreeUtil;
28
import com.perl5.PerlIcons;
29
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageOptionsProvider;
30
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageParentsProvider;
31
import com.perl5.lang.perl.extensions.packageprocessor.PerlPackageProcessor;
32
import com.perl5.lang.perl.idea.completion.PerlStringCompletionCache;
33
import com.perl5.lang.perl.idea.completion.providers.processors.PerlCompletionProcessor;
34
import com.perl5.lang.perl.idea.intellilang.PerlInjectionMarkersService;
35
import com.perl5.lang.perl.psi.*;
36
import com.perl5.lang.perl.psi.impl.PerlStringContentElementImpl;
37
import com.perl5.lang.perl.psi.impl.PerlUseStatementElement;
38
import com.perl5.lang.perl.psi.utils.PerlPsiUtil;
39
import com.perl5.lang.perl.util.PerlPackageUtil;
40
import com.perl5.lang.perl.util.PerlPackageUtilCore;
41
import org.jetbrains.annotations.NotNull;
42

43
import java.util.*;
44

45
import static com.perl5.lang.perl.idea.PerlElementPatterns.SIMPLE_HASH_INDEX;
46
import static com.perl5.lang.perl.parser.PerlElementTypesGenerated.COMMA;
47
import static com.perl5.lang.perl.parser.PerlElementTypesGenerated.FAT_COMMA;
48
import static com.perl5.lang.perl.parser.PerlParserUtil.isIdentifier;
49

50

51
public final class PerlStringCompletionUtil {
52
  private static final List<String> REF_TYPES = List.of(
1✔
53
    "SCALAR",
54
    "ARRAY",
55
    "HASH",
56
    "CODE",
57
    "REF",
58
    "GLOB",
59
    "LVALUE",
60
    "FORMAT",
61
    "IO",
62
    "VSTRING",
63
    "Regexp"
64
  );
65

66
  private PerlStringCompletionUtil() {
67
  }
68

69
  public static void fillWithHashIndexes(@NotNull PerlCompletionProcessor completionProcessor) {
70
    Set<String> hashIndexesCache = PerlStringCompletionCache.getInstance(completionProcessor.getProject()).getHashIndexesCache();
1✔
71

72
    for (String text : hashIndexesCache) {
1✔
73
      if (completionProcessor.matches(text) && !completionProcessor.process(LookupElementBuilder.create(text))) {
×
74
        return;
×
75
      }
76
    }
×
77

78
    PsiElement element = completionProcessor.getLeafElement();
1✔
79
    PsiFile file = completionProcessor.getContainingFile();
1✔
80

81
    file.accept(
1✔
82
      new PerlCompletionRecursiveVisitor(completionProcessor) {
1✔
83
        @Override
84
        public void visitStringContentElement(@NotNull PerlStringContentElementImpl o) {
85
          if (o != element && SIMPLE_HASH_INDEX.accepts(o)) {
1✔
86
            processStringElement(o);
1✔
87
          }
88
          super.visitStringContentElement(o);
1✔
89
        }
1✔
90

91
        @Override
92
        public void visitCommaSequenceExpr(@NotNull PsiPerlCommaSequenceExpr o) {
93
          if (o.getParent() instanceof PsiPerlAnonHash) {
×
94
            PsiElement sequenceElement = o.getFirstChild();
×
95
            boolean isKey = true;
×
96

97
            while (sequenceElement != null) {
×
98
              ProgressManager.checkCanceled();
×
99
              IElementType elementType = sequenceElement.getNode().getElementType();
×
100
              if (isKey && sequenceElement instanceof PerlString) {
×
101
                for (PerlStringContentElement stringElement : PerlPsiUtil.collectStringElements(sequenceElement)) {
×
102
                  processStringElement(stringElement);
×
103
                }
×
104
              }
105
              else if (elementType == COMMA || elementType == FAT_COMMA) {
×
106
                isKey = !isKey;
×
107
              }
108

109
              sequenceElement = PerlPsiUtil.getNextSignificantSibling(sequenceElement);
×
110
            }
×
111
          }
112
          super.visitCommaSequenceExpr(o);
×
113
        }
×
114

115
        void processStringElement(PerlStringContentElement stringContentElement) {
116
          String text = stringContentElement.getText();
1✔
117
          if (StringUtil.isNotEmpty(text) && hashIndexesCache.add(text) &&
1✔
118
              completionProcessor.matches(text) && isIdentifier(text)) {
1✔
119
            completionProcessor.process(LookupElementBuilder.create(stringContentElement, text));
1✔
120
          }
121
        }
1✔
122
      });
123
  }
1✔
124

125
  public static void fillWithExportableEntities(@NotNull PerlCompletionProcessor completionProcessor) {
126
    PsiElement element = completionProcessor.getLeafElement();
1✔
127
    final String contextPackageName = PerlPackageUtilCore.getContextNamespaceName(element);
1✔
128

129
    element.getContainingFile().accept(
1✔
130
      new PerlCompletionRecursiveVisitor(completionProcessor) {
1✔
131
        @Override
132
        public void visitSubDeclarationElement(@NotNull PerlSubDeclarationElement o) {
133
          if (contextPackageName.equals(o.getNamespaceName())) {
×
134
            String subName = o.getSubName();
×
135
            if (completionProcessor.matches(subName)) {
×
136
              completionProcessor.process(LookupElementBuilder.create(o, subName));
×
137
            }
138
          }
139
          super.visitSubDeclarationElement(o);
×
140
        }
×
141

142
        @Override
143
        protected boolean shouldVisitLightElements() {
144
          return true;
1✔
145
        }
146

147
        @Override
148
        public void visitPerlSubDefinitionElement(@NotNull PerlSubDefinitionElement o) {
149
          if (contextPackageName.equals(o.getNamespaceName())) {
1✔
150
            String subName = o.getSubName();
1✔
151
            if (completionProcessor.matches(subName)) {
1✔
152
              completionProcessor.process(LookupElementBuilder.create(o, subName));
1✔
153
            }
154
          }
155
          super.visitPerlSubDefinitionElement(o);
1✔
156
        }
1✔
157
      }
158
    );
159
  }
1✔
160

161
  public static void fillWithUseParameters(@NotNull PerlCompletionProcessor completionProcessor) {
162
    PsiElement baseElement = completionProcessor.getLeafElement();
1✔
163
    PerlUseStatementElement useStatement =
1✔
164
      PsiTreeUtil.getParentOfType(baseElement, PerlUseStatementElement.class, true, PsiPerlStatement.class);
1✔
165

166
    if (useStatement == null) {
1✔
167
      return;
×
168
    }
169

170
    List<String> typedParameters = useStatement.getImportParameters();
1✔
171
    Set<String> typedStringsSet = typedParameters == null ? Collections.emptySet() : new HashSet<>(typedParameters);
1✔
172

173
    PerlPackageProcessor packageProcessor = useStatement.getPackageProcessor();
1✔
174
    // fixme we should allow lookup elements customization by package processor
175
    if (packageProcessor instanceof PerlPackageOptionsProvider packageOptionsProvider) {
1✔
176
      Map<String, String> options = packageOptionsProvider.getOptions();
1✔
177

178
      for (Map.Entry<String, String> option : options.entrySet()) {
1✔
179
        String lookupString = option.getKey();
1✔
180
        if (!typedStringsSet.contains(lookupString) && completionProcessor.matches(lookupString) && !completionProcessor.process(
1✔
181
          LookupElementBuilder.create(lookupString).withTypeText(option.getValue(), true).withIcon(PerlIcons.PERL_OPTION))) {
1✔
182
          return;
×
183
        }
184
      }
1✔
185

186
      options = packageOptionsProvider.getOptionsBundles();
1✔
187

188
      for (Map.Entry<String, String> option : options.entrySet()) {
1✔
189
        String lookupString = option.getKey();
1✔
190
        if (!typedStringsSet.contains(lookupString) && completionProcessor.matches(lookupString) && !completionProcessor.process(
1✔
191
          LookupElementBuilder.create(lookupString).withTypeText(option.getValue(), true).withIcon(PerlIcons.PERL_OPTIONS))) {
1✔
192
          return;
×
193
        }
194
      }
1✔
195
    }
196

197
    if (packageProcessor instanceof PerlPackageParentsProvider packageParentsProvider && packageParentsProvider.hasPackageFilesOptions() &&
1✔
198
        !PerlPackageUtil.processPackageFilesForPsiElement(baseElement, (packageName, file) ->
1✔
199
          typedStringsSet.contains(packageName) ||
1✔
200
          PerlPackageCompletionUtil.processPackageLookupElement(file, packageName, null, completionProcessor, false))) {
1✔
201
      return;
×
202
    }
203

204
    Set<String> export = new HashSet<>();
1✔
205
    Set<String> exportOk = new HashSet<>();
1✔
206
    packageProcessor.addExports(useStatement, export, exportOk);
1✔
207
    exportOk.removeAll(export);
1✔
208

209
    for (String subName : export) {
1✔
210
      if (!typedStringsSet.contains(subName) && completionProcessor.matches(subName) && !completionProcessor.process(
1✔
211
        LookupElementBuilder.create(subName).withIcon(PerlIcons.SUB_GUTTER_ICON).withTypeText("default", true))) {
1✔
212
        return;
×
213
      }
214
    }
1✔
215
    for (String subName : exportOk) {
1✔
216
      if (!typedStringsSet.contains(subName) && completionProcessor.matches(subName) && !completionProcessor.process(
1✔
217
        LookupElementBuilder.create(subName).withIcon(PerlIcons.SUB_GUTTER_ICON).withTypeText("optional", true))) {
1✔
218
        return;
×
219
      }
220
    }
1✔
221
  }
1✔
222

223
  public static void fillWithRefTypes(@NotNull PerlCompletionProcessor completionProcessor) {
224
    for (String refType : REF_TYPES) {
1✔
225
      if (completionProcessor.matches(refType) && !completionProcessor.process(LookupElementBuilder.create(refType))) {
1✔
226
        return;
×
227
      }
228
    }
1✔
229
  }
1✔
230

231
  public static void fillWithInjectableMarkers(@NotNull PerlCompletionProcessor completionProcessor) {
232
    // injectable markers
233
    PerlInjectionMarkersService injectionService = PerlInjectionMarkersService.getInstance(completionProcessor.getProject());
1✔
234
    for (String marker : injectionService.getSupportedMarkers()) {
1✔
235
      if (!completionProcessor.matches(marker)) {
1✔
236
        continue;
×
237
      }
238
      Language language = injectionService.getLanguageByMarker(marker);
1✔
239
      if (language == null) {
1✔
240
        continue;
1✔
241
      }
242

243
      LookupElementBuilder newItem = LookupElementBuilder
1✔
244
        .create(marker)
1✔
245
        .withTypeText("inject with " + language.getDisplayName(), true);
1✔
246

247
      if (language.getAssociatedFileType() != null) {
1✔
248
        newItem = newItem.withIcon(language.getAssociatedFileType().getIcon());
1✔
249
      }
250

251
      if (!completionProcessor.process(newItem)) {
1✔
252
        return;
×
253
      }
254
    }
1✔
255
  }
1✔
256

257
  public static void fillWithHeredocOpeners(@NotNull PerlCompletionProcessor completionProcessor) {
258
    Project project = completionProcessor.getProject();
1✔
259
    Set<String> heredocOpenersCache = PerlStringCompletionCache.getInstance(project).getHeredocOpenersCache();
1✔
260
    // cached values
261
    for (String marker : heredocOpenersCache) {
1✔
262
      if (completionProcessor.matches(marker) && !completionProcessor.process(LookupElementBuilder.create(marker))) {
×
263
        return;
×
264
      }
265
    }
×
266

267
    PerlInjectionMarkersService injectionService = PerlInjectionMarkersService.getInstance(project);
1✔
268

269
    // collect new values
270
    PsiFile file = completionProcessor.getContainingFile();
1✔
271
    file.accept(new PerlCompletionRecursiveVisitor(completionProcessor) {
1✔
272
      @Override
273
      public void visitHeredocOpener(@NotNull PsiPerlHeredocOpener o) {
274
        String openerName = o.getName();
1✔
275
        if (StringUtil.isNotEmpty(openerName) && heredocOpenersCache.add(openerName) &&
1✔
276
            injectionService.getLanguageByMarker(openerName) == null &&
1✔
277
            !completionProcessor.process(LookupElementBuilder.create(o, openerName))) {
1✔
278
          return;
×
279
        }
280
        super.visitHeredocOpener(o);
1✔
281
      }
1✔
282
    });
283
  }
1✔
284
}
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