Skip to content

Commit

Permalink
Add bulk add functionality and improve UX of environment variable editor
Browse files Browse the repository at this point in the history
  • Loading branch information
ElektroKill committed Apr 28, 2024
1 parent 32d6199 commit 9ab622f
Show file tree
Hide file tree
Showing 11 changed files with 227 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<Grid>
Expand All @@ -44,6 +42,7 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ListView
Name="listView"
Margin="5 5 5 0"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
Expand Down Expand Up @@ -76,11 +75,13 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Button Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.AddEnvironmentVariableButton}" Style="{StaticResource DialogButton}" Command="{Binding AddEnvironmentVariableCommand}" />
<Button Grid.Row="1" Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.RemoveEnvironmentVariableButton}" Style="{StaticResource DialogButton}" Command="{Binding RemoveEnvironmentVariableCommand}" />
<Button Grid.Row="2" Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.ResetEnvironmentVariablesButton}" Style="{StaticResource DialogButton}" Command="{Binding ResetCommand}" />
<Button Grid.Row="1" Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.AddManyEnvironmentVariablesButton}" Style="{StaticResource DialogButton}" Command="{Binding AddManyEnvironmentVariablesCommand}"/>
<Button Grid.Row="2" Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.RemoveEnvironmentVariableButton}" Style="{StaticResource DialogButton}" Command="{Binding RemoveEnvironmentVariableCommand}" />
<Button Grid.Row="3" Margin="0 5 0 0" Content="{x:Static p:dnSpy_Debugger_Resources.ResetEnvironmentVariablesButton}" Style="{StaticResource DialogButton}" Command="{Binding ResetCommand}" />
</Grid>
</Grid>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/


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 {
Expand All @@ -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());

Expand All @@ -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<EnvironmentVariableVM>(SelectedItems);
SelectedItems.Clear();
foreach (var environmentVariableVm in itemsToRemove)
EnvironmentVariables.Remove(environmentVariableVm);

SelectedItems.Add(EnvironmentVariables[Math.Min(oldSelectedIndex, EnvironmentVariables.Count - 1)]);
}

void Reset() {
Expand All @@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<string, string> env) {
env = new Dictionary<string, string>();

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();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,15 @@ Are you sure you want to expand it?</value>
<data name="AddEnvironmentVariableButton" xml:space="preserve">
<value>Add</value>
</data>
<data name="AddEnvironmentVariablesMsgBoxTitle" xml:space="preserve">
<value>Add Environment Variables</value>
</data>
<data name="AddEnvironmentVariablesMsgBoxLabel" xml:space="preserve">
<value>Environment</value>
</data>
<data name="AddManyEnvironmentVariablesButton" xml:space="preserve">
<value>Add Many</value>
</data>
<data name="ExceptionDescription" xml:space="preserve">
<value>Exception description</value>
</data>
Expand All @@ -963,6 +972,9 @@ Are you sure you want to expand it?</value>
<data name="EditConditions_Title" xml:space="preserve">
<value>Edit Conditions</value>
</data>
<data name="EditEnvironmentVariables_Title" xml:space="preserve">
<value>Edit Environment Variables</value>
</data>
<data name="Button_AddCondition" xml:space="preserve">
<value>_Add Condition</value>
</data>
Expand Down Expand Up @@ -1344,6 +1356,9 @@ Do you wish to continue?</value>
<data name="InsertTracepointCommand" xml:space="preserve">
<value>Insert _Tracepoint</value>
</data>
<data name="InvalidInputString" xml:space="preserve">
<value>Invalid Input String</value>
</data>
<data name="UnwindToThisFrameCommand" xml:space="preserve">
<value>_Unwind To This Frame</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion Extensions/dnSpy.Scripting.Roslyn/Common/ScriptGlobals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string>? verifier = null, Window? ownerWindow = null) =>
public T? Ask<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string>? 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) =>
Expand Down
2 changes: 1 addition & 1 deletion dnSpy/dnSpy.Contracts.DnSpy/App/IMessageBoxService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public interface IMessageBoxService {
/// it's a valid value, else an error message to show to the user.</param>
/// <param name="ownerWindow">Owner window or null to use the main window</param>
/// <returns></returns>
T Ask<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string?>? verifier = null, Window? ownerWindow = null);
T? Ask<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string?>? verifier = null, Window? ownerWindow = null);

/// <summary>
/// Shows an exception message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ public interface IScriptGlobals : ITextPrinter {
/// it's a valid value, else an error message to show to the user.</param>
/// <param name="ownerWindow">Owner window or null to use the main window</param>
/// <returns></returns>
T Ask<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string>? verifier = null, Window? ownerWindow = null);
T? Ask<T>(string labelMessage, string? defaultText = null, string? title = null, Func<string, T>? converter = null, Func<string, string>? verifier = null, Window? ownerWindow = null);

/// <summary>
/// Shows an exception message
Expand Down
Loading

0 comments on commit 9ab622f

Please sign in to comment.