diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml
index 0cce9e1dcd..3dee2cdbeb 100644
--- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml
@@ -23,13 +23,11 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:winlocal="clr-namespace:dnSpy.Contracts.Controls;assembly=dnSpy.Contracts.DnSpy"
xmlns:mvvm="clr-namespace:dnSpy.Contracts.MVVM;assembly=dnSpy.Contracts.DnSpy"
- xmlns:mvvmvc="clr-namespace:dnSpy.Contracts.MVVM.Converters;assembly=dnSpy.Contracts.DnSpy"
- xmlns:img="clr-namespace:dnSpy.Contracts.Images;assembly=dnSpy.Contracts.DnSpy"
xmlns:ui="clr-namespace:dnSpy.Contracts.Controls.ToolWindows;assembly=dnSpy.Contracts.DnSpy"
xmlns:p="clr-namespace:dnSpy.Debugger.Properties"
mc:Ignorable="d"
Style="{StaticResource DialogWindowStyle}"
- Title="Edit environment variables" Height="350" Width="500"
+ Title="{x:Static p:dnSpy_Debugger_Resources.EditEnvironmentVariables_Title}" Height="350" Width="500"
MinHeight="350" MinWidth="500"
WindowStartupLocation="CenterOwner">
@@ -44,6 +42,7 @@
+
-
-
+
+
+
diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml.cs
index f23252271d..a8107e3b61 100644
--- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml.cs
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentDlg.xaml.cs
@@ -23,6 +23,12 @@ namespace dnSpy.Debugger.Dialogs.EditEnvironment {
public partial class EditEnvironmentDlg : WindowBase {
public EditEnvironmentDlg() {
InitializeComponent();
+ listView.SelectionChanged += (_, e) => {
+ if (e.AddedItems.Count > 0)
+ listView.ScrollIntoView(e.AddedItems[e.AddedItems.Count - 1]);
+ else if (listView.SelectedItems.Count > 0)
+ listView.ScrollIntoView(listView.SelectedItems[listView.SelectedItems.Count - 1]);
+ };
}
}
}
diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentVM.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentVM.cs
index 54a35e1766..da925d1bdb 100644
--- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentVM.cs
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EditEnvironmentVM.cs
@@ -17,15 +17,16 @@ You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
-
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Input;
+using dnSpy.Contracts.App;
using dnSpy.Contracts.Controls.ToolWindows;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.MVVM;
using dnSpy.Contracts.Text;
+using dnSpy.Debugger.Properties;
namespace dnSpy.Debugger.Dialogs.EditEnvironment {
sealed class EditEnvironmentVM : ViewModelBase {
@@ -41,6 +42,7 @@ sealed class EditEnvironmentVM : ViewModelBase {
IEditValueProvider? valueEditValueProvider;
public ICommand AddEnvironmentVariableCommand => new RelayCommand(a => AddEnvironmentVariable());
+ public ICommand AddManyEnvironmentVariablesCommand => new RelayCommand(a => AddManyEnvironmentVariables());
public ICommand RemoveEnvironmentVariableCommand => new RelayCommand(a => RemoveEnvironmentVariable(), a => SelectedItems.Count > 0);
public ICommand ResetCommand => new RelayCommand(a => Reset());
@@ -59,15 +61,38 @@ public EditEnvironmentVM(EditValueProviderService editValueProviderService, DbgE
void AddEnvironmentVariable() {
SelectedItems.Clear();
var vm = new EnvironmentVariableVM(KeyEditValueProvider, ValueEditValueProvider);
- EnvironmentVariables.Insert(0, vm);
+ EnvironmentVariables.Add(vm);
SelectedItems.Add(vm);
}
+ void AddManyEnvironmentVariables() {
+ var variables = MsgBox.Instance.Ask(dnSpy_Debugger_Resources.AddEnvironmentVariablesMsgBoxLabel, null, dnSpy_Debugger_Resources.AddEnvironmentVariablesMsgBoxTitle, s => {
+ new EnvironmentStringParser(s).TryParse(out var env);
+ return env;
+ }, s => new EnvironmentStringParser(s).TryParse(out _) ? null : dnSpy_Debugger_Resources.InvalidInputString);
+ if (variables is null)
+ return;
+
+ SelectedItems.Clear();
+ foreach (var pair in variables) {
+ var variableVm = new EnvironmentVariableVM(KeyEditValueProvider, ValueEditValueProvider) {
+ Key = pair.Key,
+ Value = pair.Value
+ };
+ EnvironmentVariables.Add(variableVm);
+ SelectedItems.Add(variableVm);
+ }
+ }
+
void RemoveEnvironmentVariable() {
+ var oldSelectedIndex = EnvironmentVariables.IndexOf(SelectedItems[0]);
+
var itemsToRemove = new List(SelectedItems);
SelectedItems.Clear();
foreach (var environmentVariableVm in itemsToRemove)
EnvironmentVariables.Remove(environmentVariableVm);
+
+ SelectedItems.Add(EnvironmentVariables[Math.Min(oldSelectedIndex, EnvironmentVariables.Count - 1)]);
}
void Reset() {
@@ -87,8 +112,11 @@ void InitializeFrom(DbgEnvironment environment) {
public void CopyTo(DbgEnvironment environment) {
environment.Clear();
- foreach (var environmentVariableVm in EnvironmentVariables)
- environment.Add(environmentVariableVm.Key, environmentVariableVm.Value);
+ foreach (var vm in EnvironmentVariables) {
+ if (string.IsNullOrEmpty(vm.Key))
+ continue;
+ environment.Add(vm.Key, vm.Value);
+ }
}
}
}
diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EnvironmentStringParser.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EnvironmentStringParser.cs
new file mode 100644
index 0000000000..910376f944
--- /dev/null
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Dialogs/EditEnvironment/EnvironmentStringParser.cs
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2024 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System.Collections.Generic;
+
+namespace dnSpy.Debugger.Dialogs.EditEnvironment {
+ public struct EnvironmentStringParser {
+ readonly string str;
+ int pos;
+
+ public EnvironmentStringParser(string str) => this.str = str;
+
+ public bool TryParse(out Dictionary env) {
+ env = new Dictionary();
+
+ for (;;) {
+ var key = GetNextToken();
+ if (key.Kind == TokenKind.EOF)
+ break;
+ if (key.Kind != TokenKind.String)
+ return false;
+ var assign = GetNextToken();
+ if (assign.Kind != TokenKind.Assign)
+ return false;
+ var value = GetNextToken();
+ if (value.Kind != TokenKind.String)
+ return false;
+
+ env[key.Value] = value.Value;
+
+ var seperator = GetNextToken();
+ if (seperator.Kind == TokenKind.EOF)
+ break;
+ if (seperator.Kind != TokenKind.Separator)
+ return false;
+ }
+
+ return true;
+ }
+
+ Token GetNextToken() {
+ if (pos >= str.Length)
+ return new Token(TokenKind.EOF);
+
+ var currentChar = str[pos++];
+ if (currentChar == ';')
+ return new Token(TokenKind.Separator);
+ if (currentChar == '=')
+ return new Token(TokenKind.Assign);
+
+ if (currentChar == '"') {
+ int stringStartPos = pos;
+ while (true) {
+ if (pos >= str.Length)
+ return new Token(TokenKind.Invalid);
+ if (str[pos++] == '"')
+ break;
+ }
+ if (pos == stringStartPos)
+ return new Token(TokenKind.Invalid);
+ return new Token(TokenKind.String, str.Substring(stringStartPos, pos - stringStartPos - 1));
+ }
+
+ int startPos = pos - 1;
+ while (true) {
+ if (pos >= str.Length || str[pos] is ';' or '=')
+ break;
+ pos++;
+ }
+ return new Token(TokenKind.String, str.Substring(startPos, pos - startPos));
+ }
+
+ enum TokenKind {
+ Invalid,
+ Separator,
+ Assign,
+ String,
+ EOF,
+ }
+
+ readonly struct Token {
+ public readonly TokenKind Kind;
+ public readonly string Value;
+
+ public Token(TokenKind kind) {
+ Kind = kind;
+ Value = string.Empty;
+ }
+
+ public Token(TokenKind kind, string value) {
+ Kind = kind;
+ Value = value;
+ }
+
+ public override string ToString() => Kind == TokenKind.String ? Value : Kind.ToString();
+ }
+ }
+}
diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.Designer.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.Designer.cs
index 0e770b03e2..98e76f9390 100644
--- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.Designer.cs
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.Designer.cs
@@ -77,6 +77,24 @@ public static string AddEnvironmentVariableButton {
}
}
+ ///
+ /// Looks up a localized string similar to Environment.
+ ///
+ public static string AddEnvironmentVariablesMsgBoxLabel {
+ get {
+ return ResourceManager.GetString("AddEnvironmentVariablesMsgBoxLabel", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Add Environment Variables.
+ ///
+ public static string AddEnvironmentVariablesMsgBoxTitle {
+ get {
+ return ResourceManager.GetString("AddEnvironmentVariablesMsgBoxTitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Add.
///
@@ -95,6 +113,15 @@ public static string AddExceptionCommand {
}
}
+ ///
+ /// Looks up a localized string similar to Add Many.
+ ///
+ public static string AddManyEnvironmentVariablesButton {
+ get {
+ return ResourceManager.GetString("AddManyEnvironmentVariablesButton", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to <All {0} not in this list>.
///
@@ -1841,6 +1868,15 @@ public static string EditConditionsCommand {
}
}
+ ///
+ /// Looks up a localized string similar to Edit Environment Variables.
+ ///
+ public static string EditEnvironmentVariables_Title {
+ get {
+ return ResourceManager.GetString("EditEnvironmentVariables_Title", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Edit Labels.
///
@@ -2393,6 +2429,15 @@ public static string InternalDebuggerError {
}
}
+ ///
+ /// Looks up a localized string similar to Invalid Input String.
+ ///
+ public static string InvalidInputString {
+ get {
+ return ResourceManager.GetString("InvalidInputString", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Language.
///
diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.resx b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.resx
index 99a778d1ca..3eaf53c4c4 100644
--- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.resx
+++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/Properties/dnSpy.Debugger.Resources.resx
@@ -954,6 +954,15 @@ Are you sure you want to expand it?
Add
+
+ Add Environment Variables
+
+
+ Environment
+
+
+ Add Many
+
Exception description
@@ -963,6 +972,9 @@ Are you sure you want to expand it?
Edit Conditions
+
+ Edit Environment Variables
+
_Add Condition
@@ -1344,6 +1356,9 @@ Do you wish to continue?
Insert _Tracepoint
+
+ Invalid Input String
+
_Unwind To This Frame
diff --git a/Extensions/dnSpy.Scripting.Roslyn/Common/ScriptGlobals.cs b/Extensions/dnSpy.Scripting.Roslyn/Common/ScriptGlobals.cs
index 434853d648..dc588733bc 100644
--- a/Extensions/dnSpy.Scripting.Roslyn/Common/ScriptGlobals.cs
+++ b/Extensions/dnSpy.Scripting.Roslyn/Common/ScriptGlobals.cs
@@ -113,7 +113,7 @@ public MsgBoxButton ShowYesNoCancel(string message, Window? ownerWindow = null)
public MsgBoxButton ShowYNC(string message, Window? ownerWindow = null) => ShowYesNoCancel(message, ownerWindow);
- public T Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null) =>
+ public T? Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null) =>
dispatcher.UI(() => MsgBox.Instance.Ask(labelMessage, defaultText, title, converter, verifier, ownerWindow));
public void Show(Exception exception, string? msg = null, Window? ownerWindow = null) =>
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/App/IMessageBoxService.cs b/dnSpy/dnSpy.Contracts.DnSpy/App/IMessageBoxService.cs
index 220603d529..3a9a1e2b69 100644
--- a/dnSpy/dnSpy.Contracts.DnSpy/App/IMessageBoxService.cs
+++ b/dnSpy/dnSpy.Contracts.DnSpy/App/IMessageBoxService.cs
@@ -60,7 +60,7 @@ public interface IMessageBoxService {
/// it's a valid value, else an error message to show to the user.
/// Owner window or null to use the main window
///
- T Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null);
+ T? Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null);
///
/// Shows an exception message
diff --git a/dnSpy/dnSpy.Contracts.DnSpy/Scripting/Roslyn/IScriptGlobals.cs b/dnSpy/dnSpy.Contracts.DnSpy/Scripting/Roslyn/IScriptGlobals.cs
index 972a1bf7cb..8040c73992 100644
--- a/dnSpy/dnSpy.Contracts.DnSpy/Scripting/Roslyn/IScriptGlobals.cs
+++ b/dnSpy/dnSpy.Contracts.DnSpy/Scripting/Roslyn/IScriptGlobals.cs
@@ -339,7 +339,7 @@ public interface IScriptGlobals : ITextPrinter {
/// it's a valid value, else an error message to show to the user.
/// Owner window or null to use the main window
///
- T Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null);
+ T? Ask(string labelMessage, string? defaultText = null, string? title = null, Func? converter = null, Func? verifier = null, Window? ownerWindow = null);
///
/// Shows an exception message
diff --git a/dnSpy/dnSpy/Documents/Tabs/Dialogs/OpenDocumentListVM.cs b/dnSpy/dnSpy/Documents/Tabs/Dialogs/OpenDocumentListVM.cs
index d2343a39be..fa7846a653 100644
--- a/dnSpy/dnSpy/Documents/Tabs/Dialogs/OpenDocumentListVM.cs
+++ b/dnSpy/dnSpy/Documents/Tabs/Dialogs/OpenDocumentListVM.cs
@@ -101,14 +101,14 @@ public bool ShowSavedLists {
readonly DocumentListService documentListService;
readonly HashSet removedDocumentLists;
readonly List addedDocumentLists;
- readonly Func askUser;
+ readonly Func askUser;
readonly CancellationTokenSource cancellationTokenSource;
readonly CancellationToken cancellationToken;
public IClassificationFormatMap ClassificationFormatMap { get; }
public ITextElementProvider TextElementProvider { get; }
- public OpenDocumentListVM(bool syntaxHighlight, IClassificationFormatMap classificationFormatMap, ITextElementProvider textElementProvider, DocumentListService documentListService, Func askUser) {
+ public OpenDocumentListVM(bool syntaxHighlight, IClassificationFormatMap classificationFormatMap, ITextElementProvider textElementProvider, DocumentListService documentListService, Func askUser) {
SyntaxHighlight = syntaxHighlight;
ClassificationFormatMap = classificationFormatMap;
TextElementProvider = textElementProvider;
@@ -188,7 +188,7 @@ void CreateList() {
if (!CanCreateList)
return;
var name = askUser(dnSpy_Resources.OpenList_AskForName);
- if (string.IsNullOrEmpty(name))
+ if (string2.IsNullOrEmpty(name))
return;
var vm = new DocumentListVM(this, new DocumentList(name), false, true);
diff --git a/dnSpy/dnSpy/MainApp/MessageBoxService.cs b/dnSpy/dnSpy/MainApp/MessageBoxService.cs
index 0a0a44a4df..2109a412a4 100644
--- a/dnSpy/dnSpy/MainApp/MessageBoxService.cs
+++ b/dnSpy/dnSpy/MainApp/MessageBoxService.cs
@@ -132,7 +132,7 @@ static void CopyText(MsgBoxVM vm) {
catch (ExternalException) { }
}
- public T Ask(string labelMessage, string? defaultText, string? title, Func? converter, Func? verifier, Window? ownerWindow) {
+ public T? Ask(string labelMessage, string? defaultText, string? title, Func? converter, Func? verifier, Window? ownerWindow) {
var win = new AskDlg();
if (converter is null)
converter = CreateDefaultConverter();
@@ -146,8 +146,8 @@ public T Ask(string labelMessage, string? defaultText, string? title, Func CreateDefaultConverter() {