Skip to content

Commit 605d12c

Browse files
authored
Improve the path completion and tilde support in path (#275)
- Improve the path completion and tilde support in path - Make path completion better handle quotes and spaces in the path - Make path completion able to work for paths starting with `~` - Add support for tilde expansion in the path for `/code save <file-path>` - Update the minimal PowerShell version required for loading the `AIShell` module - Comment out the `/render` command, which is for debugging purposes.
1 parent d439a3e commit 605d12c

File tree

6 files changed

+107
-20
lines changed

6 files changed

+107
-20
lines changed

shell/AIShell.Integration/AIShell.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
CompanyName = 'Microsoft Corporation'
88
Copyright = '(c) Microsoft Corporation. All rights reserved.'
99
Description = 'Integration with the AIShell to provide intelligent shell experience'
10-
PowerShellVersion = '7.4'
10+
PowerShellVersion = '7.4.6'
1111
FunctionsToExport = @()
1212
CmdletsToExport = @('Start-AIShell','Invoke-AIShell','Resolve-Error')
1313
VariablesToExport = '*'

shell/AIShell.Kernel/Command/CodeCommand.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public CodeCommand()
2525
post.AddArgument(nth);
2626

2727
var append = new Option<bool>("--append", "Append to the end of the file.");
28-
var file = new Argument<FileInfo>("file", "The file path to save the code to.");
28+
var file = new Argument<string>("file", "The file path to save the code to.");
2929
save.AddArgument(file);
3030
save.AddOption(append);
3131

@@ -88,7 +88,7 @@ private void CopyAction(int nth)
8888
shell.OnUserAction(new CodePayload(UserAction.CodeCopy, code));
8989
}
9090

91-
private void SaveAction(FileInfo file, bool append)
91+
private void SaveAction(string path, bool append)
9292
{
9393
var shell = (Shell)Shell;
9494
var host = shell.Host;
@@ -100,6 +100,22 @@ private void SaveAction(FileInfo file, bool append)
100100
return;
101101
}
102102

103+
if (string.IsNullOrEmpty(path))
104+
{
105+
host.WriteErrorLine($"Please specify a file path for saving the code.");
106+
return;
107+
}
108+
109+
path = Utils.ResolveTilde(path);
110+
FileInfo file = new(path);
111+
112+
string fullName = file.FullName;
113+
if (Directory.Exists(fullName))
114+
{
115+
host.WriteErrorLine($"The specified path '{fullName}' points to an existing directory. Please specify a file path instead.");
116+
return;
117+
}
118+
103119
try
104120
{
105121
using FileStream stream = file.Open(append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None);

shell/AIShell.Kernel/Command/CommandRunner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal CommandRunner(Shell shell)
3535
new RefreshCommand(),
3636
new RetryCommand(),
3737
new HelpCommand(),
38-
new RenderCommand(),
38+
//new RenderCommand(),
3939
};
4040

4141
LoadCommands(buildin, Core);

shell/AIShell.Kernel/Command/RenderCommand.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,34 @@ internal sealed class RenderCommand : CommandBase
1010
public RenderCommand()
1111
: base("render", "Render a markdown file, for diagnosis purpose.")
1212
{
13-
var file = new Argument<FileInfo>("file", "The file path to save the code to.");
13+
var file = new Argument<string>("file", "The file path to save the code to.");
1414
var append = new Option<bool>("--streaming", "Render in the streaming manner.");
1515

1616
AddArgument(file);
1717
AddOption(append);
18-
this.SetHandler(SaveAction, file, append);
18+
this.SetHandler(RenderAction, file, append);
1919
}
2020

21-
private void SaveAction(FileInfo file, bool streaming)
21+
private void RenderAction(string path, bool streaming)
2222
{
2323
var host = Shell.Host;
2424

25+
if (string.IsNullOrEmpty(path))
26+
{
27+
host.WriteErrorLine($"Please specify a file path for rendering its content.");
28+
return;
29+
}
30+
31+
path = Utils.ResolveTilde(path);
32+
FileInfo file = new(path);
33+
34+
string fullName = file.FullName;
35+
if (Directory.Exists(fullName))
36+
{
37+
host.WriteErrorLine($"The specified path '{fullName}' points to an existing directory. Please specify a file path instead.");
38+
return;
39+
}
40+
2541
try
2642
{
2743
using FileStream stream = file.OpenRead();

shell/AIShell.Kernel/Utility/ReadLineHelper.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,42 @@ private List<CompletionResult> CompleteCommandName(string wildcard)
8888

8989
private List<CompletionResult> CompleteFileSystemPath(string wordToComplete)
9090
{
91-
if (!Path.IsPathFullyQualified(wordToComplete))
91+
bool startsWithTilde = false;
92+
bool alreadyQuoted = wordToComplete.Contains(' ');
93+
string homeDirectory = null;
94+
List<CompletionResult> result = null;
95+
96+
// Check if the path starts with tilde.
97+
static bool StartsWithTilde(string path)
98+
{
99+
return !string.IsNullOrEmpty(path)
100+
&& path.Length >= 2
101+
&& path[0] is '~'
102+
&& path[1] == Path.DirectorySeparatorChar;
103+
}
104+
105+
// Check if the path should be quoted.
106+
string QuoteIfNeeded(string path)
107+
{
108+
// Do not add quoting if the original string is already quoted.
109+
return !alreadyQuoted && path.Contains(' ') ? $"\"{path}\"" : path;
110+
}
111+
112+
// Add one result to the result list.
113+
void AddOneResult(string path, bool isContainer)
114+
{
115+
result ??= [];
116+
string filePath = startsWithTilde ? path.Replace(homeDirectory, "~") : path;
117+
string text = QuoteIfNeeded(filePath);
118+
119+
CompletionResultType resultType = isContainer
120+
? CompletionResultType.ProviderContainer
121+
: CompletionResultType.ProviderItem;
122+
result.Add(new CompletionResult(text, text, resultType, toolTip: null));
123+
}
124+
125+
if (!Path.IsPathFullyQualified(wordToComplete) &&
126+
(startsWithTilde = StartsWithTilde(wordToComplete)) is false)
92127
{
93128
return null;
94129
}
@@ -105,32 +140,28 @@ private List<CompletionResult> CompleteFileSystemPath(string wordToComplete)
105140
fileName = Path.GetFileName(wordToComplete) + "*";
106141
}
107142

143+
if (startsWithTilde)
144+
{
145+
rootPath = Utils.ResolveTilde(rootPath);
146+
homeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
147+
}
148+
108149
if (!Directory.Exists(rootPath))
109150
{
110151
return null;
111152
}
112153

113-
List<CompletionResult> result = null;
114154
foreach (string dir in Directory.EnumerateDirectories(rootPath, fileName, _enumerationOptions))
115155
{
116-
result ??= new();
117-
string text = QuoteIfNeeded(dir);
118-
result.Add(new CompletionResult(text, text, CompletionResultType.ProviderContainer, toolTip: null));
156+
AddOneResult(dir, isContainer: true);
119157
}
120158

121159
foreach (string file in Directory.EnumerateFiles(rootPath, fileName, _enumerationOptions))
122160
{
123-
result ??= new();
124-
string text = QuoteIfNeeded(file);
125-
result.Add(new CompletionResult(text, text, CompletionResultType.ProviderItem, toolTip: null));
161+
AddOneResult(file, isContainer: false);
126162
}
127163

128164
return result;
129-
130-
static string QuoteIfNeeded(string path)
131-
{
132-
return path.Contains(' ') ? $"\"{path}\"" : path;
133-
}
134165
}
135166

136167
/// <summary>

shell/AIShell.Kernel/Utility/Utils.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,30 @@ internal static JsonSerializerOptions GetJsonSerializerOptions()
8484
};
8585
}
8686

87+
/// <summary>
88+
/// Try resolving the leading tilde in a path.
89+
/// </summary>
90+
internal static string ResolveTilde(string path)
91+
{
92+
if (path.StartsWith('~'))
93+
{
94+
string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
95+
96+
if (path.Length is 1)
97+
{
98+
// The path is just '~'.
99+
return home;
100+
}
101+
102+
if (path[1] == Path.DirectorySeparatorChar)
103+
{
104+
return Path.Join(home, path.AsSpan(2));
105+
}
106+
}
107+
108+
return path;
109+
}
110+
87111
/// <summary>
88112
/// Check if the <paramref name="left"/> contains <paramref name="right"/> regardless of
89113
/// the leading and trailing space characters on each line if both are multi-line strings.

0 commit comments

Comments
 (0)