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

devonfw / IDEasy / 22303886886

23 Feb 2026 11:19AM UTC coverage: 70.647% (+0.2%) from 70.474%
22303886886

Pull #1714

github

web-flow
Merge f1f7e1e61 into 379acdc9d
Pull Request #1714: #404: #1713: advanced logging

4069 of 6360 branches covered (63.98%)

Branch coverage included in aggregate %.

10644 of 14466 relevant lines covered (73.58%)

3.1 hits per line

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

3.75
cli/src/main/java/com/devonfw/tools/ide/commandlet/ShellCommandlet.java
1
package com.devonfw.tools.ide.commandlet;
2

3
import java.io.IOException;
4
import java.nio.file.Path;
5
import java.nio.file.Paths;
6
import java.util.Iterator;
7

8
import org.fusesource.jansi.AnsiConsole;
9
import org.jline.reader.Completer;
10
import org.jline.reader.EndOfFileException;
11
import org.jline.reader.LineReader;
12
import org.jline.reader.LineReaderBuilder;
13
import org.jline.reader.MaskingCallback;
14
import org.jline.reader.Parser;
15
import org.jline.reader.UserInterruptException;
16
import org.jline.reader.impl.DefaultParser;
17
import org.jline.reader.impl.completer.AggregateCompleter;
18
import org.jline.reader.impl.completer.StringsCompleter;
19
import org.jline.terminal.Terminal;
20
import org.jline.terminal.TerminalBuilder;
21
import org.jline.widget.AutosuggestionWidgets;
22
import org.slf4j.Logger;
23
import org.slf4j.LoggerFactory;
24

25
import com.devonfw.tools.ide.cli.CliArgument;
26
import com.devonfw.tools.ide.cli.CliArguments;
27
import com.devonfw.tools.ide.cli.CliException;
28
import com.devonfw.tools.ide.completion.IdeCompleter;
29
import com.devonfw.tools.ide.context.AbstractIdeContext;
30
import com.devonfw.tools.ide.context.IdeContext;
31
import com.devonfw.tools.ide.property.BooleanProperty;
32
import com.devonfw.tools.ide.property.KeywordProperty;
33
import com.devonfw.tools.ide.property.Property;
34

35
/**
36
 * {@link Commandlet} for internal interactive shell with build-in auto-completion and help.
37
 */
38
public final class ShellCommandlet extends Commandlet {
39

40
  private static final Logger LOG = LoggerFactory.getLogger(ShellCommandlet.class);
4✔
41

42
  private static final int AUTOCOMPLETER_MAX_RESULTS = 50;
43

44
  private static final int RC_EXIT = 987654321;
45

46
  /**
47
   * The constructor.
48
   *
49
   * @param context the {@link IdeContext}.
50
   */
51
  public ShellCommandlet(IdeContext context) {
52

53
    super(context);
3✔
54
    addKeyword(getName());
4✔
55
  }
1✔
56

57
  @Override
58
  public String getName() {
59

60
    return "shell";
2✔
61
  }
62

63
  @Override
64
  public boolean isIdeHomeRequired() {
65

66
    return false;
2✔
67
  }
68

69
  @Override
70
  protected void doRun() {
71

72
    try {
73
      Parser parser = new DefaultParser();
×
74
      try (Terminal terminal = TerminalBuilder.builder().build()) {
×
75
        // initialize our own completer here and add exit as an autocompletion option
76
        Completer completer = new AggregateCompleter(
×
77
            new StringsCompleter("exit"), new IdeCompleter((AbstractIdeContext) this.context));
78

79
        LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(completer).parser(parser)
×
80
            .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build();
×
81

82
        // Create autosuggestion widgets
83
        AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader);
×
84
        // Enable autosuggestions
85
        autosuggestionWidgets.enable();
×
86

87
        // TODO: implement TailTipWidgets, see: https://github.com/devonfw/IDEasy/issues/169
88

89
        String rightPrompt = null;
×
90
        String line;
91

92
        AnsiConsole.systemInstall();
×
93
        while (true) {
94
          try {
95
            String prompt = context.getCwd() + "$ ide ";
×
96
            line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
×
97
            line = line.trim();
×
98
            if (line.equals("exit")) {
×
99
              return;
×
100
            }
101
            reader.getHistory().add(line);
×
102
            int rc = runCommand(line);
×
103
            if (rc == RC_EXIT) {
×
104
              return;
×
105
            }
106
          } catch (UserInterruptException e) {
×
107
            // Ignore CTRL+C
108
            return;
×
109
          } catch (EndOfFileException e) {
×
110
            // CTRL+D
111
            return;
×
112
          } finally {
113
            AnsiConsole.systemUninstall();
×
114
          }
×
115
        }
116

117
      } catch (IOException e) {
×
118
        throw new RuntimeException(e);
×
119
      }
120
    } catch (CliException e) {
×
121
      throw e;
×
122
    } catch (Exception e) {
×
123
      throw new RuntimeException("Unexpected error during interactive auto-completion", e);
×
124
    }
125
  }
126

127
  /**
128
   * Converts String of arguments to array and runs the command
129
   *
130
   * @param args String of arguments
131
   * @return status code
132
   */
133
  private int runCommand(String args) {
134

135
    if ("exit".equals(args) || "quit".equals(args)) {
×
136
      return RC_EXIT;
×
137
    }
138
    String[] arguments = args.split(" ", 0);
×
139
    CliArguments cliArgs = new CliArguments(arguments);
×
140
    cliArgs.next();
×
141

142
    if ("cd".equals(arguments[0])) {
×
143
      return changeDirectory(cliArgs);
×
144
    }
145

146
    return ((AbstractIdeContext) this.context).run(cliArgs);
×
147
  }
148

149
  private int changeDirectory(CliArguments cliArgs) {
150
    if (!cliArgs.hasNext()) {
×
151
      Path homeDir = this.context.getUserHome();
×
152
      context.setCwd(homeDir, context.getWorkspaceName(), context.getIdeHome());
×
153
      return 0;
×
154
    }
155

156
    String targetDir = String.valueOf(cliArgs.next());
×
157
    Path path = Paths.get(targetDir);
×
158

159
    // If the given path is relative, resolve it relative to the current directory
160
    if (!path.isAbsolute()) {
×
161
      path = context.getCwd().resolve(targetDir).normalize();
×
162
    }
163

164
    if (context.getFileAccess().isExpectedFolder(path)) {
×
165
      context.setCwd(path, context.getWorkspaceName(), context.getIdeHome());
×
166
      return 0;
×
167
    } else {
168
      return 1;
×
169
    }
170
  }
171

172
  /**
173
   * @param argument the current {@link CliArgument} (position) to match.
174
   * @param commandlet the potential {@link Commandlet} to match.
175
   * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have been applied (set in the {@link Commandlet}
176
   *     and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
177
   */
178
  private boolean apply(CliArgument argument, Commandlet commandlet) {
179

180
    LOG.trace("Trying to match arguments to commandlet {}", commandlet.getName());
×
181
    CliArgument currentArgument = argument;
×
182
    Iterator<Property<?>> valueIterator = commandlet.getValues().iterator();
×
183
    Property<?> currentProperty = null;
×
184
    boolean endOpts = false;
×
185
    while (!currentArgument.isEnd()) {
×
186
      if (currentArgument.isEndOptions()) {
×
187
        endOpts = true;
×
188
      } else {
189
        String arg = currentArgument.get();
×
190
        LOG.trace("Trying to match argument '{}'", currentArgument);
×
191
        if ((currentProperty != null) && (currentProperty.isExpectValue())) {
×
192
          currentProperty.setValueAsString(arg, this.context);
×
193
          if (!currentProperty.isMultiValued()) {
×
194
            currentProperty = null;
×
195
          }
196
        } else {
197
          Property<?> property = null;
×
198
          if (!endOpts) {
×
199
            property = commandlet.getOption(currentArgument.getKey());
×
200
          }
201
          if (property == null) {
×
202
            if (!valueIterator.hasNext()) {
×
203
              LOG.trace("No option or next value found");
×
204
              return false;
×
205
            }
206
            currentProperty = valueIterator.next();
×
207
            LOG.trace("Next value candidate is {}", currentProperty);
×
208
            if (currentProperty instanceof KeywordProperty keyword) {
×
209
              if (keyword.matches(arg)) {
×
210
                keyword.setValue(Boolean.TRUE);
×
211
                LOG.trace("Keyword matched");
×
212
              } else {
213
                LOG.trace("Missing keyword");
×
214
                return false;
×
215
              }
216
            } else {
217
              boolean success = currentProperty.assignValueAsString(arg, this.context, commandlet);
×
218
              if (!success && currentProperty.isRequired()) {
×
219
                return false;
×
220
              }
221
            }
222
            if ((currentProperty != null) && !currentProperty.isMultiValued()) {
×
223
              currentProperty = null;
×
224
            }
225
          } else {
226
            LOG.trace("Found option by name");
×
227
            String value = currentArgument.getValue();
×
228
            if (value != null) {
×
229
              property.setValueAsString(value, this.context);
×
230
            } else if (property instanceof BooleanProperty) {
×
231
              ((BooleanProperty) property).setValue(Boolean.TRUE);
×
232
            } else {
233
              currentProperty = property;
×
234
              if (property.isEndOptions()) {
×
235
                endOpts = true;
×
236
              }
237
              throw new UnsupportedOperationException("not implemented");
×
238
            }
239
          }
240
        }
241
      }
242
      currentArgument = currentArgument.getNext(!endOpts);
×
243
    }
244
    return commandlet.validate().isValid();
×
245
  }
246
}
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