添加项目文件。

This commit is contained in:
ShrlAlgo
2025-09-16 16:06:41 +08:00
parent 0e7807b826
commit 98c65ceb3d
922 changed files with 1009489 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
<Window
x:Class="ScriptPad.AddReferenceWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ScriptPad"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="程序集引用"
Width="600"
Height="400"
Background="{DynamicResource MaterialDesign.Brush.Background}"
TextElement.Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Szmedi.RvKits;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<ListBox Name="ReferenceList" Grid.Row="0" />
<StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
<Button Name="okBtn" Click="OkBtn_Click" Content="确定" />
<Button Name="cancelBtn" Click="CancelBtn_Click" Content="取消" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace ScriptPad
{
/// <summary>
/// AddReferenceWindow.xaml 的交互逻辑
/// </summary>
public partial class AddReferenceWindow : Window
{
private CsScript script;
private List<string> list;
public AddReferenceWindow(CsScript script)
{
this.script = script;
InitializeComponent();
this.list = script.GetReferences().OfType<Microsoft.CodeAnalysis.PortableExecutableReference>().Select(p => p.FilePath).ToList();
var path = typeof(object).Assembly.Location;
path = Path.GetDirectoryName(path);
var dir = new DirectoryInfo(path);
var dic = dir.GetFiles().Where(p => p.Extension == ".dll").ToDictionary(p => p.Name);
foreach (var item in dic.Keys.OrderBy(p => p.Substring(0, p.Length - 4)))
{
var cb = new CheckBox();
cb.Content = item;
cb.IsThreeState = false;
cb.ToolTip = dic[item].FullName;
if (list.Contains(dic[item].FullName))
cb.IsChecked = true;
ReferenceList.Items.Add(cb);
}
}
private void CancelBtn_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void OkBtn_Click(object sender, RoutedEventArgs e)
{
foreach (var item in ReferenceList.Items)
{
var cb = item as CheckBox;
var path = cb.ToolTip as string;
if(list.Contains(path) && !cb.IsChecked.Value)
{
this.script.RemoveReference(path);
}
if(!list.Contains(path) && cb.IsChecked.Value)
{
this.script.AddReference(path);
}
}
this.Close();
}
}
}

View File

@@ -0,0 +1,137 @@
<UserControl
x:Class="ScriptPad.CodeEditor"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:editor="http://icsharpcode.net/sharpdevelop/avalonedit"
xmlns:local="clr-namespace:ScriptPad"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
d:DesignHeight="450"
d:DesignWidth="800"
Background="{DynamicResource MaterialDesign.Brush.Background}"
TextElement.Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Szmedi.RvKits;component/ScriptPad/resource/ImageResource.xaml" />
<ResourceDictionary Source="pack://application:,,,/Szmedi.RvKits;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
<RowDefinition Height="20" MaxHeight="500" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<ToolBar Grid.Row="0" ToolBarTray.IsLocked="True">
<ToolBar.Template>
<ControlTemplate TargetType="ToolBar">
<ToolBarPanel Background="{TemplateBinding Background}" IsItemsHost="True" />
</ControlTemplate>
</ToolBar.Template>
<ToolBar.Resources>
<Style TargetType="{x:Type Image}">
<Setter Property="Height" Value="16" />
<Setter Property="Margin" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Opacity" Value="0.30" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToolBar.Resources>
<Button
Name="OpenFile_btn"
Margin="3"
Click="OpenFile_btn_Click"
ToolTip="从文件加载">
<md:PackIcon Kind="File" />
<!--<Image Source="/ScriptPad;component/Resource/Images/OpenFile_16x.png" />-->
</Button>
<Button Name="SaveFile_btn" Click="SaveFile_btn_Click" ToolTip="保存脚本">
<!--<Image Source="/ScriptPad;component/Resource/Images/Save_16x.png" />-->
<md:PackIcon Kind="ContentSave" />
</Button>
<Button Command="Undo" ToolTip="撤销">
<md:PackIcon Kind="Undo" />
</Button>
<Button Command="Redo" ToolTip="重做">
<md:PackIcon Kind="Redo" />
</Button>
<Separator />
<Button Name="runBtn" Click="runBtn_Click" ToolTip="运行">
<md:PackIcon Kind="Play" />
</Button>
<Separator />
<Button Name="formatBtn" Click="formatBtn_Click">
<md:PackIcon Kind="FormatText" />
</Button>
<Button Name="CommentBtn" Click="CommentBtn_Click">
<md:PackIcon Kind="CommentOutline" />
</Button>
<Button Name="UnCommentBtn" Click="UnCommentBtn_Click">
<md:PackIcon Kind="CommentOffOutline" />
</Button>
<Separator />
<Button Name="AddReference" Click="Reference_Click" ToolTip="引用">
<md:PackIcon Kind="Attachment" />
</Button>
</ToolBar>
<editor:TextEditor
x:Name="codeEditor"
Grid.Row="1"
Background="{DynamicResource MaterialDesign.Brush.Background}"
FontFamily="Consolas"
FontSize="14"
Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
ShowLineNumbers="True"
SyntaxHighlighting="C#">
<editor:TextEditor.ContextMenu>
<ContextMenu>
<MenuItem Command="Find" Header="查找">
<MenuItem.Icon>
<md:PackIcon Kind="Search" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="Cut" Header="剪切">
<MenuItem.Icon>
<md:PackIcon Kind="ContentCut" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="Copy" Header="复制">
<MenuItem.Icon>
<md:PackIcon Kind="ContentCopy" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Command="Paste" Header="粘贴">
<MenuItem.Icon>
<md:PackIcon Kind="ContentPaste" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</editor:TextEditor.ContextMenu>
</editor:TextEditor>
<GridSplitter Grid.Row="2" Height="6" HorizontalAlignment="Stretch" />
<RichTextBox
Name="outTextbox"
Grid.Row="3"
FontFamily="Consolas"
FontSize="14"
VerticalScrollBarVisibility="Visible">
<FlowDocument Name="flowDocument" />
</RichTextBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,300 @@
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
using ICSharpCode.AvalonEdit.Highlighting;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using ScriptPad.Editor;
using ScriptPad.Roslyn;
using System.Reactive;
using System.Reactive.Linq;
namespace ScriptPad
{
/// <summary>
/// CodeEditor.xaml 的交互逻辑
/// </summary>
public partial class CodeEditor : UserControl
{
private CompletionWindow completionWindow;
private CancellationTokenSource completionCancellation;
private TextMarkerService markerService;
public TextContainer Container { get; private set; }
private static int script;
public CsScript Script;
public CodeEditor(string path = null)
{
InitializeComponent();
// 需要提升效率, 暂时不用
codeEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
if (string.IsNullOrEmpty(path))
{
script++;
Script = new CsScript("script" + script, ScriptGlobals.templateScript);
}
else
{
Script = CsScript.CreateFromFile(path);
}
this.codeEditor.Text = Script.Text;
ICSharpCode.AvalonEdit.Search.SearchPanel.Install(codeEditor);
//codeEditor.TextArea.IndentationStrategy = new CSIndentationStrategy();
var csFoldingStrategy = new CSharpFoldingStrategy();
var foldingManager = FoldingManager.Install(codeEditor.TextArea);
this.codeEditor.GetTextChangeds().StartWith(EventArgs.Empty)
.Throttle(TimeSpan.FromMilliseconds(200))
.ObserveOnDispatcher()
.Subscribe(p => csFoldingStrategy.UpdateFoldings(foldingManager, codeEditor.Document));
this.codeEditor.TextArea.GetTextEnterings()
.Where(p => p.Text.Length > 0 && !IsAllowedLanguageLetter(p.Text[0]))
.Subscribe(p => completionWindow?.CompletionList.RequestInsertion(p));
// 需要提升效率
markerService = new TextMarkerService(codeEditor, this.Script);
this.Container = new TextContainer(this.codeEditor.Document);
Observable.FromEventPattern(this.Container, nameof(this.Container.TextChanged))
.Subscribe(p => this.Script.UpdateText((p.Sender as TextContainer).CurrentText));
}
/// <summary>
/// 关闭代码编辑窗口
/// </summary>
internal void Close()
{
if (Script.IsChanged)
{
var result = MessageBox.Show("文件已修改, 是否保存?", "保存", MessageBoxButton.YesNoCancel);
if (result == MessageBoxResult.OK)
{
Script.Save();
}
if (result == MessageBoxResult.Cancel)
{
throw new TaskCanceledException();
}
}
}
private bool TryCompleteBracket(TextCompositionEventArgs e)
{
if (e.Text.Last() == '{')
{
codeEditor.Document.Insert(codeEditor.CaretOffset, "}");
codeEditor.CaretOffset--;
return true;
}
else if (e.Text.Last() == '(')
{
codeEditor.Document.Insert(codeEditor.CaretOffset, ")");
codeEditor.CaretOffset--;
return true;
}
if (e.Text.Last() == '[')
{
codeEditor.Document.Insert(codeEditor.CaretOffset, "]");
codeEditor.CaretOffset--;
return true;
}
return false;
}
private async void textEditor_TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
try
{
if (TryCompleteBracket(e))
return;
char? triggerChar = e.Text.FirstOrDefault();
completionCancellation = new CancellationTokenSource();
var position = codeEditor.CaretOffset;
var cancellationToken = completionCancellation.Token;
var isTrigger = Service.ScriptCompletionService.IsTrigger(Script.ID, this.Container.CurrentText, position, triggerChar);
if (completionWindow == null && (triggerChar == null || triggerChar == '.' || IsAllowedLanguageLetter(triggerChar.Value)))
{
var list = await Service.ScriptCompletionService.GetCompletionsAsync(Script.ID, position);
if (!list.Any())
{
return;
}
cancellationToken.ThrowIfCancellationRequested();
completionWindow = new CompletionWindow(codeEditor.TextArea)
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
MaxWidth = 340,
Width = 340,
MaxHeight = 206,
Height = 206
};
foreach (var item in list)
{
completionWindow.CompletionList.CompletionData.Add(item);
}
if (triggerChar == null || IsAllowedLanguageLetter(triggerChar.Value))
{
var word = GetWord(position);
completionWindow.StartOffset = word.Item1;
completionWindow.CompletionList.SelectItem(word.Item2);
}
completionWindow.Show();
completionWindow.Closed += (s2, e2) =>
{
completionWindow = null;
};
}
}
catch (OperationCanceledException)
{
}
}
private Tuple<int, string> GetWord(int position)
{
var wordStart = TextUtilities.GetNextCaretPosition(codeEditor.TextArea.Document, position, LogicalDirection.Backward, CaretPositioningMode.WordStart);
var text = codeEditor.TextArea.Document.GetText(wordStart, position - wordStart);
return new Tuple<int, string>(wordStart, text);
}
private static bool IsAllowedLanguageLetter(char character)
{
return TextUtilities.GetCharacterClass(character) == CharacterClass.IdentifierPart;
}
private void OpenFile_btn_Click(object sender, RoutedEventArgs e)
{
System.Windows.Forms.OpenFileDialog openFile = new System.Windows.Forms.OpenFileDialog()
{
Filter = "C# 脚本文件(*.csx)|*.csx"
};
if (openFile.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
this.Script = CsScript.CreateFromFile(openFile.FileName);
this.codeEditor.Text = Script.Text;
this.markerService.Script = Script;
}
}
private async void formatBtn_Click(object sender, RoutedEventArgs e)
{
var changes = await Script.Format();
if (changes.Any())
{
changes = changes.Reverse();
codeEditor.Document.BeginUpdate();
foreach (var item in changes)
{
codeEditor.Document.Replace(item.Span.Start, item.Span.Length, item.NewText, OffsetChangeMappingType.RemoveAndInsert);
}
codeEditor.Document.EndUpdate();
}
}
private async void runBtn_Click(object sender, RoutedEventArgs e)
{
await Run();
}
public async Task Run()
{
try
{
flowDocument.Blocks.Clear();
flowDocument.Blocks.Add(new Paragraph());
Console.SetOut(new DelegateTextWriter((flowDocument.Blocks.First() as Paragraph).Inlines.Add));
await ScriptRunner.Run(Script);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private void SaveFile_btn_Click(object sender, RoutedEventArgs e)
{
Script.Save();
}
private void CommentBtn_Click(object sender, RoutedEventArgs e)
{
var document = codeEditor.Document;
var startLine = document.GetLineByOffset(codeEditor.SelectionStart);
var endLine = document.GetLineByOffset(codeEditor.SelectionStart + codeEditor.SelectionLength);
document.BeginUpdate();
var line = startLine;
while (line != null && line.LineNumber <= endLine.LineNumber)
{
var whitespace = TextUtilities.GetLeadingWhitespace(document, line);
if (line.Length > whitespace.Length)
{
var text = document.GetText(whitespace) + "//";
document.Replace(whitespace.Offset, whitespace.Length, text, OffsetChangeMappingType.RemoveAndInsert);
}
line = line.NextLine;
}
document.EndUpdate();
}
private void UnCommentBtn_Click(object sender, RoutedEventArgs e)
{
var document = codeEditor.Document;
var startLine = document.GetLineByOffset(codeEditor.SelectionStart);
var endLine = document.GetLineByOffset(codeEditor.SelectionStart + codeEditor.SelectionLength);
document.BeginUpdate();
var line = startLine;
while (line != null && line.LineNumber <= endLine.LineNumber)
{
var whitespace = TextUtilities.GetLeadingWhitespace(document, line);
if (line.Length > whitespace.Length + 2)
{
var text = document.GetText(whitespace.EndOffset, 2);
if (text == "//")
document.Remove(whitespace.EndOffset, 2);
}
line = line.NextLine;
}
document.EndUpdate();
}
private void Reference_Click(object sender, RoutedEventArgs e)
{
new ReferenceWindow(this.Script).ShowDialog();
}
}
}

View File

@@ -0,0 +1,195 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using ScriptPad.Roslyn;
using Microsoft.CodeAnalysis.Text;
namespace ScriptPad
{
public class CsScript
{
/// <summary>
/// 是否更改过
/// </summary>
public bool IsChanged { get; private set; }
public readonly DocumentId ID;
public string Name { get; set; }
public string Path { get; set; }
public string Text { get; private set; }
public IReadOnlyCollection<PortableExecutableReference> References => Workspace.GetReferences(this.ID).OfType<PortableExecutableReference>().ToList();
/// <summary>
/// 创建具有指定名字和内容的脚本对象
/// </summary>
/// <param name="name"></param>
/// <param name="text"></param>
public CsScript(string name, string text)
{
this.Name = name;
this.Text = text;
if (text == null)
this.Text = "";
ID = Workspace.AddProjectWithDocument(name, this.Text);
IsChanged = false;
}
private ScriptingWorkspace Workspace => ScriptingWorkspace.GetInstance();
/// <summary>
/// 从文件创建 Script 对象
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static CsScript CreateFromFile(string path)
{
var info = new FileInfo(path);
var text = File.ReadAllLines(path);
var i = 0;
List<string> references = new List<string>();
for (; i < text.Length; i++)
{
if (text[i].StartsWith("#r "))
{
references.Add(text[i].Trim().Substring(4, text[i].Length - 5)); // #r "
}
else
{
break;
}
}
var code = new StringBuilder();
for (; i < text.Length; i++)
{
code.Append(text[i]);
code.Append("\r\n");
}
var script = new CsScript(info.Name, code.ToString());
foreach (var item in references)
{
script.AddReference(item);
}
script.Path = path;
return script;
}
/// <summary>
/// 添加引用
/// </summary>
/// <param name="path">文件路径</param>
public void AddReference(string path)
{
Workspace.AddReference(path, this.ID);
}
/// <summary>
/// 删除引用
/// </summary>
/// <param name="path"></param>
public void RemoveReference(string path)
{
Workspace.RemoveReference(path, this.ID);
}
/// <summary>
/// 整理脚本
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<Microsoft.CodeAnalysis.Text.TextChange>> Format()
{
var document = Workspace.GetDocument(ID);
var formattedDocument = await Microsoft.CodeAnalysis.Formatting.Formatter.FormatAsync(document).ConfigureAwait(false);
return await formattedDocument.GetTextChangesAsync(document);
}
/// <summary>
/// 获取诊断信息
/// </summary>
/// <returns></returns>
public async Task<ImmutableArray<Diagnostic>> GetDiagnostics()
{
var project = Workspace.GetProject(ID);
var compilation = await project.GetCompilationAsync();
return compilation.GetDiagnostics();
}
/// <summary>
/// 获取脚本内容
/// </summary>
/// <returns></returns>
public async Task<string> GetScriptText()
{
var text = await Workspace.GetDocument(ID).GetTextAsync();
return text.ToString();
}
/// <summary>
/// 获取脚本内容
/// </summary>
/// <returns></returns>
public string ToCode()
{
var code = new StringBuilder();
foreach (var item in References.Select(p => p.FilePath).Except(ScriptGlobals.InitAssemblies.Select(p => p.Location)))
{
var refstr = $"#r \"{item}\"\r\n";
code.Append(refstr);
}
code.Append(GetScriptText().Result);
return code.ToString();
}
/// <summary>
/// 保存
/// </summary>
public void Save()
{
if (string.IsNullOrEmpty(Path))
{
var dialog = new SaveFileDialog()
{
Filter = "C# 脚本|*.csx",
Title = "保存文件"
};
if (dialog.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(dialog.FileName, ToCode());
this.Path = dialog.FileName;
}
}
else
{
File.WriteAllText(Path, ToCode());
}
IsChanged = false;
}
public void UpdateText(Document document)
{
IsChanged = true;
Workspace.TryApplyChanges(document.Project.Solution);
}
public void UpdateText(SourceText sourceText)
{
Workspace.UpdateText(ID,sourceText);
}
internal IEnumerable<MetadataReference> GetReferences()
{
return Workspace.GetReferences(this.ID);
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
namespace ScriptPad
{
public class DelegateTextWriter : TextWriter
{
private readonly Action<string> appendTextAction;
public DelegateTextWriter(Action<string> appendTextAction) : base(CultureInfo.CurrentCulture)
{
this.appendTextAction = appendTextAction;
}
public override Encoding Encoding => Encoding.UTF8;
public override void Write(char value)
{
appendTextAction(value.ToString());
}
public override void Write(char[] buffer, int index, int count)
{
if (index != 0 || count != buffer.Length)
{
buffer = buffer.Skip(index).Take(count).ToArray();
}
appendTextAction(new string(buffer));
}
public override void Write(string value)
{
appendTextAction(value);
}
public override object InitializeLifetimeService()
{
return null;
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ReactiveUI />
</Weavers>

View File

@@ -0,0 +1,45 @@
<Window
x:Class="ScriptPad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ScriptPad"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
Title="C#脚本"
Width="800"
Height="450"
Background="{DynamicResource MaterialDesign.Brush.Background}"
Closing="Window_Closing"
TextElement.Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
UseLayoutRounding="True"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Szmedi.RvKits;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
<RoutedCommand x:Key="runCommand" />
</ResourceDictionary>
</Window.Resources>
<Window.InputBindings>
<KeyBinding Key="F5" Command="{StaticResource runCommand}" />
</Window.InputBindings>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource runCommand}" Executed="RunCommand_Executed" />
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TabControl
Name="tc"
HorizontalContentAlignment="Left"
Background="{DynamicResource MaterialDesign.Brush.Background}"
Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
SelectionChanged="TabControl_SelectionChanged"
Style="{StaticResource MaterialDesignFilledUniformTabControl}">
<TabItem Header="+" ToolTip="新建文件" />
</TabControl>
</Grid>
</Window>

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ScriptPad
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//if (string.IsNullOrWhiteSpace(Properties.Settings.Default.WorkFolder))
//{
// Properties.Settings.Default.WorkFolder = Environment.CurrentDirectory;
//}
//var dir = new System.IO.DirectoryInfo(Properties.Settings.Default.WorkFolder);
//treeRoot.Header = dir.FullName;
//treeRoot.IsExpanded = true;
//foreach (var item in dir.EnumerateFiles())
//{
// if (item.Extension == ".csx")
// {
// this.treeRoot.Items.Add(new TreeViewItem() { Header = item.Name });
// }
//}
AddEditor();
}
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tc.SelectedIndex == tc.Items.Count - 1)
{
AddEditor();
}
}
private void AddEditor(string path = null)
{
var editor = new CodeEditor();
var tb = new TextBlock();
tb.Text = editor.Script.Name;
tb.ContextMenu = new ContextMenu();
var menuItem = new MenuItem();
menuItem.Header = "关闭";
tb.ContextMenu.Items.Add(menuItem);
var tabitem = (TabItem)tc.Items[tc.Items.Count - 1];
tabitem.Header = tb;// editor.Script.Name;
tabitem.Content = editor;
menuItem.Click += (sender, e) =>
{
CloseTab(tabitem);
};
var tab = new TabItem() { Header = "+" };
tc.Items.Add(tab);
}
/// <summary>
/// 关闭标签页
/// </summary>
/// <param name="tab"></param>
private void CloseTab(TabItem tab)
{
try
{
var codeEditor = tab.Content as CodeEditor;
codeEditor.Close();
tc.Items.Remove(tab);
if (tc.Items.Count == 1)
{
AddEditor();
}
}
catch (TaskCanceledException)
{
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var names = tc.Items.Cast<TabItem>()
.Select(tab => tab.Content as CodeEditor)
.Where(editor => editor != null)
.Select(editor => editor.Script.Name)
.ToList();
}
private async void RunCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
var tabItem = this.tc.SelectedItem as TabItem;
var codeEditor = tabItem.Content as CodeEditor;
await codeEditor.Run();
}
}
}

View File

@@ -0,0 +1,55 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Nice3point.Revit.Toolkit.External;
using RevitTools.Utils.Revit;
using ScriptPad;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Szmedi.RvKits.ScriptPad
{
[Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
[Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
public class OpenScriptPadCommand : ExternalCommand
{
private static ExEventUtils ExEventUtils;
public override void Execute()
{
var assembly = typeof(OpenScriptPadCommand).Assembly;
var dir = Path.GetDirectoryName(assembly.Location);
ExEventUtils ??= ExEventUtils.GetOrCreate();
ScriptGlobals.GlobalObject = new RevitGlobals()
{
commandData = ExternalCommandData,
uiApp = UiApplication,
ExEventUtils = ExEventUtils
};
ScriptGlobals.StartScript = "ExEventUtils.InvokeAsync(main)";
ScriptGlobals.templateScript = File.ReadAllText(Path.Combine(dir,"Templates", "template.txt"));
ScriptGlobals.InitAssemblies = new List<Assembly>
{
typeof(object).Assembly, // mscorelib
typeof(Uri).Assembly, // System
typeof(Enumerable).Assembly, // System.Core
typeof(Element).Assembly, // Autodesk.Revit.DB
typeof(UIApplication).Assembly // Autodesk.Revit.UI
};
var window = new MainWindow();
new System.Windows.Interop.WindowInteropHelper(window).Owner = Autodesk.Windows.ComponentManager.ApplicationWindow;
window.Show();
}
}
}

View File

@@ -0,0 +1,67 @@
<Window
x:Class="ScriptPad.ReferenceWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ScriptPad"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="引用"
Width="600"
Height="450"
Background="{DynamicResource MaterialDesign.Brush.Background}"
TextElement.Foreground="{DynamicResource MaterialDesign.Brush.Foreground}"
mc:Ignorable="d">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Szmedi.RvKits;component/WPFUI.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<ListBox Name="ReferenceList" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal">
<Button
Name="addBtn"
Width="auto"
Height="auto"
Click="AddBtn_Click"
Content="管理程序集引用" />
<Button
Name="browseBtn"
Width="auto"
Height="auto"
Click="BrowseBtn_Click"
Content="浏览..." />
<Button
Name="deleteBtn"
Width="auto"
Height="auto"
Click="DeleteBtn_Click"
Content="删除" />
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button
Name="CloseBtn"
Grid.Column="5"
Width="auto"
Height="auto"
Click="Close_Click"
Content="关闭" />
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;
namespace ScriptPad
{
/// <summary>
/// ReferenceWindow.xaml 的交互逻辑
/// </summary>
public partial class ReferenceWindow : Window
{
private CsScript script;
public ReferenceWindow(CsScript script)
{
InitializeComponent();
this.script = script;
ShowReferences();
}
private void BrowseBtn_Click(object sender, RoutedEventArgs e)
{
System.Windows.Forms.OpenFileDialog openFileDialog = new System.Windows.Forms.OpenFileDialog()
{
Filter = "dll文件|*.dll|exe文件|*.exe",
Multiselect = true
};
if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
foreach (var item in openFileDialog.FileNames)
{
try
{
script.AddReference(item);
}
catch(Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
ShowReferences();
}
}
private void ShowReferences()
{
ReferenceList.Items.Clear();
foreach (var item in script.References)
{
var path = item.FilePath;
ReferenceList.Items.Add(path);
}
}
private void DeleteBtn_Click(object sender, RoutedEventArgs e)
{
var items = ReferenceList.SelectedItems.OfType<string>().ToList();
foreach (var item in items)
{
ReferenceList.Items.Remove(item);
script.RemoveReference(item);
}
if(items.Count >0)
{
ShowReferences();
}
}
private void Close_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void AddBtn_Click(object sender, RoutedEventArgs e)
{
new AddReferenceWindow(this.script).ShowDialog();
ShowReferences();
}
}
}

View File

@@ -0,0 +1,13 @@
using Autodesk.Revit.UI;
using RevitTools.Utils.Revit;
namespace Szmedi.RvKits.ScriptPad
{
public class RevitGlobals
{
public ExternalCommandData commandData;
public UIApplication uiApp;
public ExEventUtils ExEventUtils;
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ScriptPad
{
public static class ScriptGlobals
{
public static object GlobalObject = new object();
public static string StartScript;
public static string templateScript;
public static List<Assembly> InitAssemblies;
}
}

View File

@@ -0,0 +1,137 @@
using System.Collections.Generic;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;
namespace ScriptPad.Editor
{
public abstract class FoldingStrategy
{
public string startToken { get; private set; }
public string endToken { get; private set; }
public FoldingStrategy(string start, string end)
{
this.startToken = start;
this.endToken = end;
}
public virtual void SetName(TextDocument document, NewFolding folding)
{
return;
}
public virtual void OnMatchStart(TextDocument textDocument, int index)
{
return;
}
}
public class BraceFoldingStrategy : FoldingStrategy
{
public BraceFoldingStrategy() : base("{", "}")
{
}
}
public class RegionFoldingStrategy : FoldingStrategy
{
public RegionFoldingStrategy() : base("#region", "#endregion")
{
}
}
public class CSharpFoldingStrategy
{
private List<FoldingStrategy> foldingStrategies;
public CSharpFoldingStrategy()
{
foldingStrategies = new List<FoldingStrategy>()
{
new BraceFoldingStrategy(),
new RegionFoldingStrategy()
};
}
public void UpdateFoldings(FoldingManager manager, TextDocument document)
{
int firstErrorOffset;
IEnumerable<NewFolding> newFoldings = CreateNewFoldings(document, out firstErrorOffset);
manager.UpdateFoldings(newFoldings, firstErrorOffset);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
{
firstErrorOffset = -1;
return CreateNewFoldings(document);
}
/// <summary>
/// Create <see cref="NewFolding"/>s for the specified document.
/// </summary>
public IEnumerable<NewFolding> CreateNewFoldings(ITextSource document)
{
List<NewFolding> newFoldings = new List<NewFolding>();
Stack<int> startOffsets = new Stack<int>();
int lastNewLineOffset = 0;
for (int i = 0; i < document.TextLength; i++)
{
var c = document.GetCharAt(i);
if (Is(document.Text, i, "\r\n"))
{
lastNewLineOffset++;
continue;
}
foreach (var item in foldingStrategies)
{
if (Is(document.Text, i, item.startToken))
{
startOffsets.Push(i);
i += item.endToken.Length;
}
else if (Is(document.Text, i, item.endToken) && startOffsets.Count > 0)
{
var startOffset = startOffsets.Pop();
if (startOffset < i)
{
var folding = new NewFolding(startOffset, i + 1);
SetName(folding);
newFoldings.Add(folding);
}
i += item.endToken.Length;
}
}
}
newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
return newFoldings;
}
private bool Is(string source, int index, string str)
{
if (index + str.Length > source.Length)
{
return false;
}
for (var i = 0; i < str.Length; i++)
{
if (source[index + i] != str[i])
{
return false;
}
}
return true;
}
protected virtual void SetName(NewFolding folding)
{
return;
}
}
}

View File

@@ -0,0 +1,52 @@
using ICSharpCode.AvalonEdit.Document;
using System;
namespace ScriptPad
{
class CSIndentationStrategy : ICSharpCode.AvalonEdit.Indentation.IIndentationStrategy
{
string IndentationString = " ";
public void IndentLine(ICSharpCode.AvalonEdit.Document.TextDocument document, DocumentLine line)
{
if (document == null)
throw new ArgumentNullException("document");
if (line == null)
throw new ArgumentNullException("line");
DocumentLine previousLine = line.PreviousLine;
if (previousLine != null)
{
ISegment indentationSegment = TextUtilities.GetWhitespaceAfter(document, previousLine.Offset);
string indentation = document.GetText(indentationSegment);
if (previousLine.EndOffset > 0)
{
var c = document.GetCharAt(previousLine.EndOffset - 1);
if (c == '{')
{
//indentation += IndentationString;
indentationSegment = TextUtilities.GetWhitespaceAfter(document, line.Offset);
document.Replace(indentationSegment.Offset, indentationSegment.Length, indentation, OffsetChangeMappingType.RemoveAndInsert);
}
else
{
indentationSegment = TextUtilities.GetWhitespaceAfter(document, line.Offset);
document.Replace(indentationSegment.Offset, indentationSegment.Length, indentation, OffsetChangeMappingType.RemoveAndInsert);
}
}
}
}
public void IndentLines(ICSharpCode.AvalonEdit.Document.TextDocument document, int beginLine, int endLine)
{
}
}
}

View File

@@ -0,0 +1,44 @@
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.CodeAnalysis;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Editing;
using Microsoft.CodeAnalysis.Tags;
using System.Linq;
namespace ScriptPad.Editor
{
public class CodeCompletionData : ICompletionData
{
public CodeCompletionData(DocumentId id, Microsoft.CodeAnalysis.Completion.CompletionItem item)
{
this.Text = item.DisplayText;
image = new Lazy<ImageSource>(() => ImageResource.GetImage(item.Tags));
description = new Lazy<object>(() => Service.ScriptCompletionService.GetDescriptionAsync(id, item).Result);
this.Content = Text;
}
public string Text { get; }
public object Description { get => description.Value; }
public object Content { get; set; }
public ImageSource Image { get => image.Value; }
public double Priority => 0;
public void Complete(TextArea textArea, ISegment completionSegment, EventArgs insertionRequestEventArgs)
{
textArea.Document.Replace(completionSegment, Text);
}
private Lazy<object> description;
private Lazy<ImageSource> image;
}
}

View File

@@ -0,0 +1,29 @@
using System.Reactive.Linq;
using System;
using ICSharpCode.AvalonEdit.Editing;
using System.Windows.Input;
using ICSharpCode.AvalonEdit;
namespace ScriptPad.Editor
{
static class CodeEditorReactiveExtensions
{
public static IObservable<TextCompositionEventArgs> GetTextEnterings(this TextArea textArea)
{
return Observable.FromEventPattern<TextCompositionEventArgs>(textArea, nameof(textArea.TextEntering))
.Select(p => p.EventArgs);
}
public static IObservable<TextCompositionEventArgs> GetTextEntereds(this TextArea textArea)
{
return Observable.FromEventPattern<TextCompositionEventArgs>(textArea, nameof(textArea.TextEntered))
.Select(p => p.EventArgs);
}
public static IObservable<EventArgs> GetTextChangeds(this TextEditor editor)
{
return Observable.FromEventPattern<EventArgs>(editor, nameof(editor.TextChanged))
.Select(p => p.EventArgs);
}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using ICSharpCode.AvalonEdit.Document;
using Microsoft.CodeAnalysis.Text;
namespace ScriptPad.Editor
{
public class TextContainer : SourceTextContainer , IDisposable
{
public TextContainer(TextDocument document)
{
this.TextDocument = document;
this.currentText = SourceText.From(document.GetText(0, document.TextLength));
document.Changed += Document_Changed;
}
private void Document_Changed(object sender, DocumentChangeEventArgs e)
{
var old = currentText;
var remove = new TextChange(new TextSpan(e.Offset, e.RemovalLength), "");
currentText = currentText.WithChanges(remove);
var insert = new TextChange(new TextSpan(e.Offset, 0), e.InsertedText.Text);
currentText = currentText.WithChanges(insert);
TextChanged?.Invoke(this, new Microsoft.CodeAnalysis.Text.TextChangeEventArgs(old, currentText, remove, insert));
}
public TextDocument TextDocument { get; private set; }
private SourceText currentText;
public override SourceText CurrentText => currentText;
public override event EventHandler<Microsoft.CodeAnalysis.Text.TextChangeEventArgs> TextChanged;
public void Dispose()
{
this.TextDocument.Changed -= Document_Changed;
}
}
}

View File

@@ -0,0 +1,20 @@
using ICSharpCode.AvalonEdit.Document;
using System.Windows.Media;
namespace ScriptPad.Editor
{
public class TextMarker : TextSegment
{
public TextMarker(int startOffset, int length, string message, Color markerColor)
{
StartOffset = startOffset;
Length = length;
Message = message;
MarkerColor = markerColor;
}
public string Message { get; }
public Color MarkerColor { get; }
}
}

View File

@@ -0,0 +1,196 @@
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Rendering;
using Microsoft.CodeAnalysis;
using ScriptPad.Roslyn;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace ScriptPad.Editor
{
public class TextMarkerService : IBackgroundRenderer
{
private readonly TextEditor textEditor;
public CsScript Script { get; set; }
private readonly TextSegmentCollection<TextMarker> markers;
private ToolTip toolTip;
DispatcherTimer Timer = new DispatcherTimer();
public TextMarkerService(TextEditor textEditor, CsScript script)
{
this.textEditor = textEditor;
this.Script = script;
this.markers = new TextSegmentCollection<TextMarker>(textEditor.Document);
TextView textView = textEditor.TextArea.TextView;
textView.BackgroundRenderers.Add(this);
textView.MouseHover += TextViewMouseHover;
textView.MouseHoverStopped += TextViewMouseHoverStopped;
textView.VisualLinesChanged += TextViewVisualLinesChanged;
Timer.Interval = TimeSpan.FromSeconds(2);
Timer.Tick += Timer_Tick;
Timer.Start();
}
private async void Timer_Tick(object sender, EventArgs e)
{
var document = textEditor.Document;
Clear();
var diagnostics = await Script.GetDiagnostics();
var listItems = diagnostics
.Where(x => x.Severity != DiagnosticSeverity.Hidden)
.Select(ErrorListItem.CreateErrorListItem);
foreach (var item in listItems)
{
if (item.ErrorSeverity == ErrorSeverity.Info)
continue;
var startOffset = document.GetOffset(new TextLocation(item.StartLine + 1, item.StartColumn + 1));
var endOffset = document.GetOffset(new TextLocation(item.EndLine + 1, item.EndColumn + 1));
if (item.ErrorSeverity == ErrorSeverity.Error)
Create(startOffset, endOffset - startOffset, item.Description, Colors.Red);
else
Create(startOffset, endOffset - startOffset, item.Description, Colors.DarkGreen);
}
}
public KnownLayer Layer => KnownLayer.Selection;
public void Create(int offset, int length, string message, Color color)
{
var marker = new TextMarker(offset, length, message, color);
markers.Add(marker);
textEditor.TextArea.TextView.Redraw(marker);
}
public void Clear()
{
var oldMarkers = markers.ToArray();
markers.Clear();
foreach (TextMarker m in oldMarkers)
{
textEditor.TextArea.TextView.Redraw(m);
}
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
if (!markers.Any() || !textView.VisualLinesValid)
{
return;
}
var visualLines = textView.VisualLines;
if (visualLines.Count == 0)
{
return;
}
int viewStart = visualLines.First().FirstDocumentLine.Offset;
int viewEnd = visualLines.Last().LastDocumentLine.EndOffset;
foreach (TextMarker marker in markers.FindOverlappingSegments(viewStart, viewEnd - viewStart))
{
foreach (Rect rect in BackgroundGeometryBuilder.GetRectsForSegment(textView, marker))
{
Point startPoint = rect.BottomLeft;
Point endPoint = rect.BottomRight;
var pen = new Pen(new SolidColorBrush(marker.MarkerColor), 1);
pen.Freeze();
const double offset = 2.5;
int count = Math.Max((int)((endPoint.X - startPoint.X) / offset) + 1, 4);
var geometry = new StreamGeometry();
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(startPoint, false, false);
ctx.PolyLineTo(CreatePoints(startPoint, offset, count).ToArray(), true, false);
}
geometry.Freeze();
drawingContext.DrawGeometry(Brushes.Transparent, pen, geometry);
break;
}
}
}
public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements)
{
}
private void TextViewMouseHover(object sender, MouseEventArgs e)
{
if (!markers.Any()) { return; }
TextViewPosition? position = textEditor.TextArea.TextView.GetPositionFloor(
e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset);
if (position.HasValue)
{
TextLocation logicalPosition = position.Value.Location;
int offset = textEditor.Document.GetOffset(logicalPosition);
var markersAtOffset = markers.FindSegmentsContaining(offset);
TextMarker marker = markersAtOffset.LastOrDefault(m => !string.IsNullOrEmpty(m.Message));
if (marker != null)
{
if (toolTip == null)
{
toolTip = new ToolTip();
toolTip.Closed += (s2, e2) => toolTip = null;
toolTip.PlacementTarget = textEditor;
toolTip.Content = new TextBlock
{
Text = marker.Message,
TextWrapping = TextWrapping.Wrap
};
toolTip.IsOpen = true;
e.Handled = true;
}
}
}
}
private void TextViewMouseHoverStopped(object sender, MouseEventArgs e)
{
if (toolTip != null)
{
toolTip.IsOpen = false;
e.Handled = true;
}
}
private void TextViewVisualLinesChanged(object sender, EventArgs e)
{
//if (toolTip != null)
//{
// toolTip.IsOpen = false;
//}
}
private static IEnumerable<Point> CreatePoints(Point start, double offset, int count)
{
for (int i = 0; i < count; i++)
{
yield return new Point(start.X + (i * offset), start.Y - ((i + 1) % 2 == 0 ? offset : 0));
}
}
}
}

View File

@@ -0,0 +1,80 @@
using Microsoft.CodeAnalysis.Tags;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Windows;
using System.Windows.Media;
namespace ScriptPad
{
public class ImageResource
{
private static ResourceDictionary ResourceDictionary = new Lazy<ResourceDictionary>(() =>
{
var dic = new ResourceDictionary();
dic.Source = new Uri("/Szmedi.RvKits;component/ScriptPad/resource/ImageResource.xaml", UriKind.Relative);
return dic;
}, true).Value;
private static ImageSource GetImage(string resourceKey)
{
return (ImageSource)ResourceDictionary[resourceKey];
}
public static ImageSource GetImage(ImmutableArray<string> tags)
{
var tag = tags.FirstOrDefault();
if (tag == null) { return null; }
switch (tag)
{
case WellKnownTags.Class:
return GetImage("ClassImageSource");
case WellKnownTags.Constant:
return GetImage("ConstantImageSource");
case WellKnownTags.Delegate:
return GetImage("DelegateImageSource");
case WellKnownTags.Enum:
return GetImage("EnumImageSource");
case WellKnownTags.EnumMember:
return GetImage("EnumItemImageSource");
case WellKnownTags.Event:
return GetImage("EventImageSource");
case WellKnownTags.ExtensionMethod:
return GetImage("ExtensionMethodImageSource");
case WellKnownTags.Field:
return GetImage("FieldImageSource");
case WellKnownTags.Interface:
return GetImage("InterfaceImageSource");
case WellKnownTags.Keyword:
return GetImage("KeywordImageSource");
case WellKnownTags.Method:
return GetImage("MethodImageSource");
case WellKnownTags.Module:
return GetImage("ModuleImageSource");
case WellKnownTags.Namespace:
return GetImage("NamespaceImageSource");
case WellKnownTags.Property:
return GetImage("PropertyImageSource");
case WellKnownTags.Structure:
return GetImage("StructureImageSource");
}
return null;
}
}
}

View File

@@ -0,0 +1,103 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 工具栏图标 -->
<Image
x:Key="NewFileImage"
Width="16"
Height="16"
Source="Images/NewFile_16x.png" />
<Image
x:Key="AddReference"
Width="16"
Height="16"
Source="Images/AddReference_16x.png" />
<Image
x:Key="CommentImage"
Width="16"
Height="16"
Source="Images/CommentCode_16x.png" />
<Image
x:Key="FarmatImage"
Width="16"
Height="16"
Source="Images/FormatDocument_16x.png" />
<Image
x:Key="OpenFileImage"
Width="16"
Height="16"
Source="Images/OpenFile_16x.png" />
<Image
x:Key="OpenFolderImage"
Width="16"
Height="16"
Source="Images/OpenFolder_16x.png" />
<Image
x:Key="RanImage"
Width="16"
Height="16"
Source="Images/Run_16x.png" />
<Image
x:Key="RedoImage"
Width="16"
Height="16"
Source="Images/Redo_16x.png" />
<Image
x:Key="SaveImage"
Width="16"
Height="16"
Source="Images/Save_16x.png" />
<!--<Image
x:Key="StopImage"
Width="16"
Height="16"
Source="Images/Stop_16x.png" />-->
<Image
x:Key="UndoImage"
Width="16"
Height="16"
Source="Images/Undo_16x.png" />
<Image
x:Key="UnCommentImage"
Width="16"
Height="16"
Source="Images/UnCommentCode_16x.png" />
<!-- 右键菜单图标 -->
<Image
x:Key="Search"
Width="16"
Height="16"
Source="Images/Search_16x.png" />
<Image
x:Key="CutImage"
Width="16"
Height="16"
Source="Images/Cut_16x.png" />
<Image
x:Key="CopyImage"
Width="16"
Height="16"
Source="Images/Copy_16x.png" />
<Image
x:Key="PasteImage"
Width="16"
Height="16"
Source="Images/Paste_16x.png" />
<!-- 自动提示图标 -->
<!--<ImageSource x:Key="AbstractClassImageSource">Images/AbstraceClass_16x.png</ImageSource>-->
<ImageSource x:Key="ClassImageSource">Images/Class_16x.png</ImageSource>
<ImageSource x:Key="ConstantImageSource">Images/Constant_16x.png</ImageSource>
<ImageSource x:Key="DelegateImageSource">Images/Delegate_16x.png</ImageSource>
<ImageSource x:Key="EnumImageSource">Images/Enumerator_16x.png</ImageSource>
<ImageSource x:Key="EnumItemImageSource">Images/EnumItem_16x.png</ImageSource>
<!--<ImageSource x:Key="ExtersionMethodImageSource">Images/ExtersionMethod_16x.png</ImageSource>-->
<ImageSource x:Key="FieldImageSource">Images/Field_16x.png</ImageSource>
<ImageSource x:Key="InterfaceImageSource">Images/Interface_16x.png</ImageSource>
<ImageSource x:Key="KeywordImageSource">Images/KeywordSnippet_16x.png</ImageSource>
<ImageSource x:Key="MethodImageSource">Images/Method_16x.png</ImageSource>
<ImageSource x:Key="ModuleImageSource">Images/Module_16x.png</ImageSource>
<ImageSource x:Key="NamespaceImageSource">Images/Namespace_16x.png</ImageSource>
<ImageSource x:Key="PropertyImageSource">Images/Property_16x.png</ImageSource>
<ImageSource x:Key="StructureImageSource">Images/Structure_16x.png</ImageSource>
</ResourceDictionary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -0,0 +1,51 @@
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace RevitTools.Utils.Revit
{
/// <summary>
/// 外部事件处理程序
/// </summary>
public class ExEventHandle : IExternalEventHandler
{
/// <summary>
/// 创建具有指定名字的外部事件处理程序
/// </summary>
/// <param name="name"></param>
public ExEventHandle(string name)
{
this.name = name;
}
private readonly string name;
/// <summary>
/// 工作项队列
/// </summary>
internal readonly ConcurrentQueue<ExEventWorkItem> ExEventQueue = new ConcurrentQueue<ExEventWorkItem>();
/// <summary>
/// 执行外部事件函数
/// </summary>
/// <param name="app"></param>
public void Execute(UIApplication app)
{
while (ExEventQueue.TryDequeue(out ExEventWorkItem workItem))
{
workItem.InvokeAction(app);
}
}
/// <summary>
/// 获取字符串标识
/// </summary>
/// <returns></returns>
public string GetName()
{
return name;
}
}
}

View File

@@ -0,0 +1,78 @@
using Autodesk.Revit.UI;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace RevitTools.Utils.Revit
{
/// <summary>
/// 外部事件封装类
/// </summary>
public class ExEventUtils
{
/// <summary>
/// 创建外部事件
/// </summary>
/// <param name="handleName"></param>
private ExEventUtils(string handleName)
{
this.Handle = new ExEventHandle(handleName);
this.ExEvent = ExternalEvent.Create(this.Handle);
}
/// <summary>
/// 外部事件实例
/// </summary>
private readonly ExternalEvent ExEvent;
/// <summary>
/// 外部事件处理程序
/// </summary>
private readonly ExEventHandle Handle;
/// <summary>
/// 在外部事件中执行函数, 可以等待函数执行完成
/// </summary>
/// <param name="action">要执行的函数</param>
/// <param name="transactionGroupName">事务组名</param>
/// <returns></returns>
public async Task InvokeAsync(Action<UIApplication> action)
{
var workItem = new ExEventWorkItem(action);
Handle.ExEventQueue.Enqueue(workItem);
ExEvent.Raise();
await workItem.CompletionSource.Task;
}
/// <summary>
/// 在外部事件中执行函数, 并且可以等待函数执行完成
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="func">要执行的函数</param>
/// <returns></returns>
public async Task<T> InvokeAsync<T>(Func<UIApplication, T> func)
{
object func1(UIApplication app)
{
return func(app);
}
var workItem = new ExEventWorkItem(func1);
Handle.ExEventQueue.Enqueue(workItem);
ExEvent.Raise();
var result = await workItem.CompletionSource.Task;
return (T)result;
}
/// <summary>
/// 获取或创建外部事件处理实例
/// </summary>
/// <param name="name">外部事件处理程序名称</param>
/// <returns></returns>
public static ExEventUtils GetOrCreate(string name = "ScriptPadExEventHandle")
{
return EventUtils.GetOrAdd(name, new ExEventUtils(name));
}
private static readonly ConcurrentDictionary<string, ExEventUtils> EventUtils = new ConcurrentDictionary<string, ExEventUtils>();
}
}

View File

@@ -0,0 +1,64 @@
using Autodesk.Revit.UI;
using System;
using System.Threading.Tasks;
namespace RevitTools.Utils.Revit
{
/// <summary>
/// 外部事件工作项
/// </summary>
internal class ExEventWorkItem
{
/// <summary>
/// 异步阻塞信号
/// </summary>
public TaskCompletionSource<object> CompletionSource { get; protected set; }
/// <summary>
/// 在外部事件中执行的, 带有一个返回值的函数
/// </summary>
private Action<UIApplication> Action { get; set; }
/// <summary>
/// 在外部事件中执行的, 带有一个返回值的函数
/// </summary>
private Func<UIApplication, object> Func { get; set; }
/// <summary>
/// 执行外部事件函数
/// </summary>
/// <param name="application"></param>
public void InvokeAction(UIApplication application)
{
try
{
if (Action != null)
{
Action.Invoke(application);
this.CompletionSource.TrySetResult(null);
}
else
{
var result = Func.Invoke(application);
this.CompletionSource.TrySetResult(result);
}
}
catch (Exception ex)
{
this.CompletionSource.TrySetException(ex);
}
}
public ExEventWorkItem(Action<UIApplication> action)
{
this.CompletionSource = new TaskCompletionSource<object>();
this.Action = action;
}
public ExEventWorkItem(Func<UIApplication, object> action)
{
this.CompletionSource = new TaskCompletionSource<object>();
this.Func = action;
}
}
}

View File

@@ -0,0 +1,70 @@
using Microsoft.CodeAnalysis;
namespace ScriptPad.Roslyn
{
/// <summary>
/// 错误项
/// </summary>
public class ErrorListItem
{
public ErrorListItem(ErrorSeverity errorSeverity, string description, int startLine, int startColumn, int endLine, int endColumn)
{
ErrorSeverity = errorSeverity;
Description = description;
StartLine = startLine;
StartColumn = startColumn;
EndLine = endLine;
EndColumn = endColumn;
}
/// <summary>
/// 严重性
/// </summary>
public ErrorSeverity ErrorSeverity { get; }
/// <summary>
/// 描述
/// </summary>
public string Description { get; }
/// <summary>
/// 起始行
/// </summary>
public int StartLine { get; }
/// <summary>
/// 起始列
/// </summary>
public int StartColumn { get; }
/// <summary>
/// 结束行
/// </summary>
public int EndLine { get; }
/// <summary>
/// 结束列
/// </summary>
public int EndColumn { get; }
public static ErrorListItem CreateErrorListItem(Diagnostic diagnostic)
{
var mappedSpan = diagnostic.Location.GetMappedLineSpan();
ErrorSeverity errorSeverity;
if (diagnostic.Severity == DiagnosticSeverity.Error)
{
errorSeverity = ErrorSeverity.Error;
}
else if (diagnostic.Severity == DiagnosticSeverity.Warning)
{
errorSeverity = ErrorSeverity.Warning;
}
else
{
errorSeverity = ErrorSeverity.Info;
}
return new ErrorListItem(errorSeverity, diagnostic.GetMessage(), mappedSpan.Span.Start.Line, mappedSpan.Span.Start.Character,
mappedSpan.Span.End.Line, mappedSpan.Span.End.Character);
}
}
}

View File

@@ -0,0 +1,23 @@
namespace ScriptPad.Roslyn
{
/// <summary>
/// 错误严重性
/// </summary>
public enum ErrorSeverity
{
/// <summary>
/// 信息
/// </summary>
Info,
/// <summary>
/// 警告
/// </summary>
Warning,
/// <summary>
/// 错误
/// </summary>
Error
}
}

View File

@@ -0,0 +1,24 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting;
namespace ScriptPad.Roslyn
{
public class ScriptRunner
{
public static async Task Run(CsScript Script)
{
var options = ScriptOptions.Default;
options = options.AddReferences(Script.GetReferences());
options = options.AddReferences(ScriptGlobals.InitAssemblies);
var script = CSharpScript.Create(await Script.GetScriptText(), options, globalsType: ScriptGlobals.GlobalObject.GetType());
if (!string.IsNullOrWhiteSpace(ScriptGlobals.StartScript))
script = script.ContinueWith(ScriptGlobals.StartScript, options);
await script.RunAsync(ScriptGlobals.GlobalObject);
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using System.IO;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
using System.Reflection;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using System.Text;
using Microsoft.CodeAnalysis.Host.Mef;
using System.Composition.Hosting;
namespace ScriptPad.Roslyn
{
internal class ScriptingWorkspace : Workspace
{
private ScriptingWorkspace(HostServices hostServices) : base(hostServices, WorkspaceKind.Interactive)
{
}
public Document GetDocument(DocumentId id)
{
return CurrentSolution.GetDocument(id);
}
public DocumentId AddProjectWithDocument(string documentFileName, string text)
{
var fileName = Path.GetFileName(documentFileName);
var name = Path.GetFileNameWithoutExtension(documentFileName);
var projectId = ProjectId.CreateNewId();
var references = ScriptGlobals.InitAssemblies.Distinct().Select(CreateReference).ToList();
var projectInfo = ProjectInfo.Create(
projectId,
VersionStamp.Default,
name,
name,
LanguageNames.CSharp,
isSubmission: true,
compilationOptions: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithScriptClassName(name),
metadataReferences: references,
parseOptions: new CSharpParseOptions(languageVersion: LanguageVersion.Latest));
OnProjectAdded(projectInfo);
var documentId = DocumentId.CreateNewId(projectId);
var documentInfo = DocumentInfo.Create(
documentId,
fileName,
sourceCodeKind: SourceCodeKind.Script,
loader: TextLoader.From(TextAndVersion.Create(SourceText.From(text, Encoding.UTF8), VersionStamp.Create())));
OnDocumentAdded(documentInfo);
return documentId;
}
public Project GetProject(DocumentId id)
{
return CurrentSolution.GetProject(id.ProjectId);
}
public void RemoveProject(DocumentId id)
{
OnProjectRemoved(id.ProjectId);
}
public void UpdateText(DocumentId documentId, string text)
{
OnDocumentTextChanged(documentId, SourceText.From(text, Encoding.UTF8), PreservationMode.PreserveValue);
}
public void UpdateText(DocumentId documentid, SourceText text)
{
OnDocumentTextChanged(documentid, text, PreservationMode.PreserveValue);
}
public Task<IReadOnlyList<Diagnostic>> GetDiagnosticsAsync(DocumentId documentId, CancellationToken cancellationToken)
{
return Task.Run(async () =>
{
var project = CurrentSolution.GetProject(documentId.ProjectId);
var compilation = await project.GetCompilationAsync(cancellationToken);
return (IReadOnlyList<Diagnostic>)compilation.GetDiagnostics(cancellationToken);
}, cancellationToken);
}
public override bool CanApplyChange(ApplyChangesKind feature)
{
return base.CanApplyChange(feature);
}
private MetadataReference CreateReference(Assembly assembly)
{
return MetadataReference.CreateFromFile(assembly.Location);
}
public void AddReference(string path, DocumentId id)
{
var references = GetReferences(id).OfType<PortableExecutableReference>();
if (references.Any(p => p.FilePath == path))
return;
PortableExecutableReference data = MetadataReference.CreateFromFile(path);
var pid = GetDocument(id).Project.Id;
OnMetadataReferenceAdded(pid, data);
}
public void RemoveReference(string path, DocumentId id)
{
var project = GetDocument(id).Project;
var data = project.MetadataReferences.FirstOrDefault(p => (p as PortableExecutableReference).FilePath == path);
OnMetadataReferenceRemoved(project.Id, data);
}
public IEnumerable<MetadataReference> GetReferences(DocumentId id)
{
var project = GetDocument(id).Project;
return project.MetadataReferences;
}
private static Lazy<ScriptingWorkspace> instance = new Lazy<ScriptingWorkspace>(() =>
{
var compositionHost = new ContainerConfiguration().WithAssemblies(MefHostServices.DefaultAssemblies).CreateContainer();
var hostService = MefHostServices.Create(compositionHost);
return new ScriptingWorkspace(hostService);
}, true);
public static ScriptingWorkspace GetInstance()
{
return instance.Value;
}
}
}

View File

@@ -0,0 +1,42 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ScriptPad.Service
{
public class ScriptCompletionService
{
public static async Task<IEnumerable<Editor.CodeCompletionData>> GetCompletionsAsync(DocumentId id, int position)
{
var document = Roslyn.ScriptingWorkspace.GetInstance().GetDocument(id);
var completionService = CompletionService.GetService(document);
var list = await completionService.GetCompletionsAsync(document, position);
if (list == null || !list.Items.Any())
return new List<Editor.CodeCompletionData>();
return list.Items.Select(p => new Editor.CodeCompletionData(id, p));
}
public static async Task<string> GetDescriptionAsync(DocumentId id, CompletionItem completionItem)
{
var document = Roslyn.ScriptingWorkspace.GetInstance().GetDocument(id);
var completionService = CompletionService.GetService(document);
var des = await completionService.GetDescriptionAsync(document, completionItem);
return des.Text;
}
public static bool IsTrigger(DocumentId id, SourceText text, int position, char? triggerChar)
{
var document = Roslyn.ScriptingWorkspace.GetInstance().GetDocument(id);
var completionService = CompletionService.GetService(document);
if(triggerChar != null)
return completionService.ShouldTriggerCompletion(text, position, CompletionTrigger.CreateInsertionTrigger(triggerChar.Value));
return completionService.ShouldTriggerCompletion(text, position, CompletionTrigger.Invoke);
}
}
}