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

Camelcade / Perl5-IDEA / #525521581

06 Jun 2025 08:26AM UTC coverage: 82.33% (-0.02%) from 82.354%
#525521581

push

github

hurricup
Moved debugger properties to the respective bundle

18 of 26 new or added lines in 8 files covered. (69.23%)

158 existing lines in 31 files now uncovered.

30858 of 37481 relevant lines covered (82.33%)

0.82 hits per line

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

21.21
/plugin/core/src/main/java/com/perl5/lang/perl/idea/sdk/host/PerlHostHandler.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.sdk.host;
18

19
import com.intellij.openapi.Disposable;
20
import com.intellij.openapi.application.ApplicationManager;
21
import com.intellij.openapi.fileChooser.FileChooser;
22
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
23
import com.intellij.openapi.options.UnnamedConfigurable;
24
import com.intellij.openapi.project.Project;
25
import com.intellij.openapi.projectRoots.Sdk;
26
import com.intellij.openapi.ui.InputValidator;
27
import com.intellij.openapi.ui.Messages;
28
import com.intellij.openapi.util.NlsActions.ActionText;
29
import com.intellij.openapi.util.NlsContexts;
30
import com.intellij.openapi.util.Ref;
31
import com.intellij.openapi.util.io.FileUtil;
32
import com.intellij.openapi.util.text.StringUtil;
33
import com.intellij.openapi.vfs.VirtualFile;
34
import com.intellij.openapi.vfs.VirtualFileSystem;
35
import com.perl5.lang.perl.idea.sdk.AbstractPerlHandler;
36
import com.perl5.lang.perl.idea.sdk.PerlHandlerBean;
37
import com.perl5.lang.perl.idea.sdk.PerlHandlerCollector;
38
import com.perl5.lang.perl.idea.sdk.host.os.PerlOsHandler;
39
import org.jdom.Element;
40
import org.jetbrains.annotations.Contract;
41
import org.jetbrains.annotations.NotNull;
42
import org.jetbrains.annotations.Nullable;
43

44
import javax.swing.*;
45
import java.io.File;
46
import java.util.List;
47
import java.util.Objects;
48
import java.util.function.BiConsumer;
49
import java.util.function.Consumer;
50
import java.util.function.Function;
51
import java.util.function.Predicate;
52
import java.util.stream.Stream;
53

54
/**
55
 * Know how to work with different types of hosts
56
 */
57
public abstract class PerlHostHandler<Data extends PerlHostData<Data, Handler>, Handler extends PerlHostHandler<Data, Handler>>
58
  extends AbstractPerlHandler<Data, Handler> {
59
  private static final String TAG_NAME = "host";
60

61
  private static final PerlHandlerCollector<PerlHostHandler<?, ?>> EP = new PerlHandlerCollector<>("com.perl5.hostHandler");
1✔
62

63
  public PerlHostHandler(@NotNull PerlHandlerBean bean) {
64
    super(bean);
1✔
65
  }
1✔
66

67
  /**
68
   * Chooses a path conforming with {@code pathPredicate} on user-selected host and passes selected path and host data
69
   * to the {@code selectionConsumer}
70
   *
71
   * @param defaultPathFunction function computing a default path from {@code hostData}
72
   * @param useDefaultIfExists  true if default path should be used silently if exists
73
   * @param nameValidator       restricts visible file names
74
   * @param pathValidator       validates a path selected by user and returns error message or null if everything is fine
75
   * @param selectionConsumer   a callback for selected result. Accepts path selected and the host data
76
   * @param disposable          session-bound things may be attached to this disposable, which is going to be disposed by parent configurable
77
   */
78
  public void chooseFileInteractively(@NlsContexts.DialogTitle @NotNull String dialogTitle,
79
                                      @Nullable Function<? super PerlHostData<?, ?>, ? extends File> defaultPathFunction,
80
                                      boolean useDefaultIfExists,
81
                                      @NotNull Predicate<? super String> nameValidator,
82
                                      @NotNull Function<? super String, String> pathValidator,
83
                                      @NotNull BiConsumer<? super String, ? super PerlHostData<?, ?>> selectionConsumer,
84
                                      @NotNull Disposable disposable) {
85
    Data hostData = createDataInteractively();
×
86
    if (hostData == null) {
×
UNCOV
87
      return;
×
88
    }
89
    VirtualFileSystem fileSystem = hostData.getFileSystem(disposable);
×
90
    File defaultPath = defaultPathFunction == null ? null : defaultPathFunction.apply(hostData);
×
91
    Consumer<String> resultConsumer = it -> selectionConsumer.accept(it, hostData);
×
92
    if (fileSystem != null) {
×
UNCOV
93
      chooseFileInteractively(
×
94
        dialogTitle, defaultPath, useDefaultIfExists, nameValidator, pathValidator, resultConsumer, hostData, fileSystem);
95
    }
96
    else {
UNCOV
97
      chooseFileInteractively(
×
98
        dialogTitle, defaultPath, useDefaultIfExists, nameValidator, pathValidator, resultConsumer, hostData);
99
    }
UNCOV
100
  }
×
101

102
  /**
103
   * Choose a file if host does not provide a file system
104
   */
105
  protected void chooseFileInteractively(@NlsContexts.DialogTitle @NotNull String dialogTitle,
106
                                         @Nullable File defaultPath,
107
                                         boolean useDefaultIfExists,
108
                                         @NotNull Predicate<? super String> nameValidator,
109
                                         @NotNull Function<? super String, String> pathValidator,
110
                                         @NotNull Consumer<? super String> selectionConsumer,
111
                                         @NotNull Data hostData) {
112
    Ref<String> pathRef = Ref.create();
×
113
    ApplicationManager.getApplication().invokeAndWait(
×
UNCOV
114
      () -> pathRef.set(Messages.showInputDialog(
×
115
        (Project)null, null, dialogTitle, null,
116
        defaultPath == null ? null : FileUtil.toSystemIndependentName(defaultPath.getPath()),
×
UNCOV
117
        new InputValidator() {
×
118
          @Override
119
          public boolean checkInput(String inputString) {
120
            if (StringUtil.isEmpty(inputString)) {
×
UNCOV
121
              return false;
×
122
            }
UNCOV
123
            return nameValidator.test(new File(inputString).getName());
×
124
          }
125

126
          @Override
127
          public boolean canClose(String inputString) {
UNCOV
128
            return StringUtil.isNotEmpty(inputString) && pathValidator.apply(inputString) == null;
×
129
          }
130
        })));
131
    String chosenPath = pathRef.get();
×
132
    if (StringUtil.isNotEmpty(chosenPath)) {
×
UNCOV
133
      selectionConsumer.accept(chosenPath);
×
134
    }
UNCOV
135
  }
×
136

137
  /**
138
   * Choose a file if host provides a file system
139
   */
140
  protected void chooseFileInteractively(@NlsContexts.DialogTitle @NotNull String dialogTitle,
141
                                         @Nullable File defaultPath,
142
                                         boolean useDefaultIfExists,
143
                                         @NotNull Predicate<? super String> nameValidator,
144
                                         @NotNull Function<? super String, String> pathValidator,
145
                                         @NotNull Consumer<? super String> selectionConsumer,
146
                                         @NotNull Data hostData,
147
                                         @NotNull VirtualFileSystem fileSystem) {
UNCOV
148
    VirtualFile defaultFile = defaultPath == null ? null : fileSystem.findFileByPath(defaultPath.getPath());
×
149

150
    if (useDefaultIfExists && defaultFile != null && defaultFile.exists() && !defaultFile.isDirectory()) {
×
151
      selectionConsumer.accept(defaultFile.getPath());
×
UNCOV
152
      return;
×
153
    }
154

155
    final var descriptor = createDescriptor(dialogTitle, nameValidator, pathValidator);
×
156
    customizeFileChooser(descriptor, fileSystem);
×
157
    Ref<String> pathRef = Ref.create();
×
158
    ApplicationManager.getApplication().invokeAndWait(() -> FileChooser.chooseFiles(descriptor, null, defaultFile, chosen -> {
×
159
      String selectedPath = chosen.getFirst().getPath();
×
160
      if (StringUtil.isEmpty(pathValidator.apply(selectedPath))) {
×
UNCOV
161
        pathRef.set(selectedPath);
×
162
      }
163
    }));
×
164
    if (!pathRef.isNull()) {
×
UNCOV
165
      selectionConsumer.accept(pathRef.get());
×
166
    }
UNCOV
167
  }
×
168

169
  private @NotNull FileChooserDescriptor createDescriptor(@NlsContexts.DialogTitle @NotNull String dialogTitle,
170
                                                          @NotNull Predicate<? super String> nameValidator,
171
                                                          @NotNull Function<? super String, String> pathValidator) {
UNCOV
172
    final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, isChooseFolders(), false, false, false, false) {
×
173
      @SuppressWarnings("deprecation")
174
      @Override
175
      public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
UNCOV
176
        return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() || nameValidator.test(file.getName()));
×
177
      }
178

179
      @Override
180
      public void validateSelectedFiles(VirtualFile[] files) throws Exception {
181
        if (files.length != 0) {
×
182
          String errorMessage = pathValidator.apply(files[0].getPath());
×
183
          if (StringUtil.isNotEmpty(errorMessage)) {
×
UNCOV
184
            throw new Exception(errorMessage);
×
185
          }
186
        }
UNCOV
187
      }
×
188
    };
189
    descriptor.setTitle(dialogTitle);
×
UNCOV
190
    return descriptor;
×
191
  }
192

193
  /**
194
   * @return true iff we should allow to choose folders - only case is local mac chooser
195
   */
UNCOV
196
  protected boolean isChooseFolders() {return false;}
×
197

198
  /**
199
   * host handler may customize a chooser descriptor if necessary, e.g. add fs roots
200
   */
201
  protected void customizeFileChooser(@NotNull FileChooserDescriptor descriptor, @NotNull VirtualFileSystem fileSystem) {
UNCOV
202
  }
×
203

204
  /**
205
   * Creates a necessary host data with possible interactions with user
206
   *
207
   * @return host data or null if user cancelled the process
208
   */
209
  protected abstract @Nullable Data createDataInteractively();
210

211
  /**
212
   * @return a menu item title for this handler, used in UI. E.g. new interpreter menu item
213
   */
214
  public abstract @NotNull @ActionText String getMenuItemTitle();
215

216
  /**
217
   * @return short lowercased name, for interpreters list
218
   */
219
  public abstract @NotNull String getShortName();
220

221
  /**
222
   * @return true iff this handler can be used in the application. E.g. has proper os, proper plugins, etc.
223
   */
224
  public abstract boolean isApplicable();
225

226
  /**
227
   * @return an OS handler for this host if it's available without data. E.g for local, wsl, docker cases
228
   */
229
  public abstract @Nullable PerlOsHandler getOsHandler();
230

231
  /**
232
   * @return true  iff it is a localhost handler. Despite docker and wsl are local as well, they considered as remote.
233
   */
234
  public abstract  boolean isLocal();
235

236
  @Override
237
  protected final @NotNull String getTagName() {
238
    return TAG_NAME;
1✔
239
  }
240

241
  /**
242
   * @return configurable for the {@code project} settings related to this host data type if any. E.g. docker has additional cli arguments on project level
243
   */
244
  public @Nullable UnnamedConfigurable getSettingsConfigurable(@NotNull Project project) {
245
    return null;
1✔
246
  }
247

248
  public abstract @Nullable Icon getIcon();
249

250
  public static @NotNull List<? extends PerlHostHandler<?, ?>> all() {
251
    return EP.getExtensionsList();
1✔
252
  }
253

254
  public static void forEach(@NotNull Consumer<? super PerlHostHandler<?, ?>> action) {
255
    all().forEach(action);
×
UNCOV
256
  }
×
257

258
  public static @NotNull Stream<? extends PerlHostHandler<?, ?>> stream() {
259
    return all().stream();
1✔
260
  }
261

262
  @Contract("null->null")
263
  public static @Nullable PerlHostHandler<?, ?> from(@Nullable Sdk sdk) {
264
    PerlHostData<?, ?> perlHostData = PerlHostData.from(sdk);
1✔
265
    return perlHostData == null ? null : perlHostData.getHandler();
1✔
266
  }
267

268
  /**
269
   * Attempts to load {@link PerlHostData} from the {@code parentElement}
270
   *
271
   * @return data read or null if EP not found
272
   */
273
  public static @Nullable PerlHostData<?, ?> load(@NotNull Element parentElement) {
274
    Element element = parentElement.getChild(TAG_NAME);
1✔
275
    if (element == null) {
1✔
UNCOV
276
      return null;
×
277
    }
278
    PerlHostHandler<?, ?> handler = EP.findSingle(element.getAttributeValue(ID_ATTRIBUTE));
1✔
279
    return handler != null ? handler.loadData(element) : null;
1✔
280
  }
281

282
  public static @NotNull PerlHostHandler<?, ?> getDefaultHandler() {
283
    return Objects.requireNonNull(EP.findSingle("localhost"), "Local handler MUST always present");
1✔
284
  }
285
}
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