diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Commands/OpenURLCommand.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Commands/OpenURLCommand.cs
new file mode 100644
index 0000000000..fd3f3a8f18
--- /dev/null
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Commands/OpenURLCommand.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CmdPal.Ext.WebSearch.Helpers;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
+
+namespace Microsoft.CmdPal.Ext.WebSearch.Commands;
+
+internal sealed partial class OpenURLCommand : InvokableCommand
+{
+ private readonly SettingsManager _settingsManager;
+
+ public string Url { get; internal set; } = string.Empty;
+
+ internal OpenURLCommand(string url, SettingsManager settingsManager)
+ {
+ Url = url;
+ BrowserInfo.UpdateIfTimePassed();
+ Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
+ Name = string.Empty;
+ _settingsManager = settingsManager;
+ }
+
+ public override CommandResult Invoke()
+ {
+ if (!ShellHelpers.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, $"{Url}"))
+ {
+ // TODO GH# 138 --> actually display feedback from the extension somewhere.
+ return CommandResult.KeepOpen();
+ }
+
+ return CommandResult.Dismiss();
+ }
+}
diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/FallbackOpenURLItem.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/FallbackOpenURLItem.cs
new file mode 100644
index 0000000000..721af2ec8e
--- /dev/null
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/FallbackOpenURLItem.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Globalization;
+using System.Text;
+using Microsoft.CmdPal.Ext.WebSearch.Commands;
+using Microsoft.CmdPal.Ext.WebSearch.Helpers;
+using Microsoft.CmdPal.Ext.WebSearch.Properties;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
+
+namespace Microsoft.CmdPal.Ext.WebSearch;
+
+internal sealed partial class FallbackOpenURLItem : FallbackCommandItem
+{
+ private readonly OpenURLCommand _executeItem;
+ private static readonly CompositeFormat PluginOpenURL = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open_url);
+ private static readonly CompositeFormat PluginOpenUrlInBrowser = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open_url_in_browser);
+
+ public FallbackOpenURLItem(SettingsManager settings)
+ : base(new OpenURLCommand(string.Empty, settings), string.Empty)
+ {
+ _executeItem = (OpenURLCommand)this.Command!;
+ Title = string.Empty;
+ _executeItem.Name = string.Empty;
+ Subtitle = string.Empty;
+ Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
+ }
+
+ public override void UpdateQuery(string query)
+ {
+ if (!IsValidUrl(query))
+ {
+ Title = string.Empty;
+ Subtitle = string.Empty;
+ return;
+ }
+
+ var success = Uri.TryCreate(query, UriKind.Absolute, out var uri);
+
+ // if url not contain schema, add http:// by default.
+ if (!success)
+ {
+ query = "https://" + query;
+ }
+
+ _executeItem.Url = query;
+ _executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.open_in_default_browser;
+
+ Title = string.Format(CultureInfo.CurrentCulture, PluginOpenURL, query);
+ Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpenUrlInBrowser, BrowserInfo.Name ?? BrowserInfo.MSEdgeName);
+ }
+
+ public static bool IsValidUrl(string url)
+ {
+ if (string.IsNullOrWhiteSpace(url))
+ {
+ return false;
+ }
+
+ if (!url.Contains('.', StringComparison.OrdinalIgnoreCase))
+ {
+ // eg: 'com', 'org'. We don't think it's a valid url.
+ // This can simplify the logic of checking if the url is valid.
+ return false;
+ }
+
+ if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
+ {
+ return true;
+ }
+
+ if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
+ !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) &&
+ !url.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) &&
+ !url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
+ {
+ if (Uri.IsWellFormedUriString("https://" + url, UriKind.Absolute))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.Designer.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.Designer.cs
index e138b74ecc..15a8c4631f 100644
--- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.Designer.cs
@@ -195,6 +195,24 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Open "{0}".
+ ///
+ public static string plugin_open_url {
+ get {
+ return ResourceManager.GetString("plugin_open_url", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open url in {0}.
+ ///
+ public static string plugin_open_url_in_browser {
+ get {
+ return ResourceManager.GetString("plugin_open_url_in_browser", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Failed to open {0}..
///
diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.resx b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.resx
index 9caaca6c2f..aec5b771c9 100644
--- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.resx
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.resx
@@ -163,6 +163,12 @@
Search the web in {0}
+
+ Open "{0}"
+
+
+ Open url in {0}
+
Failed to open {0}.
diff --git a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/WebSearchCommandsProvider.cs b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/WebSearchCommandsProvider.cs
index 7772d9b8b3..6768ff8baf 100644
--- a/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/WebSearchCommandsProvider.cs
+++ b/src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.WebSearch/WebSearchCommandsProvider.cs
@@ -14,6 +14,7 @@ public partial class WebSearchCommandsProvider : CommandProvider
{
private readonly SettingsManager _settingsManager = new();
private readonly FallbackExecuteSearchItem _fallbackItem;
+ private readonly FallbackOpenURLItem _openUrlFallbackItem;
public WebSearchCommandsProvider()
{
@@ -23,6 +24,7 @@ public partial class WebSearchCommandsProvider : CommandProvider
Settings = _settingsManager.Settings;
_fallbackItem = new FallbackExecuteSearchItem(_settingsManager);
+ _openUrlFallbackItem = new FallbackOpenURLItem(_settingsManager);
}
public override ICommandItem[] TopLevelCommands()
@@ -36,5 +38,5 @@ public partial class WebSearchCommandsProvider : CommandProvider
];
}
- public override IFallbackCommandItem[]? FallbackCommands() => [_fallbackItem];
+ public override IFallbackCommandItem[]? FallbackCommands() => [_openUrlFallbackItem, _fallbackItem];
}