Browse Source

Keep ITextEditor in the IDE (comment out the copy in ICSharpCode.Editor)

newNRvisualizers
Daniel Grunwald 14 years ago
parent
commit
5633492481
  1. 2
      src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs
  2. 40
      src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs
  3. 8
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs
  4. 10
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs
  5. 4
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs
  6. 4
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs
  7. 4
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs
  8. 4
      src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs
  9. 2
      src/Main/Base/Project/Src/Editor/ITextEditor.cs
  10. 2
      src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs
  11. 2
      src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs
  12. 260
      src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs

2
src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs

@ -7,6 +7,7 @@ using System.Threading.Tasks;
namespace ICSharpCode.Editor namespace ICSharpCode.Editor
{ {
/*
/// <summary> /// <summary>
/// Interface for text editors. /// Interface for text editors.
/// </summary> /// </summary>
@ -64,6 +65,7 @@ namespace ICSharpCode.Editor
/// </remarks> /// </remarks>
// Task<bool> ShowLinkedElements(IEnumerable<LinkedElement> linkedElements); // Task<bool> ShowLinkedElements(IEnumerable<LinkedElement> linkedElements);
} }
*/
/// <summary> /// <summary>
/// Represents the caret in a text editor. /// Represents the caret in a text editor.

40
src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs

@ -90,22 +90,26 @@ namespace ICSharpCode.NRefactory.Documentation
if (fileName == null) if (fileName == null)
throw new ArgumentNullException("fileName"); throw new ArgumentNullException("fileName");
using (XmlTextReader xmlReader = new XmlTextReader(fileName)) { using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) {
xmlReader.XmlResolver = null; // no DTD resolving using (XmlTextReader xmlReader = new XmlTextReader(fs)) {
xmlReader.MoveToContent(); xmlReader.XmlResolver = null; // no DTD resolving
if (string.IsNullOrEmpty(xmlReader.GetAttribute("redirect"))) { xmlReader.MoveToContent();
this.fileName = fileName; if (string.IsNullOrEmpty(xmlReader.GetAttribute("redirect"))) {
ReadXmlDoc(xmlReader); this.fileName = fileName;
} else { ReadXmlDoc(xmlReader);
string redirectionTarget = GetRedirectionTarget(xmlReader.GetAttribute("redirect"));
if (redirectionTarget != null) {
Debug.WriteLine("XmlDoc " + fileName + " is redirecting to " + redirectionTarget);
using (XmlTextReader redirectedXmlReader = new XmlTextReader(redirectionTarget)) {
this.fileName = redirectionTarget;
ReadXmlDoc(redirectedXmlReader);
}
} else { } else {
throw new XmlException("XmlDoc " + fileName + " is redirecting to " + xmlReader.GetAttribute("redirect") + ", but that file was not found."); string redirectionTarget = GetRedirectionTarget(xmlReader.GetAttribute("redirect"));
if (redirectionTarget != null) {
Debug.WriteLine("XmlDoc " + fileName + " is redirecting to " + redirectionTarget);
using (FileStream redirectedFs = new FileStream(redirectionTarget, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) {
using (XmlTextReader redirectedXmlReader = new XmlTextReader(redirectedFs)) {
this.fileName = redirectionTarget;
ReadXmlDoc(redirectedXmlReader);
}
}
} else {
throw new XmlException("XmlDoc " + fileName + " is redirecting to " + xmlReader.GetAttribute("redirect") + ", but that file was not found.");
}
} }
} }
} }
@ -138,7 +142,11 @@ namespace ICSharpCode.NRefactory.Documentation
return dir + Path.DirectorySeparatorChar; return dir + Path.DirectorySeparatorChar;
} }
internal static string LookupLocalizedXmlDoc(string fileName) /// <summary>
/// Given the assembly file name, looks up the XML documentation file name.
/// Returns null if no XML documentation file is found.
/// </summary>
public static string LookupLocalizedXmlDoc(string fileName)
{ {
string xmlFileName = Path.ChangeExtension(fileName, ".xml"); string xmlFileName = Path.ChangeExtension(fileName, ".xml");
string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName;

8
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs

@ -8,6 +8,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.TypeSystem.Implementation;
using Mono.Cecil; using Mono.Cecil;
@ -44,6 +45,11 @@ namespace ICSharpCode.NRefactory.TypeSystem
/// </summary> /// </summary>
public IInterningProvider InterningProvider { get; set; } public IInterningProvider InterningProvider { get; set; }
/// <summary>
/// Gets/Sets the cancellation token used by the cecil loader.
/// </summary>
public CancellationToken CancellationToken { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether this instance stores references to the cecil objects. /// Gets a value indicating whether this instance stores references to the cecil objects.
/// </summary> /// </summary>
@ -91,6 +97,7 @@ namespace ICSharpCode.NRefactory.TypeSystem
List<CecilTypeDefinition> types = new List<CecilTypeDefinition>(); List<CecilTypeDefinition> types = new List<CecilTypeDefinition>();
foreach (ModuleDefinition module in assemblyDefinition.Modules) { foreach (ModuleDefinition module in assemblyDefinition.Modules) {
foreach (TypeDefinition td in module.Types) { foreach (TypeDefinition td in module.Types) {
this.CancellationToken.ThrowIfCancellationRequested();
if (this.IncludeInternalMembers || (td.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) { if (this.IncludeInternalMembers || (td.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) {
string name = td.FullName; string name = td.FullName;
if (name.Length == 0 || name[0] == '<') if (name.Length == 0 || name[0] == '<')
@ -719,6 +726,7 @@ namespace ICSharpCode.NRefactory.TypeSystem
public void Init(CecilLoader loader) public void Init(CecilLoader loader)
{ {
loader.CancellationToken.ThrowIfCancellationRequested();
InitModifiers(); InitModifiers();
if (typeDefinition.HasGenericParameters) { if (typeDefinition.HasGenericParameters) {

10
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs

@ -265,7 +265,15 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
} }
public virtual string Documentation { public virtual string Documentation {
get { return null; } get {
// To save memory, we don't store the documentation provider within the type,
// but use our the project content as a documentation provider:
IDocumentationProvider provider = projectContent as IDocumentationProvider;
if (provider != null)
return provider.GetDocumentation(this);
else
return null;
}
} }
public Accessibility Accessibility { public Accessibility Accessibility {

4
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs

@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
get { return memberDefinition; } get { return memberDefinition; }
} }
public override string Documentation {
get { return memberDefinition.Documentation; }
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hashCode = 0; int hashCode = 0;

4
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs

@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
get { return memberDefinition; } get { return memberDefinition; }
} }
public override string Documentation {
get { return memberDefinition.Documentation; }
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hashCode = 0; int hashCode = 0;

4
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs

@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
get { return memberDefinition; } get { return memberDefinition; }
} }
public override string Documentation {
get { return memberDefinition.Documentation; }
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hashCode = 0; int hashCode = 0;

4
src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs

@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation
get { return memberDefinition; } get { return memberDefinition; }
} }
public override string Documentation {
get { return memberDefinition.Documentation; }
}
public override int GetHashCode() public override int GetHashCode()
{ {
int hashCode = 0; int hashCode = 0;

2
src/Main/Base/Project/Src/Editor/ITextEditor.cs

@ -19,7 +19,6 @@ namespace ICSharpCode.SharpDevelop.Editor
} }
} }
/*
/// <summary> /// <summary>
/// Interface for text editors. /// Interface for text editors.
/// </summary> /// </summary>
@ -120,7 +119,6 @@ namespace ICSharpCode.SharpDevelop.Editor
/// </summary> /// </summary>
IEnumerable<ICompletionItem> GetSnippets(); IEnumerable<ICompletionItem> GetSnippets();
} }
*/
public interface ITextEditorOptions : INotifyPropertyChanged public interface ITextEditorOptions : INotifyPropertyChanged
{ {

2
src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs

@ -47,7 +47,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search
} }
set { set {
if (textEditor != null) { if (textEditor != null) {
textEditor.Caret.Position = document.OffsetToPosition(value + 1); textEditor.Caret.Location = document.GetLocation(value);
} else { } else {
currentOffset = value; currentOffset = value;
} }

2
src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs generated

@ -3,6 +3,7 @@
namespace ICSharpCode.SharpDevelop.Gui namespace ICSharpCode.SharpDevelop.Gui
{ {
/*
partial class ExtractInterfaceDialog : System.Windows.Forms.Form partial class ExtractInterfaceDialog : System.Windows.Forms.Form
{ {
/// <summary> /// <summary>
@ -267,4 +268,5 @@ namespace ICSharpCode.SharpDevelop.Gui
private System.Windows.Forms.TextBox txtInterfaceName; private System.Windows.Forms.TextBox txtInterfaceName;
private System.Windows.Forms.Label lblInterfaceName; private System.Windows.Forms.Label lblInterfaceName;
} }
*/
} }

260
src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs

@ -3,10 +3,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Project;
using System.IO; using System.IO;
//using RegistryContentPair = System.Collections.Generic.KeyValuePair<ICSharpCode.SharpDevelop.Dom.ProjectContentRegistry, ICSharpCode.SharpDevelop.Dom.IProjectContent>; using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using ICSharpCode.Core;
using ICSharpCode.NRefactory.Documentation;
using ICSharpCode.NRefactory.TypeSystem;
using Mono.Cecil;
namespace ICSharpCode.SharpDevelop namespace ICSharpCode.SharpDevelop
{ {
@ -15,151 +20,172 @@ namespace ICSharpCode.SharpDevelop
/// </summary> /// </summary>
public static class AssemblyParserService public static class AssemblyParserService
{ {
/* #region Get Assembly By File Name
static IList<ProjectContentRegistryDescriptor> registries; sealed class LoadedAssembly
static ProjectContentRegistry defaultProjectContentRegistry = new ProjectContentRegistry();
static string domPersistencePath;
internal static void Initialize()
{ {
if (registries == null) { public readonly Task<IProjectContent> ProjectContent;
registries = AddInTree.BuildItems<ProjectContentRegistryDescriptor>("/Workspace/ProjectContentRegistry", null, false); public readonly DateTime AssemblyFileLastWriteTime;
if (!string.IsNullOrEmpty(domPersistencePath)) {
Directory.CreateDirectory(domPersistencePath); public LoadedAssembly(Task<IProjectContent> projectContent, DateTime assemblyFileLastWriteTime)
defaultProjectContentRegistry.ActivatePersistence(domPersistencePath); {
} this.ProjectContent = projectContent;
this.AssemblyFileLastWriteTime = assemblyFileLastWriteTime;
} }
} }
/// <summary> static Dictionary<FileName, WeakReference> projectContentDictionary = new Dictionary<FileName, WeakReference>();
/// Gets/Sets the cache directory used for DOM persistence.
/// </summary> [ThreadStatic] static Dictionary<FileName, LoadedAssembly> up2dateProjectContents;
public static string DomPersistencePath {
get { public static IProjectContent GetAssembly(FileName fileName, CancellationToken cancellationToken = default(CancellationToken))
return domPersistencePath; {
} bool isNewTask;
set { LoadedAssembly asm = GetLoadedAssembly(fileName, cancellationToken, out isNewTask);
if (registries != null) if (isNewTask)
throw new InvalidOperationException("Cannot set DomPersistencePath after ParserService was initialized"); asm.ProjectContent.RunSynchronously();
domPersistencePath = value; return asm.ProjectContent.Result;
}
} }
public static ProjectContentRegistry DefaultProjectContentRegistry { public static Task<IProjectContent> GetAssemblyAsync(FileName fileName, CancellationToken cancellationToken = default(CancellationToken))
get { {
return defaultProjectContentRegistry; bool isNewTask;
} LoadedAssembly asm = GetLoadedAssembly(fileName, cancellationToken, out isNewTask);
if (isNewTask)
asm.ProjectContent.Start();
return asm.ProjectContent;
} }
public static ProjectContentRegistry GetRegistryForReference(ReferenceProjectItem item) /// <summary>
/// "using (AssemblyParserService.AvoidRedundantChecks())"
/// Within the using block, the AssemblyParserService will only check once per assembly if the
/// existing cached project content (if any) is up to date.
/// Any additional accesses will return that cached project content without causing an update check.
/// This applies only to the thread that called AvoidRedundantChecks() - other threads will
/// perform update checks as usual.
/// </summary>
public static IDisposable AvoidRedundantChecks()
{ {
if (item is ProjectReferenceProjectItem || item.Project == null) { if (up2dateProjectContents != null)
return defaultProjectContentRegistry; return null;
} up2dateProjectContents = new Dictionary<FileName, LoadedAssembly>();
foreach (ProjectContentRegistryDescriptor registry in registries) { return new CallbackOnDispose(delegate { up2dateProjectContents = null; });
if (registry.UseRegistryForProject(item.Project)) {
ProjectContentRegistry r = registry.Registry;
if (r != null) {
return r;
} else {
return defaultProjectContentRegistry; // fallback when registry class not found
}
}
}
return defaultProjectContentRegistry;
} }
public static IProjectContent GetExistingProjectContentForReference(ReferenceProjectItem item) static LoadedAssembly GetLoadedAssembly(FileName fileName, CancellationToken cancellationToken, out bool isNewTask)
{ {
if (item is ProjectReferenceProjectItem) { isNewTask = false;
if (((ProjectReferenceProjectItem)item).ReferencedProject == null) LoadedAssembly asm;
{ if (up2dateProjectContents != null) {
return null; if (up2dateProjectContents.TryGetValue(fileName, out asm))
return asm;
}
DateTime lastWriteTime = File.GetLastWriteTimeUtc(fileName);
lock (projectContentDictionary) {
WeakReference wr;
if (projectContentDictionary.TryGetValue(fileName, out wr)) {
asm = (LoadedAssembly)wr.Target;
if (asm != null && asm.AssemblyFileLastWriteTime == lastWriteTime) {
return asm;
}
} else {
wr = null;
}
var task = new Task<IProjectContent>(() => LoadAssembly(fileName, cancellationToken), cancellationToken);
task.Wait();
isNewTask = true;
asm = new LoadedAssembly(task, lastWriteTime);
if (wr != null) {
wr.Target = asm;
} else {
wr = new WeakReference(asm);
projectContentDictionary.Add(fileName, wr);
} }
return ParserService.GetProjectContent(((ProjectReferenceProjectItem)item).ReferencedProject); return asm;
} }
return GetRegistryForReference(item).GetExistingProjectContent(item.FileName);
} }
#endregion
public static IProjectContent GetProjectContentForReference(ReferenceProjectItem item) #region Load Assembly + XML documentation
static IProjectContent LoadAssembly(string fileName, CancellationToken cancellationToken)
{ {
if (item is ProjectReferenceProjectItem) { var param = new ReaderParameters();
if (((ProjectReferenceProjectItem)item).ReferencedProject == null) param.AssemblyResolver = new DummyAssemblyResolver();
{ AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(fileName, param);
return null;
CecilLoader l = new CecilLoader();
string xmlDocFile = FindXmlDocumentation(fileName, asm.MainModule.Runtime);
if (xmlDocFile != null) {
try {
l.DocumentationProvider = new XmlDocumentationProvider(xmlDocFile);
} catch (XmlException ex) {
LoggingService.Warn("Ignoring error while reading xml doc from " + xmlDocFile, ex);
} catch (IOException ex) {
LoggingService.Warn("Ignoring error while reading xml doc from " + xmlDocFile, ex);
} catch (UnauthorizedAccessException ex) {
LoggingService.Warn("Ignoring error while reading xml doc from " + xmlDocFile, ex);
} }
return ParserService.GetProjectContent(((ProjectReferenceProjectItem)item).ReferencedProject);
} }
return GetRegistryForReference(item).GetProjectContentForReference(item.Include, item.FileName); l.InterningProvider = new SimpleInterningProvider();
l.CancellationToken = cancellationToken;
return l.LoadAssembly(asm);
} }
/// <summary> // used to prevent Cecil from loading referenced assemblies
/// Refreshes the project content for the specified reference if required. sealed class DummyAssemblyResolver : IAssemblyResolver
/// This method does nothing if the reference is not an assembly reference, is not loaded or already is up-to-date.
/// </summary>
public static void RefreshProjectContentForReference(ReferenceProjectItem item)
{ {
if (item is ProjectReferenceProjectItem) { public AssemblyDefinition Resolve(AssemblyNameReference name)
return; {
return null;
} }
ProjectContentRegistry registry = GetRegistryForReference(item);
registry.RunLocked(
delegate {
IProjectContent rpc = GetExistingProjectContentForReference(item);
if (rpc == null) {
LoggingService.Debug("RefreshProjectContentForReference: not refreshing (rpc==null) " + item.FileName);
return;
}
if (rpc.IsUpToDate) {
LoggingService.Debug("RefreshProjectContentForReference: not refreshing (rpc.IsUpToDate) " + item.FileName);
return;
}
LoggingService.Debug("RefreshProjectContentForReference " + item.FileName);
HashSet<IProject> projectsToRefresh = new HashSet<IProject>();
HashSet<IProjectContent> unloadedReferenceContents = new HashSet<IProjectContent>();
UnloadReferencedContent(projectsToRefresh, unloadedReferenceContents, registry, rpc);
foreach (IProject p in projectsToRefresh) { public AssemblyDefinition Resolve(string fullName)
ParserService.Reparse(p, true, false); {
} return null;
}); }
}
static void UnloadReferencedContent(HashSet<IProject> projectsToRefresh, HashSet<IProjectContent> unloadedReferenceContents, ProjectContentRegistry referencedContentRegistry, IProjectContent referencedContent) public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
{ {
LoggingService.Debug("Unload referenced content " + referencedContent); return null;
List<RegistryContentPair> otherContentsToUnload = new List<RegistryContentPair>();
foreach (ProjectContentRegistryDescriptor registry in registries) {
if (registry.IsRegistryLoaded) {
foreach (IProjectContent pc in registry.Registry.GetLoadedProjectContents()) {
if (pc.ReferencedContents.Contains(referencedContent)) {
if (unloadedReferenceContents.Add(pc)) {
LoggingService.Debug("Mark dependent content for unloading " + pc);
otherContentsToUnload.Add(new RegistryContentPair(registry.Registry, pc));
}
}
}
}
} }
foreach (IProjectContent pc in ParserService.AllProjectContents) { public AssemblyDefinition Resolve(string fullName, ReaderParameters parameters)
IProject project = (IProject)pc.Project; {
if (projectsToRefresh.Contains(project)) return null;
continue;
if (pc.ReferencedContents.Remove(referencedContent)) {
LoggingService.Debug("UnloadReferencedContent: Mark project for reparsing " + project.Name);
projectsToRefresh.Add(project);
}
} }
}
static readonly string referenceAssembliesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\\Framework");
static readonly string frameworkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"Microsoft.NET\Framework");
foreach (RegistryContentPair pair in otherContentsToUnload) { static string FindXmlDocumentation(string assemblyFileName, TargetRuntime runtime)
UnloadReferencedContent(projectsToRefresh, unloadedReferenceContents, pair.Key, pair.Value); {
string fileName;
switch (runtime) {
case TargetRuntime.Net_1_0:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.0.3705", assemblyFileName));
break;
case TargetRuntime.Net_1_1:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v1.1.4322", assemblyFileName));
break;
case TargetRuntime.Net_2_0:
fileName = LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v2.0.50727", assemblyFileName))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.5"))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, "v3.0"))
?? LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v3.5\Profile\Client"));
break;
case TargetRuntime.Net_4_0:
default:
fileName = LookupLocalizedXmlDoc(Path.Combine(referenceAssembliesPath, @".NETFramework\v4.0", assemblyFileName))
?? LookupLocalizedXmlDoc(Path.Combine(frameworkPath, "v4.0.30319", assemblyFileName));
break;
} }
return fileName;
}
referencedContentRegistry.UnloadProjectContent(referencedContent); static string LookupLocalizedXmlDoc(string fileName)
{
return XmlDocumentationProvider.LookupLocalizedXmlDoc(fileName);
} }
*/ #endregion
} }
} }

Loading…
Cancel
Save