diff --git a/src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs b/src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs index 6d15eb7699..8a9003cb65 100644 --- a/src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs +++ b/src/Libraries/NRefactory/ICSharpCode.Editor/ITextEditor.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; namespace ICSharpCode.Editor { + /* /// /// Interface for text editors. /// @@ -64,6 +65,7 @@ namespace ICSharpCode.Editor /// // Task ShowLinkedElements(IEnumerable linkedElements); } + */ /// /// Represents the caret in a text editor. diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs index 70d88aa164..620139fef4 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/Documentation/XmlDocumentationProvider.cs @@ -90,22 +90,26 @@ namespace ICSharpCode.NRefactory.Documentation if (fileName == null) throw new ArgumentNullException("fileName"); - using (XmlTextReader xmlReader = new XmlTextReader(fileName)) { - xmlReader.XmlResolver = null; // no DTD resolving - xmlReader.MoveToContent(); - if (string.IsNullOrEmpty(xmlReader.GetAttribute("redirect"))) { - this.fileName = fileName; - ReadXmlDoc(xmlReader); - } else { - 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); - } + using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { + using (XmlTextReader xmlReader = new XmlTextReader(fs)) { + xmlReader.XmlResolver = null; // no DTD resolving + xmlReader.MoveToContent(); + if (string.IsNullOrEmpty(xmlReader.GetAttribute("redirect"))) { + this.fileName = fileName; + ReadXmlDoc(xmlReader); } 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; } - internal static string LookupLocalizedXmlDoc(string fileName) + /// + /// Given the assembly file name, looks up the XML documentation file name. + /// Returns null if no XML documentation file is found. + /// + public static string LookupLocalizedXmlDoc(string fileName) { string xmlFileName = Path.ChangeExtension(fileName, ".xml"); string currentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs index d7765d938d..ce111f1fba 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/CecilLoader.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using ICSharpCode.NRefactory.TypeSystem.Implementation; using Mono.Cecil; @@ -44,6 +45,11 @@ namespace ICSharpCode.NRefactory.TypeSystem /// public IInterningProvider InterningProvider { get; set; } + /// + /// Gets/Sets the cancellation token used by the cecil loader. + /// + public CancellationToken CancellationToken { get; set; } + /// /// Gets a value indicating whether this instance stores references to the cecil objects. /// @@ -91,6 +97,7 @@ namespace ICSharpCode.NRefactory.TypeSystem List types = new List(); foreach (ModuleDefinition module in assemblyDefinition.Modules) { foreach (TypeDefinition td in module.Types) { + this.CancellationToken.ThrowIfCancellationRequested(); if (this.IncludeInternalMembers || (td.Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public) { string name = td.FullName; if (name.Length == 0 || name[0] == '<') @@ -719,6 +726,7 @@ namespace ICSharpCode.NRefactory.TypeSystem public void Init(CecilLoader loader) { + loader.CancellationToken.ThrowIfCancellationRequested(); InitModifiers(); if (typeDefinition.HasGenericParameters) { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs index f9ee9a45ea..a162b1a14e 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/DefaultTypeDefinition.cs @@ -265,7 +265,15 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation } 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 { diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs index b344d94914..7aad82a16f 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedEvent.cs @@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return memberDefinition; } } + public override string Documentation { + get { return memberDefinition.Documentation; } + } + public override int GetHashCode() { int hashCode = 0; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs index d8fc104b1e..b58851a40b 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedField.cs @@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return memberDefinition; } } + public override string Documentation { + get { return memberDefinition.Documentation; } + } + public override int GetHashCode() { int hashCode = 0; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs index 9d76f93e26..697437bba6 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedMethod.cs @@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return memberDefinition; } } + public override string Documentation { + get { return memberDefinition.Documentation; } + } + public override int GetHashCode() { int hashCode = 0; diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs index 921ffc0299..e5db1acdb8 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory/TypeSystem/Implementation/SpecializedProperty.cs @@ -33,6 +33,10 @@ namespace ICSharpCode.NRefactory.TypeSystem.Implementation get { return memberDefinition; } } + public override string Documentation { + get { return memberDefinition.Documentation; } + } + public override int GetHashCode() { int hashCode = 0; diff --git a/src/Main/Base/Project/Src/Editor/ITextEditor.cs b/src/Main/Base/Project/Src/Editor/ITextEditor.cs index ad1acf1d7a..97f28ad311 100644 --- a/src/Main/Base/Project/Src/Editor/ITextEditor.cs +++ b/src/Main/Base/Project/Src/Editor/ITextEditor.cs @@ -19,7 +19,6 @@ namespace ICSharpCode.SharpDevelop.Editor } } - /* /// /// Interface for text editors. /// @@ -120,7 +119,6 @@ namespace ICSharpCode.SharpDevelop.Editor /// IEnumerable GetSnippets(); } - */ public interface ITextEditorOptions : INotifyPropertyChanged { diff --git a/src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs b/src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs index 087a9fca0a..4e3632773a 100644 --- a/src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs +++ b/src/Main/Base/Project/Src/Editor/Search/ProvidedDocumentInformation.cs @@ -47,7 +47,7 @@ namespace ICSharpCode.SharpDevelop.Editor.Search } set { if (textEditor != null) { - textEditor.Caret.Position = document.OffsetToPosition(value + 1); + textEditor.Caret.Location = document.GetLocation(value); } else { currentOffset = value; } diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs b/src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs index c47a69d7f8..59a8d9efe0 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/ExtractInterfaceDialog.Designer.cs @@ -3,6 +3,7 @@ namespace ICSharpCode.SharpDevelop.Gui { + /* partial class ExtractInterfaceDialog : System.Windows.Forms.Form { /// @@ -267,4 +268,5 @@ namespace ICSharpCode.SharpDevelop.Gui private System.Windows.Forms.TextBox txtInterfaceName; private System.Windows.Forms.Label lblInterfaceName; } + */ } diff --git a/src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs b/src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs index 908a9f18a1..a32933dec6 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/AssemblyParserService.cs @@ -3,10 +3,15 @@ using System; using System.Collections.Generic; -using ICSharpCode.Core; -using ICSharpCode.SharpDevelop.Project; using System.IO; -//using RegistryContentPair = System.Collections.Generic.KeyValuePair; +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 { @@ -15,151 +20,172 @@ namespace ICSharpCode.SharpDevelop /// public static class AssemblyParserService { - /* - static IList registries; - static ProjectContentRegistry defaultProjectContentRegistry = new ProjectContentRegistry(); - static string domPersistencePath; - - internal static void Initialize() + #region Get Assembly By File Name + sealed class LoadedAssembly { - if (registries == null) { - registries = AddInTree.BuildItems("/Workspace/ProjectContentRegistry", null, false); - if (!string.IsNullOrEmpty(domPersistencePath)) { - Directory.CreateDirectory(domPersistencePath); - defaultProjectContentRegistry.ActivatePersistence(domPersistencePath); - } + public readonly Task ProjectContent; + public readonly DateTime AssemblyFileLastWriteTime; + + public LoadedAssembly(Task projectContent, DateTime assemblyFileLastWriteTime) + { + this.ProjectContent = projectContent; + this.AssemblyFileLastWriteTime = assemblyFileLastWriteTime; } } - /// - /// Gets/Sets the cache directory used for DOM persistence. - /// - public static string DomPersistencePath { - get { - return domPersistencePath; - } - set { - if (registries != null) - throw new InvalidOperationException("Cannot set DomPersistencePath after ParserService was initialized"); - domPersistencePath = value; - } - } + static Dictionary projectContentDictionary = new Dictionary(); - public static ProjectContentRegistry DefaultProjectContentRegistry { - get { - return defaultProjectContentRegistry; - } - } + [ThreadStatic] static Dictionary up2dateProjectContents; - public static ProjectContentRegistry GetRegistryForReference(ReferenceProjectItem item) + public static IProjectContent GetAssembly(FileName fileName, CancellationToken cancellationToken = default(CancellationToken)) { - if (item is ProjectReferenceProjectItem || item.Project == null) { - return defaultProjectContentRegistry; - } - foreach (ProjectContentRegistryDescriptor registry in registries) { - 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; + bool isNewTask; + LoadedAssembly asm = GetLoadedAssembly(fileName, cancellationToken, out isNewTask); + if (isNewTask) + asm.ProjectContent.RunSynchronously(); + return asm.ProjectContent.Result; } - public static IProjectContent GetExistingProjectContentForReference(ReferenceProjectItem item) + public static Task GetAssemblyAsync(FileName fileName, CancellationToken cancellationToken = default(CancellationToken)) { - if (item is ProjectReferenceProjectItem) { - if (((ProjectReferenceProjectItem)item).ReferencedProject == null) - { - return null; - } - return ParserService.GetProjectContent(((ProjectReferenceProjectItem)item).ReferencedProject); - } - return GetRegistryForReference(item).GetExistingProjectContent(item.FileName); + bool isNewTask; + LoadedAssembly asm = GetLoadedAssembly(fileName, cancellationToken, out isNewTask); + if (isNewTask) + asm.ProjectContent.Start(); + return asm.ProjectContent; } - public static IProjectContent GetProjectContentForReference(ReferenceProjectItem item) + /// + /// "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. + /// + public static IDisposable AvoidRedundantChecks() { - if (item is ProjectReferenceProjectItem) { - if (((ProjectReferenceProjectItem)item).ReferencedProject == null) - { - return null; - } - return ParserService.GetProjectContent(((ProjectReferenceProjectItem)item).ReferencedProject); - } - return GetRegistryForReference(item).GetProjectContentForReference(item.Include, item.FileName); + if (up2dateProjectContents != null) + return null; + up2dateProjectContents = new Dictionary(); + return new CallbackOnDispose(delegate { up2dateProjectContents = null; }); } - /// - /// Refreshes the project content for the specified reference if required. - /// This method does nothing if the reference is not an assembly reference, is not loaded or already is up-to-date. - /// - public static void RefreshProjectContentForReference(ReferenceProjectItem item) + static LoadedAssembly GetLoadedAssembly(FileName fileName, CancellationToken cancellationToken, out bool isNewTask) { - if (item is ProjectReferenceProjectItem) { - return; + isNewTask = false; + LoadedAssembly asm; + if (up2dateProjectContents != null) { + if (up2dateProjectContents.TryGetValue(fileName, out asm)) + return asm; } - 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; + 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; } - LoggingService.Debug("RefreshProjectContentForReference " + item.FileName); - - HashSet projectsToRefresh = new HashSet(); - HashSet unloadedReferenceContents = new HashSet(); - UnloadReferencedContent(projectsToRefresh, unloadedReferenceContents, registry, rpc); - - foreach (IProject p in projectsToRefresh) { - ParserService.Reparse(p, true, false); - } - }); + } else { + wr = null; + } + var task = new Task(() => 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 asm; + } } + #endregion - static void UnloadReferencedContent(HashSet projectsToRefresh, HashSet unloadedReferenceContents, ProjectContentRegistry referencedContentRegistry, IProjectContent referencedContent) + #region Load Assembly + XML documentation + static IProjectContent LoadAssembly(string fileName, CancellationToken cancellationToken) { - LoggingService.Debug("Unload referenced content " + referencedContent); + var param = new ReaderParameters(); + param.AssemblyResolver = new DummyAssemblyResolver(); + AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(fileName, param); - List otherContentsToUnload = new List(); - 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)); - } - } - } + 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); } } + l.InterningProvider = new SimpleInterningProvider(); + l.CancellationToken = cancellationToken; + return l.LoadAssembly(asm); + } + + // used to prevent Cecil from loading referenced assemblies + sealed class DummyAssemblyResolver : IAssemblyResolver + { + public AssemblyDefinition Resolve(AssemblyNameReference name) + { + return null; + } - foreach (IProjectContent pc in ParserService.AllProjectContents) { - IProject project = (IProject)pc.Project; - if (projectsToRefresh.Contains(project)) - continue; - if (pc.ReferencedContents.Remove(referencedContent)) { - LoggingService.Debug("UnloadReferencedContent: Mark project for reparsing " + project.Name); - projectsToRefresh.Add(project); - } + public AssemblyDefinition Resolve(string fullName) + { + return null; } - foreach (RegistryContentPair pair in otherContentsToUnload) { - UnloadReferencedContent(projectsToRefresh, unloadedReferenceContents, pair.Key, pair.Value); + public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + return null; } - referencedContentRegistry.UnloadProjectContent(referencedContent); + public AssemblyDefinition Resolve(string fullName, ReaderParameters parameters) + { + return null; + } + } + + 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"); + + static string FindXmlDocumentation(string assemblyFileName, TargetRuntime runtime) + { + 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; + } + + static string LookupLocalizedXmlDoc(string fileName) + { + return XmlDocumentationProvider.LookupLocalizedXmlDoc(fileName); } - */ + #endregion } }