/*
Copyright (C) 2014-2019 de4dot@gmail.com
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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using dnSpy.Contracts.App;
using dnSpy.Contracts.MVVM;
using dnSpy.Contracts.Scripting;
using dnSpy.Contracts.Scripting.Roslyn;
using dnSpy.Contracts.Text;
using dnSpy.Contracts.Text.Classification;
using dnSpy.Contracts.Text.Editor;
using dnSpy.Contracts.Utilities;
using dnSpy.Roslyn.Text;
using dnSpy.Roslyn.Text.Classification;
using dnSpy.Scripting.Roslyn.Properties;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Scripting.Hosting;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
namespace dnSpy.Scripting.Roslyn.Common {
sealed class UserScriptOptions {
public readonly List References = new List();
public readonly List Imports = new List();
public readonly List LibPaths = new List();
public readonly List LoadPaths = new List();
}
abstract class ScriptControlVM : ViewModelBase, IReplCommandHandler, IScriptGlobalsHelper {
internal const string CMD_PREFIX = "#";
static readonly string TEXTFILES_FILTER = $"{dnSpy_Scripting_Roslyn_Resources.TextFiles} (*.txt)|*.txt|{dnSpy_Scripting_Roslyn_Resources.AllFiles} (*.*)|*.*";
protected abstract string TextFilenameNoExtension { get; }
protected abstract string CodeFilenameNoExtension { get; }
protected abstract string CodeFileExtension { get; }
protected abstract string CodeFilterText { get; }
public string ResetToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Script_ToolTip_Reset, null);
public string ClearScreenToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Script_ToolTip_ClearScreen, dnSpy_Scripting_Roslyn_Resources.ShortCutKeyCtrlL);
public string HistoryPreviousToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Script_ToolTip_HistoryPrevious, dnSpy_Scripting_Roslyn_Resources.ShortCutKeyAltUp);
public string HistoryNextToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Script_ToolTip_HistoryNext, dnSpy_Scripting_Roslyn_Resources.ShortCutKeyAltDown);
public string SaveToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Repl_Save_ToolTip, dnSpy_Scripting_Roslyn_Resources.ShortCutKeyCtrlS);
public string WordWrapToolTip => ToolTipHelper.AddKeyboardShortcut(dnSpy_Scripting_Roslyn_Resources.Repl_WordWrap_ToolTip, dnSpy_Scripting_Roslyn_Resources.ShortCutKeyCtrlECtrlW);
public ICommand ResetCommand => new RelayCommand(a => Reset(), a => CanReset);
public ICommand ClearCommand => new RelayCommand(a => ReplEditor.ClearScreen(), a => ReplEditor.CanClearScreen);
public ICommand SaveCommand => new RelayCommand(a => SaveText(), a => CanSaveText);
public ICommand HistoryPreviousCommand => new RelayCommand(a => ReplEditor.SelectPreviousCommand(), a => ReplEditor.CanSelectPreviousCommand);
public ICommand HistoryNextCommand => new RelayCommand(a => ReplEditor.SelectNextCommand(), a => ReplEditor.CanSelectNextCommand);
public bool CanReset => hasInitialized && (execState is null || !execState.IsInitializing);
public bool CanSaveText => ReplEditor.CanSaveText;
public void SaveText() => ReplEditor.SaveText(TextFilenameNoExtension, "txt", TEXTFILES_FILTER);
public bool CanSaveCode => ReplEditor.CanSaveCode;
public void SaveCode() => ReplEditor.SaveCode(CodeFilenameNoExtension, CodeFileExtension, CodeFilterText);
public void Reset(bool loadConfig = true) {
if (!CanReset)
return;
if (execState is not null) {
execState.CancellationTokenSource.Cancel();
try {
execState.Globals.RaiseScriptResetting();
}
catch {
// Ignore buggy script exceptions
}
execState.CancellationTokenSource.Dispose();
}
isResetting = true;
execState = null;
ReplEditor.Reset();
isResetting = false;
ReplEditor.OutputPrintLine(dnSpy_Scripting_Roslyn_Resources.ResettingExecutionEngine, BoxedTextColor.ReplOutputText);
InitializeExecutionEngine(loadConfig, false);
}
bool isResetting;
public bool WordWrap {
get => (ReplEditor.TextView.Options.WordWrapStyle() & WordWrapStyles.WordWrap) != 0;
set {
if (value)
WordWrapStyle |= WordWrapStyles.WordWrap;
else
WordWrapStyle &= ~WordWrapStyles.WordWrap;
}
}
WordWrapStyles WordWrapStyle {
get => ReplEditor.TextView.Options.WordWrapStyle();
set {
var oldWordWrapStyle = WordWrapStyle;
if (value == oldWordWrapStyle)
return;
ReplEditor.TextView.Options.SetOptionValue(DefaultTextViewOptions.WordWrapStyleId, value);
OnPropertyChanged(nameof(WordWrapStyle));
if (((oldWordWrapStyle ^ value) & WordWrapStyles.WordWrap) != 0)
OnPropertyChanged(nameof(WordWrap));
replSettings.WordWrapStyle = value;
}
}
bool ShowLineNumbers {
get => ReplEditor.TextView.Options.IsLineNumberMarginEnabled();
set {
if (ShowLineNumbers == value)
return;
ReplEditor.TextView.Options.SetOptionValue(DefaultTextViewHostOptions.LineNumberMarginId, value);
replSettings.ShowLineNumbers = value;
}
}
public IReplEditor ReplEditor { get; }
public IEnumerable ScriptCommands => toScriptCommand.Values;
readonly Dictionary toScriptCommand;
IEnumerable CreateScriptCommands() {
yield return new ClearCommand();
yield return new HelpCommand();
yield return new ResetCommand();
}
readonly Dispatcher dispatcher;
readonly RoslynClassificationTypes roslynClassificationTypes;
readonly IClassificationType defaultClassificationType;
readonly ReplSettings replSettings;
protected ScriptControlVM(IReplEditor replEditor, ReplSettings replSettings, IServiceLocator serviceLocator) {
dispatcher = Dispatcher.CurrentDispatcher;
this.replSettings = replSettings;
this.replSettings.PropertyChanged += ReplSettings_PropertyChanged;
ReplEditor = replEditor;
ReplEditor.CommandHandler = this;
this.serviceLocator = serviceLocator;
ReplEditor.TextView.Options.OptionChanged += Options_OptionChanged;
var themeClassificationTypeService = serviceLocator.Resolve();
roslynClassificationTypes = RoslynClassificationTypes.GetClassificationTypeInstance(themeClassificationTypeService);
defaultClassificationType = themeClassificationTypeService.GetClassificationType(TextColor.Error);
toScriptCommand = new Dictionary(StringComparer.Ordinal);
foreach (var sc in CreateScriptCommands()) {
foreach (var name in sc.Names)
toScriptCommand.Add(name, sc);
}
WordWrapStyle = replSettings.WordWrapStyle;
ShowLineNumbers = replSettings.ShowLineNumbers;
}
void ReplSettings_PropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(replSettings.WordWrapStyle))
WordWrapStyle = replSettings.WordWrapStyle;
else if (e.PropertyName == nameof(replSettings.ShowLineNumbers))
ShowLineNumbers = replSettings.ShowLineNumbers;
}
protected abstract string Logo { get; }
protected abstract string Help { get; }
protected abstract Script Create(string code, ScriptOptions options, Type globalsType, InteractiveAssemblyLoader? assemblyLoader);
public void OnVisible() {
if (hasInitialized)
return;
hasInitialized = true;
ReplEditor.OutputPrintLine(Logo, BoxedTextColor.ReplOutputText);
InitializeExecutionEngine(true, true);
}
bool hasInitialized;
void Options_OptionChanged(object? sender, EditorOptionChangedEventArgs e) {
if (e.OptionId == DefaultTextViewOptions.WordWrapStyleName) {
OnPropertyChanged(nameof(WordWrap));
replSettings.WordWrapStyle = WordWrapStyle;
}
else if (e.OptionId == DefaultTextViewHostOptions.LineNumberMarginName)
replSettings.ShowLineNumbers = ShowLineNumbers;
}
public bool IsCommand(string text) {
if (ParseScriptCommand(text) is not null)
return true;
return IsCompleteSubmission(text);
}
protected abstract bool IsCompleteSubmission(string text);
sealed class ExecState {
public ScriptOptions? ScriptOptions;
public readonly CancellationTokenSource CancellationTokenSource;
public readonly CancellationToken CancellationToken;
public readonly ScriptGlobals Globals;
public ScriptState