From 5eb2d97b3356450fd150ff692f64607fcc4b141e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 11 Feb 2012 20:28:44 +0100 Subject: [PATCH] Register parsed files in project content. --- TODOnewNR.txt | 12 +- .../Src/AvalonEditViewContent.cs | 2 +- .../AvalonEdit.AddIn/Src/CodeEditor.cs | 4 +- .../AvalonEdit.AddIn/Src/QuickClassBrowser.cs | 4 + .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Project/Src/Gui/Dialogs/NewFileDialog.cs | 2 +- .../Project/Src/Gui/Pads/DefinitionViewPad.cs | 2 +- .../Commands/ReferenceFolderNodeCommands.cs | 4 +- .../Project/Src/Project/CompilableProject.cs | 9 ++ .../Base/Project/Src/Project/CustomTool.cs | 2 +- .../Src/Project/MSBuildBasedProject.cs | 10 -- .../Project/Src/Services/File/OpenedFile.cs | 2 +- .../Src/Services/ParserService/Fusion.cs | 96 -------------- .../ParserService/ParseProjectContent.cs | 125 ++++++++---------- .../ParserService/ParsedFileListener.cs | 18 +++ .../Services/ParserService/ParserService.cs | 109 +++++++++++---- .../Base/Project/Src/Util/ExtensionMethods.cs | 10 ++ 17 files changed, 197 insertions(+), 215 deletions(-) create mode 100644 src/Main/Base/Project/Src/Services/ParserService/ParsedFileListener.cs diff --git a/TODOnewNR.txt b/TODOnewNR.txt index 97c86d918a..2f83a96dfe 100644 --- a/TODOnewNR.txt +++ b/TODOnewNR.txt @@ -6,20 +6,22 @@ Commented code, needs to be ported and re-enabled: Context Actions (EditorContext etc.) RefactoringService FindReferencesAndRenameHelper - NamespaceRefactoringsService + NamespaceRefactoringService RefactoringMenuBuilder TaskService.UpdateCommentTags CodeManipulation.cs (btw, I think this doesn't belong into AvalonEdit.AddIn - more a job for a language binding) +--> See TODO-list on Google Docs + Stuff that was renamed/moved: ICSharpCode.SharpDevelop.Dom -> the type system and resolvers now are part of ICSharpCode.NRefactory - IDocument, ITextEditor -> moved to ICSharpCode.Editor assembly + IDocument -> moved to ICSharpCode.NRefactory.Editor IClass -> ITypeDefinition ICompilationUnit -> IParsedFile - ITextBuffer -> ITextSource (in ICSharpCode.Editor assembly) + ITextBuffer -> ITextSource (in ICSharpCode.NRefactory.Editor) IReturnType -> ITypeReference (unresolved) or IType (resolved) - Location -> TextLocation or AstLocation. Use AstLocation only when directly working with the AST, and TextLocation otherwise. - TextLocation -> moved to ICSharpCode.Editor assembly + Location -> TextLocation in ICSharpCode.NRefactory + TextLocation -> moved to ICSharpCode.NRefactory Functionality changes: The result of a parser run (ParseInformation) now may contain a fully parsed AST. diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs index 91b754444f..60f3829d69 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/AvalonEditViewContent.cs @@ -176,7 +176,7 @@ namespace ICSharpCode.AvalonEdit.AddIn codeEditor.FileName = newFileName; - ParserService.ParseAsync(file.FileName, codeEditor.Document); + ParserService.ParseAsync(file.FileName, codeEditor.Document).FireAndForget(); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs index 16e18ad45b..039bf84baa 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs @@ -475,7 +475,7 @@ namespace ICSharpCode.AvalonEdit.AddIn // Immediately parse on enter. // This ensures we have up-to-date CC info about the method boundary when a user // types near the end of a method. - ParserService.ParseAsync(this.FileName, this.Document.CreateSnapshot()); + ParserService.ParseAsync(this.FileName, this.Document.CreateSnapshot()).FireAndForget(); } } } @@ -534,7 +534,7 @@ namespace ICSharpCode.AvalonEdit.AddIn ParseInformation parseInfo = ParserService.GetCachedParseInformation(this.FileName); if (parseInfo == null) { // if parse info is not yet available, start parsing on background - ParserService.ParseAsync(this.FileName, primaryTextEditorAdapter.Document); + ParserService.ParseAsync(this.FileName, primaryTextEditorAdapter.Document).FireAndForget(); // we'll receive the result using the ParseInformationUpdated event } ParseInformationUpdated(parseInfo); diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/QuickClassBrowser.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/QuickClassBrowser.cs index 9ccec4ea9a..5231fe8c73 100644 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/QuickClassBrowser.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/QuickClassBrowser.cs @@ -166,6 +166,8 @@ namespace ICSharpCode.AvalonEdit.AddIn void AddClasses(IEnumerable classes) { foreach (var c in classes) { + if (c.IsSynthetic) + continue; classItems.Add(new EntityItem(c)); AddClasses(c.NestedTypes); } @@ -252,6 +254,8 @@ namespace ICSharpCode.AvalonEdit.AddIn if (compoundClass != null) { var ambience = project.GetAmbience(); foreach (var member in compoundClass.Members) { + if (member.IsSynthetic) + continue; bool isInSamePart = string.Equals(member.UnresolvedMember.ParsedFile.FileName, selectedClass.ParsedFile.FileName, StringComparison.OrdinalIgnoreCase); memberItems.Add(new EntityItem(member, ambience) { IsInSamePart = isInSamePart }); } diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index d3e468a8cc..659b2a81ca 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -386,6 +386,7 @@ + diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs b/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs index c8744bd889..34b31d43f0 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/NewFileDialog.cs @@ -395,7 +395,7 @@ namespace ICSharpCode.SharpDevelop.Gui File.Copy(binaryFileName, parsedFileName); else File.WriteAllText(parsedFileName, parsedContent, ParserService.DefaultFileEncoding); - ParserService.ParseFileAsync(FileName.Create(parsedFileName), new StringTextSource(parsedContent)); + ParserService.ParseFileAsync(FileName.Create(parsedFileName), new StringTextSource(parsedContent)).FireAndForget(); } else { if (!String.IsNullOrEmpty(binaryFileName)) { LoggingService.Warn("binary file was skipped"); diff --git a/src/Main/Base/Project/Src/Gui/Pads/DefinitionViewPad.cs b/src/Main/Base/Project/Src/Gui/Pads/DefinitionViewPad.cs index 18533200bd..96ac73d1f5 100755 --- a/src/Main/Base/Project/Src/Gui/Pads/DefinitionViewPad.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/DefinitionViewPad.cs @@ -75,7 +75,7 @@ namespace ICSharpCode.SharpDevelop.Gui if (!ctl.IsVisible) return; LoggingService.Debug("DefinitionViewPad.Update"); - ResolveResult res = ResolveAtCaret(e); + ResolveResult res = /* TODO await */ ResolveAtCaret(e); if (res == null) return; var pos = res.GetDefinitionRegion(); if (pos.IsEmpty) return; diff --git a/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/ReferenceFolderNodeCommands.cs b/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/ReferenceFolderNodeCommands.cs index 41747bc054..30c588487f 100644 --- a/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/ReferenceFolderNodeCommands.cs +++ b/src/Main/Base/Project/Src/Gui/Pads/ProjectBrowser/Commands/ReferenceFolderNodeCommands.cs @@ -68,7 +68,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands } // Update code completion. - ParserService.ParseFileAsync(FileName.Create(webReference.WebProxyFileName)); + ParserService.ParseFileAsync(FileName.Create(webReference.WebProxyFileName)).FireAndForget(); } } catch (WebException ex) { MessageService.ShowException(ex, String.Format(StringParser.Parse("${res:ICSharpCode.SharpDevelop.Commands.ProjectBrowser.RefreshWebReference.ReadServiceDescriptionError}"), url.UpdateFromURL)); @@ -125,7 +125,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands AddWebReferenceToProjectBrowser(node, refDialog.WebReference); // Add proxy to code completion. - ParserService.ParseFileAsync(FileName.Create(refDialog.WebReference.WebProxyFileName)); + ParserService.ParseFileAsync(FileName.Create(refDialog.WebReference.WebProxyFileName)).FireAndForget(); node.Project.Save(); } diff --git a/src/Main/Base/Project/Src/Project/CompilableProject.cs b/src/Main/Base/Project/Src/Project/CompilableProject.cs index 6be11b933f..2a3bf6fe8a 100644 --- a/src/Main/Base/Project/Src/Project/CompilableProject.cs +++ b/src/Main/Base/Project/Src/Project/CompilableProject.cs @@ -507,6 +507,15 @@ namespace ICSharpCode.SharpDevelop.Project { } + public override void Dispose() + { + lock (SyncRoot) { + if (parseProjectContentContainer != null) + parseProjectContentContainer.Dispose(); + } + base.Dispose(); + } + #region IUpgradableProject [Browsable(false)] public virtual bool UpgradeDesired { diff --git a/src/Main/Base/Project/Src/Project/CustomTool.cs b/src/Main/Base/Project/Src/Project/CustomTool.cs index 8746603a0a..7c8c338530 100644 --- a/src/Main/Base/Project/Src/Project/CustomTool.cs +++ b/src/Main/Base/Project/Src/Project/CustomTool.cs @@ -201,7 +201,7 @@ namespace ICSharpCode.SharpDevelop.Project }, outputFileName, FileErrorPolicy.Inform); EnsureOutputFileIsInProject(baseItem, outputFileName); - ParserService.ParseAsync(FileName.Create(outputFileName), new StringTextSource(codeOutput)); + ParserService.ParseAsync(FileName.Create(outputFileName), new StringTextSource(codeOutput)).FireAndForget(); } public void GenerateCodeDomAsync(FileProjectItem baseItem, string outputFileName, Func func) diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index f182b19b6d..aebdc6962a 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -138,16 +138,6 @@ namespace ICSharpCode.SharpDevelop.Project } } - volatile string mscorlibPath; - - /// - /// Gets the path to mscorlib. - /// This property is set only after ResolveAssemblyReferences() is called. - /// - public string MscorlibPath { - get { return mscorlibPath; } - } - public override void ResolveAssemblyReferences() { MSBuildInternals.ResolveAssemblyReferences(this); diff --git a/src/Main/Base/Project/Src/Services/File/OpenedFile.cs b/src/Main/Base/Project/Src/Services/File/OpenedFile.cs index f097f4f119..c0dbed0573 100644 --- a/src/Main/Base/Project/Src/Services/File/OpenedFile.cs +++ b/src/Main/Base/Project/Src/Services/File/OpenedFile.cs @@ -461,7 +461,7 @@ namespace ICSharpCode.SharpDevelop // We discarded some information when closing the file, // so we need to re-parse it. if (File.Exists(this.FileName)) - ParserService.ParseAsync(this.FileName); + ParserService.ParseAsync(this.FileName).FireAndForget(); else ParserService.ClearParseInformation(this.FileName); } diff --git a/src/Main/Base/Project/Src/Services/ParserService/Fusion.cs b/src/Main/Base/Project/Src/Services/ParserService/Fusion.cs index 5f27980d5b..9ac9649bef 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/Fusion.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/Fusion.cs @@ -5,57 +5,11 @@ using System; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Text; namespace ICSharpCode.SharpDevelop.Parser { // .NET Fusion COM interfaces - [ComImport(), Guid("E707DCDE-D1CD-11D2-BAB9-00C04F8ECEAE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IAssemblyCache - { - [PreserveSig()] - int UninstallAssembly(uint dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, - IntPtr pvReserved, - out uint pulDisposition); - - [PreserveSig()] - int QueryAssemblyInfo(uint dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName, - IntPtr pAsmInfo); - - [PreserveSig()] - int CreateAssemblyCacheItem(uint dwFlags, - IntPtr pvReserved, - out IAssemblyCacheItem ppAsmItem, - [MarshalAs(UnmanagedType.LPWStr)] string pszAssemblyName); - - [PreserveSig()] - int CreateAssemblyScavenger(out object ppAsmScavenger); - - [PreserveSig()] - int InstallAssembly(uint dwFlags, - [MarshalAs(UnmanagedType.LPWStr)] string pszManifestFilePath, - IntPtr pvReserved); - } - - [ComImport(), Guid("9E3AAEB4-D1CD-11D2-BAB9-00C04F8ECEAE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IAssemblyCacheItem - { - void CreateStream([MarshalAs(UnmanagedType.LPWStr)] string pszName, - uint dwFormat, - uint dwFlags, - uint dwMaxSize, - out IStream ppStream); - - void IsNameEqual(IAssemblyName pName); - - void Commit(uint dwFlags); - - void MarkAssemblyVisible(uint dwFlags); - } - [ComImport(), Guid("CD193BC0-B4BC-11D2-9833-00C04FC31D2E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IAssemblyName { @@ -134,47 +88,8 @@ namespace ICSharpCode.SharpDevelop.Parser int Clone(out IAssemblyEnum ppEnum); } - - [ComImport(), Guid("1D23DF4D-A1E2-4B8B-93D6-6EA3DC285A54"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IHistoryReader - { - [PreserveSig()] - int GetFilePath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzFilePath, - ref uint pdwSize); - - [PreserveSig()] - int GetApplicationName([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzAppName, - ref uint pdwSize); - - [PreserveSig()] - int GetEXEModulePath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzExePath, - ref uint pdwSize); - - void GetNumActivations(out uint pdwNumActivations); - - void GetActivationDate(uint dwIdx, // One-based! - out long /* FILETIME */ pftDate); - - [PreserveSig()] - int GetRunTimeVersion(ref long /* FILETIME */ pftActivationDate, - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzRunTimeVersion, - ref uint pdwSize); - - void GetNumAssemblies(ref long /* FILETIME */ pftActivationDate, - out uint pdwNumAsms); - - void GetHistoryAssembly(ref long /* FILETIME */ pftActivationDate, - uint dwIdx, // One-based! - [MarshalAs(UnmanagedType.IUnknown)] out object ppHistAsm); - - } - internal static class Fusion { - [DllImport("fusion.dll", CharSet=CharSet.Auto)] - internal static extern int CreateAssemblyCache(out IAssemblyCache ppAsmCache, - uint dwReserved); - // dwFlags: 1 = Enumerate native image (NGEN) assemblies // 2 = Enumerate GAC assemblies // 4 = Enumerate Downloaded assemblies @@ -186,17 +101,6 @@ namespace ICSharpCode.SharpDevelop.Parser uint dwFlags, int pvReserved); - [DllImport("fusion.dll", CharSet=CharSet.Auto)] - internal static extern int CreateAssemblyNameObject(out IAssemblyName ppName, - string szAssemblyName, - uint dwFlags, - int pvReserved); - - // ????? - [DllImport("fusion.dll")] - internal static extern int CreateApplicationContext(out IApplicationContext ppAppContext, - uint dw); - [DllImport("fusion.dll")] internal static extern int GetCachePath(uint flags, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder wzDir, diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs index 5509f7a6eb..8b5e43e0b8 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs @@ -19,7 +19,14 @@ namespace ICSharpCode.SharpDevelop.Parser public class ParseProjectContentContainer : IDisposable { readonly MSBuildBasedProject project; + + /// + /// Lock for accessing mutable fields of this class. + /// To avoids deadlocks, the ParserService must not be called while holding this lock. + /// readonly object lockObj = new object(); + + readonly ParsedFileListener myListener; IProjectContent projectContent; IAssemblyReference[] references = { MinimalCorlib.Instance }; bool initializing; @@ -31,18 +38,33 @@ namespace ICSharpCode.SharpDevelop.Parser throw new ArgumentNullException("project"); this.project = project; this.projectContent = initialProjectContent.SetAssemblyName(project.AssemblyName); + this.myListener = new ParsedFileListener(OnParsedFileUpdated); this.initializing = true; LoadSolutionProjects.AddJob(Initialize, "Loading " + project.Name + "...", GetInitializationWorkAmount()); } + + void OnParsedFileUpdated(IParsedFile oldFile, IParsedFile newFile) + { + // This method is called by the parser service within the parser service lock. + lock (lockObj) { + if (!disposed) + projectContent = projectContent.UpdateProjectContent(oldFile, newFile); + } + } public void Dispose() { + ProjectService.ProjectItemAdded -= OnProjectItemAdded; + ProjectService.ProjectItemRemoved -= OnProjectItemRemoved; lock (lockObj) { - ProjectService.ProjectItemAdded -= OnProjectItemAdded; - ProjectService.ProjectItemRemoved -= OnProjectItemRemoved; + if (disposed) + return; disposed = true; } + foreach (var fileName in GetFilesToParse(project.Items)) { + ParserService.RemoveParsedFileListener(fileName, myListener); + } initializing = false; } @@ -68,9 +90,9 @@ namespace ICSharpCode.SharpDevelop.Parser if (disposed) { throw new ObjectDisposedException("ParseProjectContent"); } - ProjectService.ProjectItemAdded += OnProjectItemAdded; - ProjectService.ProjectItemRemoved += OnProjectItemRemoved; } + ProjectService.ProjectItemAdded += OnProjectItemAdded; + ProjectService.ProjectItemRemoved += OnProjectItemRemoved; double scalingFactor = 1.0 / (project.Items.Count + LoadingReferencesWorkAmount); using (IProgressMonitor initReferencesProgressMonitor = progressMonitor.CreateSubTask(LoadingReferencesWorkAmount * scalingFactor), parseProgressMonitor = progressMonitor.CreateSubTask(projectItems.Count * scalingFactor)) @@ -84,19 +106,23 @@ namespace ICSharpCode.SharpDevelop.Parser initializing = false; } + IEnumerable GetFilesToParse(IEnumerable projectItems) + { + return ( + from p in projectItems + where p.ItemType == ItemType.Compile && !String.IsNullOrEmpty(p.FileName) + select FileName.Create(p.FileName)); + } + void ParseFiles(ICollection projectItems, IProgressMonitor progressMonitor) { ParseableFileContentFinder finder = new ParseableFileContentFinder(); - var fileContents = ( - from p in projectItems.AsParallel().WithCancellation(progressMonitor.CancellationToken) - where !ItemType.NonFileItemTypes.Contains(p.ItemType) && !String.IsNullOrEmpty(p.FileName) - select FileName.Create(p.FileName) - ).ToList(); + var fileList = GetFilesToParse(projectItems).ToList(); object progressLock = new object(); - double fileCountInverse = 1.0 / fileContents.Count; + double fileCountInverse = 1.0 / fileList.Count; Parallel.ForEach( - fileContents, + fileList, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = progressMonitor.CancellationToken @@ -105,9 +131,13 @@ namespace ICSharpCode.SharpDevelop.Parser // Don't read files we don't have a parser for. // This avoids loading huge files (e.g. sdps) when we have no intention of parsing them. if (ParserService.HasParser(fileName)) { + ParserService.AddParsedFileListener(fileName, myListener, startAsyncParse: false); ITextSource content = finder.Create(fileName); - if (content != null) + if (content != null) { + // Parse the file on this thread so that AddParsedFileListener() does not + // start an asynchronous parse operation. ParserService.ParseFile(fileName, content); + } } lock (progressLock) { progressMonitor.Progress += fileCountInverse; @@ -120,29 +150,20 @@ namespace ICSharpCode.SharpDevelop.Parser { return System.Threading.Tasks.Task.Factory.StartNew( delegate { - project.ResolveAssemblyReferences(); - const double assemblyResolvingProgress = 0.3; + var referenceItems = project.ResolveAssemblyReferences(progressMonitor.CancellationToken); + const double assemblyResolvingProgress = 0.3; // 30% asm resolving, 70% asm loading progressMonitor.Progress += assemblyResolvingProgress; progressMonitor.CancellationToken.ThrowIfCancellationRequested(); List assemblyFiles = new List(); List newReferences = new List(); - string mscorlib = project.MscorlibPath; - if (mscorlib != null) - assemblyFiles.Add(mscorlib); - - foreach (ProjectItem item in projectItems) { - ReferenceProjectItem reference = item as ReferenceProjectItem; - if (reference != null) { - if (ItemType.ReferenceItemTypes.Contains(reference.ItemType)) { - ProjectReferenceProjectItem projectReference = reference as ProjectReferenceProjectItem; - if (projectReference != null) { - newReferences.Add(projectReference); - } else { - assemblyFiles.Add(reference.FileName); - } - } + foreach (var reference in referenceItems) { + ProjectReferenceProjectItem projectReference = reference as ProjectReferenceProjectItem; + if (projectReference != null) { + newReferences.Add(projectReference); + } else { + assemblyFiles.Add(reference.FileName); } } @@ -164,7 +185,7 @@ namespace ICSharpCode.SharpDevelop.Parser } // ensure that com references are built serially because we cannot invoke multiple instances of MSBuild - static Queue callAfterAddComReference = new Queue(); + static Queue callAfterAddComReference = new Queue(); static bool buildingComReference; void OnProjectItemAdded(object sender, ProjectItemEventArgs e) @@ -174,7 +195,7 @@ namespace ICSharpCode.SharpDevelop.Parser ReferenceProjectItem reference = e.ProjectItem as ReferenceProjectItem; if (reference != null) { if (reference.ItemType == ItemType.COMReference) { - System.Windows.Forms.MethodInvoker action = delegate { + Action action = delegate { // Compile project to ensure interop library is generated project.Save(); // project is not yet saved when ItemAdded fires, so save it here string message = StringParser.Parse("\n${res:MainWindow.CompilerMessages.CreatingCOMInteropAssembly}\n"); @@ -207,13 +228,9 @@ namespace ICSharpCode.SharpDevelop.Parser ReparseReferences(); } } - if (e.ProjectItem.ItemType == ItemType.Import) { - throw new NotImplementedException(); - //UpdateDefaultImports(project.Items); - } else if (e.ProjectItem.ItemType == ItemType.Compile) { - if (System.IO.File.Exists(e.ProjectItem.FileName)) { - ParserService.ParseFileAsync(FileName.Create(e.ProjectItem.FileName)); - } + if (e.ProjectItem.ItemType == ItemType.Compile) { + var fileName = FileName.Create(e.ProjectItem.FileName); + ParserService.AddParsedFileListener(fileName, myListener, startAsyncParse: true); } } @@ -235,37 +252,9 @@ namespace ICSharpCode.SharpDevelop.Parser } } - if (e.ProjectItem.ItemType == ItemType.Import) { - throw new NotImplementedException(); - //UpdateDefaultImports(project.Items); - } else if (e.ProjectItem.ItemType == ItemType.Compile) { - ParserService.ClearParseInformation(FileName.Create(e.ProjectItem.FileName)); + if (e.ProjectItem.ItemType == ItemType.Compile) { + ParserService.RemoveParsedFileListener(FileName.Create(e.ProjectItem.FileName), myListener); } } - - /* - int languageDefaultImportCount = -1; - - void UpdateDefaultImports(ICollection items) - { - if (languageDefaultImportCount < 0) { - languageDefaultImportCount = (DefaultImports != null) ? DefaultImports.Usings.Count : 0; - } - if (languageDefaultImportCount == 0) { - DefaultImports = null; - } else { - while (DefaultImports.Usings.Count > languageDefaultImportCount) { - DefaultImports.Usings.RemoveAt(languageDefaultImportCount); - } - } - foreach (ProjectItem item in items) { - if (item.ItemType == ItemType.Import) { - if (DefaultImports == null) { - DefaultImports = new DefaultUsing(this); - } - DefaultImports.Usings.Add(item.Include); - } - } - }*/ } } diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParsedFileListener.cs b/src/Main/Base/Project/Src/Services/ParserService/ParsedFileListener.cs new file mode 100644 index 0000000000..b70a037ae8 --- /dev/null +++ b/src/Main/Base/Project/Src/Services/ParserService/ParsedFileListener.cs @@ -0,0 +1,18 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.SharpDevelop.Parser +{ + /// + /// Listener for parse info changes. + /// Caution: The callback is invoked within the parser service's lock. Beware of deadlocks! + /// The method is called on the thread that performed the parse operation, which might be the main thread + /// or a background thread. + /// If possible, use the event instead, which is called on the main thread + /// and after the parser service released its lock. + /// + public delegate void ParsedFileListener(IParsedFile oldFile, IParsedFile newFile); +} diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs index 5bf1ef97fe..86af702093 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs @@ -261,6 +261,7 @@ namespace ICSharpCode.SharpDevelop.Parser volatile ParseInformation cachedParseInformation; ITextSourceVersion bufferVersion; bool disposed; + ParsedFileListener activeListeners; public FileEntry(FileName fileName) { @@ -268,6 +269,38 @@ namespace ICSharpCode.SharpDevelop.Parser this.parser = CreateParser(fileName); } + public void AddParsedFileListener(ParsedFileListener listener, bool startAsyncParse) + { + bool isNewEntry; + lock (this) { + isNewEntry = (this.activeListeners == null); + this.activeListeners += listener; + if (mainParsedFile != null) { + // We already have parse info: tell the new listener + listener(null, mainParsedFile); + // No need to start async parse. + isNewEntry = false; + } + } + if (isNewEntry && startAsyncParse) + ParseAsync(null).FireAndForget(); + } + + public void RemoveParsedFileListener(ParsedFileListener listener) + { + lock (this) { + this.activeListeners += listener; + } + } + + public bool HasParseListeners { + get { + lock (this) { + return this.activeListeners != null; + } + } + } + /// /// Intended for unit tests only /// @@ -418,26 +451,10 @@ namespace ICSharpCode.SharpDevelop.Parser } } - #warning How to update the PCs? - /*for (int i = 0; i < newUnits.Length; i++) { - IProjectContent pc = projectContents[i]; - // update the compilation unit - IParsedFile oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == pc); - // ensure the new unit is frozen beforewe make it visible to the outside world - newUnits[i].Freeze(); - pc.UpdateProjectContent(oldUnit, newUnits[i]); - }*/ + if (this.activeListeners != null) + this.activeListeners(mainParsedFile, resultParseInfo.ParsedFile); RaiseParseInformationUpdated(new ParseInformationEventArgs(mainParsedFile, resultParseInfo)); - /* - // remove all old units that don't exist anymore - foreach (IParsedFile oldUnit in oldUnits) { - if (!newUnits.Any(n => n.ProjectContent == oldUnit.ProjectContent)) { - oldUnit.ProjectContent.UpdateProjectContent(oldUnit, null); - RaiseParseInformationUpdated(new ParseInformationEventArgs(oldUnit, null, false)); - } - }*/ - this.bufferVersion = fileContentVersion; this.mainParsedFile = resultUnit; // Cached the new parse information around if it was requested, or if we had already cached parse information previously. @@ -454,16 +471,14 @@ namespace ICSharpCode.SharpDevelop.Parser IParsedFile parseInfo; lock (this) { // by setting the disposed flag, we'll cause all running ParseFile() calls to return null and not - // call into the parser anymore, so we can do the remainder of the clean-up work outside the lock + // call into the parser anymore this.disposed = true; parseInfo = this.mainParsedFile; this.bufferVersion = null; this.mainParsedFile = null; + if (parseInfo != null && this.activeListeners != null) + this.activeListeners(parseInfo, null); } - /*foreach (IParsedFile oldUnit in oldUnits) { - oldUnit.ProjectContent.UpdateProjectContent(oldUnit, null); - bool isPrimary = parseInfo == oldUnit; - }*/ RaiseParseInformationUpdated(new ParseInformationEventArgs(parseInfo, null)); } @@ -593,7 +608,9 @@ namespace ICSharpCode.SharpDevelop.Parser /// Removes all parse information (both IParsedFile and ParseInformation) for the specified file. /// This method is thread-safe. /// - public static void ClearParseInformation(FileName fileName) + /// Whether to remove the parse information even if there are listeners + /// registered for it. + public static void ClearParseInformation(FileName fileName, bool force = false) { if (fileName == null) throw new ArgumentNullException("fileName"); @@ -603,11 +620,17 @@ namespace ICSharpCode.SharpDevelop.Parser FileEntry entry; lock (syncLock) { if (fileEntryDict.TryGetValue(fileName, out entry)) { - fileEntryDict.Remove(fileName); + // avoid concurrent deregistration of parse listener between entry.HasParseListeners and entry.Clear() + lock (entry) { + if (force || !entry.HasParseListeners) { + fileEntryDict.Remove(fileName); + // Call entry.Clear() within the lock so that we don't have a race condition + // when the entry is immediately recreated by another thread. + entry.Clear(); + } + } } } - if (entry != null) - entry.Clear(); } /// @@ -954,5 +977,37 @@ namespace ICSharpCode.SharpDevelop.Parser tcs.SetResult(result); return tcs.Task; } + + /// + /// Adds a parsed file listener for the specified file. + /// If parse information for the file is already known, this method immediately invokes + /// listener(null, existingParsedFile);. + /// + /// If no parse information is already known, this parameter + /// controls whether an asynchronous parse operation is started. + public static void AddParsedFileListener(FileName fileName, ParsedFileListener listener, bool startAsyncParse) + { + if (listener == null) + throw new ArgumentNullException("listener"); + GetFileEntry(fileName, true).AddParsedFileListener(listener, startAsyncParse); + } + + /// + /// Removes a parsed file listener for the specified file. + /// This method invokes listener(existingParsedFile, null);. (unless existingParsedFile==null) + /// + /// + /// When the last file listener is removed, the stored parse information is cleared. + /// + public static void RemoveParsedFileListener(FileName fileName, ParsedFileListener listener) + { + if (listener == null) + throw new ArgumentNullException("listener"); + FileEntry entry = GetFileEntry(fileName, false); + if (entry == null) + return; + entry.RemoveParsedFileListener(listener); + ClearParseInformation(fileName, force: false); + } } } diff --git a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs index a45ccc9149..2aa71da944 100644 --- a/src/Main/Base/Project/Src/Util/ExtensionMethods.cs +++ b/src/Main/Base/Project/Src/Util/ExtensionMethods.cs @@ -69,6 +69,16 @@ namespace ICSharpCode.SharpDevelop } } + /// + /// If the task throws an exception, notifies the message service. + /// Call this method on asynchronous tasks if you do not care about the result, but do not want + /// unhandled exceptions to go unnoticed. + /// + public static void FireAndForget(this System.Threading.Tasks.Task task) + { + task.ContinueWith(t => { if (t.Exception != null) Core.MessageService.ShowException(t.Exception); }); + } + /// /// Runs an action for all elements in the input. ///