2424import org .springframework .context .annotation .Primary ;
2525import org .springframework .shell .*;
2626import org .springframework .shell .command .CommandCatalog ;
27- import org .springframework .shell .command .CommandOption ;
28- import org .springframework .shell .command .CommandRegistration ;
29- import org .springframework .shell .completion .CompletionResolver ;
3027import org .springframework .shell .context .ShellContext ;
3128import org .springframework .shell .exit .ExitCodeMappings ;
3229import org .springframework .stereotype .Component ;
33- import org .springframework .util .StringUtils ;
3430
3531import java .util .ArrayList ;
3632import java .util .Collections ;
3733import java .util .List ;
38- import java .util .function .Function ;
3934import java .util .stream .Collectors ;
4035import java .util .stream .IntStream ;
41- import java .util .stream .Stream ;
4236
4337import static com .github .fonimus .ssh .shell .ExtendedInput .*;
4438import static com .github .fonimus .ssh .shell .SshShellCommandFactory .SSH_THREAD_CONTEXT ;
5246public class ExtendedShell extends Shell {
5347
5448 private final ResultHandlerService resultHandlerService ;
55- private final CommandCatalog commandRegistry ;
5649 private final List <String > postProcessorNames = new ArrayList <>();
5750
5851 /**
@@ -72,7 +65,6 @@ public ExtendedShell(
7265 ) {
7366 super (resultHandlerService , commandRegistry , terminal , shellContext , exitCodeMappings );
7467 this .resultHandlerService = resultHandlerService ;
75- this .commandRegistry = commandRegistry ;
7668 if (postProcessors != null ) {
7769 this .postProcessorNames .addAll (postProcessors .stream ().map (PostProcessor ::getName ).collect (Collectors .toList ()));
7870 }
@@ -158,53 +150,7 @@ public List<CompletionProposal> complete(CompletionContext context) {
158150 if (context .getWords ().contains ("|" )) {
159151 return postProcessorNames .stream ().map (CompletionProposal ::new ).collect (Collectors .toList ());
160152 }
161-
162- String prefix = context .upToCursor ();
163-
164- List <CompletionProposal > candidates = new ArrayList <>(duplicatedCommandsStartingWith (prefix ));
165-
166- String best = duplicatedFindLongestCommand (prefix );
167- if (best != null ) {
168- context = context .drop (best .split (" " ).length );
169- CommandRegistration registration = commandRegistry .getRegistrations ().get (best );
170- CompletionContext argsContext = context .commandRegistration (registration );
171-
172- final List <String > words = context .getWords ().stream ().filter (StringUtils ::hasText ).collect (Collectors .toList ());
173- String lastNotEmptyWord = words .isEmpty () ? null : words .get (words .size () - 1 );
174-
175- List <CommandOption > matchedArgOptions = new ArrayList <>();
176- if (lastNotEmptyWord != null ) {
177- // last word used instead of first to check if matching args
178- matchedArgOptions .addAll (duplicatedMatchOptions (registration .getOptions (), lastNotEmptyWord ));
179- }
180- if (matchedArgOptions .isEmpty ()) {
181- // only add command options if last word did not match option
182- for (CompletionResolver resolver : completionResolvers ) {
183- List <CompletionProposal > resolved = resolver .apply (argsContext );
184- candidates .addAll (resolved .stream ().filter (cp -> !words .contains (cp .value ())).collect (Collectors .toList ()));
185- }
186- // try to check if previous word before last word is an option and last word is not empty
187- String lastOption = words .isEmpty () || words .size () < 2 ? null : words .get (words .size () - 2 );
188- String lastWord = context .getWords ().isEmpty () ? null : context .getWords ().get (context .getWords ().size () - 1 );
189- if (lastOption != null && StringUtils .hasText (lastWord )) {
190- matchedArgOptions .addAll (duplicatedMatchOptions (registration .getOptions (), lastOption ));
191- }
192- }
193-
194- List <CompletionProposal > argProposals = matchedArgOptions .stream ()
195- .flatMap (o -> {
196- Function <CompletionContext , List <CompletionProposal >> completion = o .getCompletion ();
197- if (completion != null ) {
198- List <CompletionProposal > apply = completion .apply (argsContext .commandOption (o ));
199- return apply .stream ();
200- }
201- return Stream .empty ();
202- })
203- .collect (Collectors .toList ());
204-
205- candidates .addAll (argProposals );
206- }
207- return candidates ;
153+ return super .complete (context );
208154 }
209155
210156 private static boolean isKeyCharInList (List <String > strList ) {
@@ -216,87 +162,6 @@ private static boolean isKeyCharInList(List<String> strList) {
216162 return false ;
217163 }
218164
219- //---------------------------------
220- // Private methods from Shell
221- //---------------------------------
222-
223- private List <CommandOption > duplicatedMatchOptions (List <CommandOption > options , String arg ) {
224- List <CommandOption > matched = new ArrayList <>();
225- String trimmed = StringUtils .trimLeadingCharacter (arg , '-' );
226- int count = arg .length () - trimmed .length ();
227- if (count == 1 ) {
228- if (trimmed .length () == 1 ) {
229- Character trimmedChar = trimmed .charAt (0 );
230- options .stream ()
231- .filter (o -> {
232- for (Character sn : o .getShortNames ()) {
233- if (trimmedChar .equals (sn )) {
234- return true ;
235- }
236- }
237- return false ;
238- })
239- .findFirst ()
240- .ifPresent (matched ::add );
241- } else if (trimmed .length () > 1 ) {
242- trimmed .chars ().mapToObj (i -> (char ) i )
243- .forEach (c -> options .forEach (o -> {
244- for (Character sn : o .getShortNames ()) {
245- if (c .equals (sn )) {
246- matched .add (o );
247- }
248- }
249- }));
250- }
251- } else if (count == 2 ) {
252- options .stream ()
253- .filter (o -> {
254- for (String ln : o .getLongNames ()) {
255- if (trimmed .equals (ln )) {
256- return true ;
257- }
258- }
259- return false ;
260- })
261- .findFirst ()
262- .ifPresent (matched ::add );
263- }
264- return matched ;
265- }
266-
267- private List <CompletionProposal > duplicatedCommandsStartingWith (String prefix ) {
268- // Workaround for https://github.com/spring-projects/spring-shell/issues/150
269- // (sadly, this ties this class to JLine somehow)
270- int lastWordStart = prefix .lastIndexOf (' ' ) + 1 ;
271- return commandRegistry .getRegistrations ().entrySet ().stream ()
272- .filter (e -> e .getKey ().startsWith (prefix ))
273- .map (e -> {
274- String c = e .getKey ();
275- c = c .substring (lastWordStart );
276- return duplicatedToCommandProposal (c , e .getValue ());
277- })
278- .collect (Collectors .toList ());
279- }
280-
281- private CompletionProposal duplicatedToCommandProposal (String command , CommandRegistration registration ) {
282- return new CompletionProposal (command )
283- .dontQuote (true )
284- .category ("Available commands" )
285- .description (registration .getDescription ());
286- }
287-
288- /**
289- * Returns the longest command that can be matched as first word(s) in the given buffer.
290- *
291- * @return a valid command name, or {@literal null} if none matched
292- */
293- private String duplicatedFindLongestCommand (String prefix ) {
294- String result = commandRegistry .getRegistrations ().keySet ().stream ()
295- .filter (command -> prefix .equals (command ) || prefix .startsWith (command + " " ))
296- .reduce ("" , (c1 , c2 ) -> c1 .length () > c2 .length () ? c1 : c2 );
297- return "" .equals (result ) ? null : result ;
298- }
299-
300165 /**
301166 * Shell notifier interface
302167 */
0 commit comments