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

devonfw / IDEasy / 26106165674

19 May 2026 03:08PM UTC coverage: 70.938% (-0.04%) from 70.979%
26106165674

Pull #1950

github

web-flow
Merge b2b516023 into b4eeee25f
Pull Request #1950: #836: fix exit autocompletion

4445 of 6930 branches covered (64.14%)

Branch coverage included in aggregate %.

11477 of 15515 relevant lines covered (73.97%)

3.13 hits per line

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

3.61
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
import java.util.List;
8

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

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

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

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

43
  private static final int AUTOCOMPLETER_MAX_RESULTS = 50;
44

45
  private static final int RC_EXIT = 987654321;
46

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

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

58
  @Override
59
  public String getName() {
60

61
    return "shell";
2✔
62
  }
63

64
  @Override
65
  public boolean isIdeHomeRequired() {
66

67
    return false;
2✔
68
  }
69

70
  @Override
71
  protected void doRun() {
72

73
    try {
74
      Parser parser = new DefaultParser();
×
75
      try (Terminal terminal = TerminalBuilder.builder().build()) {
×
76
        IdeCompleter ideCompleter = new IdeCompleter((AbstractIdeContext) this.context);
×
77

78
        Completer shellCompleter = new Completer() {
×
79
          @Override
80
          public void complete(LineReader reader, ParsedLine commandLine, List<Candidate> candidates) {
81
            String currentWord = commandLine.word();
×
82
            int wordIndex = commandLine.wordIndex();
×
83

84
            if (wordIndex == 0 && !currentWord.isEmpty() && "exit".startsWith(currentWord)) {
×
85
              candidates.add(new Candidate("exit"));
×
86
            }
87

88
            ideCompleter.complete(reader, commandLine, candidates);
×
89
          }
×
90
        };
91

92
        LineReader reader = LineReaderBuilder.builder().terminal(terminal).completer(shellCompleter).parser(parser)
×
93
            .variable(LineReader.LIST_MAX, AUTOCOMPLETER_MAX_RESULTS).build();
×
94

95
        // Create autosuggestion widgets
96
        AutosuggestionWidgets autosuggestionWidgets = new AutosuggestionWidgets(reader);
×
97
        // Enable autosuggestions
98
        autosuggestionWidgets.enable();
×
99

100
        // TODO: implement TailTipWidgets, see: https://github.com/devonfw/IDEasy/issues/169
101

102
        String rightPrompt = null;
×
103
        String line;
104

105
        AnsiConsole.systemInstall();
×
106
        while (true) {
107
          try {
108
            String prompt = context.getCwd() + "$ ide ";
×
109
            line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
×
110
            reader.getHistory().add(line);
×
111
            int rc = runCommand(line);
×
112
            if (rc == RC_EXIT) {
×
113
              return;
×
114
            }
115
          } catch (UserInterruptException e) {
×
116
            // Ignore CTRL+C
117
            return;
×
118
          } catch (EndOfFileException e) {
×
119
            // CTRL+D
120
            return;
×
121
          } finally {
122
            AnsiConsole.systemUninstall();
×
123
          }
×
124
        }
125

126
      } catch (IOException e) {
×
127
        throw new RuntimeException(e);
×
128
      }
129
    } catch (CliException e) {
×
130
      throw e;
×
131
    } catch (Exception e) {
×
132
      throw new RuntimeException("Unexpected error during interactive auto-completion", e);
×
133
    }
134
  }
135

136
  /**
137
   * Converts String of arguments to array and runs the command
138
   *
139
   * @param args String of arguments
140
   * @return status code
141
   */
142
  private int runCommand(String args) {
143

144
    if ("exit".equals(args) || "quit".equals(args)) {
×
145
      return RC_EXIT;
×
146
    }
147
    String[] arguments = args.split(" ", 0);
×
148
    CliArguments cliArgs = new CliArguments(arguments);
×
149
    cliArgs.next();
×
150

151
    if ("cd".equals(arguments[0])) {
×
152
      return changeDirectory(cliArgs);
×
153
    }
154

155
    return ((AbstractIdeContext) this.context).run(cliArgs);
×
156
  }
157

158
  private int changeDirectory(CliArguments cliArgs) {
159
    if (!cliArgs.hasNext()) {
×
160
      Path homeDir = this.context.getUserHome();
×
161
      context.setCwd(homeDir, context.getWorkspaceName(), context.getIdeHome());
×
162
      return 0;
×
163
    }
164

165
    String targetDir = String.valueOf(cliArgs.next());
×
166
    Path path = Paths.get(targetDir);
×
167

168
    // If the given path is relative, resolve it relative to the current directory
169
    if (!path.isAbsolute()) {
×
170
      path = context.getCwd().resolve(targetDir).normalize();
×
171
    }
172

173
    if (context.getFileAccess().isExpectedFolder(path)) {
×
174
      context.setCwd(path, context.getWorkspaceName(), context.getIdeHome());
×
175
      return 0;
×
176
    } else {
177
      return 1;
×
178
    }
179
  }
180

181
  /**
182
   * @param argument the current {@link CliArgument} (position) to match.
183
   * @param commandlet the potential {@link Commandlet} to match.
184
   * @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}
185
   *     and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
186
   */
187
  private boolean apply(CliArgument argument, Commandlet commandlet) {
188

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