diff --git a/src/Spe/Client/Applications/PowerShellIse.cs b/src/Spe/Client/Applications/PowerShellIse.cs index 35cfa294..e2e2df86 100644 --- a/src/Spe/Client/Applications/PowerShellIse.cs +++ b/src/Spe/Client/Applications/PowerShellIse.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Specialized; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; +using System.Web.UI.WebControls; using Sitecore; using Sitecore.Configuration; using Sitecore.Data; @@ -11,6 +13,7 @@ using Sitecore.Data.Managers; using Sitecore.Diagnostics; using Sitecore.IO; +using Sitecore.Mvc.Extensions; using Sitecore.Resources; using Sitecore.Security; using Sitecore.Security.Accounts; @@ -32,6 +35,7 @@ using Spe.Core.Settings; using Spe.Core.Settings.Authorization; using Spe.Core.VersionDecoupling; +using Literal = Sitecore.Web.UI.HtmlControls.Literal; namespace Spe.Client.Applications { @@ -42,11 +46,12 @@ public class PowerShellIse : BaseForm, IHasCommandContext, IPowerShellRunner public const string DefaultLanguage = "CurrentLanguage"; protected Memo Editor; + protected Memo OpenedScripts; + protected Memo ScriptItemIdMemo; + protected Memo ScriptItemDbMemo; protected Literal Progress; protected Border ProgressOverlay; - protected Border Result; protected Border RibbonPanel; - protected Literal ScriptName; protected Border ScriptResult; protected Memo SelectionText; protected Memo Breakpoints; @@ -54,7 +59,8 @@ public class PowerShellIse : BaseForm, IHasCommandContext, IPowerShellRunner protected GridPanel ElevatedPanel; protected GridPanel ElevationBlockedPanel; protected Border InfoPanel; - + protected Tabstrip Tabs; + protected Border TabsPanel; public bool Debugging { get; set; } public bool InBreakpoint { get; set; } @@ -76,16 +82,32 @@ public string ParentFrameName set { ServerProperties["ParentFrameName"] = value; } } - public static string ScriptItemId + public string ScriptItemId { - get { return StringUtil.GetString(Context.ClientPage.ServerProperties["ItemID"]); } - set { Context.ClientPage.ServerProperties["ItemID"] = value; } + get { return ScriptItemIdMemo.Value; } + set + { + if (value.IsWhiteSpaceOrNull()) + { + Log.Error("ScriptItemId is null or empty", this); + } + + ScriptItemIdMemo.Value = value; + } } - public static string ScriptItemDb + public string ScriptItemDb { - get { return StringUtil.GetString(Context.ClientPage.ServerProperties["ItemDb"]); } - set { Context.ClientPage.ServerProperties["ItemDb"] = value; } + get { return ScriptItemDbMemo.Value; } + set + { + if (value.IsWhiteSpaceOrNull()) + { + Log.Error("ScriptItemDb is null or empty", this); + } + + ScriptItemDbMemo.Value = value; + } } public static string ContextItemId @@ -106,6 +128,12 @@ public static bool ScriptModified set { Context.ClientPage.ServerProperties["ScriptModified"] = value ? "1" : string.Empty; } } + public static int TabSequencer + { + get { return (int)(Context.ClientPage.ServerProperties["TabSequencer"] ?? 0); } + set { Context.ClientPage.ServerProperties["TabSequencer"] = value; } + } + public static bool UseContext { get @@ -115,7 +143,7 @@ public static bool UseContext set { Context.ClientPage.ServerProperties["UseContext"] = value ? string.Empty : "0"; } } - public static Item ScriptItem + public Item ScriptItem { get { @@ -161,7 +189,7 @@ public CommandContext GetCommandContext() { var itemNotNull = Sitecore.Client.CoreDatabase.GetItem("{FDD5B2D5-31BE-41C3-AA76-64E5CC63B187}"); // /sitecore/content/Applications/PowerShell/PowerShellIse/Ribbon - var context = new CommandContext {RibbonSourceUri = itemNotNull.Uri}; + var context = new CommandContext { RibbonSourceUri = itemNotNull.Uri }; return context; } @@ -173,7 +201,8 @@ public bool MonitorActive private static bool IsHackedParameter(string parameter) { - var xssCleanup = new Regex(@"]*>[\s\S]*?|]*>[\s\S]*?|"); + var xssCleanup = + new Regex(@"]*>[\s\S]*?|]*>[\s\S]*?|"); if (xssCleanup.IsMatch(parameter)) { return true; @@ -193,20 +222,23 @@ protected override void OnLoad(EventArgs e) } base.OnLoad(e); - + if (Monitor == null) { if (!Context.ClientPage.IsEvent) { - Monitor = new SpeJobMonitor {ID = "Monitor"}; + Monitor = new SpeJobMonitor { ID = "Monitor" }; Context.ClientPage.Controls.Add(Monitor); } else { - Monitor = (SpeJobMonitor) Context.ClientPage.FindControl("Monitor"); + Monitor = (SpeJobMonitor)Context.ClientPage.FindControl("Monitor"); } } + Monitor.JobFinished += MonitorOnJobFinished; + + Tabs.OnChange += TabsOnChange; if (Context.ClientPage.IsEvent) return; @@ -219,11 +251,11 @@ protected override void OnLoad(EventArgs e) var itemId = WebUtil.GetQueryString("id"); var itemDb = WebUtil.GetQueryString("db"); - if (itemId.Length > 0) + + if (!itemId.IsWhiteSpaceOrNull()) { ScriptItemId = itemId; ScriptItemDb = itemDb; - LoadItem(itemDb, itemId); } ContextItemDb = Context.ContentDatabase.Name; @@ -238,6 +270,12 @@ protected override void OnLoad(EventArgs e) UpdateRibbon(); } + private void TabsOnChange(object sender, EventArgs e) + { + var tab = Tabs.Controls.OfType().Skip(Tabs.Active).FirstOrDefault(); + SelectTabByIndex(Tabs.Active + 1); + } + public override void HandleMessage(Message message) { Error.AssertObject(message, "message"); @@ -261,6 +299,7 @@ public override void HandleMessage(Message message) ? Context.Language.Name : CurrentLanguage; } + Dispatcher.Dispatch(message, context); } @@ -289,6 +328,7 @@ protected void Open(ClientPipelineArgs args) if (obj != null) str = obj.ID.ToString(); } + var urlString = new UrlString(UIUtil.GetUri("control:PowerShellScriptBrowser")); urlString.Append("id", selected); urlString.Append("fo", str); @@ -310,14 +350,13 @@ protected void MruOpen(ClientPipelineArgs args) LoadItem(args.Parameters["db"], args.Parameters["id"]); } - + [HandleMessage("ise:changecontextaccount", true)] protected void SecurityChangeContextAccount(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); args.CarryResultToNextProcessor = false; args.AbortPipeline(); - //LoadItem(args.Parameters["db"], args.Parameters["id"]); } [HandleMessage("item:load", true)] @@ -339,10 +378,6 @@ protected void MruUpdate(Item scriptItem) var id = scriptItem.ID.ToString(); var name = scriptItem.Name; var icon = scriptItem[FieldIDs.Icon]; - var scriptName = scriptItem.Paths.Path.Substring(ApplicationSettings.ScriptLibraryPath.Length); - ScriptName.Text = scriptName; - SheerResponse.Eval($"spe.changeWindowTitle('{scriptName}', false);"); - UpdateStartbarTitle(icon, name); var mruMenu = ApplicationSettings.GetIseMruContainerItem(); var mruItems = mruMenu.Children; @@ -370,6 +405,7 @@ protected void MruUpdate(Item scriptItem) mruItem.Delete(); continue; } + if (!(mruItem["Message"].Contains(id))) { var item = mruItem; @@ -392,11 +428,8 @@ protected void NewScript(ClientPipelineArgs args) ScriptItemDb = string.Empty; Editor.Value = string.Empty; ScriptResult.Value = "
";
-            SheerResponse.Eval("spe.changeWindowTitle('Untitled', true);");
+            CreateNewTab(null);
             UpdateRibbon();
-
-            const string icon = "powershell/16x16/ise8.png";
-            UpdateStartbarTitle(icon, "Untitled");
         }
 
         [HandleMessage("ise:saveas", true)]
@@ -404,10 +437,11 @@ protected void SaveAs(ClientPipelineArgs args)
         {
             Assert.ArgumentNotNull(args, "args");
             args.Parameters["message"] = "ise:saveas";
-            if (!RequestSessionElevationEx(args,ApplicationNames.ItemSave,SessionElevationManager.SaveAction))
+            if (!RequestSessionElevationEx(args, ApplicationNames.ItemSave, SessionElevationManager.SaveAction))
             {
                 return;
             }
+
             if (args.IsPostBack)
             {
                 if (!args.HasResult)
@@ -437,12 +471,14 @@ protected void SaveAs(ClientPipelineArgs args)
                     if (obj != null)
                         str = obj.ID.ToString();
                 }
+
                 var urlString = new UrlString(UIUtil.GetUri("control:PowerShellScriptBrowser"));
                 urlString.Append("id", selected);
                 urlString.Append("fo", str);
                 urlString.Append("ro", root);
                 urlString.Append("he", Texts.PowerShellIse_SaveAs_Select_Script_Library);
-                urlString.Append("txt", Texts.PowerShellIse_SaveAs_Select_the_Library_that_you_want_to_save_your_script_to_);
+                urlString.Append("txt",
+                    Texts.PowerShellIse_SaveAs_Select_the_Library_that_you_want_to_save_your_script_to_);
                 urlString.Append("ic", icon);
                 urlString.Append("btn", Sitecore.Texts.SELECT);
                 SheerResponse.ShowModalDialog(urlString.ToString(), true);
@@ -465,12 +501,28 @@ protected void SaveItem(ClientPipelineArgs args)
                 {
                     return;
                 }
+
                 var scriptItem = ScriptItem;
                 if (scriptItem == null)
                     return;
                 scriptItem.Edit(
                     editArgs => { scriptItem.Fields[Templates.Script.Fields.ScriptBody].Value = Editor.Value; });
                 SheerResponse.Eval("spe.updateModificationFlag(true);");
+                var tabIndex = Tabs.Active + 1;
+                UpdateTabInfo(scriptItem, tabIndex);
+            }
+        }
+
+        [HandleMessage("ise:loadinitialscript", true)]
+        protected void LoadInitialScript(ClientPipelineArgs args)
+        {
+            if (ScriptItemId.Length > 0)
+            {
+                LoadItem(ScriptItemDb, ScriptItemId);
+            }
+            else
+            {
+                CreateNewTab(null);
             }
         }
 
@@ -495,9 +547,11 @@ private void LoadItem(string db, string id)
             if (scriptItem[Templates.Script.Fields.ScriptBody] != null)
             {
                 Editor.Value = scriptItem[Templates.Script.Fields.ScriptBody];
-                SheerResponse.Eval("spe.updateEditor();");
+                var createdNew = CreateNewTab(scriptItem);
+
                 ScriptItemId = scriptItem.ID.ToString();
                 ScriptItemDb = scriptItem.Database.Name;
+                SelectTabByIndex(Tabs.Controls.Count);
                 MruUpdate(scriptItem);
                 UpdateRibbon();
             }
@@ -507,6 +561,79 @@ private void LoadItem(string db, string id)
             }
         }
 
+        private bool CreateNewTab(Item scriptItem)
+        {
+            var itemEditing = scriptItem != null;
+            var path = itemEditing
+                ? scriptItem.Paths.Path.Substring(ApplicationSettings.ScriptLibraryPath.Length)
+                : String.Empty;
+            var tabIndex = 0;
+            var searchPath = $"{path}:";
+            var openedScript = OpenedScripts.Value.Split('\n').Select(line => line.Trim())
+                .FirstOrDefault(line => line.StartsWith(searchPath, StringComparison.OrdinalIgnoreCase));
+            var scriptAlreadyOpened = itemEditing && openedScript != null;
+            if (scriptAlreadyOpened)
+            {
+                tabIndex = int.Parse(openedScript.Split(':')[1]);
+            }
+            else
+            {
+                TabSequencer++;
+                SheerResponse.Eval($"spe.createEditor({TabSequencer});");
+                var newTab = new Tab
+                    { Header = $"{Tabs.Controls.Count + 1}", Active = true };
+                Tabs.Controls.Add(newTab);
+                tabIndex = Tabs.Controls.Count;
+                UpdateTabInfo(scriptItem, tabIndex);
+            }
+
+            SelectTabByIndex(tabIndex);
+            SheerResponse.Eval("spe.updateEditor();");
+            return !scriptAlreadyOpened;
+        }
+
+        private void SelectTabByIndex(int tabIndex)
+        {
+            if (tabIndex < 0)
+                return;
+
+            var tabIndexString = tabIndex.ToString();
+            Tabs.Controls.OfType().ForEach(tab => tab.Active = tab.Header == tabIndexString);
+            Tabs.Active = tabIndex - 1;
+            var tabsHtml = HtmlUtil.RenderControl(Tabs);
+            TabsPanel.InnerHtml = tabsHtml;
+
+            SheerResponse.Eval($"spe.changeTab({tabIndex});");
+        }
+
+        private void UpdateTabInfo(Item scriptItem, int tabIndex)
+        {
+            var itemEditing = scriptItem != null;
+            var path = itemEditing
+                ? scriptItem.Paths.Path.Substring(ApplicationSettings.ScriptLibraryPath.Length)
+                : String.Empty;
+
+            var title = itemEditing ? scriptItem.Name : $"Untitled{TabSequencer}";
+            var icon = itemEditing ? scriptItem[FieldIDs.Icon] : "powershell/16x16/ise8.png";
+            path = itemEditing ? path : title;
+            var id = itemEditing ? scriptItem.ID.ToString() : string.Empty;
+            var db = itemEditing ? scriptItem.Database.Name : string.Empty;
+
+            var builder = new ImageBuilder
+            {
+                Src = Images.GetThemedImageSource(icon, ImageDimension.id16x16),
+                Width = 16,
+                Height = 16,
+                Margin = "0px 8px 0px 0px",
+                Align = "middle"
+            };
+            var startbarHtml = $"{builder}{title} - ISE";
+
+            SheerResponse.Eval($"spe.changeTabDetails({tabIndex},'{path}', '{startbarHtml}', '{id}', '{db}');");
+            SelectTabByIndex(tabIndex);
+        }
+
+
         [HandleMessage("ise:run", true)]
         protected virtual void ClientExecute(ClientPipelineArgs args)
         {
@@ -527,6 +654,7 @@ protected virtual void ClientExecute(ClientPipelineArgs args)
                     {
                         scriptSession.SetItemLocationContext(ContextItem);
                     }
+
                     scriptSession.SetExecutedScript(ScriptItem);
                     scriptSession.ExecuteScriptPart(Editor.Value);
                     ClearOutput();
@@ -540,6 +668,7 @@ protected virtual void ClientExecute(ClientPipelineArgs args)
                     var error = ScriptSession.GetExceptionString(exc, ScriptSession.ExceptionStringFormat.Html);
                     PrintSessionUpdate($"
{error}
"); } + if (settings.SaveLastScript) { settings.Load(); @@ -573,7 +702,7 @@ protected virtual void JobExecuteSelection(ClientPipelineArgs args) protected virtual void JobExecuteScript(ClientPipelineArgs args, string scriptToExecute, bool debug) { if (!RequestSessionElevationEx(args, ApplicationNames.ISE, SessionElevationManager.ExecuteAction)) - { + { return; } @@ -602,8 +731,10 @@ protected virtual void JobExecuteScript(ClientPipelineArgs args, string scriptTo var bPoints = strBrPoints.Select(int.Parse); scriptSession.SetBreakpoints(bPoints); } + scriptToExecute = scriptSession.DebugFile; } + if (UseContext) { scriptSession.SetItemLocationContext(ContextItem); @@ -633,16 +764,17 @@ protected virtual void JobExecuteScript(ClientPipelineArgs args, string scriptTo "ScriptResult", string.Format( "
" + - ""+
-                    Texts.PowerShellIse_JobExecuteScript_Working+
+                    "<img src=" + - "
"+ - Texts.PowerShellIse_JobExecuteScript_Please_wait___0_+ + "
" + + Texts.PowerShellIse_JobExecuteScript_Please_wait___0_ + "
" + "
" + "
", executionMessage));
 
-            Context.ClientPage.ClientResponse.Eval("if(spe.preventCloseWhenRunning){spe.preventCloseWhenRunning(true);}");
+            Context.ClientPage.ClientResponse.Eval(
+                "if(spe.preventCloseWhenRunning){spe.preventCloseWhenRunning(true);}");
 
             scriptSession.Debugging = debug;
             Monitor.Start($"{DefaultSessionName}", "ISE", progressBoxRunner.Run,
@@ -721,7 +853,8 @@ private static void PrintSessionUpdate(string result)
         {
             if (!string.IsNullOrEmpty(result))
             {
-                var xssCleanup = new Regex(@"]*>[\s\S]*?|]*>[\s\S]*?|");
+                var xssCleanup =
+                    new Regex(@"]*>[\s\S]*?|]*>[\s\S]*?|");
                 if (xssCleanup.IsMatch(result))
                 {
                     result = xssCleanup.Replace(result, "
"); @@ -740,6 +873,7 @@ protected void ExecuteInternal(ScriptSession scriptSession, string script) { scriptSession.InitBreakpoints(); } + scriptSession.ExecuteScriptPart(script); } finally @@ -750,6 +884,7 @@ protected void ExecuteInternal(ScriptSession scriptSession, string script) scriptSession.DebugFile = string.Empty; scriptSession.ExecuteScriptPart("Get-PSBreakpoint | Remove-PSBreakpoint"); } + scriptSession.Debugging = false; scriptSession.Interactive = false; } @@ -772,6 +907,7 @@ protected virtual void JobAbort(ClientPipelineArgs args) ScriptRunning = false; UpdateRibbon(); } + Monitor.SessionID = string.Empty; ScriptRunning = false; } @@ -787,9 +923,11 @@ private void MonitorOnJobFinished(object sender, EventArgs eventArgs) if (result?.Exception != null) { - var error = ScriptSession.GetExceptionString(result.Exception, ScriptSession.ExceptionStringFormat.Html); + var error = ScriptSession.GetExceptionString(result.Exception, + ScriptSession.ExceptionStringFormat.Html); PrintSessionUpdate($"
{error}
"); } + SheerResponse.SetInnerHtml("PleaseWait", ""); ProgressOverlay.Visible = false; ScriptRunning = false; @@ -802,7 +940,8 @@ private void MonitorOnJobFinished(object sender, EventArgs eventArgs) protected virtual void UpdateProgress(ClientPipelineArgs args) { var showProgress = ScriptRunning && - !string.Equals(args.Parameters["RecordType"], "Completed", StringComparison.OrdinalIgnoreCase); + !string.Equals(args.Parameters["RecordType"], "Completed", + StringComparison.OrdinalIgnoreCase); ProgressOverlay.Visible = showProgress; var sb = new StringBuilder(); if (showProgress) @@ -824,9 +963,9 @@ protected virtual void UpdateProgress(ClientPipelineArgs args) { var secondsRemaining = int.Parse(args.Parameters["SecondsRemaining"]); if (secondsRemaining > -1) - sb.AppendFormat("

{0:c} "+ - Texts.PowerShellIse_UpdateProgress_remaining+ - ".

", + sb.AppendFormat("

{0:c} " + + Texts.PowerShellIse_UpdateProgress_remaining + + ".

", new TimeSpan(0, 0, secondsRemaining)); } @@ -847,6 +986,82 @@ protected void NotifyScriptModified(Message message) UpdateRibbon(); } + [HandleMessage("ise:closetab")] + protected void CloseTabInitiated(Message message) + { + bool.TryParse(message.Arguments["modified"], out var modified); + if (!int.TryParse(message.Arguments["index"], out var tabIndex)) + tabIndex = -1; + + if (!int.TryParse(message.Arguments["selectIndex"], out var previouslySelectedIndex)) + previouslySelectedIndex = -1; + + if (modified) + { + var parameters = new NameValueCollection + { + ["index"] = $"{tabIndex}", + ["message"] = + Texts.PowerShellIse_The_script_is_modified_Do_you_want_to_close_it_without_saving_the_changes, + ["selectIndex"] = $"{previouslySelectedIndex}" + }; + Context.ClientPage.Start(this, "CloseModifiedScript", parameters); + } + else + { + CloseTab(tabIndex, previouslySelectedIndex); + } + + UpdateRibbon(); + } + + protected void CloseModifiedScript(ClientPipelineArgs args) + { + if (args.IsPostBack) + { + if (args.Result == "yes") + { + if (!int.TryParse(args.Parameters["index"], out int index)) + { + index = -1; + } + + if (!int.TryParse(args.Parameters["selectIndex"], out int selectIndex)) + selectIndex = -1; + + CloseTab(index, selectIndex); + } + } + else + { + var index = int.Parse(args.Parameters["index"]); + SelectTabByIndex(index); + SheerResponse.Confirm(args.Parameters["message"]); + args.WaitForPostBack(); + } + } + + private void CloseTab(int index, int newSelectedIndex) + { + if (index < 0) + return; + var tab = Tabs.Controls.OfType().Skip(Tabs.Active).FirstOrDefault(); + if (tab != null) + { + Tabs.Controls.RemoveAt(Tabs.Controls.Count - 1); + } + + SheerResponse.Eval($"spe.closeScript({index});"); + if (Tabs.Controls.Count == 0) + { + CreateNewTab(null); + } + else + { + SelectTabByIndex(newSelectedIndex); + } + } + [HandleMessage("ise:updateribbon")] protected void UpdateRibbon(Message message) { @@ -858,7 +1073,7 @@ protected void UpdateRibbon(Message message) /// private void UpdateRibbon() { - var ribbon = new Ribbon {ID = "PowerShellRibbon"}; + var ribbon = new Ribbon { ID = "PowerShellRibbon" }; var item = ScriptItem; ribbon.CommandContext = new CommandContext(item); ribbon.ShowContextualTabs = false; @@ -890,10 +1105,10 @@ private void UpdateRibbon() ? Texts.PowerShellIse_UpdateRibbon_Single_execution : (sessionName == DefaultSessionName) ? Factory.GetDatabase("core") - .GetItem( - "/sitecore/content/Applications/PowerShell/PowerShellIse/Menus/Sessions/ISE editing session") - ? - .DisplayName ?? DefaultSessionName + .GetItem( + "/sitecore/content/Applications/PowerShell/PowerShellIse/Menus/Sessions/ISE editing session") + ? + .DisplayName ?? DefaultSessionName : sessionName; var obj2 = Context.Database.GetItem("/sitecore/content/Applications/PowerShell/PowerShellIse/Ribbon"); Error.AssertItemFound(obj2, "/sitecore/content/Applications/PowerShell/PowerShellIse/Ribbon"); @@ -915,20 +1130,6 @@ private void UpdateRibbon() UpdateWarning(); } - private void UpdateStartbarTitle(string icon, string title) - { - var builder = new ImageBuilder - { - Src = Images.GetThemedImageSource(icon, ImageDimension.id16x16), - Width = 16, - Height = 16, - Margin = "0px 8px 0px 0px", - Align = "middle" - }; - var startbarTitle = $"{builder}{title}"; - SheerResponse.Eval($"spe.changeStartbarTitle('{startbarTitle}');"); - } - private void UpdateWarning(string updateFromMessage = "") { var isSessionElevated = SessionElevationManager.IsSessionTokenElevated(ApplicationNames.ISE); @@ -961,6 +1162,7 @@ private void UpdateWarning(string updateFromMessage = "") controlContent = HtmlUtil.RenderControl(ElevationRequiredPanel); } } + break; case SessionElevationManager.TokenDefinition.ElevationAction.Block: controlContent = HtmlUtil.RenderControl(ElevationBlockedPanel); @@ -970,7 +1172,6 @@ private void UpdateWarning(string updateFromMessage = "") InfoPanel.InnerHtml = controlContent; InfoPanel.Visible = !hidePanel; SheerResponse.Eval($"spe.showInfoPanel({(!hidePanel).ToString().ToLower()}, '{updateFromMessage}');"); - } [HandleMessage("item:updated", true)] @@ -984,10 +1185,11 @@ protected void SetCurrentSessionId(ClientPipelineArgs args) { Assert.ArgumentNotNull(args, "args"); var sessionId = args.Parameters["id"]; - if(IsHackedParameter(sessionId)) + if (IsHackedParameter(sessionId)) { return; } + CurrentSessionId = sessionId; SheerResponse.Eval($"spe.changeSessionId('{sessionId}');"); UpdateRibbon(); @@ -1002,6 +1204,7 @@ protected void SetCurrentLanguage(ClientPipelineArgs args) { return; } + CurrentLanguage = language; new LanguageHistory().Add(language); UpdateRibbon(); @@ -1016,6 +1219,7 @@ protected void SetCurrentUser(ClientPipelineArgs args) { return; } + CurrentUser = user; new UserHistory().Add(user); UpdateRibbon(); @@ -1040,6 +1244,7 @@ protected void SetContextItem(ClientPipelineArgs args) ContextItemDb = contextDb; ContextItemId = contextId; } + UpdateRibbon(); } @@ -1050,7 +1255,10 @@ protected virtual void UpdateSettings(ClientPipelineArgs args) var backgroundColor = OutputLine.ProcessHtmlColor(settings.BackgroundColor); var bottomPadding = CurrentVersion.IsAtLeast(SitecoreVersion.V80) ? 0 : 10; SheerResponse.Eval( - $"spe.changeSettings('{settings.FontFamilyStyle}', {settings.FontSize}, '{backgroundColor}', {bottomPadding}, {settings.LiveAutocompletion.ToString().ToLower()});"); + $"spe.changeSettings('{settings.FontFamilyStyle}', {settings.FontSize}, " + + $"'{backgroundColor}', {bottomPadding}," + + $" {settings.LiveAutocompletion.ToString().ToLower()}," + + $" {settings.PerTabOutput.ToString().ToLower()});"); } [HandleMessage("ise:setbreakpoint", true)] @@ -1158,6 +1366,7 @@ public void SessionElevationPipeline(ClientPipelineArgs args) { message = args.Parameters["message"] + "(elevationResult=1)"; } + UpdateWarning(message); } } @@ -1180,7 +1389,7 @@ private bool RequestSessionElevationEx(ClientPipelineArgs args, string appName, var pipelineArgs = new ClientPipelineArgs { - Parameters = {["message"] = args.Parameters["message"], ["app"] = appName, ["action"] = action} + Parameters = { ["message"] = args.Parameters["message"], ["app"] = appName, ["action"] = action } }; Context.ClientPage.Start(this, nameof(SessionElevationPipeline), pipelineArgs); return false; diff --git a/src/Spe/Core/Settings/ApplicationSettings.cs b/src/Spe/Core/Settings/ApplicationSettings.cs index 51c0db08..d73f8320 100644 --- a/src/Spe/Core/Settings/ApplicationSettings.cs +++ b/src/Spe/Core/Settings/ApplicationSettings.cs @@ -30,6 +30,7 @@ public class ApplicationSettings private const string LastScriptSettingFieldName = "LastScript"; private const string SaveLastScriptSettingFieldName = "SaveLastScript"; private const string LiveAutocompletionSettingFieldName = "LiveAutocompletion"; + private const string PerTabOutputSettingFieldName = "PerTabOutput"; private const string HostWidthSettingFieldName = "HostWidth"; private const string ForegroundColorSettingFieldName = "ForegroundColor"; private const string BackgroundColorSettingFieldName = "BackgroundColor"; @@ -105,6 +106,7 @@ public string FontFamilyStyle } public bool LiveAutocompletion { get; set; } + public bool PerTabOutput { get; set; } private string AppSettingsPath => SettingsItemPath + ApplicationName + "/"; private string CurrentUserSettingsPath => AppSettingsPath + CurrentDomain + "/" + CurrentUserName; @@ -229,7 +231,8 @@ public void Save() { configuration[LastScriptSettingFieldName] = HttpUtility.HtmlEncode(LastScript); ((CheckboxField) configuration.Fields[SaveLastScriptSettingFieldName]).Checked = SaveLastScript; - ((CheckboxField)configuration.Fields[LiveAutocompletionSettingFieldName]).Checked = LiveAutocompletion; + ((CheckboxField)configuration.Fields[LiveAutocompletionSettingFieldName]).Checked = LiveAutocompletion; + ((CheckboxField)configuration.Fields[PerTabOutputSettingFieldName]).Checked = PerTabOutput; configuration[HostWidthSettingFieldName] = HostWidth.ToString(CultureInfo.InvariantCulture); configuration[ForegroundColorSettingFieldName] = ForegroundColor.ToString(); configuration[BackgroundColorSettingFieldName] = BackgroundColor.ToString(); @@ -257,6 +260,9 @@ internal void Load() LiveAutocompletion = TryGetSettingValue(LiveAutocompletionSettingFieldName, false, () => ((CheckboxField) configuration.Fields[LiveAutocompletionSettingFieldName]).Checked); + PerTabOutput = + TryGetSettingValue(PerTabOutputSettingFieldName, false, + () => ((CheckboxField) configuration.Fields[PerTabOutputSettingFieldName]).Checked); HostWidth = TryGetSettingValue(HostWidthSettingFieldName,150, () => int.TryParse(configuration[HostWidthSettingFieldName], out var hostWidth) ? hostWidth : 150); @@ -297,6 +303,7 @@ private void SetToDefault() LastScript = string.Empty; SaveLastScript = true; LiveAutocompletion = false; + PerTabOutput = false; HostWidth = 150; ForegroundColor = ConsoleColor.White; BackgroundColor = ConsoleColor.DarkBlue; diff --git a/src/Spe/Texts.cs b/src/Spe/Texts.cs index 25ab4835..4c13109d 100644 --- a/src/Spe/Texts.cs +++ b/src/Spe/Texts.cs @@ -26,7 +26,8 @@ public class Texts public const string PowerShellIse_JobExecuteScript_Working = "Working"; public const string PowerShellIse_LoadItem_The_item_is_not_a_script_ = "The item is not a script."; public const string PowerShellIse_Open_Open_Script = "Open Script"; - + public const string PowerShellIse_The_script_is_modified_Do_you_want_to_close_it_without_saving_the_changes = "The script is modified. Do you want to close it without saving the changes?"; + public const string PowerShellIse_Open_Select_the_script_item_that_you_want_to_open_ = "Select the script item that you want to open."; diff --git a/src/Spe/sitecore modules/PowerShell/Assets/TabClose.png b/src/Spe/sitecore modules/PowerShell/Assets/TabClose.png new file mode 100644 index 00000000..1c1dac4e Binary files /dev/null and b/src/Spe/sitecore modules/PowerShell/Assets/TabClose.png differ diff --git a/src/Spe/sitecore modules/PowerShell/Scripts/ise.js b/src/Spe/sitecore modules/PowerShell/Scripts/ise.js index e029293c..de2679ed 100644 --- a/src/Spe/sitecore modules/PowerShell/Scripts/ise.js +++ b/src/Spe/sitecore modules/PowerShell/Scripts/ise.js @@ -1,5 +1,5 @@ -(function($, window, spe, ace, undefined) { - $(function() { +(function ($, window, spe, ace, undefined) { + $(function () { var tips = [ "You can press Ctrl+Space to show the Auto Suggest drop down that will show you all the matching comands/parameters/files depending on your caret position", "You can show help by pressing Ctrl+Enter for the closest command to the left of the cursor.", @@ -18,9 +18,27 @@ "You can search for keywords using the Ctrl+F hotkey.", "You can toggle a comment block using the Ctrl+Shift+/ hotkey.", "You can toggle a comment using the Ctrl+/ hotkey.", - "You can find more documentation in the Sitecore PowerShell Extensions book." + "You can find more documentation in the Sitecore PowerShell Extensions book." ]; + class EditorSession { + constructor(selectedText, editor, name, editorContainerId, path, index) { + this.index = index; // int: sort order + this.editor = editor; // string: the ace editor + this.name = name; // string: Script name + this.editorContainerId = editorContainerId; + this.path = path; // string: path + this.selectedText = selectedText; // string: selected text + this.startBarHtml = ""; // string: start bar html + this.tabTitleInnerHTML = ""; // string: tab title inner html + this.windowTitleInnerHTML = ""; // string: window title inner html + this.isModified = false; // bool: is modified + this.initialAssignment = true; // bool: initial assignment + this.breakpoints = ""; // string: breakpoints + this.results = ""; // string: Script execution results + } + } + var TokenTooltip = ace.require("tooltip").TokenTooltip; var guid = "ISE_Editing_Session"; const speVariablesCacheKey = "spe::variables"; @@ -30,35 +48,34 @@ var resultsBottomOffset = 10; var typingTimer; var resultsVisibilityIntent = true; - - var editor = $($("#Editor")[0]); - editor.hide(); + var editorSessions = []; + var editorFontFamily = "Monaco"; + var editorFontSize = 12; + var editorLiveAutocompletion = false; + var currentEditorIndex = 1; + var currentEditorSession; + var currentAceEditor; + var previousIndex = 1; + var perTabResults = true; + var editor = $($("#Editor")[0]); + var openedScriptsMemo = $($("#OpenedScripts")[0]); + var selectionTextMemo = $($("#SelectionText")[0]); + var breakpointsMemo = $($("#Breakpoints")[0]); + var scriptItemIdMemo = $($("#ScriptItemIdMemo")[0]); + var scriptItemDbMemo = $($("#ScriptItemDbMemo")[0]); // Setup the ace code editor. - var codeeditor = ace.edit("CodeEditor"); - codeeditor.setTheme("ace/theme/powershellise"); - codeeditor.session.setMode("ace/mode/powershell"); - codeeditor.setShowPrintMargin(false); - codeeditor.session.setValue(editor.val()); - codeeditor.session.on("change", function () { - editor.val(codeeditor.session.getValue()); - }); - codeeditor.tokenTooltip = new TokenTooltip(codeeditor); - - addProxy(codeeditor, "onPaste", function () { - spe.updateRibbon(); - }); addProxy(scForm, "invoke", function (args) { if (args[0] === "ise:immediatewindow") { clearVariablesCache(); } }); - + function registerEventListenersForRibbonButtons() { [].forEach.call(document.querySelectorAll('.scRibbonToolbarSmallGalleryButton, .scRibbonToolbarLargeComboButtonBottom'), function (div) { div.addEventListener("click", - function() { + function () { clearTimeout(typingTimer); }); }); @@ -71,7 +88,7 @@ } registerEventListenersForRibbonButtons(); - + window.parent.focus(); window.focus(); @@ -84,32 +101,40 @@ }; } - function setFocusOnConsole() { - $("body").focus(); - $(codeeditor).focus(); - ("WebForm_AutoFocus" in this) && WebForm_AutoFocus && WebForm_AutoFocus("CodeEditor"); - } - - window.addEventListener("focus", function(event) { + window.addEventListener("focus", function (event) { setFocusOnConsole(); }, false); function getQueryStringValue(key) { key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); - var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)")); + var match = location.search.match(new RegExp("[?&]" + key + "=([^&]+)(&|$)")); return match && decodeURIComponent(match[1].replace(/\+/g, " ")); } - if(getQueryStringValue("sc_bw") === "1"){ - //$("#RibbonPanel").css("padding-top","50px"); - $("#Wrapper").css("padding-top","0px"); + if (getQueryStringValue("sc_bw") === "1") { + $("#Wrapper").css("padding-top", "0px"); + } + + function hasSessionWithIndex(targetIndex) { + return editorSessions.some(session => session.index === targetIndex); + } + + function getSessionByIndex(index) { + // Find the session with the specified index + return editorSessions.find(session => session.index === index); } - setTimeout(setFocusOnConsole, 1000); + function setFocusOnConsole() { + $("body").focus(); + $(currentAceEditor).focus(); + ("WebForm_AutoFocus" in this) && WebForm_AutoFocus && WebForm_AutoFocus("CodeEditor"); + } + + setTimeout(setFocusOnConsole, 1000); spe.updateRibbon = function () { - if (!codeeditor.getReadOnly()) { - scForm.postRequest("", "", "", "ise:scriptchanged(modified=" + !codeeditor.session.getUndoManager().isClean() + ")"); + if (!currentAceEditor.getReadOnly()) { + scForm.postRequest("", "", "", "ise:scriptchanged(modified=" + !currentEditorSession.isModified + ")"); registerEventListenersForRibbonButtons(); } }; @@ -125,249 +150,343 @@ var posx = $("#PosX"); var posy = $("#PosY"); - $("#CodeEditor").on("keyup mousedown", function() { - var position = codeeditor.getCursorPosition(); - posx.text(position.column); - posy.text((position.row + 1)); - spe.updateRibbonNeeded(); - }); - $("#CodeEditor").on("keyup mouseup", function() { - var range = codeeditor.getSelectionRange(); - $("#SelectionText")[0].value = codeeditor.session.getTextRange(range); - }); + spe.updateModificationFlag = function (clear) { + + if (currentEditorSession.initialAssignment) { + currentEditorSession.initialAssignment = false; + } else + if (clear === currentEditorSession.isModified) { + currentEditorSession.isModified = !clear; + spe.applyWindowTitle(currentEditorIndex); + } + }; + + spe.changeTab = function (index) { + + if (!hasSessionWithIndex(index)) { + return; + } + + previousIndex = currentEditorIndex; + currentEditorIndex = index; + currentEditorSession = getSessionByIndex(currentEditorIndex); + currentAceEditor = currentEditorSession.editor; + clearVariablesCache(); + editor.val(currentAceEditor.session.getValue()); + selectionTextMemo.val(currentEditorSession.selectedText); + breakpointsMemo.val(currentEditorSession.breakpoints); + scriptItemIdMemo.val(currentEditorSession.scriptId); + scriptItemDbMemo.val(currentEditorSession.scriptDb); + spe.applyWindowTitle(currentEditorIndex); + + editorSessions.forEach(function (editorSession) { + if (editorSession.index === index) { + $("#" + editorSession.editorContainerId).show(); + } else { + $("#" + editorSession.editorContainerId).hide(); + } + }); + + if(perTabResults) { + $("#ScriptResultCode").text(""); + $("#ScriptResultCode").append(currentEditorSession.results); + $("#Result").scrollTop($("#Result")[0].scrollHeight); + } + } + + spe.createEditor = function (index) { + previousIndex = currentEditorIndex; + + currentEditorIndex = editorSessions.length + 1; + + var editorContainerId = "CodeEditor" + index; + $("#CodeEditors").append("
"); + currentAceEditor = ace.edit(editorContainerId); - $("#CopyResultsToClipboard").on("click", function() { - clipboard.copy(spe.getOutput()); - }); + const newSession = new EditorSession("", currentAceEditor, "", editorContainerId, "", currentEditorIndex); + editorSessions.push(newSession); + currentEditorSession = newSession; - ace.config.loadModule("ace/ext/emmet", function() { - ace.require("ace/lib/net").loadScript("/sitecore modules/PowerShell/Scripts/ace/emmet-core/emmet.js", function() { - codeeditor.setOption("enableEmmet", true); + currentAceEditor.setTheme("ace/theme/powershellise"); + currentAceEditor.session.setMode("ace/mode/powershell"); + currentAceEditor.setShowPrintMargin(false); + currentAceEditor.session.setValue(editor.val()); + currentAceEditor.session.on("change", function () { + editor.val(currentAceEditor.session.getValue()); + }); + currentAceEditor.tokenTooltip = new TokenTooltip(currentAceEditor); + currentAceEditor.setOption("fontFamily", editorFontFamily); + currentAceEditor.setOption("fontSize", editorFontSize); + currentAceEditor.setOptions({ + enableLiveAutocompletion: editorLiveAutocompletion }); - codeeditor.setOptions({ - enableSnippets: true, - enableBasicAutocompletion: true + addProxy(currentAceEditor, "onPaste", function () { + spe.updateRibbon(); }); - }); - ace.config.loadModule("ace/ext/language_tools", function(module) { - codeeditor.setOptions({ - enableSnippets: true, - enableBasicAutocompletion: true + $("#CodeEditor" + currentEditorIndex).on("keyup mousedown", function () { + var position = currentAceEditor.getCursorPosition(); + posx.text(position.column); + posy.text((position.row + 1)); + spe.updateRibbonNeeded(); }); - var keyWordCompleter = { - insertMatch: function(editor) { + $("#CodeEditor" + currentEditorIndex).on("keyup mouseup", function () { + var range = currentAceEditor.getSelectionRange(); + currentEditorSession.selectedText = currentAceEditor.session.getTextRange(range); + selectionTextMemo.val(currentEditorSession.selectedText); + }); - var data = editor.completer.popup.getData(editor.completer.popup.getRow()); + ace.config.loadModule("ace/ext/emmet", function () { + ace.require("ace/lib/net").loadScript("/sitecore modules/PowerShell/Scripts/ace/emmet-core/emmet.js", function () { + currentAceEditor.setOption("enableEmmet", true); + }); - var ranges = editor.selection.getAllRanges(); - for (var i = 0, range; range = ranges[i]; i++) { - if (data.meta === "Signature") { - data.value = data.fullValue; - } else if (data.meta === "Type") { - while (range.start.column > 0 && codeeditor.session.getTextRange(range).lastIndexOf("[") !== 0) { - range.start.column--; - } - range.start.column++; - data.value = data.fullValue; - } else if (data.meta === "Item" || data.meta === "ProviderItem" || data.meta === "ProviderContainer") { - range.start.column = data.position; - data.value = data.fullValue; - - //try trim prefix quotes - range.start.column--; - range.end.column++; - var replacedText = codeeditor.session.getTextRange(range); - var charStart = replacedText.charAt(0); - var charEnd = replacedText.charAt(replacedText.length-1); - if (charStart !== '"' && charStart !== "'") { + currentAceEditor.setOptions({ + enableSnippets: true, + enableBasicAutocompletion: true + }); + }); + + ace.config.loadModule("ace/ext/language_tools", function (module) { + currentAceEditor.setOptions({ + enableSnippets: true, + enableBasicAutocompletion: true + }); + + var keyWordCompleter = { + insertMatch: function (editor) { + + var data = editor.completer.popup.getData(editor.completer.popup.getRow()); + + var ranges = editor.selection.getAllRanges(); + for (var i = 0, range; range = ranges[i]; i++) { + if (data.meta === "Signature") { + data.value = data.fullValue; + } else if (data.meta === "Type") { + while (range.start.column > 0 && currentAceEditor.session.getTextRange(range).lastIndexOf("[") !== 0) { + range.start.column--; + } range.start.column++; - } + data.value = data.fullValue; + } else if (data.meta === "Item" || data.meta === "ProviderItem" || data.meta === "ProviderContainer") { + range.start.column = data.position; + data.value = data.fullValue; - //try trim trailing quotes - if (charEnd !== '"' && charEnd !== "'") { - range.end.column--; + //try trim prefix quotes + range.start.column--; + range.end.column++; + var replacedText = currentAceEditor.session.getTextRange(range); + var charStart = replacedText.charAt(0); + var charEnd = replacedText.charAt(replacedText.length - 1); + if (charStart !== '"' && charStart !== "'") { + range.start.column++; + } + + //try trim trailing quotes + if (charEnd !== '"' && charEnd !== "'") { + range.end.column--; + } + } else { + range.start.column -= editor.completer.completions.filterText.length; } - } else { - range.start.column -= editor.completer.completions.filterText.length; + editor.session.remove(range); } - editor.session.remove(range); - } - editor.execCommand("insertstring", data.value || data); - $.lastPrefix = ""; + editor.execCommand("insertstring", data.value || data); + $.lastPrefix = ""; - }, - getCompletions: function(editor, session, pos, prefix, callback) { - session.$mode.$keywordList = []; + }, + getCompletions: function (editor, session, pos, prefix, callback) { + session.$mode.$keywordList = []; - var range = codeeditor.getSelectionRange(); - range.start.column = 0; - var line = codeeditor.session.getTextRange(range); + var range = currentAceEditor.getSelectionRange(); + range.start.column = 0; + var line = currentAceEditor.session.getTextRange(range); - if (line) { + if (line) { - if (!$.tabCompletions || !$.lastPrefix || $.lastPrefix.length === 0 || prefix.indexOf($.lastPrefix) !== 0) { - $.lastPrefix = prefix; - _getTabCompletions(line); + if (!$.tabCompletions || !$.lastPrefix || $.lastPrefix.length === 0 || prefix.indexOf($.lastPrefix) !== 0) { + $.lastPrefix = prefix; + _getTabCompletions(line); + } + } else { + $.tabCompletions = [""]; } - } else { - $.tabCompletions = [""]; - } - var keywords = $.tabCompletions; + var keywords = $.tabCompletions; - if (keywords && keywords.length > 0 && keywords[0].indexOf("Signature", 0) === 0) { + if (keywords && keywords.length > 0 && keywords[0].indexOf("Signature", 0) === 0) { - callback(null, []); - $.tabCompletions = null; + callback(null, []); + $.tabCompletions = null; + + var msgType = "information"; + if (keywords.length === 1 && keywords[0].indexOf("not found in session", 0) > 0) { + msgType = "error"; + } - var msgType = "information"; - if (keywords.length === 1 && keywords[0].indexOf("not found in session", 0) > 0) { - msgType = "error"; + session.setAnnotations(keywords.map(function (word) { + var hint = word.split("|"); + return { + row: pos.row, + column: pos.column, + text: hint[3], + type: msgType // error, warning or information + }; + })); + ace.config.loadModule("ace/ext/error_marker", function (module) { + module.showErrorMarker(currentAceEditor, -1); + }); + return; } - session.setAnnotations(keywords.map(function (word) { + var psCompleter = this; + callback(null, keywords.map(function (word) { var hint = word.split("|"); return { - row: pos.row, - column: pos.column, - text: hint[3], - type: msgType // error, warning or information + name: hint[1], + value: hint[1], + score: 1000, + meta: hint[0], + position: hint[2], + fullValue: hint[3], + completer: psCompleter }; })); - ace.config.loadModule("ace/ext/error_marker", function (module) { - module.showErrorMarker(codeeditor, -1); - }); - return; } + }; - var psCompleter = this; - callback(null, keywords.map(function(word) { - var hint = word.split("|"); - return { - name: hint[1], - value: hint[1], - score: 1000, - meta: hint[0], - position: hint[2], - fullValue: hint[3], - completer: psCompleter - }; - })); - } - }; + module.addCompleter(keyWordCompleter); + }); - module.addCompleter(keyWordCompleter); - }); + currentAceEditor.setAutoScrollEditorIntoView(true); - codeeditor.setAutoScrollEditorIntoView(true); + currentAceEditor.on("guttermousedown", function (editor) { + var target = editor.domEvent.target; + if (target.className.indexOf("ace_gutter-cell") === -1) + return; - codeeditor.on("guttermousedown", function(editor) { - var target = editor.domEvent.target; - if (target.className.indexOf("ace_gutter-cell") === -1) - return; + if (editor.clientX > 25 + target.getBoundingClientRect().left) + return; - if (editor.clientX > 25 + target.getBoundingClientRect().left) - return; + var currRow = editor.getDocumentPosition().row; + spe.breakpointSet(currRow, "toggle"); + editor.stop(); + var sparseKeys = Object.keys(editor.editor.session.getBreakpoints()); + currentEditorSession.breakpoints = sparseKeys.toString(); + breakpointsMemo.val(currentEditorSession.breakpoints); + }); - var currRow = editor.getDocumentPosition().row; - spe.breakpointSet(currRow, "toggle"); - editor.stop(); - var sparseKeys = Object.keys(editor.editor.session.getBreakpoints()); - $("#Breakpoints")[0].value = sparseKeys.toString(); - }); + currentAceEditor.on("input", function () { + spe.updateModificationFlag(false); + }); - codeeditor.on("input", function () { - spe.updateModificationFlag(false); - }); + var codeeeditorcommands = [ + { + name: "help", + bindKey: {win: "ctrl-enter|shift-enter", mac: "ctrl-enter|command-enter", sender: "codeeditor|cli"}, + exec: function (env, args, request) { + var range = currentAceEditor.getSelectionRange(); + if (range.start.row === range.end.row && range.start.column === range.end.column) { + range.start.column = 0; + } + var command = currentAceEditor.session.getTextRange(range); + if (command) { + spe.showCommandHelp(command); + } + }, + readOnly: true + }, { + name: "fontSizeIncrease", + bindKey: {win: "Ctrl-Alt-Shift-=|Ctrl-Alt-Shift-+", mac: "Ctrl-Alt-Shift-=|Ctrl-Alt-Shift-+"}, + exec: function (editor) { + spe.changeFontSize(editor.getFontSize() + 1); + }, + readOnly: true + }, { + name: "fontSizeDecrease", + bindKey: {win: "Ctrl-Alt-Shift--", mac: "Ctrl-Alt-Shift--"}, + exec: function (editor) { + spe.changeFontSize(Math.max(editor.getFontSize() - 1, 8)); + }, + readOnly: true + }, { + name: "setDebugPoint", + bindKey: {win: "F8", mac: "F8"}, + exec: function (editor) { + var currRow = editor.selection.getCursor().row; + spe.breakpointSet(currRow, "toggle"); + }, + readOnly: true + } + ]; - var codeeeditorcommands = [ - { - name: "help", - bindKey: { win: "ctrl-enter|shift-enter", mac: "ctrl-enter|command-enter", sender: "codeeditor|cli" }, - exec: function(env, args, request) { - var range = codeeditor.getSelectionRange(); - if (range.start.row === range.end.row && range.start.column === range.end.column) { - range.start.column = 0; - } - var command = codeeditor.session.getTextRange(range); - if (command) { - spe.showCommandHelp(command); - } - }, - readOnly: true - }, { - name: "fontSizeIncrease", - bindKey: { win: "Ctrl-Alt-Shift-=|Ctrl-Alt-Shift-+", mac: "Ctrl-Alt-Shift-=|Ctrl-Alt-Shift-+" }, - exec: function(editor) { - spe.changeFontSize(editor.getFontSize() + 1); - }, - readOnly: true - }, { - name: "fontSizeDecrease", - bindKey: { win: "Ctrl-Alt-Shift--", mac: "Ctrl-Alt-Shift--" }, - exec: function(editor) { - spe.changeFontSize(Math.max(editor.getFontSize() - 1, 8)); - }, - readOnly: true - }, { - name: "setDebugPoint", - bindKey: { win: "F8", mac: "F8" }, - exec: function (editor) { - var currRow = editor.selection.getCursor().row; - spe.breakpointSet(currRow, "toggle"); - }, - readOnly: true - } - ]; + currentAceEditor.commands.addCommands(codeeeditorcommands); - codeeditor.commands.addCommands(codeeeditorcommands); + } - spe.getOutput = function() { - return $("#ScriptResultCode")[0].innerText; + spe.changeLiveAutocompletion = function (liveAutocompletion) { + editorLiveAutocompletion = liveAutocompletion; + editorSessions.forEach(function (editor) { + editor.editor.setOptions({ + enableLiveAutocompletion: liveAutocompletion + }); + }); }; - spe.changeFontSize = function(setting) { - setting = parseInt(setting) || 12; - codeeditor.setOption("fontSize", setting); - $("#ScriptResult").css({ "font-size": setting + "px" }); - }; - spe.changeLiveAutocompletion = function(liveAutocompletion) { - codeeditor.setOptions({ - enableLiveAutocompletion: liveAutocompletion - }); + $("#CopyResultsToClipboard").on("click", function () { + clipboard.copy(spe.getOutput()); + }); + + spe.getOutput = function () { + return $("#ScriptResultCode")[0].innerText; }; - spe.clearOutput = function() { + spe.clearOutput = function () { $("#ScriptResultCode").text(""); $("#Result").scrollTop($("#Result")[0].scrollHeight); + currentEditorSession.results = ""; clearVariablesCache(); }; - spe.appendOutput = function(outputToAppend) { + spe.appendOutput = function (outputToAppend) { var decoded = $("
").html(outputToAppend).text(); $("#ScriptResultCode").append(decoded); $("#Result").scrollTop($("#Result")[0].scrollHeight); + currentEditorSession.results = currentEditorSession.results + decoded; clearVariablesCache(); }; - spe.changeFontFamily = function(setting) { + spe.changeFontFamily = function (setting) { setting = setting || "Monaco"; - codeeditor.setOption("fontFamily", setting); + editorFontFamily = setting; + editorSessions.forEach(function (editor) { + editor.editor.setOption("fontFamily", setting); + }); + document.getElementById("ScriptResult").style.fontFamily = setting; }; + spe.changeFontSize = function (setting) { + setting = parseInt(setting) || 12; + editorFontSize = setting; + editorSessions.forEach(function (editor) { + editor.editor.setOption("fontSize", setting); + }); + $("#ScriptResult").css({"font-size": setting + "px"}); + }; + + spe.changeBackgroundColor = function (setting) { - $("#ScriptResult").css({ "background-color": setting }); - $("#Result").css({ "background-color": setting }); + $("#ScriptResult").css({"background-color": setting}); + $("#Result").css({"background-color": setting}); }; - spe.changeSettings = function(fontFamily, fontSize, backgroundColor, bottomOffset, liveAutocompletion) { + spe.changeSettings = function (fontFamily, fontSize, backgroundColor, bottomOffset, liveAutocompletion, perTabOutput) { spe.changeBackgroundColor(backgroundColor); spe.changeFontFamily(fontFamily); spe.changeFontSize(fontSize); @@ -375,54 +494,44 @@ spe.changeLiveAutocompletion(liveAutocompletion); } resultsBottomOffset = bottomOffset; + perTabResults = perTabOutput; }; spe.changeSessionId = function (sessionId) { guid = sessionId; }; - spe.debugStart = function(sessionId) { - codeeditor.setReadOnly(true); + spe.debugStart = function (sessionId) { + currentAceEditor.setReadOnly(true); }; spe.debugStop = function (sessionId) { setTimeout(spe.breakpointHandled, 100); - codeeditor.setReadOnly(false); - }; - - spe.updateModificationFlag = function (clear) { - if (clear) { - codeeditor.getSession().getUndoManager().markClean(); - } - var scriptModified = $("#scriptModified", window.parent.document); - if (codeeditor.getSession().getUndoManager().isClean()) - scriptModified.hide(); - else - scriptModified.show(); + currentAceEditor.setReadOnly(false); }; - spe.toggleBreakpoint = function(row, set) { + spe.toggleBreakpoint = function (row, set) { if (set) { - codeeditor.session.setBreakpoint(row); + currentAceEditor.session.setBreakpoint(row); } else { - codeeditor.session.clearBreakpoint(row); + currentAceEditor.session.clearBreakpoint(row); } scForm.postRequest("", "", "", "ise:togglebreakpoint(line=" + row + ",state=" + set + ")"); }; - spe.breakpointSet = function(row, action) { + spe.breakpointSet = function (row, action) { if (action === "toggle") { - if (codeeditor.session.getBreakpoints()[row] === "ace_breakpoint") { + if (currentAceEditor.session.getBreakpoints()[row] === "ace_breakpoint") { spe.toggleBreakpoint(row, false); } else { spe.toggleBreakpoint(row, true); } } else if (action === "Set" || action === "Enabled") { - if (codeeditor.session.getBreakpoints()[row] !== "ace_breakpoint") { + if (currentAceEditor.session.getBreakpoints()[row] !== "ace_breakpoint") { spe.toggleBreakpoint(row, true); } } else if (action === "Removed" || action === "Disabled") { - if (codeeditor.session.getBreakpoints()[row] === "ace_breakpoint") { + if (currentAceEditor.session.getBreakpoints()[row] === "ace_breakpoint") { spe.toggleBreakpoint(row, false); } } @@ -434,30 +543,27 @@ scContent.ribbonNavigatorButtonClick(this, event, "PowerShellRibbon_Strip_DebugStrip"); var Range = ace.require("ace/range").Range; setTimeout(function () { - debugMarkers.push(codeeditor.session.addMarker(new Range(line, column, endLine, endColumn + 1), "breakpoint", "text")); + debugMarkers.push(currentAceEditor.session.addMarker(new Range(line, column, endLine, endColumn + 1), "breakpoint", "text")); }, 100); - if (line < codeeditor.getFirstVisibleRow() || line > codeeditor.getLastVisibleRow()) { - codeeditor.gotoLine(line); + if (line < currentAceEditor.getFirstVisibleRow() || line > currentAceEditor.getLastVisibleRow()) { + currentAceEditor.gotoLine(line); } }; - spe.breakpointHandled = function() { + spe.breakpointHandled = function () { while (debugMarkers.length > 0) { - codeeditor.session.removeMarker(debugMarkers.shift()); + currentAceEditor.session.removeMarker(debugMarkers.shift()); } scContent.ribbonNavigatorButtonClick(this, event, "PowerShellRibbon_Strip_ImageStrip"); }; - scForm.postRequest("", "", "", "ise:updatesettings"); - spe.updateEditor = function () { - codeeditor.getSession().setValue(editor.val()); - spe.clearBreakpoints(); + currentAceEditor.getSession().setValue(editor.val()); }; spe.insertEditorContent = function (text) { - var position = codeeditor.getCursorPosition(); - codeeditor.getSession().insert(position, text); + var position = currentAceEditor.getCursorPosition(); + currentAceEditor.getSession().insert(position, text); spe.clearBreakpoints(); }; @@ -466,16 +572,16 @@ spe.preventCloseWhenRunning(false); } clearVariablesCache(); - if(!resultsVisibilityIntent){ + if (!resultsVisibilityIntent) { setTimeout(spe.closeResults, 2000); } }; - spe.clearBreakpoints = function() { - var breakPoints = Object.keys(codeeditor.session.getBreakpoints()); + spe.clearBreakpoints = function () { + var breakPoints = Object.keys(currentAceEditor.session.getBreakpoints()); var bpCount = breakPoints.length; for (var i = 0; i < bpCount; i++) { - codeeditor.session.clearBreakpoint(breakPoints[i]); + currentAceEditor.session.clearBreakpoint(breakPoints[i]); } }; @@ -487,40 +593,115 @@ return string.replace(new RegExp(escapeRegExp(find), "g"), replace); } - spe.changeWindowTitle = function(newTitle, clearWindow) { - if (clearWindow) { - codeeditor.getSession().setValue(""); - } - newTitle = replaceAll(newTitle, "/", " / "); + spe.changeTabDetails = function (codeEditorIndex, newTitle, startBarHtml, scriptId, scriptDb) { + + var editorSession = getSessionByIndex(codeEditorIndex); + var itemName = newTitle.split("/").last(); + editorSession.name = itemName + editorSession.path = newTitle; + const newTabTitle = replaceAll(editorSession.path, "/", " / "); + editorSession.tabTitleInnerHTML = " " + editorSession.name + + "" + + " "; + const newWindowTitle = replaceAll(editorSession.path, "/", " / "); + editorSession.windowTitleInnerHTML = " " + newWindowTitle + " - "; + editorSession.tabTitleHtml = replaceAll(editorSession.path, "/", " / "); + editorSession.scriptId = scriptId; + editorSession.scriptDb = scriptDb; + editorSession.startBarHtml = startBarHtml; + var openedScripts = editorSessions.map(session => session.path + ":" + session.index).join('\n'); + openedScriptsMemo.val(openedScripts); + } + + spe.applyWindowTitle = function (codeEditorIndex) { + + var editorSession = getSessionByIndex(codeEditorIndex); var windowCaption = $("#WindowCaption", window.parent.document); if (windowCaption.length > 0) { - windowCaption[0].innerHTML = "" + newTitle + " (*) - "; + windowCaption[0].innerHTML = + editorSession.windowTitleInnerHTML. + replace("#tabindex#", editorSession.index). + replace("#styles#", editorSession.isModified ? "display:inline;" : "display:none;"); + } - codeeditor.getSession().getUndoManager().markClean(); - spe.clearBreakpoints(); - }; - spe.changeStartbarTitle = function (newTitle) { + var styles = $("#ModifiedStatusStyles"); + if (styles.length === 0) { + var head = $("head"); + head.append(""); + styles = $("#ModifiedStatusStyles"); + } + var stylesinnerHTML = ""; + editorSessions.forEach(function (currEditor) { + var tabHeader = $("#Tabs_tab_" + (currEditor.index - 1)) + if (tabHeader.length > 0) { + tabHeader[0].innerHTML = currEditor.tabTitleInnerHTML.replaceAll("#tabindex#", currEditor.index); + if (currEditor.isModified) { + stylesinnerHTML = stylesinnerHTML + ".ModifiedMark" + currEditor.index + "{display:inline;}"; + } + } + }); + styles[0].innerHTML = stylesinnerHTML; + + if (window.parent && window.parent.frameElement) { var frameId = window.parent.frameElement.id; var startbar = window.parent.parent.document.getElementById('startbar_application_' + frameId); - $(startbar).find('span').html(newTitle); + $(startbar).find('span').html(editorSession.startBarHtml); } }; - spe.resizeEditor = function() { - codeeditor.resize(); - var resultsHeight =$(window).height() -$("#ResultsSplitter").offset().top - $("#ResultsSplitter").height() - $("#StatusBar").height() - resultsBottomOffset - 10; - $("#Result").height(resultsHeight); + + spe.closeTab = function (event, index) { + + event.stopPropagation(); + var closingEditorSession = getSessionByIndex(index); + var selectIndex = currentEditorIndex; + if (index === currentEditorIndex) { + selectIndex = previousIndex; + } + if (selectIndex > index) { + selectIndex--; + } + selectIndex = Math.min(selectIndex, editorSessions.length - 1); + scForm.postRequest("", "", "", "ise:closetab(index=" + index + ",modified=" + closingEditorSession.isModified + ",selectIndex=" + selectIndex + ")"); + }; + + spe.closeScript = function (index) { + + // Remove the session with the specified index + var closingEditorSession = getSessionByIndex(index); + + editorSessions = editorSessions.filter(session => session.index !== index); + + // cleanup the DOM and script content + var editorContainer = $(closingEditorSession.editor.container) + closingEditorSession.editor.destroy(); + editorContainer.remove(); + editor.val(""); + + // Update the indices of the remaining sessions + editorSessions.forEach((session, i) => { + session.index = i + 1; + }); + } + + spe.resizeEditor = function () { + if (currentAceEditor !== undefined) { + currentAceEditor.resize(); + } + var resultsHeight = $(window).height() - $("#ResultsSplitter").offset().top - $("#ResultsSplitter").height() - $("#StatusBar").height() - resultsBottomOffset - 10; + $("#Result").height(resultsHeight); $("#Result").width($(window).width() - $("#Result").offset().left * 2); - $("#ProgressOverlay").css("top",($("#Result").offset().top+4)+"px"); + $("#ProgressOverlay").css("top", ($("#Result").offset().top + 4) + "px"); + }; function isEmpty(val) { return (val === undefined || val == null || val.length <= 0) ? true : false; } - spe.showInfoPanel = function(showPanel, updateFromMessage) { + spe.showInfoPanel = function (showPanel, updateFromMessage) { if (showPanel) { $("#InfoPanel").css("display", "block"); } else { @@ -532,21 +713,21 @@ } }; - spe.requestElevation = function() { + spe.requestElevation = function () { scForm.postRequest("", "", "", "ise:requestelevation"); }; - spe.restoreResults = function() { + spe.restoreResults = function () { $("#ResultsSplitter").show(); $("#ResultsRow").show(); spe.resizeEditor(); $("#ResultsStatusBarAction").removeClass("status-bar-results-hidden") }; - spe.closeResults = function() { + spe.closeResults = function () { $("#ResultsSplitter").hide(); - $("#ResultsRow").hide("slow", function() { - codeeditor.resize(); + $("#ResultsRow").hide("slow", function () { + currentAceEditor.resize(); }); $("#ResultsStatusBarAction").addClass("status-bar-results-hidden") }; @@ -599,7 +780,7 @@ return cachedValue; } - getPowerShellResponse({ "guid": sessionId, "variableName": variableName }, "GetVariableValue", + getPowerShellResponse({"guid": sessionId, "variableName": variableName}, "GetVariableValue", function (json) { data = json.d; }); @@ -608,16 +789,16 @@ return data; }; - spe.getAutocompletionPrefix = function(text) { + spe.getAutocompletionPrefix = function (text) { var data; - getPowerShellResponse({ "guid": guid, "command": text }, "GetAutoCompletionPrefix", - function(json) { + getPowerShellResponse({"guid": guid, "command": text}, "GetAutoCompletionPrefix", + function (json) { data = JSON.parse(json.d); }); return data; }; - spe.showCommandHelp = function(command) { + spe.showCommandHelp = function (command) { _getCommandHelp(command); if (spe.ajaxDialog) spe.ajaxDialog.remove(); @@ -631,7 +812,7 @@ spe.ajaxDialog.dialog("close"); }); }, - close: function(event, ui) { + close: function (event, ui) { $(this).remove(); }, height: $(window).height() - 20, @@ -645,7 +826,7 @@ $.commandHelp = ""; - spe.changeWindowTitle($("#ScriptName")[0].innerHTML, false); + var tipIndex = Math.floor(Math.random() * tips.length); var tip = tips[tipIndex]; @@ -653,37 +834,37 @@ $("#StatusTip").html(tip); $("#TipOfTheSession").position({ - my: "left bottom", - at: "left bottom-30px", - within: $("#Result"), - of: $("#Result") - }).css({ - right: 0, - left: 0 - }).hide() - .show("drop", { direction: "down" }, "400") + my: "left bottom", + at: "left bottom-30px", + within: $("#Result"), + of: $("#Result") + }).css({ + right: 0, + left: 0 + }).hide() + .show("drop", {direction: "down"}, "400") .delay(5000) - .hide("drop", { direction: "down" }, "400", - function() { - $(".status-bar-text").animate({ backgroundColor: "#fcefa1" }).animate({ backgroundColor: "#fff" }); + .hide("drop", {direction: "down"}, "400", + function () { + $(".status-bar-text").animate({backgroundColor: "#fcefa1"}).animate({backgroundColor: "#fff"}); }); - $(".status-bar-text").click(function() { + $(".status-bar-text").click(function () { tipIndex++; if (tipIndex >= tips.length) { tipIndex = 0; } var nextTip = tips[tipIndex]; - $(".status-bar-text").animate({ backgroundColor: "#fcefa1" }, - function() { + $(".status-bar-text").animate({backgroundColor: "#fcefa1"}, + function () { $("#TipText").html(nextTip); $("#StatusTip").html(nextTip); - }).animate({ backgroundColor: "#fff" }); + }).animate({backgroundColor: "#fff"}); }); - - $("#ShowHideResults").click(function() { + + $("#ShowHideResults").click(function () { if ($("#ResultsRow").is(":visible")) { resultsVisibilityIntent = false; spe.closeResults(); @@ -692,44 +873,51 @@ spe.restoreResults(); } }); - + function _getCommandHelp(str) { - getPowerShellResponse({ "guid": guid, "command": str }, "GetHelpForCommand", - function(json) { + getPowerShellResponse({"guid": guid, "command": str}, "GetHelpForCommand", + function (json) { var data = JSON.parse(json.d); $.commandHelp = data[0]; }); } function _getTabCompletions(str) { - getPowerShellResponse({ "guid": guid, "command": str }, "CompleteAceCommand", - function(json) { + getPowerShellResponse({"guid": guid, "command": str}, "CompleteAceCommand", + function (json) { var data = JSON.parse(json.d); $.tabCompletions = data; }); } function getPowerShellResponse(callData, remotefunction, doneFunction, errorFunction) { - if(remotefunction != "GetVariableValue"){ + if (remotefunction != "GetVariableValue") { spe.requestElevation(); - }; + } + ; var datastring = JSON.stringify(callData); $.ajax({ - type: "POST", - contentType: "application/json; charset=utf-8", - dataType: "json", - url: "/sitecore modules/PowerShell/Services/PowerShellWebService.asmx/" + remotefunction, - data: datastring, - processData: false, - cache: false, - async: false - }).done(doneFunction) + type: "POST", + contentType: "application/json; charset=utf-8", + dataType: "json", + url: "/sitecore modules/PowerShell/Services/PowerShellWebService.asmx/" + remotefunction, + data: datastring, + processData: false, + cache: false, + async: false + }).done(doneFunction) .fail(errorFunction); } - $(window).on('resize', function(){ - spe.resizeEditor(); + $(window).on('resize', function () { + spe.resizeEditor(); }).trigger('resize'); + + setTimeout(function () { + scForm.postRequest("", "", "", "ise:updatesettings"); + scForm.postRequest("", "", "", "ise:loadinitialscript"); + }, 100); + }); }(jQuery, window, window.spe = window.spe || {}, window.ace = window.ace || {})); diff --git a/src/Spe/sitecore modules/PowerShell/Styles/ise.css b/src/Spe/sitecore modules/PowerShell/Styles/ise.css index 0d092dc5..406b1a7f 100644 --- a/src/Spe/sitecore modules/PowerShell/Styles/ise.css +++ b/src/Spe/sitecore modules/PowerShell/Styles/ise.css @@ -9,7 +9,10 @@ background-color: white; } -#ResultsSplitter + tr > td { height: 100%; } +#ResultsSplitter + tr > td { + height: 100%; + overflow: auto; +} #Databases { border: solid 1px ButtonShadow; @@ -194,10 +197,10 @@ a.ps-link { padding: 0 4px 0 4px; } -#Result { - position: fixed; - bottom: 27px; -} +/*#Result {*/ +/* position: fixed;*/ +/* bottom: 27px;*/ +/*}*/ #HelpClose { font-size: 18px; @@ -417,4 +420,59 @@ a.ps-link { #ResultsStatusBarAction.status-bar-results-hidden { background-color: rgb(1, 36, 86); -} \ No newline at end of file +} + +.scTabPage { + padding: 4px 0 0 0; +} + +.scTab, .scTab_Hover, .scTabActive, .scTabActive_Hover { + text-transform: none; + line-height: 30px; +} + +.scTabActive:after, .scTabActive_Hover:after { + height: 30px; +} + +.ff .scTabstrip { + margin-top: 4px; + height: 33px; +} + +.scTabs { + top: 30px; + display:none; +} + +.aceCodeEditor, #EditingArea, #Content, #Wrapper, #Result { + width: 100%; + height: 100%; +} + +#TabsPanel { + width: 100%; + height: 35px; +} + +#EditingArea { + overflow: auto; +} + +#CodeEditors { + width: 100%; + height: calc(100% - 40px); +} + +.scriptModified{ + display:none; + color:#fc2929 +} + +.closeTab { + display: inline-block; + cursor: pointer; + background: url(/sitecore%20modules/PowerShell/Assets/TabClose.png) no-repeat center; + padding: 0 4px 0 4px; + margin: 0 -10px 0 10px; +} diff --git a/src/Spe/sitecore modules/Shell/PowerShell/PowerShellIse.xml b/src/Spe/sitecore modules/Shell/PowerShell/PowerShellIse.xml index 6ea8206c..7339182a 100644 --- a/src/Spe/sitecore modules/Shell/PowerShell/PowerShellIse.xml +++ b/src/Spe/sitecore modules/Shell/PowerShell/PowerShellIse.xml @@ -3,15 +3,15 @@ - - + + + - @@ -32,11 +32,14 @@