From aa74ea23ce2bc432fd3633806f7bf979dfe18e9a Mon Sep 17 00:00:00 2001 From: MJ Gallego Date: Sat, 15 Apr 2023 16:04:55 +0200 Subject: [PATCH] Completion proposal support for complete jline parameter - to allow completing single argument with multiple tab clicks, for example file paths. - Backport #512 - Fixes #834 --- .../boot/CompleterAutoConfiguration.java | 2 +- .../shell/CompletionProposal.java | 16 ++++++++++ .../shell/standard/FileValueProvider.java | 30 +++++++++++-------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java index 2cd84499b..7cae04310 100644 --- a/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java +++ b/spring-shell-autoconfigure/src/main/java/org/springframework/shell/boot/CompleterAutoConfiguration.java @@ -63,7 +63,7 @@ public void complete(LineReader reader, ParsedLine line, List candida p.description(), null, null, - true) + p.complete()) ) .forEach(candidates::add); } diff --git a/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java b/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java index 449350800..6a13c0b91 100644 --- a/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java +++ b/spring-shell-core/src/main/java/org/springframework/shell/CompletionProposal.java @@ -49,6 +49,12 @@ public class CompletionProposal { */ private boolean dontQuote = false; + /** + * Whether the proposal cant be completed further. By setting complete to false then it will not append an space + * making it easier to continue tab completion + */ + private boolean complete = true; + public CompletionProposal(String value) { this.value = this.displayText = value; } @@ -89,6 +95,16 @@ public CompletionProposal category(String category) { return this; } + public CompletionProposal complete(boolean complete) { + this.complete = complete; + return this; + } + + public boolean complete() { + return complete; + } + + public CompletionProposal dontQuote(boolean dontQuote) { this.dontQuote = dontQuote; return this; diff --git a/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java b/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java index 0f7093412..3777da7b3 100644 --- a/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java +++ b/spring-shell-standard/src/main/java/org/springframework/shell/standard/FileValueProvider.java @@ -41,19 +41,23 @@ public class FileValueProvider implements ValueProvider { @Override public List complete(CompletionContext completionContext) { - String input = completionContext.currentWordUpToCursor(); - int lastSlash = input.lastIndexOf(File.separatorChar); - Path dir = lastSlash > -1 ? Paths.get(input.substring(0, lastSlash+1)) : Paths.get(""); - String prefix = input.substring(lastSlash + 1, input.length()); + String input = completionContext.currentWordUpToCursor(); + int lastSlash = input.lastIndexOf(File.separatorChar); + Path dir = lastSlash > -1 ? Paths.get(input.substring(0, lastSlash + 1)) : Paths.get(""); + String prefix = input.substring(lastSlash + 1); - try { - return Files - .find(dir, 1, (p, a) -> p.getFileName() != null && p.getFileName().toString().startsWith(prefix), - FOLLOW_LINKS) - .map(p -> new CompletionProposal(p.toString())) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + try { + return Files + .find(dir, 1, (p, a) -> p.getFileName() != null && p.getFileName().toString().startsWith(prefix), + FOLLOW_LINKS) + .map(p -> { + boolean directory = Files.isDirectory(p); + String value = p.toString() + (directory ? File.separatorChar : ""); + return new CompletionProposal(value).complete(!directory); + }) + .collect(Collectors.toList()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } }