#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

838 lines
28 KiB

// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using System.Threading.Tasks;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Editor;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
namespace ICSharpCode.SharpDevelop
{
/// <summary>
/// Stores the compilation units for files.
/// </summary>
public static class ParserService
{
static readonly object syncLock = new object();
static IList<ParserDescriptor> parserDescriptors;
static Dictionary<IProject, IProjectContent> projectContents = new Dictionary<IProject, IProjectContent>();
static Dictionary<string, FileEntry> fileEntryDict = new Dictionary<string, FileEntry>(StringComparer.OrdinalIgnoreCase);
static DefaultProjectContent defaultProjectContent;
#region Manage Project Contents
/// <summary>
/// Fetches the current project content.
/// </summary>
public static IProjectContent CurrentProjectContent {
[DebuggerStepThrough]
get {
IProject currentProject = ProjectService.CurrentProject;
lock (syncLock) {
if (currentProject == null || !projectContents.ContainsKey(currentProject)) {
return DefaultProjectContent;
}
return projectContents[currentProject];
}
}
}
public static IProjectContent GetProjectContent(IProject project)
{
lock (projectContents) {
IProjectContent pc;
if (projectContents.TryGetValue(project, out pc)) {
return pc;
}
}
return null;
}
/// <summary>
/// Gets the list of project contents of all open projects. Does not include assembly project contents.
/// </summary>
public static IEnumerable<IProjectContent> AllProjectContents {
get {
lock (syncLock) {
return projectContents.Values.ToArray();
}
}
}
/// <summary>
/// Gets the default project content used for files outside of projects.
/// </summary>
public static IProjectContent DefaultProjectContent {
get {
lock (syncLock) {
if (defaultProjectContent == null) {
CreateDefaultProjectContent();
}
return defaultProjectContent;
}
}
}
static void CreateDefaultProjectContent()
{
LoggingService.Info("Creating default project content");
//LoggingService.Debug("Stacktrace is:\n" + Environment.StackTrace);
defaultProjectContent = new DefaultProjectContent();
defaultProjectContent.AddReferencedContent(AssemblyParserService.DefaultProjectContentRegistry.Mscorlib);
}
/// <summary>
/// Gets all project contents that contain the specified file.
/// </summary>
static List<IProjectContent> GetProjectContents(string fileName)
{
List<IProjectContent> result = new List<IProjectContent>();
lock (projectContents) {
foreach (KeyValuePair<IProject, IProjectContent> projectContent in projectContents) {
if (projectContent.Key.IsFileInProject(fileName)) {
result.Add(projectContent.Value);
}
}
}
if (result.Count == 0)
result.Add(DefaultProjectContent);
return result;
}
internal static void RemoveProjectContentForRemovedProject(IProject project)
{
lock (projectContents) {
projectContents.Remove(project);
}
}
#endregion
#region Initialization + ParserThread
internal static void InitializeParserService()
{
if (parserDescriptors == null) {
parserDescriptors = AddInTree.BuildItems<ParserDescriptor>("/Workspace/Parser", null, false);
AssemblyParserService.Initialize();
LoadSolutionProjects.Initialize();
}
}
static DispatcherTimer timer;
internal static void StartParserThread()
{
WorkbenchSingleton.DebugAssertMainThread();
timer = new DispatcherTimer(DispatcherPriority.Background);
timer.Interval = TimeSpan.FromSeconds(2);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
}
internal static void StopParserThread()
{
timer.Stop();
}
static System.Threading.Tasks.Task lastParseRun;
static void timer_Tick(object sender, EventArgs e)
{
if (lastParseRun != null) {
// don't start another parse run if the last one is still running
if (!lastParseRun.IsCompleted)
return;
lastParseRun = null;
}
IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveViewContent;
if (viewContent == null)
return;
string fileName = viewContent.PrimaryFileName;
if (fileName == null)
return;
ITextBuffer snapshot;
IEditable editable = viewContent as IEditable;
if (editable != null)
snapshot = editable.CreateSnapshot();
else
snapshot = GetParseableFileContent(viewContent.PrimaryFileName);
lastParseRun = BeginParse(fileName, snapshot).ContinueWith(
delegate(Task<ParseInformation> backgroundTask) {
ParseInformation parseInfo = backgroundTask.Result;
RaiseParserUpdateStepFinished(new ParserUpdateStepEventArgs(fileName, snapshot, parseInfo));
});
}
#endregion
#region GetParser / ExpressionFinder /etc.
static readonly string[] DefaultTaskListTokens = {"HACK", "TODO", "UNDONE", "FIXME"};
/// <summary>
/// Gets/Sets the task list tokens.
/// This property is thread-safe.
/// </summary>
public static string[] TaskListTokens {
get { return PropertyService.Get("SharpDevelop.TaskListTokens", DefaultTaskListTokens); }
set { PropertyService.Set("SharpDevelop.TaskListTokens", value); }
}
/// <summary>
/// Creates a new IParser instance that can parse the specified file.
/// This method is thread-safe.
/// </summary>
public static IParser CreateParser(string fileName)
{
if (fileName == null)
throw new ArgumentNullException("fileName");
if (parserDescriptors == null)
return null;
foreach (ParserDescriptor descriptor in parserDescriptors) {
if (descriptor.CanParse(fileName)) {
IParser p = descriptor.CreateParser();
if (p != null) {
p.LexerTags = TaskListTokens;
return p;
}
}
}
return null;
}
/// <summary>
/// Creates an IExpressionFinder instance for the specified file.
/// This method is thread-safe.
/// </summary>
public static IExpressionFinder GetExpressionFinder(string fileName)
{
IParser parser = CreateParser(fileName);
if (parser != null) {
return parser.CreateExpressionFinder(fileName);
}
return null;
}
public static ArrayList CtrlSpace(int caretLine, int caretColumn,
string fileName, string fileContent, ExpressionContext context)
{
IResolver resolver = CreateResolver(fileName);
if (resolver != null) {
return resolver.CtrlSpace(caretLine, caretColumn, GetParseInformation(fileName), fileContent, context);
}
return null;
}
public static IResolver CreateResolver(string fileName)
{
IParser parser = CreateParser(fileName);
if (parser != null) {
return parser.CreateResolver();
}
return null;
}
public static ResolveResult Resolve(ExpressionResult expressionResult,
int caretLineNumber, int caretColumn,
string fileName, string fileContent)
{
if (expressionResult.Region.IsEmpty) {
expressionResult.Region = new DomRegion(caretLineNumber, caretColumn);
}
IResolver resolver = CreateResolver(fileName);
if (resolver != null) {
ParseInformation parseInfo = GetParseInformation(fileName);
return resolver.Resolve(expressionResult, parseInfo, fileContent);
}
return null;
}
#endregion
#region GetParseableFileContent
/// <summary>
/// Gets the default file encoding.
/// This property is thread-safe.
/// </summary>
public static Encoding DefaultFileEncoding {
get {
// TODO: how to implement without the old editor / so that it's actually thread-safe?
return DefaultEditor.Gui.Editor.SharpDevelopTextEditorProperties.Instance.Encoding;
}
}
/// <summary>
/// Gets the content of the specified file.
/// This method is thread-safe. This method involves waiting for the main thread, so using it while
/// holding a lock can lead to deadlocks.
/// </summary>
public static ITextBuffer GetParseableFileContent(string fileName)
{
return Gui.WorkbenchSingleton.SafeThreadFunction(GetParseableFileContentInternal, fileName);
}
static ITextBuffer GetParseableFileContentInternal(string fileName)
{
IViewContent viewContent = FileService.GetOpenFile(fileName);
IEditable editable = viewContent as IEditable;
if (editable != null) {
return editable.CreateSnapshot();
}
//ITextBuffer res = project.GetParseableFileContent(fileName);
//if (res != null)
// return res;
OpenedFile file = FileService.GetOpenedFile(fileName);
if (file != null) {
IFileDocumentProvider p = file.CurrentView as IFileDocumentProvider;
if (p != null) {
IDocument document = p.GetDocumentForFile(file);
if (document != null) {
return document.CreateSnapshot();
}
}
using(Stream s = file.OpenRead()) {
// load file
return new StringTextBuffer(ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(s, DefaultFileEncoding));
}
}
// load file
return new StringTextBuffer(ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(fileName, DefaultFileEncoding));
}
#endregion
#region Parse Information Management
static readonly ICompilationUnit[] emptyCompilationUnitArray = new ICompilationUnit[0];
sealed class FileEntry
{
readonly string fileName;
internal readonly IParser parser;
volatile ParseInformation parseInfo;
ITextBufferVersion bufferVersion;
ICompilationUnit[] oldUnits = emptyCompilationUnitArray;
bool disposed;
public FileEntry(string fileName)
{
this.fileName = fileName;
this.parser = CreateParser(fileName);
}
/// <summary>
/// for unit tests only
/// </summary>
public ParseInformation RegisterParseInformation(ICompilationUnit cu)
{
lock (this) {
oldUnits = new ICompilationUnit[] { cu };
return this.parseInfo = new ParseInformation(cu);
}
}
public ParseInformation GetParseInformation()
{
ParseInformation p = this.parseInfo; // read volatile
if (p != null)
return p;
else
return ParseFile(null, null);
}
public ParseInformation GetExistingParseInformation()
{
return this.parseInfo; // read volatile
}
public ParseInformation ParseFile(IProjectContent parentProjectContent, ITextBuffer fileContent)
{
if (parser == null)
return null;
if (fileContent == null) {
// GetParseableFileContent must not be called inside any lock
// (otherwise we'd risk deadlocks because GetParseableFileContent must invoke on the main thread)
fileContent = GetParseableFileContent(fileName);
}
ITextBufferVersion fileContentVersion = fileContent.Version;
List<IProjectContent> projectContents;
lock (this) {
if (this.disposed)
return null;
if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) {
if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) {
// Special case: (necessary due to parentProjectContent optimization)
// Detect when a file belongs to multiple projects but the ParserService hasn't realized
// that, yet. In this case, do another parse run to detect all parent projects.
if (!(parentProjectContent != null && this.oldUnits.Length == 1 && this.oldUnits[0].ProjectContent != parentProjectContent)) {
return this.parseInfo;
}
}
}
if (parentProjectContent != null && (oldUnits.Length == 0 || (oldUnits.Length == 1 && oldUnits[0].ProjectContent == parentProjectContent))) {
// Optimization: if parentProjectContent is specified and doesn't conflict with what we already know,
// we will use it instead of doing an expensive GetProjectContents call.
projectContents = new List<IProjectContent>();
projectContents.Add(parentProjectContent);
} else {
projectContents = GetProjectContents(fileName);
}
}
// We now leave the lock to do the actual parsing.
// This is done to allow IParser implementations to invoke methods on the main thread without
// risking deadlocks.
// parse once for each project content that contains the file
ICompilationUnit[] newUnits = new ICompilationUnit[projectContents.Count];
ICompilationUnit resultUnit = null;
for (int i = 0; i < newUnits.Length; i++) {
IProjectContent pc = projectContents[i];
try {
newUnits[i] = parser.Parse(pc, fileName, fileContent);
} catch (Exception ex) {
throw new ApplicationException("Error parsing " + fileName, ex);
}
if (i == 0 || pc == parentProjectContent)
resultUnit = newUnits[i];
}
lock (this) {
if (this.disposed)
return null;
// ensure we never go backwards in time (we need to repeat this check after we've reacquired the lock)
if (fileContentVersion != null && this.bufferVersion != null && this.bufferVersion.BelongsToSameDocumentAs(fileContentVersion)) {
if (this.bufferVersion.CompareAge(fileContentVersion) >= 0) {
return this.parseInfo;
}
}
for (int i = 0; i < newUnits.Length; i++) {
IProjectContent pc = projectContents[i];
// update the compilation unit
ICompilationUnit oldUnit = oldUnits.FirstOrDefault(o => o.ProjectContent == pc);
pc.UpdateCompilationUnit(oldUnit, newUnits[i], fileName);
RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, pc, oldUnit, newUnits[i]));
}
// remove all old units that don't exist anymore
foreach (ICompilationUnit oldUnit in oldUnits) {
if (!newUnits.Any(n => n.ProjectContent == oldUnit.ProjectContent)) {
oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit);
RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null));
}
}
this.bufferVersion = fileContentVersion;
this.oldUnits = newUnits;
ParseInformation newParseInfo = new ParseInformation(resultUnit);
this.parseInfo = newParseInfo;
return newParseInfo;
}
}
public void Clear()
{
ICompilationUnit[] oldUnits;
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
this.disposed = true;
oldUnits = this.oldUnits;
this.oldUnits = null;
this.bufferVersion = null;
}
foreach (ICompilationUnit oldUnit in oldUnits) {
oldUnit.ProjectContent.RemoveCompilationUnit(oldUnit);
RaiseParseInformationUpdated(new ParseInformationEventArgs(fileName, oldUnit.ProjectContent, oldUnit, null));
}
}
public Task<ParseInformation> BeginParse(ITextBuffer fileContent)
{
return System.Threading.Tasks.Task.Factory.StartNew(
delegate {
try {
return ParseFile(null, fileContent);
} catch (Exception ex) {
MessageService.ShowError(ex, "Error during async parse");
return null;
}
}
);
}
}
static FileEntry GetFileEntry(string fileName, bool createOnDemand)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentException("fileName");
fileName = FileUtility.NormalizePath(fileName);
FileEntry entry;
lock (syncLock) {
if (!fileEntryDict.TryGetValue(fileName, out entry)) {
if (!createOnDemand)
return null;
entry = new FileEntry(fileName);
fileEntryDict.Add(fileName, entry);
}
}
return entry;
}
/// <summary>
/// Removes all parse information stored for the specified file.
/// This method is thread-safe.
/// </summary>
public static void ClearParseInformation(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentException("fileName");
LoggingService.Info("ClearParseInformation: " + fileName);
fileName = FileUtility.NormalizePath(fileName);
FileEntry entry;
lock (syncLock) {
if (fileEntryDict.TryGetValue(fileName, out entry)) {
fileEntryDict.Remove(fileName);
}
}
if (entry != null)
entry.Clear();
}
/// <summary>
/// Gets parse information for the specified file.
/// Blocks if the file wasn't parsed yet, but may return an old parsed version.
/// This method is thread-safe. This method involves waiting for the main thread, so using it while
/// holding a lock can lead to deadlocks. You might want to use <see cref="GetExistingParseInformation"/> instead.
/// </summary>
/// <returns>Returns the ParseInformation for the specified file, or null if the file cannot be parsed.
/// The returned ParseInformation might be stale (re-parse is not forced).</returns>
public static ParseInformation GetParseInformation(string fileName)
{
return GetFileEntry(fileName, true).GetParseInformation();
}
/// <summary>
/// Gets parse information for the specified file.
/// This method is thread-safe.
/// </summary>
/// <returns>Returns the ParseInformation for the specified file, or null if the file has not been parsed already.</returns>
public static ParseInformation GetExistingParseInformation(string fileName)
{
FileEntry entry = GetFileEntry(fileName, false);
if (entry != null)
return entry.GetExistingParseInformation();
else
return null;
}
/// <summary>
/// Gets parse information for the specified file.
/// Blocks until a recent copy of the parse information is available.
/// This method is thread-safe. This method involves waiting for the main thread, so using it while
/// holding a lock can lead to deadlocks. You might want to use the overload taking ITextBuffer instead.
/// </summary>
/// <returns>Returns the ParseInformation for the specified file, or null if the file cannot be parsed.
/// The returned ParseInformation will not be stale (re-parse is forced if required).</returns>
public static ParseInformation ParseFile(string fileName)
{
return GetFileEntry(fileName, true).ParseFile(null, null);
}
/// <summary>
/// Gets parse information for the specified file.
/// The fileContent is taken as a hint - if a newer version than it is already available, that will be used instead.
/// This method is thread-safe.
/// </summary>
/// <returns>Returns the ParseInformation for the specified file, or null if the file cannot be parsed.
/// The returned ParseInformation will not be stale (re-parse is forced if required).</returns>
public static ParseInformation ParseFile(string fileName, ITextBuffer fileContent)
{
if (fileContent == null)
throw new ArgumentNullException("fileContent");
return GetFileEntry(fileName, true).ParseFile(null, fileContent);
}
/// <summary>
/// Gets parse information for the specified file.
/// The fileContent is taken as a hint - if a newer version than it is already available, that will be used instead.
/// This method is thread-safe.
/// </summary>
/// <returns>Returns the ParseInformation for the specified file, or null if the file cannot be parsed.
/// The returned ParseInformation will not be stale (re-parse is forced if required).</returns>
public static ParseInformation ParseFile(IProjectContent parentProjectContent, string fileName, ITextBuffer fileContent)
{
if (fileContent == null)
throw new ArgumentNullException("fileContent");
return GetFileEntry(fileName, true).ParseFile(parentProjectContent, fileContent);
}
/// <summary>
/// Begins an asynchronous reparse.
/// This method is thread-safe. The returned task might wait for the main thread to be ready, beware of deadlocks.
/// You might want to use the overload taking ITextBuffer instead.
/// </summary>
/// <returns>
/// Returns a task that will make the parse result available.
/// </returns>
public static Task<ParseInformation> BeginParse(string fileName)
{
return GetFileEntry(fileName, true).BeginParse(null);
}
/// <summary>
/// Begins an asynchronous reparse.
/// This method is thread-safe.
/// </summary>
/// <returns>
/// Returns a task that will make the parse result available.
/// </returns>
public static Task<ParseInformation> BeginParse(string fileName, ITextBuffer fileContent)
{
if (fileContent == null)
throw new ArgumentNullException("fileContent");
// create snapshot (in case someone passes a mutable document to BeginParse)
return GetFileEntry(fileName, true).BeginParse(fileContent.CreateSnapshot());
}
/// <summary>
/// Parses the current view content.
/// This method can only be called from the main thread.
/// </summary>
public static ParseInformation ParseCurrentViewContent()
{
WorkbenchSingleton.AssertMainThread();
IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveViewContent;
if (viewContent != null)
return ParseViewContent(viewContent);
else
return null;
}
/// <summary>
/// Parses the specified view content.
/// This method can only be called from the main thread.
/// </summary>
public static ParseInformation ParseViewContent(IViewContent viewContent)
{
if (viewContent == null)
throw new ArgumentNullException("viewContent");
WorkbenchSingleton.AssertMainThread();
if (string.IsNullOrEmpty(viewContent.PrimaryFileName))
return null;
IEditable editable = viewContent as IEditable;
if (editable != null)
return ParseFile(viewContent.PrimaryFileName, editable.CreateSnapshot());
else
return ParseFile(viewContent.PrimaryFileName);
}
/// <summary>
/// Parses the current view content.
/// This method can only be called from the main thread.
/// </summary>
public static Task<ParseInformation> BeginParseCurrentViewContent()
{
WorkbenchSingleton.AssertMainThread();
IViewContent viewContent = WorkbenchSingleton.Workbench.ActiveViewContent;
if (viewContent != null)
return BeginParseViewContent(viewContent);
else
return NullTask();
}
/// <summary>
/// Begins parsing the specified view content.
/// This method can only be called from the main thread.
/// </summary>
public static Task<ParseInformation> BeginParseViewContent(IViewContent viewContent)
{
if (viewContent == null)
throw new ArgumentNullException("viewContent");
WorkbenchSingleton.AssertMainThread();
if (string.IsNullOrEmpty(viewContent.PrimaryFileName))
return NullTask();
IEditable editable = viewContent as IEditable;
if (editable != null)
return BeginParse(viewContent.PrimaryFileName, editable.CreateSnapshot());
else
return BeginParse(viewContent.PrimaryFileName);
}
static Task<ParseInformation> NullTask()
{
return System.Threading.Tasks.Task.Factory.StartNew<ParseInformation>(
delegate { return null; }
);
}
/// <summary>
/// Gets the parser instance that is responsible for the specified file.
/// Will create a new IParser instance on demand.
/// This method is thread-safe.
/// </summary>
public static IParser GetParser(string fileName)
{
return GetFileEntry(fileName, true).parser;
}
/// <summary>
/// Registers a compilation unit in the parser service.
/// Does not fire the OnParseInformationUpdated event, please use this for unit tests only!
/// </summary>
public static ParseInformation RegisterParseInformation(string fileName, ICompilationUnit cu)
{
FileEntry entry = GetFileEntry(fileName, true);
return entry.RegisterParseInformation(cu);
}
/// <summary>
/// Replaces the list of available parsers.
/// Please use this for unit tests only!
/// </summary>
public static void RegisterAvailableParsers(params ParserDescriptor[] descriptors)
{
lock (syncLock) {
parserDescriptors = new List<ParserDescriptor>();
parserDescriptors.AddRange(descriptors);
}
}
#endregion
#region ParseInformationUpdated / ParserUpdateStepFinished events
/// <summary>
/// Occurs whenever parse information was updated. This event is raised on the main thread.
/// </summary>
public static event EventHandler<ParseInformationEventArgs> ParseInformationUpdated = delegate {};
static void RaiseParseInformationUpdated(ParseInformationEventArgs e)
{
// RaiseParseInformationUpdated is called inside a lock, but we don't want to raise the event inside that lock.
// To ensure events are raised in the same order, we always invoke on the main thread.
Gui.WorkbenchSingleton.SafeThreadAsyncCall(
delegate {
ParseInformationUpdated(null, e);
});
}
/// <summary>
/// Occurs when the parse step started by a timer finishes.
/// This event is raised on the main thread.
/// </summary>
public static event EventHandler<ParserUpdateStepEventArgs> ParserUpdateStepFinished = delegate {};
static void RaiseParserUpdateStepFinished(ParserUpdateStepEventArgs e)
{
Gui.WorkbenchSingleton.SafeThreadAsyncCall(
delegate {
ParserUpdateStepFinished(null, e);
});
}
#endregion
#region LoadSolutionProjects
public static void Reparse(IProject project, bool initReferences, bool parseCode)
{
if (project == null)
throw new ArgumentNullException("project");
LoadSolutionProjects.Reparse(project, initReferences, parseCode);
}
/// <summary>
/// Gets whether the LoadSolutionProjects thread is currently running.
/// </summary>
public static bool LoadSolutionProjectsThreadRunning {
get { return LoadSolutionProjects.IsThreadRunning; }
}
/// <summary>
/// Occurs when the 'load solution projects' thread has finished.
/// This event is not raised when the 'load solution projects' is aborted because the solution was closed.
/// This event is raised on the main thread.
/// </summary>
public static event EventHandler LoadSolutionProjectsThreadEnded {
add { LoadSolutionProjects.ThreadEnded += value; }
remove { LoadSolutionProjects.ThreadEnded -= value; }
}
internal static void OnSolutionLoaded()
{
List<ParseProjectContent> createdContents = new List<ParseProjectContent>();
foreach (IProject project in ProjectService.OpenSolution.Projects) {
try {
LoggingService.Debug("Creating project content for " + project.Name);
ParseProjectContent newContent = project.CreateProjectContent();
if (newContent != null) {
lock (projectContents) {
projectContents[project] = newContent;
}
createdContents.Add(newContent);
}
} catch (Exception e) {
MessageService.ShowError(e, "Error while retrieving project contents from " + project);
}
}
LoadSolutionProjects.OnSolutionLoaded(createdContents);
}
internal static void OnSolutionClosed()
{
LoadSolutionProjects.OnSolutionClosed();
lock (projectContents) {
foreach (IProjectContent content in projectContents.Values) {
content.Dispose();
}
projectContents.Clear();
}
ClearAllFileEntries();
}
static void ClearAllFileEntries()
{
FileEntry[] entries;
lock (fileEntryDict) {
entries = fileEntryDict.Values.ToArray();
fileEntryDict.Clear();
}
foreach (FileEntry entry in entries)
entry.Clear();
}
/// <remarks>Can return null.</remarks>
internal static IProjectContent CreateProjectContentForAddedProject(IProject project)
{
ParseProjectContent newContent = project.CreateProjectContent();
if (newContent != null) {
lock (projectContents) {
projectContents[project] = newContent;
}
LoadSolutionProjects.InitNewProject(newContent);
}
return newContent;
}
#endregion
}
}