Skip to content

Commit a44bc19

Browse files
Feature: Added open in VS/VS Code to status bar (#12645)
1 parent d2a4a0b commit a44bc19

File tree

12 files changed

+276
-4
lines changed

12 files changed

+276
-4
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Files.App.Contexts;
5+
using Files.App.Shell;
6+
7+
namespace Files.App.Actions
8+
{
9+
internal sealed class OpenInVSAction : ObservableObject, IAction
10+
{
11+
private readonly IContentPageContext _context;
12+
13+
private readonly bool _isVSInstalled;
14+
15+
public string Label { get; } = "OpenInVS".GetLocalizedResource();
16+
17+
public string Description { get; } = "OpenInVSDescription".GetLocalizedResource();
18+
19+
public bool IsExecutable =>
20+
_isVSInstalled &&
21+
!string.IsNullOrWhiteSpace(_context.SolutionFilePath);
22+
23+
public OpenInVSAction()
24+
{
25+
_context = Ioc.Default.GetRequiredService<IContentPageContext>();
26+
27+
_isVSInstalled = SoftwareHelpers.IsVSInstalled();
28+
if (_isVSInstalled )
29+
_context.PropertyChanged += Context_PropertyChanged;
30+
}
31+
32+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
33+
{
34+
if (e.PropertyName == nameof(IContentPageContext.SolutionFilePath))
35+
OnPropertyChanged(nameof(IsExecutable));
36+
}
37+
38+
public Task ExecuteAsync()
39+
{
40+
Win32API.RunPowershellCommand($"start {_context.SolutionFilePath}", false);
41+
42+
return Task.CompletedTask;
43+
}
44+
}
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Files.App.Contexts;
5+
using Files.App.Shell;
6+
7+
namespace Files.App.Actions
8+
{
9+
internal sealed class OpenInVSCodeAction : ObservableObject, IAction
10+
{
11+
private readonly IContentPageContext _context;
12+
13+
private readonly bool _isVSCodeInstalled;
14+
15+
public string Label { get; } = "OpenInVSCode".GetLocalizedResource();
16+
17+
public string Description { get; } = "OpenInVSCodeDescription".GetLocalizedResource();
18+
19+
public bool IsExecutable =>
20+
_isVSCodeInstalled &&
21+
_context.Folder is not null;
22+
23+
public OpenInVSCodeAction()
24+
{
25+
_context = Ioc.Default.GetRequiredService<IContentPageContext>();
26+
27+
_isVSCodeInstalled = SoftwareHelpers.IsVSCodeInstalled();
28+
if (_isVSCodeInstalled)
29+
_context.PropertyChanged += Context_PropertyChanged;
30+
}
31+
32+
public Task ExecuteAsync()
33+
{
34+
Win32API.RunPowershellCommand($"code {_context.ShellPage?.FilesystemViewModel.WorkingDirectory}", false);
35+
36+
return Task.CompletedTask;
37+
}
38+
39+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
40+
{
41+
if (e.PropertyName == nameof(IContentPageContext.Folder))
42+
OnPropertyChanged(nameof(IsExecutable));
43+
}
44+
}
45+
}

src/Files.App/Commands/CommandCodes.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ public enum CommandCodes
9797
RotateRight,
9898

9999
// Open
100+
OpenInVS,
101+
OpenInVSCode,
100102
OpenProperties,
101103
OpenSettings,
102104
OpenTerminal,

src/Files.App/Commands/Manager/CommandManager.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public IRichCommand this[HotKey hotKey]
8585
public IRichCommand OpenItem => commands[CommandCodes.OpenItem];
8686
public IRichCommand OpenItemWithApplicationPicker => commands[CommandCodes.OpenItemWithApplicationPicker];
8787
public IRichCommand OpenParentFolder => commands[CommandCodes.OpenParentFolder];
88+
public IRichCommand OpenInVS => commands[CommandCodes.OpenInVS];
89+
public IRichCommand OpenInVSCode => commands[CommandCodes.OpenInVSCode];
8890
public IRichCommand OpenProperties => commands[CommandCodes.OpenProperties];
8991
public IRichCommand OpenSettings => commands[CommandCodes.OpenSettings];
9092
public IRichCommand OpenTerminal => commands[CommandCodes.OpenTerminal];
@@ -236,6 +238,8 @@ public CommandManager()
236238
[CommandCodes.OpenItem] = new OpenItemAction(),
237239
[CommandCodes.OpenItemWithApplicationPicker] = new OpenItemWithApplicationPickerAction(),
238240
[CommandCodes.OpenParentFolder] = new OpenParentFolderAction(),
241+
[CommandCodes.OpenInVS] = new OpenInVSAction(),
242+
[CommandCodes.OpenInVSCode] = new OpenInVSCodeAction(),
239243
[CommandCodes.OpenProperties] = new OpenPropertiesAction(),
240244
[CommandCodes.OpenSettings] = new OpenSettingsAction(),
241245
[CommandCodes.OpenTerminal] = new OpenTerminalAction(),

src/Files.App/Commands/Manager/ICommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ public interface ICommandManager : IEnumerable<IRichCommand>
8383
IRichCommand RotateLeft { get; }
8484
IRichCommand RotateRight { get; }
8585

86+
IRichCommand OpenInVS { get; }
87+
IRichCommand OpenInVSCode { get; }
8688
IRichCommand OpenProperties { get; }
8789
IRichCommand OpenSettings { get; }
8890
IRichCommand OpenTerminal { get; }

src/Files.App/Contexts/ContentPage/ContentPageContext.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ internal class ContentPageContext : ObservableObject, IContentPageContext
4949

5050
public bool ShowSearchUnindexedItemsMessage => ShellPage is not null && ShellPage.InstanceViewModel.ShowSearchUnindexedItemsMessage;
5151

52-
public bool CanExecuteGitAction => ShellPage is not null && ShellPage.InstanceViewModel.IsGitRepository && !GitHelpers.IsExecutingGitAction;
52+
public bool IsGitRepository => ShellPage is not null && ShellPage.InstanceViewModel.IsGitRepository;
53+
54+
public bool CanExecuteGitAction => IsGitRepository && !GitHelpers.IsExecutingGitAction;
55+
56+
public string? SolutionFilePath => ShellPage?.FilesystemViewModel.SolutionFilePath;
5357

5458
public ContentPageContext()
5559
{
@@ -150,6 +154,7 @@ private void InstanceViewModel_PropertyChanged(object? sender, PropertyChangedEv
150154
OnPropertyChanged(nameof(ShowSearchUnindexedItemsMessage));
151155
break;
152156
case nameof(CurrentInstanceViewModel.IsGitRepository):
157+
OnPropertyChanged(nameof(IsGitRepository));
153158
OnPropertyChanged(nameof(CanExecuteGitAction));
154159
break;
155160
}
@@ -175,8 +180,15 @@ private void ToolbarViewModel_PropertyChanged(object? sender, PropertyChangedEve
175180

176181
private void FilesystemViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
177182
{
178-
if (e.PropertyName is nameof(ItemViewModel.CurrentFolder))
179-
OnPropertyChanged(nameof(Folder));
183+
switch (e.PropertyName)
184+
{
185+
case nameof(ItemViewModel.CurrentFolder):
186+
OnPropertyChanged(nameof(Folder));
187+
break;
188+
case nameof(ItemViewModel.SolutionFilePath):
189+
OnPropertyChanged(nameof(SolutionFilePath));
190+
break;
191+
}
180192
}
181193

182194
private void Update()
@@ -194,6 +206,7 @@ private void Update()
194206
OnPropertyChanged(nameof(IsMultiPaneEnabled));
195207
OnPropertyChanged(nameof(IsMultiPaneActive));
196208
OnPropertyChanged(nameof(ShowSearchUnindexedItemsMessage));
209+
OnPropertyChanged(nameof(IsGitRepository));
197210
OnPropertyChanged(nameof(CanExecuteGitAction));
198211
}
199212

src/Files.App/Contexts/ContentPage/IContentPageContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public interface IContentPageContext : INotifyPropertyChanged
3232

3333
bool ShowSearchUnindexedItemsMessage { get; }
3434

35+
bool IsGitRepository { get; }
3536
bool CanExecuteGitAction { get; }
37+
38+
string? SolutionFilePath { get; }
3639
}
3740
}

src/Files.App/Data/Models/ItemViewModel.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
using Files.App.Helpers.FileListCache;
99
using Files.App.Shell;
1010
using Files.App.Storage.FtpStorage;
11-
using Files.App.UserControls;
1211
using Files.App.ViewModels.Previews;
12+
using Files.Backend.Helpers;
1313
using Files.Backend.Services;
1414
using Files.Backend.Services.SizeProvider;
1515
using Files.Backend.ViewModels.Dialogs;
@@ -76,6 +76,13 @@ public ListedItem? CurrentFolder
7676
private set => SetProperty(ref currentFolder, value);
7777
}
7878

79+
private string? _SolutionFilePath;
80+
public string? SolutionFilePath
81+
{
82+
get => _SolutionFilePath;
83+
private set => SetProperty(ref _SolutionFilePath, value);
84+
}
85+
7986
public CollectionViewSource viewSource;
8087

8188
private FileSystemWatcher watcher;
@@ -1732,6 +1739,8 @@ await Task.Run(async () =>
17321739
}, defaultIconPairs: DefaultIcons);
17331740

17341741
filesAndFolders.AddRange(fileList);
1742+
1743+
await dispatcherQueue.EnqueueOrInvokeAsync(CheckForSolutionFile, Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
17351744
await OrderFilesAndFoldersAsync();
17361745
await ApplyFilesAndFoldersChangesAsync();
17371746
});
@@ -1769,6 +1778,20 @@ private Task EnumFromStorageFolderAsync(string path, BaseStorageFolder? rootFold
17691778
}, cancellationToken);
17701779
}
17711780

1781+
private void CheckForSolutionFile()
1782+
{
1783+
for (int i = 0; i < filesAndFolders.Count; i++)
1784+
{
1785+
if (FileExtensionHelpers.HasExtension(filesAndFolders[i].FileExtension, ".sln"))
1786+
{
1787+
SolutionFilePath = filesAndFolders[i].ItemPath;
1788+
return;
1789+
}
1790+
}
1791+
1792+
SolutionFilePath = null;
1793+
}
1794+
17721795
private async Task<CloudDriveSyncStatus> CheckCloudDriveSyncStatusAsync(IStorageItem item)
17731796
{
17741797
int? syncStatus = null;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Microsoft.Win32;
5+
6+
namespace Files.App.Helpers
7+
{
8+
internal static class SoftwareHelpers
9+
{
10+
public static bool IsVSCodeInstalled()
11+
{
12+
string registryKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall";
13+
14+
var key = Registry.CurrentUser.OpenSubKey(registryKey);
15+
if (key is null)
16+
return false;
17+
18+
string? displayName;
19+
20+
foreach (var subKey in key.GetSubKeyNames().Select(key.OpenSubKey))
21+
{
22+
displayName = subKey?.GetValue("DisplayName") as string;
23+
if (!string.IsNullOrWhiteSpace(displayName) && displayName.StartsWith("Microsoft Visual Studio Code"))
24+
{
25+
key.Close();
26+
27+
return true;
28+
}
29+
}
30+
31+
key.Close();
32+
33+
return false;
34+
}
35+
36+
public static bool IsVSInstalled()
37+
{
38+
string registryKey = @"SOFTWARE\Microsoft\VisualStudio";
39+
40+
var key = Registry.LocalMachine.OpenSubKey(registryKey);
41+
if (key is null)
42+
return false;
43+
44+
key.Close();
45+
46+
return true;
47+
}
48+
}
49+
}

src/Files.App/ResourceDictionaries/PathIcons.xaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,42 @@
19481948
</Setter.Value>
19491949
</Setter>
19501950
</Style>
1951+
1952+
<Style x:Key="ColorIconOpen" TargetType="local:OpacityIcon">
1953+
<Setter Property="Template">
1954+
<Setter.Value>
1955+
<ControlTemplate>
1956+
<Viewbox
1957+
Width="16"
1958+
Height="16"
1959+
Stretch="Fill">
1960+
<Grid Width="16" Height="16">
1961+
<Path
1962+
x:Name="Path1"
1963+
Data="M8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2Z"
1964+
Fill="{ThemeResource IconAltBrush}" />
1965+
<Path
1966+
x:Name="Path2"
1967+
Data="M8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2ZM1 8C1 4.13401 4.13401 1 8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15C4.13401 15 1 11.866 1 8Z"
1968+
Fill="{ThemeResource IconBaseBrush}" />
1969+
<Path
1970+
x:Name="Path3"
1971+
Data="M10.9621 5.30861C10.938 5.25051 10.9026 5.19602 10.8557 5.14857C10.8543 5.14715 10.8528 5.14574 10.8514 5.14433C10.7611 5.05509 10.637 5 10.5 5H6.5C6.22386 5 6 5.22386 6 5.5C6 5.77614 6.22386 6 6.5 6H9.29289L5.14645 10.1464C4.95118 10.3417 4.95118 10.6583 5.14645 10.8536C5.34171 11.0488 5.65829 11.0488 5.85355 10.8536L10 6.70711V9.5C10 9.77614 10.2239 10 10.5 10C10.7761 10 11 9.77614 11 9.5V5.50035C11 5.49923 11 5.49812 11 5.497C10.9996 5.43287 10.987 5.3688 10.9621 5.30861Z"
1972+
Fill="{ThemeResource AccentFillColorDefaultBrush}" />
1973+
</Grid>
1974+
1975+
<VisualStateManager.VisualStateGroups>
1976+
<VisualStateGroup>
1977+
<VisualState x:Name="Normal" />
1978+
<VisualState x:Name="Disabled" />
1979+
<VisualState x:Name="Selected" />
1980+
</VisualStateGroup>
1981+
</VisualStateManager.VisualStateGroups>
1982+
</Viewbox>
1983+
</ControlTemplate>
1984+
</Setter.Value>
1985+
</Setter>
1986+
</Style>
19511987
</ResourceDictionary>
19521988
</ResourceDictionary.MergedDictionaries>
19531989
</ResourceDictionary>

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,4 +3314,16 @@
33143314
<data name="ManageBranches" xml:space="preserve">
33153315
<value>Manage branches</value>
33163316
</data>
3317+
<data name="OpenInVS" xml:space="preserve">
3318+
<value>Visual Studio</value>
3319+
</data>
3320+
<data name="OpenInVSDescription" xml:space="preserve">
3321+
<value>Open the current directory in Visual Studio</value>
3322+
</data>
3323+
<data name="OpenInVSCode" xml:space="preserve">
3324+
<value>VS Code</value>
3325+
</data>
3326+
<data name="OpenInVSCodeDescription" xml:space="preserve">
3327+
<value>Open the current directory in Visual Studio Code</value>
3328+
</data>
33173329
</root>

src/Files.App/UserControls/StatusBarControl.xaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,44 @@
145145
Orientation="Horizontal"
146146
Spacing="4">
147147

148+
<!-- Open in VS Code Button -->
149+
<Button
150+
x:Name="OpenInVSCodeButton"
151+
Height="24"
152+
Padding="8,0,8,0"
153+
VerticalAlignment="Center"
154+
x:Load="{x:Bind converters:MultiBooleanConverter.AndNotConvert(Commands.OpenInVSCode.IsExecutable, Commands.OpenInVS.IsExecutable), Mode=OneWay}"
155+
Background="Transparent"
156+
BorderBrush="Transparent"
157+
Command="{x:Bind Commands.OpenInVSCode}"
158+
ToolTipService.ToolTip="{x:Bind Commands.OpenInVSCode.LabelWithHotKey, Mode=OneWay}">
159+
<Button.Content>
160+
<StackPanel Orientation="Horizontal" Spacing="8">
161+
<usercontrols:OpacityIcon Style="{StaticResource ColorIconOpen}" />
162+
<TextBlock Text="{x:Bind Commands.OpenInVSCode.Label, Mode=OneWay}" />
163+
</StackPanel>
164+
</Button.Content>
165+
</Button>
166+
167+
<!-- Open in VS Button -->
168+
<Button
169+
x:Name="OpenInVSButton"
170+
Height="24"
171+
Padding="8,0,8,0"
172+
VerticalAlignment="Center"
173+
x:Load="{x:Bind Commands.OpenInVS.IsExecutable, Mode=OneWay}"
174+
Background="Transparent"
175+
BorderBrush="Transparent"
176+
Command="{x:Bind Commands.OpenInVS}"
177+
ToolTipService.ToolTip="{x:Bind Commands.OpenInVS.LabelWithHotKey, Mode=OneWay}">
178+
<Button.Content>
179+
<StackPanel Orientation="Horizontal" Spacing="8">
180+
<usercontrols:OpacityIcon Style="{StaticResource ColorIconOpen}" />
181+
<TextBlock Text="{x:Bind Commands.OpenInVS.Label, Mode=OneWay}" />
182+
</StackPanel>
183+
</Button.Content>
184+
</Button>
185+
148186
<!-- Pull Button -->
149187
<Button
150188
x:Name="GitPullButton"

0 commit comments

Comments
 (0)