100 changed files with 1892 additions and 1832 deletions
@ -1,160 +0,0 @@ |
|||||||
// 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 System.Collections.Generic; |
|
||||||
using System.Linq; |
|
||||||
using System.Text; |
|
||||||
using System.Collections.ObjectModel; |
|
||||||
|
|
||||||
namespace ICSharpCode.CppBinding.Project |
|
||||||
{ |
|
||||||
public interface IMultiDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>> |
|
||||||
{ |
|
||||||
void Add(TKey key, TValue value); |
|
||||||
bool Contains(TKey key, TValue value); |
|
||||||
bool ContainsKey(TKey key); |
|
||||||
bool Remove(TKey key, TValue value); |
|
||||||
IList<TValue> this[TKey key] { get; } |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A dictionary that allows multiple pairs with the same key.
|
|
||||||
/// </summary>
|
|
||||||
public class MultiDictionary<TKey, TValue> : IMultiDictionary<TKey,TValue> |
|
||||||
{ |
|
||||||
public MultiDictionary() |
|
||||||
: this(new Dictionary<TKey, IList<TValue>>()) |
|
||||||
{ } |
|
||||||
|
|
||||||
public MultiDictionary(IDictionary<TKey, IList<TValue>> innerDictionary) |
|
||||||
{ |
|
||||||
if (innerDictionary == null) |
|
||||||
throw new ArgumentNullException("innerDictionary"); |
|
||||||
dict = innerDictionary; |
|
||||||
count = CountElements(dict); |
|
||||||
} |
|
||||||
IDictionary<TKey, IList<TValue>> dict; |
|
||||||
int count; |
|
||||||
|
|
||||||
public void Add(TKey key, TValue value) |
|
||||||
{ |
|
||||||
IList<TValue> valueList; |
|
||||||
if (!dict.TryGetValue(key, out valueList)) |
|
||||||
{ |
|
||||||
valueList = new List<TValue>(); |
|
||||||
dict.Add(key, valueList); |
|
||||||
} |
|
||||||
valueList.Add(value); |
|
||||||
count++; |
|
||||||
} |
|
||||||
|
|
||||||
public bool Contains(TKey key, TValue value) |
|
||||||
{ |
|
||||||
IList<TValue> valueList; |
|
||||||
if (!dict.TryGetValue(key, out valueList)) |
|
||||||
return false; |
|
||||||
return valueList.Contains(value); |
|
||||||
} |
|
||||||
|
|
||||||
public bool ContainsKey(TKey key) |
|
||||||
{ |
|
||||||
return dict.ContainsKey(key); |
|
||||||
} |
|
||||||
|
|
||||||
public bool Remove(TKey key, TValue value) |
|
||||||
{ |
|
||||||
IList<TValue> valueList; |
|
||||||
if (!dict.TryGetValue(key, out valueList)) |
|
||||||
return false; |
|
||||||
return valueList.Remove(value); |
|
||||||
} |
|
||||||
|
|
||||||
public IList<TValue> this[TKey key] |
|
||||||
{ |
|
||||||
get |
|
||||||
{ |
|
||||||
return new ReadOnlyCollection<TValue>(dict[key]); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#region ICollection<KeyValuePair<TKey,TValue>> Members
|
|
||||||
|
|
||||||
public void Add(KeyValuePair<TKey, TValue> item) |
|
||||||
{ |
|
||||||
Add(item.Key, item.Value); |
|
||||||
} |
|
||||||
|
|
||||||
public void Clear() |
|
||||||
{ |
|
||||||
dict.Clear(); |
|
||||||
count = 0; |
|
||||||
} |
|
||||||
|
|
||||||
public bool Contains(KeyValuePair<TKey, TValue> item) |
|
||||||
{ |
|
||||||
return Contains(item.Key, item.Value); |
|
||||||
} |
|
||||||
|
|
||||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
|
||||||
{ |
|
||||||
if (array == null) |
|
||||||
throw new ArgumentNullException("array"); |
|
||||||
if (arrayIndex < 0) |
|
||||||
throw new ArgumentOutOfRangeException("arrayIndex"); |
|
||||||
if (array.Rank != 1) |
|
||||||
throw new ArgumentException("Array is multidimensional", "array"); |
|
||||||
if (arrayIndex + count >= array.Length) |
|
||||||
throw new ArgumentException("Array is to small", "array"); |
|
||||||
|
|
||||||
foreach (KeyValuePair<TKey, IList<TValue>> item in dict) |
|
||||||
foreach (TValue value in item.Value) |
|
||||||
array[arrayIndex++] = new KeyValuePair<TKey, TValue>(item.Key, value); |
|
||||||
} |
|
||||||
|
|
||||||
public int Count |
|
||||||
{ |
|
||||||
get { return count; } |
|
||||||
} |
|
||||||
|
|
||||||
public bool IsReadOnly |
|
||||||
{ |
|
||||||
get { return false; } |
|
||||||
} |
|
||||||
|
|
||||||
public bool Remove(KeyValuePair<TKey, TValue> item) |
|
||||||
{ |
|
||||||
return Remove(item.Key, item.Value); |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() |
|
||||||
{ |
|
||||||
foreach (KeyValuePair<TKey, IList<TValue>> item in dict) |
|
||||||
foreach (TValue value in item.Value) |
|
||||||
yield return new KeyValuePair<TKey, TValue>(item.Key, value); |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IEnumerable Members
|
|
||||||
|
|
||||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
|
||||||
{ |
|
||||||
return GetEnumerator(); |
|
||||||
} |
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
static int CountElements(IDictionary<TKey, IList<TValue>> dict) |
|
||||||
{ |
|
||||||
int count = 0; |
|
||||||
foreach (KeyValuePair<TKey, IList<TValue>> item in dict) |
|
||||||
count += item.Value.Count; |
|
||||||
return count; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,55 @@ |
|||||||
|
// 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 System.Text; |
||||||
|
using System.Threading; |
||||||
|
using ICSharpCode.Core; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// File service.
|
||||||
|
/// </summary>
|
||||||
|
public interface IFileService |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the default file encoding.
|
||||||
|
/// This property is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
Encoding DefaultFileEncoding { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the content of the specified file.
|
||||||
|
/// If the file is currently open in SharpDevelop, retrieves a snapshot
|
||||||
|
/// of the editor content.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. This method involves waiting for the main thread, so using it while
|
||||||
|
/// holding a lock can lead to deadlocks.
|
||||||
|
/// </remarks>
|
||||||
|
ITextSource GetFileContent(FileName fileName); |
||||||
|
|
||||||
|
/// <inheritdoc cref="GetParseableFileContent(FileName)"/>
|
||||||
|
ITextSource GetFileContent(string fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file content for a file that is currently open.
|
||||||
|
/// Returns null if the file is not open.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe. This method involves waiting for the main thread, so using it while
|
||||||
|
/// holding a lock can lead to deadlocks.
|
||||||
|
/// </remarks>
|
||||||
|
ITextSource GetFileContentForOpenFile(FileName fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file content from disk, ignoring open files.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// </remarks>
|
||||||
|
ITextSource GetFileContentFromDisk(FileName fileName, CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
// 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 System.ComponentModel; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Windows.Threading; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Represents a thread running a message loop.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMessageLoop |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the thread corresponding to this message loop.
|
||||||
|
/// </summary>
|
||||||
|
Thread Thread { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the dispatcher for this message loop.
|
||||||
|
/// </summary>
|
||||||
|
Dispatcher Dispatcher { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the synchronization context corresponding to this message loop.
|
||||||
|
/// </summary>
|
||||||
|
SynchronizationContext SynchronizationContext { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="ISynchronizeInvoke"/> implementation corresponding to this message loop.
|
||||||
|
/// </summary>
|
||||||
|
ISynchronizeInvoke SynchronizingObject { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the current thread is different from the thread running this message loop.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks><c>InvokeRequired = !CheckAcess()</c></remarks>
|
||||||
|
bool InvokeRequired { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the current thread is the same as the thread running this message loop.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks><c>CheckAccess() = !InvokeRequired</c></remarks>
|
||||||
|
bool CheckAccess(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws an exception if the current thread is different from the thread running this message loop.
|
||||||
|
/// </summary>
|
||||||
|
void VerifyAccess(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified callback on the message loop and waits for its completion.
|
||||||
|
/// If the current thread is the thread running the message loop, executes the callback
|
||||||
|
/// directly without pumping the message loop.
|
||||||
|
/// </summary>
|
||||||
|
void InvokeIfRequired(Action callback); |
||||||
|
/// <inheritdoc see="Invoke(Action)"/>
|
||||||
|
void InvokeIfRequired(Action callback, DispatcherPriority priority); |
||||||
|
/// <inheritdoc see="Invoke(Action)"/>
|
||||||
|
void InvokeIfRequired(Action callback, DispatcherPriority priority, CancellationToken cancellationToken); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified callback, waits for its completion, and returns the result.
|
||||||
|
/// If the current thread is the thread running the message loop, executes the callback
|
||||||
|
/// directly without pumping the message loop.
|
||||||
|
/// </summary>
|
||||||
|
T InvokeIfRequired<T>(Func<T> callback); |
||||||
|
/// <inheritdoc see="Invoke{T}(Func{T})"/>
|
||||||
|
T InvokeIfRequired<T>(Func<T> callback, DispatcherPriority priority); |
||||||
|
/// <inheritdoc see="Invoke{T}(Func{T})"/>
|
||||||
|
T InvokeIfRequired<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the specified callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns a task that is signalled when the execution of the callback is completed.</returns>
|
||||||
|
Task InvokeAsync(Action callback); |
||||||
|
/// <inheritdoc see="InvokeAsync(Action)"/>
|
||||||
|
Task InvokeAsync(Action callback, DispatcherPriority priority); |
||||||
|
/// <inheritdoc see="InvokeAsync(Action)"/>
|
||||||
|
Task InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken); |
||||||
|
|
||||||
|
/// <inheritdoc see="InvokeAsync(Action)"/>
|
||||||
|
Task<T> InvokeAsync<T>(Func<T> callback); |
||||||
|
/// <inheritdoc see="InvokeAsync(Action)"/>
|
||||||
|
Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority); |
||||||
|
/// <inheritdoc see="InvokeAsync(Action)"/>
|
||||||
|
Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
// 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 System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Parser |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Interface for global assembly cache service.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGlobalAssemblyCacheService |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the file name is within the GAC.
|
||||||
|
/// </summary>
|
||||||
|
bool IsGACAssembly(string fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the names of all assemblies in the GAC.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerable<DomAssemblyName> GetGacAssemblyFullNames(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file name for an assembly stored in the GAC.
|
||||||
|
/// Returns null if the assembly cannot be found.
|
||||||
|
/// </summary>
|
||||||
|
string FindAssemblyInNetGac(DomAssemblyName reference); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,291 @@ |
|||||||
|
// 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 System.Collections.Generic; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using ICSharpCode.Core; |
||||||
|
using ICSharpCode.NRefactory; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.NRefactory.Semantics; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
using ICSharpCode.SharpDevelop.Refactoring; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Parser |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Manages parse runs and caches ParseInformation.
|
||||||
|
/// </summary>
|
||||||
|
public interface IParserService |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets/Sets the task list tokens.
|
||||||
|
/// The getter of this property is thread-safe;
|
||||||
|
/// the setter must only be called on the main thread.
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<string> TaskListTokens { get; set; } |
||||||
|
|
||||||
|
#region Load Solution Projects Thread
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the solution is being loaded, or a major re-parse is happening
|
||||||
|
/// (e.g. after adding a project).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This property is only changed by the main thread.</remarks>
|
||||||
|
bool LoadSolutionProjectsThreadRunning { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is raised when the LoadSolutionProjectsThreadRunning property changes to <c>true</c>.
|
||||||
|
/// This always happens on the main thread.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler LoadSolutionProjectsThreadStarted; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This event is raised when the LoadSolutionProjectsThreadRunning property changes to <c>false</c>.
|
||||||
|
/// This always happens on the main thread.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler LoadSolutionProjectsThreadEnded; // TODO: rename to finished
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GetCompilation
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or creates a compilation for the specified project.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// This method never returns null - in case of errors, a dummy compilation is created.
|
||||||
|
/// </remarks>
|
||||||
|
ICompilation GetCompilation(IProject project); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or creates a compilation for the project that contains the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// This method never returns null - in case of errors, a dummy compilation is created.
|
||||||
|
/// </remarks>
|
||||||
|
ICompilation GetCompilationForFile(FileName fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a snapshot of the current compilations
|
||||||
|
/// This method is useful when a consistent snapshot across multiple compilations is needed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// </remarks>
|
||||||
|
SharpDevelopSolutionSnapshot GetCurrentSolutionSnapshot(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invalidates the current solution snapshot, causing
|
||||||
|
/// the next <see cref="GetCurrentSolutionSnapshot()"/> call to create a new one.
|
||||||
|
/// This method needs to be called whenever IProject.ProjectContent changes.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// </remarks>
|
||||||
|
void InvalidateCurrentSolutionSnapshot(); |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region GetExistingParsedFile
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the unresolved type system for the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file.</param>
|
||||||
|
/// <param name="version">
|
||||||
|
/// Optional: requested version of the file.
|
||||||
|
/// If this parameter is specified and the existing parsed file belongs to a different version,
|
||||||
|
/// this method will return null.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns the IParsedFile for the specified file,
|
||||||
|
/// or null if the file has not been parsed yet.
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>This method is thread-safe.</remarks>
|
||||||
|
IParsedFile GetExistingParsedFile(FileName fileName, ITextSourceVersion version = null, IProject parentProject = null); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets full parse information for the specified file, if it is available.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file.</param>
|
||||||
|
/// <param name="version">
|
||||||
|
/// Optional: requested version of the file.
|
||||||
|
/// If this parameter is specified and the existing parsed file belongs to a different version,
|
||||||
|
/// this method will return null.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// If only the IParsedFile is available (non-full parse information), this method returns null.
|
||||||
|
/// </returns>
|
||||||
|
ParseInformation GetCachedParseInformation(FileName fileName, ITextSourceVersion version = null, IProject parentProject = null); |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Parse
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified file.
|
||||||
|
/// Produces full parse information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file to parse</param>
|
||||||
|
/// <param name="fileContent">Optional: Content of the file to parse.</param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns the ParseInformation for the specified file, or null if the file cannot be parsed.
|
||||||
|
/// For files currently open in an editor, this method does not necessary reparse, but may return
|
||||||
|
/// an existing cached parse information (but only if it's still up-to-date).
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// <para>
|
||||||
|
/// If <paramref name="fileContent"/> is null, this method will block and wait for the main thread
|
||||||
|
/// to retrieve the latest file content. This can cause deadlocks if this method is called within a lock.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If <paramref name="fileContent"/> not null, the exact file version specified will be parsed.
|
||||||
|
/// This method will not wait for the main thread in that case.
|
||||||
|
/// If the specified version is older than the latest version, the old version will be parsed
|
||||||
|
/// and returned, but the old parse information will not be registered.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
ParseInformation Parse(FileName fileName, ITextSource fileContent = null, IProject parentProject = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified file.
|
||||||
|
/// This method does not request full parse information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file to parse</param>
|
||||||
|
/// <param name="fileContent">Optional: Content of the file to parse.</param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// Returns the IParsedFile for the specified file, or null if the file cannot be parsed.
|
||||||
|
/// For files currently open in an editor, this method does not necessarily reparse, but may return
|
||||||
|
/// the existing IParsedFile (but only if it's still up-to-date).
|
||||||
|
/// </returns>
|
||||||
|
/// <remarks><inheritdoc cref="Parse"/></remarks>
|
||||||
|
IParsedFile ParseFile(FileName fileName, ITextSource fileContent = null, IProject parentProject = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified file on a background thread.
|
||||||
|
/// Produces full parse information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file to parse</param>
|
||||||
|
/// <param name="fileContent">Optional: Content of the file to parse.</param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns><inheritdoc cref="Parse"/></returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// <para>
|
||||||
|
/// If <paramref name="fileContent"/> is null, the task wait for the main thread
|
||||||
|
/// to retrieve the latest file content.
|
||||||
|
/// This means that waiting for the task can cause deadlocks. (however, using C# 5 <c>await</c> is safe)
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// If <paramref name="fileContent"/> not null, the exact file version specified will be parsed.
|
||||||
|
/// This method will not wait for the main thread in that case.
|
||||||
|
/// If the specified version is older than the latest version, the old version will be parsed
|
||||||
|
/// and returned, but the old parse information will not be registered.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
Task<ParseInformation> ParseAsync(FileName fileName, ITextSource fileContent = null, IProject parentProject = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the specified file on a background thread.
|
||||||
|
/// This method does not request full parse information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file to parse</param>
|
||||||
|
/// <param name="fileContent">Optional: Content of the file to parse.</param>
|
||||||
|
/// <param name="parentProject">
|
||||||
|
/// Optional: If the file is part of multiple projects, specifies
|
||||||
|
/// which parsed version of the file to return (for example, different project settings
|
||||||
|
/// can cause the file to be parsed differently).
|
||||||
|
/// </param>
|
||||||
|
/// <returns><inheritdoc cref="ParseFile"/></returns>
|
||||||
|
/// <remarks><inheritdoc cref="ParseAsync"/></remarks>
|
||||||
|
Task<IParsedFile> ParseFileAsync(FileName fileName, ITextSource fileContent = null, IProject parentProject = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Resolve
|
||||||
|
ResolveResult Resolve(ITextEditor editor, TextLocation location, |
||||||
|
ICompilation compilation = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
ResolveResult Resolve(FileName fileName, TextLocation location, |
||||||
|
ITextSource fileContent = null, ICompilation compilation = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
Task<ResolveResult> ResolveAsync(FileName fileName, TextLocation location, |
||||||
|
ITextSource fileContent = null, ICompilation compilation = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
|
||||||
|
Task FindLocalReferencesAsync(FileName fileName, IVariable variable, Action<Reference> callback, |
||||||
|
ITextSource fileContent = null, ICompilation compilation = null, |
||||||
|
CancellationToken cancellationToken = default(CancellationToken)); |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Parsed File Listeners
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether a parser is registered for the specified file name.
|
||||||
|
/// </summary>
|
||||||
|
bool HasParser(FileName fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the cached parse information.
|
||||||
|
/// If the file does not belong to any project, this also clears the cached type system.
|
||||||
|
/// </summary>
|
||||||
|
void ClearParseInformation(FileName fileName); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a project that owns the file and wishes to receive parse information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">Name of the file contained in the project.</param>
|
||||||
|
/// <param name="project">The parent project of the file.</param>
|
||||||
|
/// <param name="startAsyncParse">
|
||||||
|
/// Whether to start an asynchronous parse operation for the specified file.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="isLinkedFile">
|
||||||
|
/// Specified whether the file is linked within the project, i.e. likely also belongs to another project.
|
||||||
|
/// The parser services tries to use the project that contains the file directly (non-linked)
|
||||||
|
/// as the primary parent project.
|
||||||
|
/// </param>
|
||||||
|
void AddOwnerProject(FileName fileName, IProject project, bool startAsyncParse, bool isLinkedFile); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a project from the owners of the file.
|
||||||
|
/// This method invokes <c>project.UpdateParseInformation(existingParsedFile, null);</c>.
|
||||||
|
/// (unless existingParsedFile==null)
|
||||||
|
/// </summary>
|
||||||
|
void RemoveOwnerProject(FileName fileName, IProject project); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs whenever parse information was updated. This event is raised on the main thread.
|
||||||
|
/// </summary>
|
||||||
|
event EventHandler<ParseInformationEventArgs> ParseInformationUpdated; |
||||||
|
#endregion
|
||||||
|
} |
||||||
|
} |
@ -1,18 +0,0 @@ |
|||||||
// 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 |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// 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 <see cref="ParserService.ParseInformationUpdated"/> event instead, which is called on the main thread
|
|
||||||
/// and after the parser service released its lock.
|
|
||||||
/// </summary>
|
|
||||||
public delegate void ParsedFileListener(IParsedFile oldFile, IParsedFile newFile); |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,115 @@ |
|||||||
|
// 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 System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using ICSharpCode.NRefactory; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A dictionary that allows multiple pairs with the same key.
|
||||||
|
/// </summary>
|
||||||
|
public class MultiDictionary<TKey, TValue> : ILookup<TKey, TValue> |
||||||
|
{ |
||||||
|
Dictionary<TKey, List<TValue>> dict; |
||||||
|
|
||||||
|
public MultiDictionary() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
public MultiDictionary(IEqualityComparer<TKey> comparer) |
||||||
|
{ |
||||||
|
dict = new Dictionary<TKey, List<TValue>>(comparer); |
||||||
|
} |
||||||
|
|
||||||
|
public void Add(TKey key, TValue value) |
||||||
|
{ |
||||||
|
List<TValue> valueList; |
||||||
|
if (!dict.TryGetValue(key, out valueList)) { |
||||||
|
valueList = new List<TValue>(); |
||||||
|
dict.Add(key, valueList); |
||||||
|
} |
||||||
|
valueList.Add(value); |
||||||
|
} |
||||||
|
|
||||||
|
public bool Remove(TKey key, TValue value) |
||||||
|
{ |
||||||
|
List<TValue> valueList; |
||||||
|
if (dict.TryGetValue(key, out valueList)) { |
||||||
|
if (valueList.Remove(value)) { |
||||||
|
if (valueList.Count == 0) |
||||||
|
dict.Remove(key); |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public void Clear() |
||||||
|
{ |
||||||
|
dict.Clear(); |
||||||
|
} |
||||||
|
|
||||||
|
public IReadOnlyList<TValue> this[TKey key] { |
||||||
|
get { |
||||||
|
List<TValue> list; |
||||||
|
if (dict.TryGetValue(key, out list)) |
||||||
|
return list; |
||||||
|
else |
||||||
|
return new TValue[0]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public int Count { |
||||||
|
get { return dict.Count; } |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerable<TValue> ILookup<TKey, TValue>.this[TKey key] { |
||||||
|
get { return this[key]; } |
||||||
|
} |
||||||
|
|
||||||
|
bool ILookup<TKey, TValue>.Contains(TKey key) |
||||||
|
{ |
||||||
|
return dict.ContainsKey(key); |
||||||
|
} |
||||||
|
|
||||||
|
public IEnumerator<IGrouping<TKey, TValue>> GetEnumerator() |
||||||
|
{ |
||||||
|
foreach (var pair in dict) |
||||||
|
yield return new Grouping(pair.Key, pair.Value); |
||||||
|
} |
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
sealed class Grouping : IGrouping<TKey, TValue> |
||||||
|
{ |
||||||
|
readonly TKey key; |
||||||
|
readonly List<TValue> values; |
||||||
|
|
||||||
|
public Grouping(TKey key, List<TValue> values) |
||||||
|
{ |
||||||
|
this.key = key; |
||||||
|
this.values = values; |
||||||
|
} |
||||||
|
|
||||||
|
public TKey Key { |
||||||
|
get { return key; } |
||||||
|
} |
||||||
|
|
||||||
|
public IEnumerator<TValue> GetEnumerator() |
||||||
|
{ |
||||||
|
return values.GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return values.GetEnumerator(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -1,112 +0,0 @@ |
|||||||
// 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 System.Collections.Generic; |
|
||||||
using System.Threading; |
|
||||||
|
|
||||||
namespace ICSharpCode.SharpDevelop.Util |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// A worker thread that normally sleeps, but can run a queue of commands.
|
|
||||||
///
|
|
||||||
/// This class does not create a worker thread on its own, it merely manages tasks for
|
|
||||||
/// the worker thread that calls <see cref="RunLoop"/>.
|
|
||||||
/// </summary>
|
|
||||||
public class WorkerThread |
|
||||||
{ |
|
||||||
sealed class AsyncTask : IAsyncResult |
|
||||||
{ |
|
||||||
internal readonly ManualResetEventSlim manualResetEvent = new ManualResetEventSlim(false); |
|
||||||
internal readonly Action method; |
|
||||||
volatile bool isCompleted; |
|
||||||
|
|
||||||
internal AsyncTask(Action method) |
|
||||||
{ |
|
||||||
this.method = method; |
|
||||||
} |
|
||||||
|
|
||||||
internal void SetCompleted() |
|
||||||
{ |
|
||||||
isCompleted = true; |
|
||||||
manualResetEvent.Set(); |
|
||||||
} |
|
||||||
|
|
||||||
public bool IsCompleted { |
|
||||||
get { return isCompleted; } |
|
||||||
} |
|
||||||
|
|
||||||
public WaitHandle AsyncWaitHandle { |
|
||||||
get { return manualResetEvent.WaitHandle; } |
|
||||||
} |
|
||||||
|
|
||||||
public object AsyncState { get; set; } |
|
||||||
public bool CompletedSynchronously { get { return false; } } |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs <paramref name="method"/> on the worker thread.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="method">The method to run.</param>
|
|
||||||
/// <returns>IAsyncResult that gets completed when the action has executed.</returns>
|
|
||||||
public IAsyncResult Enqueue(Action method) |
|
||||||
{ |
|
||||||
if (method == null) |
|
||||||
throw new ArgumentNullException("method"); |
|
||||||
AsyncTask task = new AsyncTask(method); |
|
||||||
lock (lockObject) { |
|
||||||
taskQueue.Enqueue(task); |
|
||||||
Monitor.Pulse(lockObject); |
|
||||||
} |
|
||||||
return task; |
|
||||||
} |
|
||||||
|
|
||||||
readonly object lockObject = new object(); |
|
||||||
|
|
||||||
// access needs lock using 'lockObject'
|
|
||||||
Queue<AsyncTask> taskQueue = new Queue<AsyncTask>(); |
|
||||||
// access needs lock using 'lockObject'
|
|
||||||
bool workerRunning; |
|
||||||
|
|
||||||
// not a shared variable: accessed only within worker thread
|
|
||||||
bool exitWorker; |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Runs the worker thread loop on the current thread.
|
|
||||||
/// </summary>
|
|
||||||
public void RunLoop() |
|
||||||
{ |
|
||||||
lock (lockObject) { |
|
||||||
if (workerRunning) |
|
||||||
throw new InvalidOperationException("There already is a worker running"); |
|
||||||
workerRunning = true; |
|
||||||
} |
|
||||||
try { |
|
||||||
exitWorker = false; |
|
||||||
while (!exitWorker) { |
|
||||||
AsyncTask task; |
|
||||||
lock (lockObject) { |
|
||||||
while (taskQueue.Count == 0) |
|
||||||
Monitor.Wait(lockObject); |
|
||||||
task = taskQueue.Dequeue(); |
|
||||||
} |
|
||||||
task.method(); |
|
||||||
task.SetCompleted(); |
|
||||||
} |
|
||||||
} finally { |
|
||||||
lock (lockObject) { |
|
||||||
workerRunning = false; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exits running the worker thread after executing all currently enqueued methods.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>IAsyncResult that gets completed when the worker thread has shut down.</returns>
|
|
||||||
public IAsyncResult ExitWorkerThread() |
|
||||||
{ |
|
||||||
return Enqueue(delegate { exitWorker = true; }); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,120 +0,0 @@ |
|||||||
// 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 System.ComponentModel; |
|
||||||
using System.Reflection; |
|
||||||
using System.Threading; |
|
||||||
using System.Windows.Threading; |
|
||||||
|
|
||||||
namespace ICSharpCode.SharpDevelop |
|
||||||
{ |
|
||||||
/// <summary>
|
|
||||||
/// Implements the ISynchronizeInvoke interface by using a WPF dispatcher
|
|
||||||
/// to perform the cross-thread call.
|
|
||||||
/// </summary>
|
|
||||||
sealed class WpfSynchronizeInvoke : ISynchronizeInvoke |
|
||||||
{ |
|
||||||
readonly Dispatcher dispatcher; |
|
||||||
|
|
||||||
public WpfSynchronizeInvoke(Dispatcher dispatcher) |
|
||||||
{ |
|
||||||
if (dispatcher == null) |
|
||||||
throw new ArgumentNullException("dispatcher"); |
|
||||||
this.dispatcher = dispatcher; |
|
||||||
} |
|
||||||
|
|
||||||
public bool InvokeRequired { |
|
||||||
get { |
|
||||||
return !dispatcher.CheckAccess(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public IAsyncResult BeginInvoke(Delegate method, object[] args) |
|
||||||
{ |
|
||||||
DispatcherOperation op; |
|
||||||
if (args == null || args.Length == 0) |
|
||||||
op = dispatcher.BeginInvoke(DispatcherPriority.Normal, method); |
|
||||||
else if (args.Length == 1) |
|
||||||
op = dispatcher.BeginInvoke(DispatcherPriority.Normal, method, args[0]); |
|
||||||
else |
|
||||||
op = dispatcher.BeginInvoke(DispatcherPriority.Normal, method, args[0], args.Splice(1)); |
|
||||||
return new AsyncResult(op); |
|
||||||
} |
|
||||||
|
|
||||||
sealed class AsyncResult : IAsyncResult |
|
||||||
{ |
|
||||||
internal readonly DispatcherOperation op; |
|
||||||
readonly object lockObj = new object(); |
|
||||||
ManualResetEvent resetEvent; |
|
||||||
|
|
||||||
public AsyncResult(DispatcherOperation op) |
|
||||||
{ |
|
||||||
this.op = op; |
|
||||||
} |
|
||||||
|
|
||||||
public bool IsCompleted { |
|
||||||
get { |
|
||||||
return op.Status == DispatcherOperationStatus.Completed; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public WaitHandle AsyncWaitHandle { |
|
||||||
get { |
|
||||||
lock (lockObj) { |
|
||||||
if (resetEvent == null) { |
|
||||||
op.Completed += op_Completed; |
|
||||||
resetEvent = new ManualResetEvent(false); |
|
||||||
if (IsCompleted) |
|
||||||
resetEvent.Set(); |
|
||||||
} |
|
||||||
return resetEvent; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void op_Completed(object sender, EventArgs e) |
|
||||||
{ |
|
||||||
lock (lockObj) { |
|
||||||
resetEvent.Set(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public object AsyncState { |
|
||||||
get { return null; } |
|
||||||
} |
|
||||||
|
|
||||||
public bool CompletedSynchronously { |
|
||||||
get { return false; } |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public object EndInvoke(IAsyncResult result) |
|
||||||
{ |
|
||||||
AsyncResult r = result as AsyncResult; |
|
||||||
if (r == null) |
|
||||||
throw new ArgumentException("result must be the return value of a WpfSynchronizeInvoke.BeginInvoke call!"); |
|
||||||
r.op.Wait(); |
|
||||||
return r.op.Result; |
|
||||||
} |
|
||||||
|
|
||||||
public object Invoke(Delegate method, object[] args) |
|
||||||
{ |
|
||||||
object result = null; |
|
||||||
Exception exception = null; |
|
||||||
dispatcher.Invoke( |
|
||||||
DispatcherPriority.Normal, |
|
||||||
(Action)delegate { |
|
||||||
try { |
|
||||||
result = method.DynamicInvoke(args); |
|
||||||
} catch (TargetInvocationException ex) { |
|
||||||
exception = ex.InnerException; |
|
||||||
} |
|
||||||
}); |
|
||||||
// if an exception occurred, re-throw it on the calling thread
|
|
||||||
if (exception != null) |
|
||||||
throw new TargetInvocationException(exception); |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,359 @@ |
|||||||
|
// 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 System.Collections.Concurrent; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Text; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Windows.Threading; |
||||||
|
using ICSharpCode.Core; |
||||||
|
using ICSharpCode.NRefactory; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.NRefactory.Semantics; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
using ICSharpCode.SharpDevelop.Gui; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
using ICSharpCode.SharpDevelop.Refactoring; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Parser |
||||||
|
{ |
||||||
|
sealed class ParserService : IParserService |
||||||
|
{ |
||||||
|
IList<ParserDescriptor> parserDescriptors; |
||||||
|
|
||||||
|
public ParserService() |
||||||
|
{ |
||||||
|
parserDescriptors = AddInTree.BuildItems<ParserDescriptor>("/Workspace/Parser", null, false); |
||||||
|
} |
||||||
|
|
||||||
|
#region ParseInformationUpdated
|
||||||
|
public event EventHandler<ParseInformationEventArgs> ParseInformationUpdated = delegate {}; |
||||||
|
|
||||||
|
internal 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.
|
||||||
|
WorkbenchSingleton.SafeThreadAsyncCall( |
||||||
|
delegate { |
||||||
|
string addition; |
||||||
|
if (e.OldParsedFile == null) |
||||||
|
addition = " (new)"; |
||||||
|
else if (e.NewParsedFile == null) |
||||||
|
addition = " (removed)"; |
||||||
|
else |
||||||
|
addition = " (updated)"; |
||||||
|
LoggingService.Debug("ParseInformationUpdated " + e.FileName + addition); |
||||||
|
ParseInformationUpdated(null, e); |
||||||
|
}); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TaskListTokens
|
||||||
|
IReadOnlyList<string> taskListTokens = LoadTaskListTokens(); |
||||||
|
|
||||||
|
public IReadOnlyList<string> TaskListTokens { |
||||||
|
get { return taskListTokens; } |
||||||
|
set { |
||||||
|
SD.MainThread.VerifyAccess(); |
||||||
|
if (!value.SequenceEqual(taskListTokens)) { |
||||||
|
taskListTokens = value.ToArray(); |
||||||
|
PropertyService.SetList("SharpDevelop.TaskListTokens", taskListTokens); |
||||||
|
// TODO: trigger reparse?
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static IReadOnlyList<string> LoadTaskListTokens() |
||||||
|
{ |
||||||
|
if (PropertyService.Contains("SharpDevelop.TaskListTokens")) |
||||||
|
return PropertyService.GetList<string>("SharpDevelop.TaskListTokens").ToArray(); |
||||||
|
else |
||||||
|
return new string[] { "HACK", "TODO", "UNDONE", "FIXME" }; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Load Solution Projects Thread
|
||||||
|
public bool LoadSolutionProjectsThreadRunning { |
||||||
|
get { return false; } |
||||||
|
} |
||||||
|
|
||||||
|
public event EventHandler LoadSolutionProjectsThreadStarted; |
||||||
|
public event EventHandler LoadSolutionProjectsThreadEnded; |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Compilation
|
||||||
|
public ICompilation GetCompilation(IProject project) |
||||||
|
{ |
||||||
|
return GetCurrentSolutionSnapshot().GetCompilation(project); |
||||||
|
} |
||||||
|
|
||||||
|
public ICompilation GetCompilationForFile(FileName fileName) |
||||||
|
{ |
||||||
|
Solution solution = ProjectService.OpenSolution; |
||||||
|
IProject project = solution != null ? solution.FindProjectContainingFile(fileName) : null; |
||||||
|
if (project != null) |
||||||
|
return GetCompilation(project); |
||||||
|
|
||||||
|
var entry = GetFileEntry(fileName, false); |
||||||
|
if (entry != null && entry.parser != null) { |
||||||
|
var parsedFile = entry.GetExistingParsedFile(null, null); |
||||||
|
if (parsedFile != null) { |
||||||
|
ICompilation compilation = entry.parser.CreateCompilationForSingleFile(fileName, parsedFile); |
||||||
|
if (compilation != null) |
||||||
|
return compilation; |
||||||
|
} |
||||||
|
} |
||||||
|
return MinimalCorlib.Instance.CreateCompilation(); |
||||||
|
} |
||||||
|
|
||||||
|
// Use a WeakReference for caching the solution snapshot - it can require
|
||||||
|
// lots of memory and may not be invalidated soon enough if the user
|
||||||
|
// is only browsing code.
|
||||||
|
volatile WeakReference<SharpDevelopSolutionSnapshot> currentSolutionSnapshot; |
||||||
|
|
||||||
|
public SharpDevelopSolutionSnapshot GetCurrentSolutionSnapshot() |
||||||
|
{ |
||||||
|
var weakRef = currentSolutionSnapshot; |
||||||
|
SharpDevelopSolutionSnapshot result; |
||||||
|
if (weakRef == null || !weakRef.TryGetTarget(out result)) { |
||||||
|
// create new snapshot if we don't have one cached
|
||||||
|
result = new SharpDevelopSolutionSnapshot(ProjectService.OpenSolution); |
||||||
|
currentSolutionSnapshot = new WeakReference<SharpDevelopSolutionSnapshot>(result); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
public void InvalidateCurrentSolutionSnapshot() |
||||||
|
{ |
||||||
|
currentSolutionSnapshot = null; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Entry management
|
||||||
|
const int cachedEntryCount = 5; |
||||||
|
Dictionary<FileName, ParserServiceEntry> fileEntryDict = new Dictionary<FileName, ParserServiceEntry>(); |
||||||
|
Queue<ParserServiceEntry> cacheExpiryQueue = new Queue<ParserServiceEntry>(); |
||||||
|
|
||||||
|
ParserServiceEntry GetFileEntry(FileName fileName, bool createIfMissing) |
||||||
|
{ |
||||||
|
if (fileName == null) |
||||||
|
throw new ArgumentNullException("fileName"); |
||||||
|
ParserServiceEntry entry; |
||||||
|
lock (fileEntryDict) { |
||||||
|
if (!fileEntryDict.TryGetValue(fileName, out entry)) { |
||||||
|
if (!createIfMissing) |
||||||
|
return null; |
||||||
|
entry = new ParserServiceEntry(this, fileName); |
||||||
|
fileEntryDict.Add(fileName, entry); |
||||||
|
} |
||||||
|
} |
||||||
|
return entry; |
||||||
|
} |
||||||
|
|
||||||
|
public void ClearParseInformation(FileName fileName) |
||||||
|
{ |
||||||
|
ParserServiceEntry entry = GetFileEntry(fileName, false); |
||||||
|
if (entry != null) { |
||||||
|
entry.ExpireCache(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal void RemoveEntry(ParserServiceEntry entry) |
||||||
|
{ |
||||||
|
Debug.Assert(Monitor.IsEntered(entry)); |
||||||
|
lock (fileEntryDict) { |
||||||
|
ParserServiceEntry entryAtKey; |
||||||
|
if (fileEntryDict.TryGetValue(entry.fileName, out entryAtKey)) { |
||||||
|
if (entry == entryAtKey) |
||||||
|
fileEntryDict.Remove(entry.fileName); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal void RegisterForCacheExpiry(ParserServiceEntry entry) |
||||||
|
{ |
||||||
|
// This method should not be called within any locks
|
||||||
|
Debug.Assert(!Monitor.IsEntered(entry)); |
||||||
|
ParserServiceEntry expiredItem = null; |
||||||
|
lock (cacheExpiryQueue) { |
||||||
|
if (cacheExpiryQueue.Count >= cachedEntryCount) { |
||||||
|
expiredItem = cacheExpiryQueue.Dequeue(); |
||||||
|
} |
||||||
|
cacheExpiryQueue.Enqueue(entry); |
||||||
|
} |
||||||
|
if (expiredItem != null) |
||||||
|
expiredItem.ExpireCache(); |
||||||
|
} |
||||||
|
|
||||||
|
public void AddOwnerProject(FileName fileName, IProject project, bool startAsyncParse, bool isLinkedFile) |
||||||
|
{ |
||||||
|
if (project == null) |
||||||
|
throw new ArgumentNullException("project"); |
||||||
|
GetFileEntry(fileName, true).AddOwnerProject(project, isLinkedFile); |
||||||
|
} |
||||||
|
|
||||||
|
public void RemoveOwnerProject(FileName fileName, IProject project) |
||||||
|
{ |
||||||
|
if (project == null) |
||||||
|
throw new ArgumentNullException("project"); |
||||||
|
var entry = GetFileEntry(fileName, false); |
||||||
|
if (entry != null) |
||||||
|
entry.RemoveOwnerProject(project); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Forward Parse() calls to entry
|
||||||
|
public IParsedFile GetExistingParsedFile(FileName fileName, ITextSourceVersion version, IProject parentProject) |
||||||
|
{ |
||||||
|
var entry = GetFileEntry(fileName, false); |
||||||
|
if (entry != null) |
||||||
|
return entry.GetExistingParsedFile(version, parentProject); |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public ParseInformation GetCachedParseInformation(FileName fileName, ITextSourceVersion version, IProject parentProject) |
||||||
|
{ |
||||||
|
var entry = GetFileEntry(fileName, false); |
||||||
|
if (entry != null) |
||||||
|
return entry.GetCachedParseInformation(version, parentProject); |
||||||
|
else |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public ParseInformation Parse(FileName fileName, ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return GetFileEntry(fileName, true).Parse(fileContent, parentProject, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public IParsedFile ParseFile(FileName fileName, ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return GetFileEntry(fileName, true).ParseFile(fileContent, parentProject, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public Task<ParseInformation> ParseAsync(FileName fileName, ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return GetFileEntry(fileName, true).ParseAsync(fileContent, parentProject, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public Task<IParsedFile> ParseFileAsync(FileName fileName, ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return GetFileEntry(fileName, true).ParseFileAsync(fileContent, parentProject, cancellationToken); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Resolve
|
||||||
|
public ResolveResult Resolve(ITextEditor editor, TextLocation location, ICompilation compilation, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (editor == null) |
||||||
|
throw new ArgumentNullException("editor"); |
||||||
|
return Resolve(editor.FileName, location, editor.Document, compilation, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public ResolveResult Resolve(FileName fileName, TextLocation location, ITextSource fileContent, ICompilation compilation, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
var entry = GetFileEntry(fileName, true); |
||||||
|
if (entry.parser == null) |
||||||
|
return ErrorResolveResult.UnknownError; |
||||||
|
IProject project = compilation != null ? compilation.GetProject() : null; |
||||||
|
var parseInfo = entry.Parse(fileContent, project, cancellationToken); |
||||||
|
if (parseInfo == null) |
||||||
|
return ErrorResolveResult.UnknownError; |
||||||
|
if (compilation == null) |
||||||
|
compilation = GetCompilationForFile(fileName); |
||||||
|
ResolveResult rr = entry.parser.Resolve(parseInfo, location, compilation, cancellationToken); |
||||||
|
LoggingService.Debug("Resolved " + location + " to " + rr); |
||||||
|
return rr; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<ResolveResult> ResolveAsync(FileName fileName, TextLocation location, ITextSource fileContent, ICompilation compilation, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
var entry = GetFileEntry(fileName, true); |
||||||
|
if (entry.parser == null) |
||||||
|
return Task.FromResult<ResolveResult>(ErrorResolveResult.UnknownError); |
||||||
|
IProject project = compilation != null ? compilation.GetProject() : null; |
||||||
|
return entry.ParseAsync(fileContent, project, cancellationToken).ContinueWith( |
||||||
|
delegate (Task<ParseInformation> parseInfoTask) { |
||||||
|
var parseInfo = parseInfoTask.Result; |
||||||
|
if (parseInfo == null) |
||||||
|
return ErrorResolveResult.UnknownError; |
||||||
|
if (compilation == null) |
||||||
|
compilation = GetCompilationForFile(fileName); |
||||||
|
ResolveResult rr = entry.parser.Resolve(parseInfo, location, compilation, cancellationToken); |
||||||
|
LoggingService.Debug("Resolved " + location + " to " + rr); |
||||||
|
return rr; |
||||||
|
}, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public async Task FindLocalReferencesAsync(FileName fileName, IVariable variable, Action<Reference> callback, ITextSource fileContent, ICompilation compilation, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
var entry = GetFileEntry(fileName, true); |
||||||
|
if (entry.parser == null) |
||||||
|
return; |
||||||
|
if (fileContent == null) |
||||||
|
fileContent = SD.FileService.GetFileContent(fileName); |
||||||
|
if (compilation == null) |
||||||
|
compilation = GetCompilationForFile(fileName); |
||||||
|
var parseInfo = await entry.ParseAsync(fileContent, compilation.GetProject(), cancellationToken).ConfigureAwait(false); |
||||||
|
await Task.Run( |
||||||
|
() => entry.parser.FindLocalReferences(parseInfo, fileContent, variable, compilation, callback, cancellationToken) |
||||||
|
); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region HasParser / CreateParser
|
||||||
|
public bool HasParser(FileName fileName) |
||||||
|
{ |
||||||
|
if (fileName == null) |
||||||
|
throw new ArgumentNullException("fileName"); |
||||||
|
if (parserDescriptors == null) |
||||||
|
return false; |
||||||
|
foreach (ParserDescriptor descriptor in parserDescriptors) { |
||||||
|
if (descriptor.CanParse(fileName)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IParser instance that can parse the specified file.
|
||||||
|
/// This method is thread-safe.
|
||||||
|
/// </summary>
|
||||||
|
internal IParser CreateParser(FileName 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.TaskListTokens = TaskListTokens; |
||||||
|
return p; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
internal void StartParserThread() |
||||||
|
{ |
||||||
|
// TODO
|
||||||
|
} |
||||||
|
|
||||||
|
internal void StopParserThread() |
||||||
|
{ |
||||||
|
// TODO
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,330 @@ |
|||||||
|
// 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 System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.IO; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
|
||||||
|
using ICSharpCode.Core; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Parser |
||||||
|
{ |
||||||
|
sealed class ParserServiceEntry |
||||||
|
{ |
||||||
|
struct ProjectEntry |
||||||
|
{ |
||||||
|
public readonly IProject Project; |
||||||
|
public readonly IParsedFile ParsedFile; |
||||||
|
public readonly ParseInformation CachedParseInformation; |
||||||
|
|
||||||
|
public ProjectEntry(IProject project, IParsedFile parsedFile, ParseInformation cachedParseInformation) |
||||||
|
{ |
||||||
|
this.Project = project; |
||||||
|
this.ParsedFile = parsedFile; |
||||||
|
this.CachedParseInformation = cachedParseInformation; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
readonly ParserService parserService; |
||||||
|
internal readonly FileName fileName; |
||||||
|
internal readonly IParser parser; |
||||||
|
List<ProjectEntry> entries = new List<ProjectEntry> { default(ProjectEntry) }; |
||||||
|
ITextSourceVersion currentVersion; |
||||||
|
|
||||||
|
public ParserServiceEntry(ParserService parserService, FileName fileName) |
||||||
|
{ |
||||||
|
this.parserService = parserService; |
||||||
|
this.fileName = fileName; |
||||||
|
this.parser = parserService.CreateParser(fileName); |
||||||
|
} |
||||||
|
|
||||||
|
#region Owner Projects
|
||||||
|
IProject PrimaryProject { |
||||||
|
get { return entries[0].Project; } |
||||||
|
} |
||||||
|
|
||||||
|
int FindIndexForProject(IProject parentProject) |
||||||
|
{ |
||||||
|
if (parentProject == null) |
||||||
|
return 0; |
||||||
|
for (int i = 0; i < entries.Count; i++) { |
||||||
|
if (entries[i].Project == parentProject) |
||||||
|
return i; |
||||||
|
} |
||||||
|
// project not found
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
public void AddOwnerProject(IProject project, bool isLinkedFile) |
||||||
|
{ |
||||||
|
Debug.Assert(project != null); |
||||||
|
lock (this) { |
||||||
|
if (FindIndexForProject(project) >= 0) |
||||||
|
throw new InvalidOperationException("The project alreadys owns the file"); |
||||||
|
ProjectEntry newEntry = new ProjectEntry(project, null, null); |
||||||
|
if (entries[0].Project == null) { |
||||||
|
entries[0] = newEntry; |
||||||
|
} else if (isLinkedFile) { |
||||||
|
entries.Add(newEntry); |
||||||
|
} else { |
||||||
|
entries.Insert(0, newEntry); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void RemoveOwnerProject(IProject project) |
||||||
|
{ |
||||||
|
Debug.Assert(project != null); |
||||||
|
lock (this) { |
||||||
|
int index = FindIndexForProject(project); |
||||||
|
if (index < 0) |
||||||
|
throw new InvalidOperationException("The project does not own the file"); |
||||||
|
if (entries.Count == 1) { |
||||||
|
entries[0] = default(ProjectEntry); |
||||||
|
} else { |
||||||
|
entries.RemoveAt(index); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares currentVersion with version.
|
||||||
|
/// -1 = currentVersion is older; 0 = same version; 1 = newVersion is older
|
||||||
|
/// </summary>
|
||||||
|
int CompareVersions(ITextSourceVersion newVersion) |
||||||
|
{ |
||||||
|
if (currentVersion != null && newVersion != null && currentVersion.BelongsToSameDocumentAs(newVersion)) |
||||||
|
return currentVersion.CompareAge(newVersion); |
||||||
|
else |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
#region Expire Cache + GetExistingParsedFile + GetCachedParseInformation
|
||||||
|
public void ExpireCache() |
||||||
|
{ |
||||||
|
lock (this) { |
||||||
|
if (PrimaryProject == null) { |
||||||
|
parserService.RemoveEntry(this); |
||||||
|
} else { |
||||||
|
for (int i = 0; i < entries.Count; i++) { |
||||||
|
var oldEntry = entries[i]; |
||||||
|
entries[i] = new ProjectEntry(oldEntry.Project, oldEntry.ParsedFile, null); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public IParsedFile GetExistingParsedFile(ITextSourceVersion version, IProject parentProject) |
||||||
|
{ |
||||||
|
lock (this) { |
||||||
|
if (version != null && CompareVersions(version) != 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
int index = FindIndexForProject(parentProject); |
||||||
|
if (index < 0) |
||||||
|
return null; |
||||||
|
return entries[index].ParsedFile; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public ParseInformation GetCachedParseInformation(ITextSourceVersion version, IProject parentProject) |
||||||
|
{ |
||||||
|
lock (this) { |
||||||
|
if (version != null && CompareVersions(version) != 0) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
int index = FindIndexForProject(parentProject); |
||||||
|
if (index < 0) |
||||||
|
return null; |
||||||
|
return entries[index].CachedParseInformation; |
||||||
|
} |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Parse
|
||||||
|
public ParseInformation Parse(ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (fileContent == null) { |
||||||
|
fileContent = SD.FileService.GetFileContent(fileName); |
||||||
|
} |
||||||
|
|
||||||
|
return DoParse(fileContent, parentProject, false, cancellationToken).CachedParseInformation; |
||||||
|
} |
||||||
|
|
||||||
|
public IParsedFile ParseFile(ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (fileContent == null) { |
||||||
|
fileContent = SD.FileService.GetFileContent(fileName); |
||||||
|
} |
||||||
|
|
||||||
|
return DoParse(fileContent, parentProject, false, cancellationToken).ParsedFile; |
||||||
|
} |
||||||
|
|
||||||
|
ProjectEntry DoParse(ITextSource fileContent, IProject parentProject, bool fullParseInformationRequested, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (parser == null) |
||||||
|
return default(ProjectEntry); |
||||||
|
|
||||||
|
if (fileContent == null) { |
||||||
|
// No file content was specified. Because the callers of this method already check for currently open files,
|
||||||
|
// we can assume that the file isn't open and simply read it from disk.
|
||||||
|
try { |
||||||
|
fileContent = SD.FileService.GetFileContentFromDisk(fileName, cancellationToken); |
||||||
|
} catch (IOException) { |
||||||
|
// It is possible that the file gets deleted/becomes inaccessible while a background parse
|
||||||
|
// operation is enqueued, so we have to handle IO exceptions.
|
||||||
|
return default(ProjectEntry); |
||||||
|
} catch (UnauthorizedAccessException) { |
||||||
|
return default(ProjectEntry); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ProjectEntry result; |
||||||
|
lock (this) { |
||||||
|
int index = FindIndexForProject(parentProject); |
||||||
|
int versionComparison = CompareVersions(fileContent.Version); |
||||||
|
if (versionComparison > 0 || index < 0) { |
||||||
|
// We're going backwards in time, or are requesting a project that is not an owner
|
||||||
|
// for this entry.
|
||||||
|
var parseInfo = parser.Parse(fileName, fileContent, fullParseInformationRequested, parentProject, cancellationToken); |
||||||
|
FreezableHelper.Freeze(parseInfo.ParsedFile); |
||||||
|
return new ProjectEntry(parentProject, parseInfo.ParsedFile, parseInfo); |
||||||
|
} else { |
||||||
|
if (versionComparison == 0 && index >= 0) { |
||||||
|
// If full parse info is requested, ensure we have full parse info.
|
||||||
|
if (!(fullParseInformationRequested && entries[index].CachedParseInformation == null)) { |
||||||
|
// We already have the requested version parsed, just return it:
|
||||||
|
return entries[index]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ParseInformationEventArgs[] results = new ParseInformationEventArgs[entries.Count]; |
||||||
|
for (int i = 0; i < entries.Count; i++) { |
||||||
|
ParseInformation parseInfo; |
||||||
|
try { |
||||||
|
parseInfo = parser.Parse(fileName, fileContent, fullParseInformationRequested, entries[i].Project, cancellationToken); |
||||||
|
} catch (Exception ex) { |
||||||
|
SD.LoggingService.Error("Got " + ex.GetType().Name + " while parsing " + fileName); |
||||||
|
throw; |
||||||
|
} |
||||||
|
if (parseInfo == null) |
||||||
|
throw new NullReferenceException(parser.GetType().Name + ".Parse() returned null"); |
||||||
|
if (fullParseInformationRequested && !parseInfo.IsFullParseInformation) |
||||||
|
throw new InvalidOperationException(parser.GetType().Name + ".Parse() did not return full parse info as requested."); |
||||||
|
FreezableHelper.Freeze(parseInfo.ParsedFile); |
||||||
|
results[i] = new ParseInformationEventArgs(entries[i].Project, entries[i].ParsedFile, parseInfo); |
||||||
|
} |
||||||
|
|
||||||
|
// Only if all parse runs succeeded, register the parse information.
|
||||||
|
currentVersion = fileContent.Version; |
||||||
|
for (int i = 0; i < entries.Count; i++) { |
||||||
|
if (fullParseInformationRequested || entries[i].CachedParseInformation != null) |
||||||
|
entries[i] = new ProjectEntry(entries[i].Project, entries[i].ParsedFile, results[i].NewParseInformation); |
||||||
|
else |
||||||
|
entries[i] = new ProjectEntry(entries[i].Project, entries[i].ParsedFile, null); |
||||||
|
if (entries[i].Project != null) |
||||||
|
entries[i].Project.OnParseInformationUpdated(results[i]); |
||||||
|
parserService.RaiseParseInformationUpdated(results[i]); |
||||||
|
} |
||||||
|
result = entries[index]; |
||||||
|
} // exit lock
|
||||||
|
parserService.RegisterForCacheExpiry(this); |
||||||
|
return result; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ParseAsync
|
||||||
|
Task<ProjectEntry> runningAsyncParseTask; |
||||||
|
ITextSourceVersion runningAsyncParseFileContentVersion; |
||||||
|
bool runningAsyncParseFullInfoRequested; |
||||||
|
|
||||||
|
public async Task<ParseInformation> ParseAsync(ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return (await DoParseAsync(fileContent, parentProject, true, cancellationToken)).CachedParseInformation; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task<IParsedFile> ParseFileAsync(ITextSource fileContent, IProject parentProject, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return (await DoParseAsync(fileContent, parentProject, false, cancellationToken)).ParsedFile; |
||||||
|
} |
||||||
|
|
||||||
|
Task<ProjectEntry> DoParseAsync(ITextSource fileContent, IProject parentProject, bool requestFullParseInformation, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
// Create snapshot of file content, if required
|
||||||
|
bool lookupOpenFileOnTargetThread; |
||||||
|
if (fileContent != null) { |
||||||
|
lookupOpenFileOnTargetThread = false; |
||||||
|
// File content was explicitly specified:
|
||||||
|
// Let's make a snapshot in case the text source is mutable.
|
||||||
|
fileContent = fileContent.CreateSnapshot(); |
||||||
|
} else if (SD.MainThread.InvokeRequired) { |
||||||
|
// fileContent == null && not on the main thread:
|
||||||
|
// Don't fetch the file content right now; if we need to SafeThreadCall() anyways,
|
||||||
|
// it's better to do so from the background task.
|
||||||
|
lookupOpenFileOnTargetThread = true; |
||||||
|
} else { |
||||||
|
// fileContent == null && we are on the main thread:
|
||||||
|
// Let's look up the file in the list of open files right now
|
||||||
|
// so that we don't need to SafeThreadCall() later on.
|
||||||
|
lookupOpenFileOnTargetThread = false; |
||||||
|
fileContent = SD.FileService.GetFileContentForOpenFile(fileName); |
||||||
|
} |
||||||
|
Task<ProjectEntry> task; |
||||||
|
lock (this) { |
||||||
|
if (fileContent != null) { |
||||||
|
// Optimization:
|
||||||
|
// don't start a background task if fileContent was specified and up-to-date parse info is available
|
||||||
|
int index = FindIndexForProject(parentProject); |
||||||
|
int versionComparison = CompareVersions(fileContent.Version); |
||||||
|
if (versionComparison == 0 && index >= 0) { |
||||||
|
// If full parse info is requested, ensure we have full parse info.
|
||||||
|
if (!(requestFullParseInformation && entries[index].CachedParseInformation == null)) { |
||||||
|
// We already have the requested version parsed, just return it:
|
||||||
|
return Task.FromResult(entries[index]); |
||||||
|
} |
||||||
|
} |
||||||
|
// Optimization:
|
||||||
|
// if an equivalent task is already running, return that one instead
|
||||||
|
if (runningAsyncParseTask != null && (!requestFullParseInformation || runningAsyncParseFullInfoRequested) |
||||||
|
&& runningAsyncParseFileContentVersion.BelongsToSameDocumentAs(fileContent.Version) |
||||||
|
&& runningAsyncParseFileContentVersion.CompareAge(fileContent.Version) == 0) |
||||||
|
{ |
||||||
|
return runningAsyncParseTask; |
||||||
|
} |
||||||
|
} |
||||||
|
task = new Task<ProjectEntry>( |
||||||
|
delegate { |
||||||
|
try { |
||||||
|
if (lookupOpenFileOnTargetThread) { |
||||||
|
fileContent = SD.FileService.GetFileContentForOpenFile(fileName); |
||||||
|
} |
||||||
|
return DoParse(fileContent, parentProject, requestFullParseInformation, cancellationToken); |
||||||
|
} finally { |
||||||
|
lock (this) { |
||||||
|
runningAsyncParseTask = null; |
||||||
|
runningAsyncParseFileContentVersion = null; |
||||||
|
} |
||||||
|
} |
||||||
|
}, cancellationToken); |
||||||
|
if (fileContent != null && fileContent.Version != null && !cancellationToken.CanBeCanceled) { |
||||||
|
runningAsyncParseTask = task; |
||||||
|
runningAsyncParseFileContentVersion = fileContent.Version; |
||||||
|
runningAsyncParseFullInfoRequested = requestFullParseInformation; |
||||||
|
} |
||||||
|
} |
||||||
|
task.Start(); |
||||||
|
return task; |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
} |
||||||
|
} |
@ -1,4 +1,4 @@ |
|||||||
<Application x:Class="ICSharpCode.SharpDevelop.Gui.App" x:ClassModifier="internal" |
<Application x:Class="ICSharpCode.SharpDevelop.Startup.App" x:ClassModifier="internal" |
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||||
xmlns:core = "http://icsharpcode.net/sharpdevelop/core" |
xmlns:core = "http://icsharpcode.net/sharpdevelop/core" |
@ -0,0 +1,146 @@ |
|||||||
|
// 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 System.ComponentModel; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using System.Windows.Threading; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Workbench |
||||||
|
{ |
||||||
|
sealed class DispatcherMessageLoop : IMessageLoop, ISynchronizeInvoke |
||||||
|
{ |
||||||
|
readonly Dispatcher dispatcher; |
||||||
|
readonly SynchronizationContext synchronizationContext; |
||||||
|
|
||||||
|
public DispatcherMessageLoop(Dispatcher dispatcher, SynchronizationContext synchronizationContext) |
||||||
|
{ |
||||||
|
this.dispatcher = dispatcher; |
||||||
|
this.synchronizationContext = synchronizationContext; |
||||||
|
} |
||||||
|
|
||||||
|
public Thread Thread { |
||||||
|
get { return dispatcher.Thread; } |
||||||
|
} |
||||||
|
|
||||||
|
public Dispatcher Dispatcher { |
||||||
|
get { return dispatcher; } |
||||||
|
} |
||||||
|
|
||||||
|
public SynchronizationContext SynchronizationContext { |
||||||
|
get { return synchronizationContext; } |
||||||
|
} |
||||||
|
|
||||||
|
public ISynchronizeInvoke SynchronizingObject { |
||||||
|
get { return this; } |
||||||
|
} |
||||||
|
|
||||||
|
public bool InvokeRequired { |
||||||
|
get { return !dispatcher.CheckAccess(); } |
||||||
|
} |
||||||
|
|
||||||
|
public bool CheckAccess() |
||||||
|
{ |
||||||
|
return dispatcher.CheckAccess(); |
||||||
|
} |
||||||
|
|
||||||
|
public void VerifyAccess() |
||||||
|
{ |
||||||
|
dispatcher.VerifyAccess(); |
||||||
|
} |
||||||
|
|
||||||
|
public void InvokeIfRequired(Action callback) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
callback(); |
||||||
|
else |
||||||
|
dispatcher.Invoke(callback); |
||||||
|
} |
||||||
|
|
||||||
|
public void InvokeIfRequired(Action callback, DispatcherPriority priority) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
callback(); |
||||||
|
else |
||||||
|
dispatcher.Invoke(callback, priority); |
||||||
|
} |
||||||
|
|
||||||
|
public void InvokeIfRequired(Action callback, DispatcherPriority priority, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
callback(); |
||||||
|
else |
||||||
|
dispatcher.Invoke(callback, priority, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public T InvokeIfRequired<T>(Func<T> callback) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
return callback(); |
||||||
|
else |
||||||
|
return dispatcher.Invoke(callback); |
||||||
|
} |
||||||
|
|
||||||
|
public T InvokeIfRequired<T>(Func<T> callback, DispatcherPriority priority) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
return callback(); |
||||||
|
else |
||||||
|
return dispatcher.Invoke(callback, priority); |
||||||
|
} |
||||||
|
|
||||||
|
public T InvokeIfRequired<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
if (dispatcher.CheckAccess()) |
||||||
|
return callback(); |
||||||
|
else |
||||||
|
return dispatcher.Invoke(callback, priority, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
public Task InvokeAsync(Action callback) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task InvokeAsync(Action callback, DispatcherPriority priority) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback, priority).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback, priority, cancellationToken).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<T> InvokeAsync<T>(Func<T> callback) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback, priority).Task; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync(callback, priority, cancellationToken).Task; |
||||||
|
} |
||||||
|
|
||||||
|
IAsyncResult ISynchronizeInvoke.BeginInvoke(Delegate method, object[] args) |
||||||
|
{ |
||||||
|
return dispatcher.InvokeAsync<object>(() => method.DynamicInvoke(args)).Task; |
||||||
|
} |
||||||
|
|
||||||
|
object ISynchronizeInvoke.EndInvoke(IAsyncResult result) |
||||||
|
{ |
||||||
|
return ((Task<object>)result).Result; |
||||||
|
} |
||||||
|
|
||||||
|
object ISynchronizeInvoke.Invoke(Delegate method, object[] args) |
||||||
|
{ |
||||||
|
return dispatcher.Invoke(() => method.DynamicInvoke(args)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
// 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 System.IO; |
||||||
|
using System.Text; |
||||||
|
using System.Threading; |
||||||
|
using ICSharpCode.AvalonEdit.Utils; |
||||||
|
using ICSharpCode.Core; |
||||||
|
using ICSharpCode.NRefactory.Editor; |
||||||
|
using ICSharpCode.SharpDevelop.Editor; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Workbench |
||||||
|
{ |
||||||
|
sealed class FileService : IFileService |
||||||
|
{ |
||||||
|
public Encoding DefaultFileEncoding { |
||||||
|
get { |
||||||
|
return Encoding.GetEncoding(SharpDevelop.FileService.DefaultFileEncodingCodePage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public ITextSource GetFileContent(FileName fileName) |
||||||
|
{ |
||||||
|
return GetFileContentForOpenFile(fileName) ?? GetFileContentFromDisk(fileName, CancellationToken.None); |
||||||
|
} |
||||||
|
|
||||||
|
public ITextSource GetFileContent(string fileName) |
||||||
|
{ |
||||||
|
return GetFileContent(FileName.Create(fileName)); |
||||||
|
} |
||||||
|
|
||||||
|
public ITextSource GetFileContentForOpenFile(FileName fileName) |
||||||
|
{ |
||||||
|
return SD.MainThread.InvokeIfRequired( |
||||||
|
delegate { |
||||||
|
OpenedFile file = SharpDevelop.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 StringTextSource(FileReader.ReadFileContent(s, DefaultFileEncoding)); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public ITextSource GetFileContentFromDisk(FileName fileName, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return new StringTextSource(FileReader.ReadFileContent(fileName, DefaultFileEncoding)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue