From 0c7ba630e4b17bc0e36502c52094ef747e87fd8c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 5 Jul 2014 10:23:10 +0200 Subject: [PATCH 01/11] fix #498: Additional problems in DebuggerDotCompletion fix #460: Debugger console: No completion inside constructors --- .../Project/CSharpBinding.csproj | 1 + .../Project/Src/CSharpLanguageBinding.cs | 280 +++++------------- .../Project/Src/CSharpTextEditorExtension.cs | 253 ++++++++++++++++ .../Src/Completion/CSharpCompletionBinding.cs | 2 - .../Src/Completion/CSharpCompletionContext.cs | 49 ++- .../Completion/CSharpCompletionDataFactory.cs | 2 +- .../Breakpoints/BreakpointEditorPopup.xaml.cs | 3 +- .../Pads/AutoCompleteTextBox.cs | 18 +- .../Debugger.AddIn/Pads/ConsolePad.cs | 5 +- .../Pads/DebuggerDotCompletion.cs | 156 +--------- .../LanguageBinding/DefaultLanguageBinding.cs | 2 +- .../LanguageBinding/ILanguageBinding.cs | 5 +- 12 files changed, 388 insertions(+), 388 deletions(-) create mode 100644 src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj index dbbd1b7739..970be27a88 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.csproj @@ -91,6 +91,7 @@ CSharpSemanticHighlighter.cs + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs index 99e66e6178..548fddd585 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpLanguageBinding.cs @@ -20,13 +20,18 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Threading; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.NRefactory; +using ICSharpCode.NRefactory.CSharp; +using ICSharpCode.NRefactory.CSharp.Refactoring; +using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop.Editor.CodeCompletion; +using ICSharpCode.SharpDevelop.Project; using CSharpBinding.Completion; using CSharpBinding.FormattingStrategy; using CSharpBinding.Refactoring; @@ -50,223 +55,98 @@ namespace CSharpBinding this.container.AddService(typeof(System.CodeDom.Compiler.CodeDomProvider), new Microsoft.CSharp.CSharpCodeProvider()); } - public override ICodeCompletionBinding CreateCompletionBinding(FileName fileName, TextLocation currentLocation, ICSharpCode.NRefactory.Editor.ITextSource fileContent) + public override ICodeCompletionBinding CreateCompletionBinding(string expressionToComplete, FileName fileName, TextLocation location, ICodeContext context) { if (fileName == null) throw new ArgumentNullException("fileName"); - return new CSharpCompletionBinding(fileName, currentLocation, fileContent); - } - } - - public class CSharpTextEditorExtension : ITextEditorExtension - { - ITextEditor editor; - IssueManager inspectionManager; - IList contextActionProviders; - CodeManipulation codeManipulation; - CaretReferenceHighlightRenderer renderer; - CodeEditorFormattingOptionsAdapter options; - TextEditorOptions originalEditorOptions; - - public void Attach(ITextEditor editor) - { - this.editor = editor; - inspectionManager = new IssueManager(editor); - codeManipulation = new CodeManipulation(editor); - renderer = new CaretReferenceHighlightRenderer(editor); - - // Patch editor options (indentation) to project-specific settings - if (!editor.ContextActionProviders.IsReadOnly) { - contextActionProviders = AddInTree.BuildItems("/SharpDevelop/ViewContent/TextEditor/C#/ContextActions", null); - editor.ContextActionProviders.AddRange(contextActionProviders); - } - - // Create instance of options adapter and register it as service - var formattingPolicy = CSharpFormattingPolicies.Instance.GetProjectOptions( - SD.ProjectService.FindProjectContainingFile(editor.FileName)); - var textEditor = editor.GetService(); - if (textEditor != null) { - options = new CodeEditorFormattingOptionsAdapter(textEditor.Options, editor.Options, formattingPolicy.OptionsContainer); - var textViewServices = textEditor.TextArea.TextView.Services; - - // Unregister any previous ITextEditorOptions instance from editor, if existing, register our impl. - textViewServices.RemoveService(typeof(ITextEditorOptions)); - textViewServices.AddService(typeof(ITextEditorOptions), options); - - // Set TextEditor's options to same object - originalEditorOptions = textEditor.Options; - textEditor.Options = options.TextEditorOptions; - } + if (context == null) + throw new ArgumentNullException("context"); + string content = GeneratePartialClassContextStub(fileName, location, context); + const string caretPoint = "$__Caret_Point__$;"; + int caretOffset = content.IndexOf(caretPoint, StringComparison.Ordinal) + expressionToComplete.Length; + SD.Log.DebugFormatted("context used for dot completion: {0}", content.Replace(caretPoint, "$" + expressionToComplete + "|$")); + var doc = new ReadOnlyDocument(content.Replace(caretPoint, expressionToComplete)); + return new CSharpCompletionBinding(fileName, doc.GetLocation(caretOffset), doc.CreateSnapshot()); } - public void Detach() + static string GeneratePartialClassContextStub(FileName fileName, TextLocation location, ICodeContext context) { - var textEditor = editor.GetService(); - if (textEditor != null) { - var textView = textEditor.TextArea.TextView; - - // Unregister our ITextEditorOptions instance from editor - var optionsService = textView.GetService(); - if ((optionsService != null) && (optionsService == options)) - textView.Services.RemoveService(typeof(ITextEditorOptions)); - - // Reset TextEditor options, too? - if ((textEditor.Options != null) && (textEditor.Options == options.TextEditorOptions)) - textEditor.Options = originalEditorOptions; - } - - codeManipulation.Dispose(); - if (inspectionManager != null) { - inspectionManager.Dispose(); - inspectionManager = null; - } - if (contextActionProviders != null) { - editor.ContextActionProviders.RemoveAll(contextActionProviders.Contains); + var compilation = SD.ParserService.GetCompilationForFile(fileName); + var file = SD.ParserService.GetExistingUnresolvedFile(fileName); + if (compilation == null || file == null) + return ""; + var unresolvedMember = file.GetMember(location); + if (unresolvedMember == null) + return ""; + var member = unresolvedMember.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); + if (member == null) + return ""; + var builder = new TypeSystemAstBuilder(); + MethodDeclaration decl; + if (unresolvedMember is IMethod) { + // If it's a method, convert it directly (including parameters + type parameters) + decl = (MethodDeclaration)builder.ConvertEntity(member); + } else { + // Otherwise, create a method anyways, and copy the parameters + decl = new MethodDeclaration(); + if (member is IParameterizedMember) { + foreach (var p in ((IParameterizedMember)member).Parameters) { + decl.Parameters.Add(builder.ConvertParameter(p)); + } + } } - renderer.Dispose(); - options = null; - this.editor = null; - } - } - - class CodeEditorFormattingOptionsAdapter : ITextEditorOptions, INotifyPropertyChanged - { - CSharpFormattingOptionsContainer container; - readonly TextEditorOptions avalonEditOptions; - readonly TextEditorOptions originalAvalonEditOptions; - readonly ITextEditorOptions originalSDOptions; - - public CodeEditorFormattingOptionsAdapter(TextEditorOptions originalAvalonEditOptions, ITextEditorOptions originalSDOptions, CSharpFormattingOptionsContainer container) - { - if (originalAvalonEditOptions == null) - throw new ArgumentNullException("originalAvalonEditOptions"); - if (originalSDOptions == null) - throw new ArgumentNullException("originalSDOptions"); - if (container == null) - throw new ArgumentNullException("container"); - - this.originalAvalonEditOptions = originalAvalonEditOptions; - this.avalonEditOptions = new TextEditorOptions(originalAvalonEditOptions); - this.originalSDOptions = originalSDOptions; - this.container = container; - - // Update overridden options once - UpdateOverriddenProperties(); - - CSharpFormattingPolicies.Instance.FormattingPolicyUpdated += OnFormattingPolicyUpdated; - this.originalAvalonEditOptions.PropertyChanged += OnOrigAvalonOptionsPropertyChanged; - this.originalSDOptions.PropertyChanged += OnSDOptionsPropertyChanged; - } - - void OnFormattingPolicyUpdated(object sender, CSharpBinding.FormattingStrategy.CSharpFormattingPolicyUpdateEventArgs e) - { - // Update editor options from changed policy - UpdateOverriddenProperties(); - - OnPropertyChanged("IndentationSize"); - OnPropertyChanged("IndentationString"); - OnPropertyChanged("ConvertTabsToSpaces"); + decl.Name = "__DebuggerStub__"; + decl.ReturnType = builder.ConvertType(member.ReturnType); + decl.Modifiers = unresolvedMember.IsStatic ? Modifiers.Static : Modifiers.None; + // Make the method look like an explicit interface implementation so that it doesn't appear in CC + decl.PrivateImplementationType = new SimpleType("__DummyType__"); + decl.Body = GenerateBodyFromContext(builder, context.LocalVariables.ToArray()); + return WrapInType(unresolvedMember.DeclaringTypeDefinition, decl).ToString(); } - - void UpdateOverriddenProperties() + + static BlockStatement GenerateBodyFromContext(TypeSystemAstBuilder builder, IVariable[] variables) { - avalonEditOptions.IndentationSize = container.GetEffectiveIndentationSize() ?? originalSDOptions.IndentationSize; - avalonEditOptions.ConvertTabsToSpaces = container.GetEffectiveConvertTabsToSpaces() ?? originalSDOptions.ConvertTabsToSpaces; + var body = new BlockStatement(); + foreach (var v in variables) + body.Statements.Add(new VariableDeclarationStatement(builder.ConvertType(v.Type), v.Name)); + body.Statements.Add(new ExpressionStatement(new IdentifierExpression("$__Caret_Point__$"))); + return body; } - void OnOrigAvalonOptionsPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + static AstNode WrapInType(IUnresolvedTypeDefinition entity, EntityDeclaration decl) { - if ((e.PropertyName != "IndentationSize") && (e.PropertyName != "IndentationString") && (e.PropertyName != "ConvertTabsToSpaces")) { - // Update values in our own TextEditorOptions instance - PropertyInfo propertyInfo = typeof(TextEditorOptions).GetProperty(e.PropertyName); - if (propertyInfo != null) { - propertyInfo.SetValue(avalonEditOptions, propertyInfo.GetValue(originalAvalonEditOptions)); + if (entity == null) + return decl; + // Wrap decl in TypeDeclaration + decl = new TypeDeclaration { + ClassType = GetClassType(entity), + Modifiers = Modifiers.Partial, + Name = entity.Name, + Members = { decl } + }; + if (entity.DeclaringTypeDefinition != null) { + // Handle nested types + return WrapInType(entity.DeclaringTypeDefinition, decl); + } + if (string.IsNullOrEmpty(entity.Namespace)) + return decl; + return new NamespaceDeclaration(entity.Namespace) { + Members = { + decl } - } else { - UpdateOverriddenProperties(); - } - OnPropertyChanged(e.PropertyName); + }; } - void OnSDOptionsPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - OnPropertyChanged(e.PropertyName); - } - - public event PropertyChangedEventHandler PropertyChanged; - - void OnPropertyChanged(string propertyName) - { - if (PropertyChanged != null) { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - } - - public TextEditorOptions TextEditorOptions + static ClassType GetClassType(IUnresolvedTypeDefinition entity) { - get { - return avalonEditOptions; - } - } - - #region Overridden properties - - public int IndentationSize { - get { - // Get value from own TextEditorOptions instance - return avalonEditOptions.IndentationSize; - } - } - - public string IndentationString { - get { - // Get value from own TextEditorOptions instance - return avalonEditOptions.IndentationString; - } - } - - public bool ConvertTabsToSpaces { - get { - // Get value from own TextEditorOptions instance - return avalonEditOptions.ConvertTabsToSpaces; - } - } - - #endregion - - #region Rest of ITextEditorOptions implementation - - public bool AutoInsertBlockEnd { - get { - return originalSDOptions.AutoInsertBlockEnd; - } - } - - public int VerticalRulerColumn { - get { - return originalSDOptions.VerticalRulerColumn; + switch (entity.Kind) { + case TypeKind.Interface: + return ClassType.Interface; + case TypeKind.Struct: + return ClassType.Struct; + default: + return ClassType.Class; } } - - public bool UnderlineErrors { - get { - return originalSDOptions.UnderlineErrors; - } - } - - public string FontFamily { - get { - return originalSDOptions.FontFamily; - } - } - - public double FontSize { - get { - return originalSDOptions.FontSize; - } - } - - #endregion - } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs new file mode 100644 index 0000000000..f308877041 --- /dev/null +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/CSharpTextEditorExtension.cs @@ -0,0 +1,253 @@ +// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using ICSharpCode.AvalonEdit; +using CSharpBinding.FormattingStrategy; +using CSharpBinding.Refactoring; +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; +namespace CSharpBinding +{ + public class CSharpTextEditorExtension : ITextEditorExtension + { + ITextEditor editor; + + IssueManager inspectionManager; + + IList contextActionProviders; + + CodeManipulation codeManipulation; + + CaretReferenceHighlightRenderer renderer; + + CodeEditorFormattingOptionsAdapter options; + + TextEditorOptions originalEditorOptions; + + public void Attach(ITextEditor editor) + { + this.editor = editor; + inspectionManager = new IssueManager(editor); + codeManipulation = new CodeManipulation(editor); + renderer = new CaretReferenceHighlightRenderer(editor); + + // Patch editor options (indentation) to project-specific settings + if (!editor.ContextActionProviders.IsReadOnly) { + contextActionProviders = AddInTree.BuildItems("/SharpDevelop/ViewContent/TextEditor/C#/ContextActions", null); + editor.ContextActionProviders.AddRange(contextActionProviders); + } + + // Create instance of options adapter and register it as service + var formattingPolicy = CSharpFormattingPolicies.Instance.GetProjectOptions(SD.ProjectService.FindProjectContainingFile(editor.FileName)); + var textEditor = editor.GetService(); + + if (textEditor != null) { + options = new CodeEditorFormattingOptionsAdapter(textEditor.Options, editor.Options, formattingPolicy.OptionsContainer); + var textViewServices = textEditor.TextArea.TextView.Services; + + // Unregister any previous ITextEditorOptions instance from editor, if existing, register our impl. + textViewServices.RemoveService(typeof(ITextEditorOptions)); + textViewServices.AddService(typeof(ITextEditorOptions), options); + + // Set TextEditor's options to same object + originalEditorOptions = textEditor.Options; + textEditor.Options = options.TextEditorOptions; + } + } + + public void Detach() + { + var textEditor = editor.GetService(); + if (textEditor != null) { + var textView = textEditor.TextArea.TextView; + // Unregister our ITextEditorOptions instance from editor + var optionsService = textView.GetService(); + if ((optionsService != null) && (optionsService == options)) + textView.Services.RemoveService(typeof(ITextEditorOptions)); + // Reset TextEditor options, too? + if ((textEditor.Options != null) && (textEditor.Options == options.TextEditorOptions)) + textEditor.Options = originalEditorOptions; + } + + codeManipulation.Dispose(); + + if (inspectionManager != null) { + inspectionManager.Dispose(); + inspectionManager = null; + } + + if (contextActionProviders != null) { + editor.ContextActionProviders.RemoveAll(contextActionProviders.Contains); + } + + renderer.Dispose(); + options = null; + this.editor = null; + } + } + + class CodeEditorFormattingOptionsAdapter : ITextEditorOptions, INotifyPropertyChanged + { + CSharpFormattingOptionsContainer container; + readonly TextEditorOptions avalonEditOptions; + readonly TextEditorOptions originalAvalonEditOptions; + readonly ITextEditorOptions originalSDOptions; + + public CodeEditorFormattingOptionsAdapter(TextEditorOptions originalAvalonEditOptions, ITextEditorOptions originalSDOptions, CSharpFormattingOptionsContainer container) + { + if (originalAvalonEditOptions == null) + throw new ArgumentNullException("originalAvalonEditOptions"); + if (originalSDOptions == null) + throw new ArgumentNullException("originalSDOptions"); + if (container == null) + throw new ArgumentNullException("container"); + + this.originalAvalonEditOptions = originalAvalonEditOptions; + this.avalonEditOptions = new TextEditorOptions(originalAvalonEditOptions); + this.originalSDOptions = originalSDOptions; + this.container = container; + + // Update overridden options once + UpdateOverriddenProperties(); + + CSharpFormattingPolicies.Instance.FormattingPolicyUpdated += OnFormattingPolicyUpdated; + this.originalAvalonEditOptions.PropertyChanged += OnOrigAvalonOptionsPropertyChanged; + this.originalSDOptions.PropertyChanged += OnSDOptionsPropertyChanged; + } + + void OnFormattingPolicyUpdated(object sender, CSharpBinding.FormattingStrategy.CSharpFormattingPolicyUpdateEventArgs e) + { + // Update editor options from changed policy + UpdateOverriddenProperties(); + + OnPropertyChanged("IndentationSize"); + OnPropertyChanged("IndentationString"); + OnPropertyChanged("ConvertTabsToSpaces"); + } + + void UpdateOverriddenProperties() + { + avalonEditOptions.IndentationSize = container.GetEffectiveIndentationSize() ?? originalSDOptions.IndentationSize; + avalonEditOptions.ConvertTabsToSpaces = container.GetEffectiveConvertTabsToSpaces() ?? originalSDOptions.ConvertTabsToSpaces; + } + + void OnOrigAvalonOptionsPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if ((e.PropertyName != "IndentationSize") && (e.PropertyName != "IndentationString") && (e.PropertyName != "ConvertTabsToSpaces")) { + // Update values in our own TextEditorOptions instance + PropertyInfo propertyInfo = typeof(TextEditorOptions).GetProperty(e.PropertyName); + if (propertyInfo != null) { + propertyInfo.SetValue(avalonEditOptions, propertyInfo.GetValue(originalAvalonEditOptions)); + } + } else { + UpdateOverriddenProperties(); + } + OnPropertyChanged(e.PropertyName); + } + + void OnSDOptionsPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + OnPropertyChanged(e.PropertyName); + } + + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public ICSharpCode.AvalonEdit.TextEditorOptions TextEditorOptions + { + get { + return avalonEditOptions; + } + } + + #region Overridden properties + + public int IndentationSize { + get { + // Get value from own TextEditorOptions instance + return avalonEditOptions.IndentationSize; + } + } + + public string IndentationString { + get { + // Get value from own TextEditorOptions instance + return avalonEditOptions.IndentationString; + } + } + + public bool ConvertTabsToSpaces { + get { + // Get value from own TextEditorOptions instance + return avalonEditOptions.ConvertTabsToSpaces; + } + } + + #endregion + + #region Rest of ITextEditorOptions implementation + + public bool AutoInsertBlockEnd { + get { + return originalSDOptions.AutoInsertBlockEnd; + } + } + + public int VerticalRulerColumn { + get { + return originalSDOptions.VerticalRulerColumn; + } + } + + public bool UnderlineErrors { + get { + return originalSDOptions.UnderlineErrors; + } + } + + public string FontFamily { + get { + return originalSDOptions.FontFamily; + } + } + + public double FontSize { + get { + return originalSDOptions.FontSize; + } + } + + #endregion + + } +} + + diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionBinding.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionBinding.cs index c81f2b3349..3f71a528d1 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionBinding.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionBinding.cs @@ -24,10 +24,8 @@ using ICSharpCode.Core; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp.Resolver; using ICSharpCode.NRefactory.Completion; -using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.Completion; using ICSharpCode.NRefactory.Editor; -using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Editor.CodeCompletion; diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionContext.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionContext.cs index 007b2e2078..ae654a0d81 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionContext.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionContext.cs @@ -17,17 +17,19 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.IO; using ICSharpCode.Core; using ICSharpCode.NRefactory; -using ICSharpCode.NRefactory.Editor; -using ICSharpCode.SharpDevelop.Project; -using CSharpBinding.Parser; +using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.Completion; using ICSharpCode.NRefactory.CSharp.TypeSystem; +using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; +using CSharpBinding.Parser; namespace CSharpBinding.Completion { @@ -35,7 +37,7 @@ namespace CSharpBinding.Completion { public readonly ITextEditor Editor; public readonly IDocument Document; - public readonly CSharpFullParseInformation ParseInformation; + public readonly IList ConditionalSymbols; public readonly ICompilation Compilation; public readonly IProjectContent ProjectContent; public readonly CSharpTypeResolveContext TypeResolveContextAtCaret; @@ -56,7 +58,7 @@ namespace CSharpBinding.Completion if (projectContent == null) return null; - return new CSharpCompletionContext(editor, parseInfo, compilation, projectContent, editor.Document, editor.Caret.Location); + return new CSharpCompletionContext(editor, parseInfo.SyntaxTree.ConditionalSymbols, compilation, projectContent, editor.Document, parseInfo.UnresolvedFile, editor.Caret.Location); } public static CSharpCompletionContext Get(ITextEditor editor, ITextSource fileContent, TextLocation currentLocation, FileName fileName) @@ -64,33 +66,50 @@ namespace CSharpBinding.Completion IDocument document = new ReadOnlyDocument(fileContent); // Don't require the very latest parse information, an older cached version is OK. - var parseInfo = SD.ParserService.Parse(fileName, document) as CSharpFullParseInformation; + var parseInfo = SD.ParserService.GetCachedParseInformation(fileName) as CSharpFullParseInformation; + if (parseInfo == null) { + parseInfo = SD.ParserService.Parse(fileName) as CSharpFullParseInformation; + } if (parseInfo == null) return null; - ICompilation compilation = SD.ParserService.GetCompilationForFile(fileName); - var projectContent = compilation.MainAssembly.UnresolvedAssembly as IProjectContent; + + var project = SD.ProjectService.FindProjectContainingFile(fileName)as CSharpProject; + if (project == null) + return null; + + var solutionSnapshot = SD.ParserService.GetCurrentSolutionSnapshot(); + var projectContent = solutionSnapshot.GetProjectContent(project); if (projectContent == null) return null; - return new CSharpCompletionContext(editor, parseInfo, compilation, projectContent, document, currentLocation); + CSharpParser parser = new CSharpParser(project.CompilerSettings); + parser.GenerateTypeSystemMode = false; + + SyntaxTree cu = parser.Parse(fileContent, Path.GetRandomFileName() + ".cs"); + cu.Freeze(); + + CSharpUnresolvedFile unresolvedFile = cu.ToTypeSystem(); + ICompilation compilation = projectContent.AddOrUpdateFiles(unresolvedFile).CreateCompilation(solutionSnapshot); + + return new CSharpCompletionContext(editor, parseInfo.SyntaxTree.ConditionalSymbols, compilation, projectContent, document, unresolvedFile, currentLocation); } - private CSharpCompletionContext(ITextEditor editor, CSharpFullParseInformation parseInfo, ICompilation compilation, IProjectContent projectContent, IDocument document, TextLocation caretLocation) + private CSharpCompletionContext(ITextEditor editor, IList conditionalSymbols, ICompilation compilation, IProjectContent projectContent, IDocument document, CSharpUnresolvedFile unresolvedFile, TextLocation caretLocation) { Debug.Assert(editor != null); - Debug.Assert(parseInfo != null); + Debug.Assert(unresolvedFile != null); Debug.Assert(compilation != null); Debug.Assert(projectContent != null); Debug.Assert(document != null); this.Editor = editor; this.Document = document; - this.ParseInformation = parseInfo; + this.ConditionalSymbols = conditionalSymbols; this.Compilation = compilation; this.ProjectContent = projectContent; - this.TypeResolveContextAtCaret = parseInfo.UnresolvedFile.GetTypeResolveContext(compilation, caretLocation); - this.CompletionContextProvider = new DefaultCompletionContextProvider(document, parseInfo.UnresolvedFile); - this.CompletionContextProvider.ConditionalSymbols.AddRange(parseInfo.SyntaxTree.ConditionalSymbols); + this.TypeResolveContextAtCaret = unresolvedFile.GetTypeResolveContext(compilation, caretLocation); + this.CompletionContextProvider = new DefaultCompletionContextProvider(document, unresolvedFile); + this.CompletionContextProvider.ConditionalSymbols.AddRange(conditionalSymbols); } } } diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs index 8fbfc7e3f7..c3f005e892 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Completion/CSharpCompletionDataFactory.cs @@ -148,7 +148,7 @@ namespace CSharpBinding.Completion IEnumerable ICompletionDataFactory.CreatePreProcessorDefinesCompletionData() { - return completionContext.ParseInformation.SyntaxTree.ConditionalSymbols.Select(def => new CompletionData(def)); + return completionContext.ConditionalSymbols.Select(def => new CompletionData(def)); } ICompletionData ICompletionDataFactory.CreateImportCompletionData(IType type, bool useFullName, bool addForTypeCreation) diff --git a/src/AddIns/Debugger/Debugger.AddIn/Breakpoints/BreakpointEditorPopup.xaml.cs b/src/AddIns/Debugger/Debugger.AddIn/Breakpoints/BreakpointEditorPopup.xaml.cs index d8d95b4b35..f22ae4e3ed 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Breakpoints/BreakpointEditorPopup.xaml.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Breakpoints/BreakpointEditorPopup.xaml.cs @@ -41,7 +41,8 @@ namespace Debugger.AddIn.Breakpoints { InitializeComponent(); this.DataContext = target; - condition.DebugContext = new DebuggerCompletionContext(target.FileName, target.Location); + condition.ContextFileName = target.FileName; + condition.ContextTextLocation = target.Location; condition.FontFamily = new FontFamily(SD.EditorControlService.GlobalOptions.FontFamily); condition.FontSize = SD.EditorControlService.GlobalOptions.FontSize; if (target.Condition == null) diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/AutoCompleteTextBox.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/AutoCompleteTextBox.cs index 8088d9771b..aca8f0c0b6 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/AutoCompleteTextBox.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/AutoCompleteTextBox.cs @@ -64,7 +64,8 @@ namespace Debugger.AddIn.Pads.Controls set { SetValue(IsEditableProperty, value); } } - public DebuggerCompletionContext DebugContext { get; set; } + public FileName ContextFileName { get; set; } + public TextLocation ContextTextLocation { get; set; } static void TextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -127,20 +128,15 @@ namespace Debugger.AddIn.Pads.Controls void editor_TextArea_TextEntered(object sender, TextCompositionEventArgs e) { if (e.Text == ".") { - DebuggerCompletionContext context = null; StackFrame frame = WindowsDebugger.CurrentStackFrame; - if (frame == null) { - if (DebugContext != null) { - context = DebugContext; - } - } else { - context = new DebuggerCompletionContext(frame); + if (frame != null) { + ContextFileName = new FileName(frame.NextStatement.Filename); + ContextTextLocation = new TextLocation(frame.NextStatement.StartLine, frame.NextStatement.StartColumn); } - if (context == null) return; - var binding = DebuggerDotCompletion.PrepareDotCompletion(editor.Text.Substring(0, editor.CaretOffset), context); + if (ContextFileName == null) return; + var binding = DebuggerDotCompletion.PrepareDotCompletion(editor.Text.Substring(0, editor.CaretOffset), ContextFileName, ContextTextLocation, SD.ParserService.ResolveContext(ContextFileName, ContextTextLocation)); if (binding == null) return; binding.HandleKeyPressed(editorAdapter, '.'); - SD.ParserService.ParseFileAsync(context.FileName).FireAndForget(); } else { // TODO : implement automated error checking CSharpParser.ParseExpression does not report useful error messages. // Error[] errors; diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs index 31c122f002..c88df18834 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/ConsolePad.cs @@ -86,10 +86,11 @@ namespace ICSharpCode.SharpDevelop.Gui.Pads void ShowDotCompletion(StackFrame frame, string currentText) { - var binding = DebuggerDotCompletion.PrepareDotCompletion(currentText, new DebuggerCompletionContext(frame)); + var fileName = new ICSharpCode.Core.FileName(frame.NextStatement.Filename); + var textLocation = new TextLocation(frame.NextStatement.StartLine, frame.NextStatement.StartColumn); + var binding = DebuggerDotCompletion.PrepareDotCompletion(currentText, fileName, textLocation, SD.ParserService.ResolveContext(fileName, textLocation)); if (binding == null) return; binding.HandleKeyPressed(console.TextEditor, '.'); - SD.ParserService.ParseFileAsync(new ICSharpCode.Core.FileName(frame.NextStatement.Filename)).FireAndForget(); } protected override ToolBar BuildToolBar() diff --git a/src/AddIns/Debugger/Debugger.AddIn/Pads/DebuggerDotCompletion.cs b/src/AddIns/Debugger/Debugger.AddIn/Pads/DebuggerDotCompletion.cs index 4518c832ae..854abc5cca 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Pads/DebuggerDotCompletion.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Pads/DebuggerDotCompletion.cs @@ -41,162 +41,12 @@ namespace Debugger.AddIn.Pads.Controls return !errors.Any(); } - public static ICodeCompletionBinding PrepareDotCompletion(string expressionToComplete, DebuggerCompletionContext context) + public static ICodeCompletionBinding PrepareDotCompletion(string expressionToComplete, FileName fileName, TextLocation location, ICodeContext context) { - var lang = SD.LanguageService.GetLanguageByFileName(context.FileName); + var lang = SD.LanguageService.GetLanguageByFileName(fileName); if (lang == null) return null; - string content = GeneratePartialClassContextStub(context); - const string caretPoint = "$__Caret_Point__$;"; - int caretOffset = content.IndexOf(caretPoint, StringComparison.Ordinal) + expressionToComplete.Length; - SD.Log.DebugFormatted("context used for dot completion: {0}", content.Replace(caretPoint, "$" + expressionToComplete + "|$")); - var doc = new ReadOnlyDocument(content.Replace(caretPoint, expressionToComplete)); - return lang.CreateCompletionBinding(context.FileName, doc.GetLocation(caretOffset), doc.CreateSnapshot()); - } - - static string GeneratePartialClassContextStub(DebuggerCompletionContext context) - { - var compilation = SD.ParserService.GetCompilationForFile(context.FileName); - var file = SD.ParserService.GetExistingUnresolvedFile(context.FileName); - if (compilation == null || file == null) - return ""; - var unresolvedMember = file.GetMember(context.Location); - if (unresolvedMember == null) - return ""; - var member = unresolvedMember.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); - if (member == null) - return ""; - var builder = new TypeSystemAstBuilder(); - MethodDeclaration decl; - if (unresolvedMember is IMethod) { - // If it's a method, convert it directly (including parameters + type parameters) - decl = (MethodDeclaration)builder.ConvertEntity(member); - } else { - // Otherwise, create a method anyways, and copy the parameters - decl = new MethodDeclaration(); - if (member is IParameterizedMember) { - foreach (var p in ((IParameterizedMember)member).Parameters) { - decl.Parameters.Add(builder.ConvertParameter(p)); - } - } - } - decl.Name = "__DebuggerStub__"; - decl.ReturnType = builder.ConvertType(member.ReturnType); - decl.Modifiers = unresolvedMember.IsStatic ? Modifiers.Static : Modifiers.None; - // Make the method look like an explicit interface implementation so that it doesn't appear in CC - decl.PrivateImplementationType = new SimpleType("__DummyType__"); - decl.Body = GenerateBodyFromContext(builder, context); - return WrapInType(unresolvedMember.DeclaringTypeDefinition, decl).ToString(); - } - - static BlockStatement GenerateBodyFromContext(TypeSystemAstBuilder builder, DebuggerCompletionContext context) - { - var body = new BlockStatement(); - foreach (var v in context.Variables) - body.Statements.Add(new VariableDeclarationStatement(builder.ConvertType(v.Type), v.Name)); - body.Statements.Add(new ExpressionStatement(new IdentifierExpression("$__Caret_Point__$"))); - return body; - } - - static AstNode WrapInType(IUnresolvedTypeDefinition entity, EntityDeclaration decl) - { - if (entity == null) - return decl; - // Wrap decl in TypeDeclaration - decl = new TypeDeclaration { - ClassType = GetClassType(entity), - Modifiers = Modifiers.Partial, - Name = entity.Name, - Members = { decl } - }; - if (entity.DeclaringTypeDefinition != null) { - // Handle nested types - return WrapInType(entity.DeclaringTypeDefinition, decl); - } - if (string.IsNullOrEmpty(entity.Namespace)) - return decl; - return new NamespaceDeclaration(entity.Namespace) { - Members = { - decl - } - }; - } - - static ClassType GetClassType(IUnresolvedTypeDefinition entity) - { - switch (entity.Kind) { - case TypeKind.Interface: - return ClassType.Interface; - case TypeKind.Struct: - return ClassType.Struct; - default: - return ClassType.Class; - } - } - } - - public class LocalVariable - { - readonly IType type; - - public IType Type { - get { - return type; - } - } - - readonly string name; - - public string Name { - get { - return name; - } - } - - public LocalVariable(IType type, string name) - { - this.type = type; - this.name = name; - } - } - - public class DebuggerCompletionContext - { - readonly FileName fileName; - TextLocation location; - readonly LocalVariable[] variables; - - public DebuggerCompletionContext(StackFrame frame) - { - fileName = new FileName(frame.NextStatement.Filename); - location = new TextLocation(frame.NextStatement.StartLine, frame.NextStatement.StartColumn); - variables = frame.GetLocalVariables().Select(v => new LocalVariable(v.Type, v.Name)).ToArray(); - } - - public DebuggerCompletionContext(FileName fileName, TextLocation location) - { - this.fileName = fileName; - this.location = location; - this.variables = SD.ParserService.ResolveContext(fileName, location) - .LocalVariables.Select(v => new LocalVariable(v.Type, v.Name)).ToArray(); - } - - public FileName FileName { - get { - return fileName; - } - } - - public TextLocation Location { - get { - return location; - } - } - - public LocalVariable[] Variables { - get { - return variables; - } + return lang.CreateCompletionBinding(expressionToComplete, fileName, location, context); } } } diff --git a/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs b/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs index 7a3b678371..c1b1bf7216 100644 --- a/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs +++ b/src/Main/Base/Project/Src/Services/LanguageBinding/DefaultLanguageBinding.cs @@ -75,7 +75,7 @@ namespace ICSharpCode.SharpDevelop } } - public virtual ICodeCompletionBinding CreateCompletionBinding(FileName fileName, TextLocation currentLocation, ITextSource fileContent) + public virtual ICodeCompletionBinding CreateCompletionBinding(string expressionToComplete, FileName fileName, TextLocation location, ICodeContext context) { throw new NotSupportedException(); } diff --git a/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs b/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs index 863e7508fe..49ad72abea 100644 --- a/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs +++ b/src/Main/Base/Project/Src/Services/LanguageBinding/ILanguageBinding.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Linq; using ICSharpCode.Core; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Editor; @@ -62,8 +63,8 @@ namespace ICSharpCode.SharpDevelop } /// - /// Creates a completion binding which works with a fileName and a location as context. + /// Creates a completion binding for a given expression and context. /// - ICodeCompletionBinding CreateCompletionBinding(FileName fileName, TextLocation currentLocation, ITextSource fileContent); + ICodeCompletionBinding CreateCompletionBinding(string expressionToComplete, FileName fileName, TextLocation location, ICodeContext context); } } From 3dbfb428bd5612c2706ab25318d22e11fbc4a9e9 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:22:45 +0200 Subject: [PATCH 02/11] Protect against resolve call on empty location. --- src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs | 2 ++ src/Main/SharpDevelop/Parser/ParserService.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs index 59192cf6c4..5e54a5a373 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs @@ -633,6 +633,8 @@ namespace ICSharpCode.SharpDevelop.Services return; if (CurrentStackFrame == null) return; + if (e.InDocument) + return; var resolveResult = SD.ParserService.Resolve(e.Editor, e.LogicalPosition, CurrentStackFrame.AppDomain.Compilation); if (resolveResult == null) return; diff --git a/src/Main/SharpDevelop/Parser/ParserService.cs b/src/Main/SharpDevelop/Parser/ParserService.cs index 54872bd241..5d679e29d6 100644 --- a/src/Main/SharpDevelop/Parser/ParserService.cs +++ b/src/Main/SharpDevelop/Parser/ParserService.cs @@ -278,7 +278,7 @@ namespace ICSharpCode.SharpDevelop.Parser public ResolveResult Resolve(FileName fileName, TextLocation location, ITextSource fileContent, ICompilation compilation, CancellationToken cancellationToken) { var entry = GetFileEntry(fileName, true); - if (entry.parser == null) + if (entry.parser == null || location.IsEmpty) return ErrorResolveResult.UnknownError; IProject project = compilation != null ? compilation.GetProject() : null; var parseInfo = entry.Parse(fileContent, project, cancellationToken); @@ -334,7 +334,7 @@ namespace ICSharpCode.SharpDevelop.Parser public Task ResolveAsync(FileName fileName, TextLocation location, ITextSource fileContent, ICompilation compilation, CancellationToken cancellationToken) { var entry = GetFileEntry(fileName, true); - if (entry.parser == null) + if (entry.parser == null || location.IsEmpty) return Task.FromResult(ErrorResolveResult.UnknownError); IProject project = compilation != null ? compilation.GetProject() : null; return entry.ParseAsync(fileContent, project, cancellationToken).ContinueWith( From 9256ed4d39dd4aa8283fac0fe8e3e7322820cef8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:24:22 +0200 Subject: [PATCH 03/11] Fix inverted condition. --- src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs index 5e54a5a373..5bc4f6f773 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs +++ b/src/AddIns/Debugger/Debugger.AddIn/Service/WindowsDebugger.cs @@ -633,7 +633,7 @@ namespace ICSharpCode.SharpDevelop.Services return; if (CurrentStackFrame == null) return; - if (e.InDocument) + if (!e.InDocument) return; var resolveResult = SD.ParserService.Resolve(e.Editor, e.LogicalPosition, CurrentStackFrame.AppDomain.Compilation); if (resolveResult == null) From 4ce0129433a7306b01414aca93bcb7eaf7b0ce03 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:27:47 +0200 Subject: [PATCH 04/11] Handle BadImageFormatException in ProjectContentContainer.DoResolveReferences() --- .../Base/Project/Parser/ProjectContentContainer.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Main/Base/Project/Parser/ProjectContentContainer.cs b/src/Main/Base/Project/Parser/ProjectContentContainer.cs index ab80021948..91c5572ea0 100644 --- a/src/Main/Base/Project/Parser/ProjectContentContainer.cs +++ b/src/Main/Base/Project/Parser/ProjectContentContainer.cs @@ -463,9 +463,15 @@ namespace ICSharpCode.SharpDevelop.Parser foreach (var file in assemblyFiles) { progressMonitor.CancellationToken.ThrowIfCancellationRequested(); if (File.Exists(file)) { - var pc = SD.AssemblyParserService.GetAssembly(file, false, progressMonitor.CancellationToken); - if (pc != null) { - newReferences.Add(pc); + try { + var pc = SD.AssemblyParserService.GetAssembly(file, false, progressMonitor.CancellationToken); + if (pc != null) { + newReferences.Add(pc); + } + } catch (IOException ex) { + LoggingService.Warn(ex); + } catch (BadImageFormatException ex) { + LoggingService.Warn(ex); } } progressMonitor.Progress += (1.0 - assemblyResolvingProgress) / assemblyFiles.Count; From 5602be8fd6208d493c36f8a62a17ae424e460aae Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:42:33 +0200 Subject: [PATCH 05/11] Disable UseOfMemberOfNullReference issue. It's currently too unstable and performance-hungry. --- .../CSharpBinding/Project/CSharpBinding.addin | 1 - .../Uncategorized/UseOfMemberOfNullReference.cs | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin index b1ad8f4e40..b70e3a2dcf 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/CSharpBinding.addin @@ -363,7 +363,6 @@ - diff --git a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/UseOfMemberOfNullReference.cs b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/UseOfMemberOfNullReference.cs index ab941fa33a..905f761268 100644 --- a/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/UseOfMemberOfNullReference.cs +++ b/src/Libraries/NRefactory/ICSharpCode.NRefactory.CSharp.Refactoring/CodeIssues/Uncategorized/UseOfMemberOfNullReference.cs @@ -34,10 +34,10 @@ using ICSharpCode.NRefactory.CSharp.Analysis; namespace ICSharpCode.NRefactory.CSharp.Refactoring { - [IssueDescription("Use of (non-extension method) member of null value will cause a NullReferenceException", - Description = "Detects when a member of a null value is used", - Category = IssueCategories.CodeQualityIssues, - Severity = Severity.Warning)] +// [IssueDescription("Use of (non-extension method) member of null value will cause a NullReferenceException", +// Description = "Detects when a member of a null value is used", +// Category = IssueCategories.CodeQualityIssues, +// Severity = Severity.Warning)] public class UseOfMemberOfNullReference : GatherVisitorCodeIssueProvider { static readonly ISet ProblematicNullStates = new HashSet { From 65adfdb812c184aa50839d9fb6665280a7df306f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:47:25 +0200 Subject: [PATCH 06/11] Handle UnauthorizedAccessException in AbstractProject.IsReadOnly --- src/Main/Base/Project/Src/Project/AbstractProject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Main/Base/Project/Src/Project/AbstractProject.cs b/src/Main/Base/Project/Src/Project/AbstractProject.cs index 781860436a..38b46a2071 100644 --- a/src/Main/Base/Project/Src/Project/AbstractProject.cs +++ b/src/Main/Base/Project/Src/Project/AbstractProject.cs @@ -188,7 +188,7 @@ namespace ICSharpCode.SharpDevelop.Project try { FileAttributes attributes = File.GetAttributes(FileName); return ((FileAttributes.ReadOnly & attributes) == FileAttributes.ReadOnly); - } catch (FileNotFoundException) { + } catch (UnauthorizedAccessException) { return false; } catch (IOException) { // directory not found, network path not available, etc. From a56a10c8f89f06cf6f7797f18dc1edcc831ca186 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:50:54 +0200 Subject: [PATCH 07/11] Fix NullReferenceException in SharpTreeViewItem.ContextMenuOpening --- .../SharpTreeView/ICSharpCode.TreeView/SharpTreeViewItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SharpTreeView/ICSharpCode.TreeView/SharpTreeViewItem.cs b/src/Libraries/SharpTreeView/ICSharpCode.TreeView/SharpTreeViewItem.cs index 3c1ff02d91..5f55072fc3 100644 --- a/src/Libraries/SharpTreeView/ICSharpCode.TreeView/SharpTreeViewItem.cs +++ b/src/Libraries/SharpTreeView/ICSharpCode.TreeView/SharpTreeViewItem.cs @@ -118,7 +118,8 @@ namespace ICSharpCode.TreeView protected override void OnContextMenuOpening(ContextMenuEventArgs e) { - Node.ShowContextMenu(e); + if (Node != null) + Node.ShowContextMenu(e); } #endregion From 8b4c26259460a19736a49eaed3372efd05d5f520 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:53:00 +0200 Subject: [PATCH 08/11] Fix NullReferenceException in Git.FindGit if PATH is not set. --- src/AddIns/VersionControl/GitAddIn/Src/Git.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AddIns/VersionControl/GitAddIn/Src/Git.cs b/src/AddIns/VersionControl/GitAddIn/Src/Git.cs index c5f3f10694..a7480c487f 100644 --- a/src/AddIns/VersionControl/GitAddIn/Src/Git.cs +++ b/src/AddIns/VersionControl/GitAddIn/Src/Git.cs @@ -94,7 +94,8 @@ namespace ICSharpCode.GitAddIn /// public static string FindGit() { - string[] paths = Environment.GetEnvironmentVariable("PATH").Split(';'); + string pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty; + string[] paths = pathVariable.Split(new char[]{';'}, StringSplitOptions.RemoveEmptyEntries); foreach (string path in paths) { try { string exe = Path.Combine(path, "git.exe"); From 4b4518db8698268bdcf07746b3c4861d04297004 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 5 Jul 2014 17:55:14 +0200 Subject: [PATCH 09/11] Fix NullReferenceException in TestResultsReader.Dispose when connecting the named pipe failed. --- .../Analysis/UnitTesting/TestRunner/TestResultsReader.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs index f3bc984cb3..08e94b2d77 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestResultsReader.cs @@ -61,8 +61,10 @@ namespace ICSharpCode.UnitTesting public void Dispose() { - reader.Dispose(); - namedPipe.Dispose(); + if (reader != null) + reader.Dispose(); + if (namedPipe != null) + namedPipe.Dispose(); } public event EventHandler TestFinished; From 382c621383b0c7a6ec2cac8d8a14627ddae54437 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Jul 2014 12:18:15 +0200 Subject: [PATCH 10/11] Use ReaderWriterLock in ParserServiceEntry to prevent the parser thread from blocking the UI thread. --- src/Main/SharpDevelop/Parser/ParserService.cs | 4 +- .../SharpDevelop/Parser/ParserServiceEntry.cs | 96 +++++++++++++------ 2 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/Main/SharpDevelop/Parser/ParserService.cs b/src/Main/SharpDevelop/Parser/ParserService.cs index 5d679e29d6..46b54517a8 100644 --- a/src/Main/SharpDevelop/Parser/ParserService.cs +++ b/src/Main/SharpDevelop/Parser/ParserService.cs @@ -174,7 +174,7 @@ namespace ICSharpCode.SharpDevelop.Parser internal void RemoveEntry(ParserServiceEntry entry) { - Debug.Assert(Monitor.IsEntered(entry)); + Debug.Assert(entry.rwLock.IsWriteLockHeld); lock (fileEntryDict) { ParserServiceEntry entryAtKey; if (fileEntryDict.TryGetValue(entry.fileName, out entryAtKey)) { @@ -190,7 +190,7 @@ namespace ICSharpCode.SharpDevelop.Parser internal void RegisterForCacheExpiry(ParserServiceEntry entry) { // This method should not be called within any locks - Debug.Assert(!Monitor.IsEntered(entry)); + Debug.Assert(!(entry.rwLock.IsReadLockHeld || entry.rwLock.IsUpgradeableReadLockHeld || entry.rwLock.IsWriteLockHeld)); ParserServiceEntry expiredItem = null; lock (cacheExpiryQueue) { cacheExpiryQueue.Remove(entry); // remove entry from queue if it's already enqueued diff --git a/src/Main/SharpDevelop/Parser/ParserServiceEntry.cs b/src/Main/SharpDevelop/Parser/ParserServiceEntry.cs index 44d9036bcb..5092be70af 100644 --- a/src/Main/SharpDevelop/Parser/ParserServiceEntry.cs +++ b/src/Main/SharpDevelop/Parser/ParserServiceEntry.cs @@ -54,6 +54,10 @@ namespace ICSharpCode.SharpDevelop.Parser List entries = new List { default(ProjectEntry) }; ITextSourceVersion currentVersion; + // Lock ordering: runningAsyncParseLock, rwLock, lock(parserService.fileEntryDict) + // (to avoid deadlocks, the locks may only be acquired in this order) + internal readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); + public ParserServiceEntry(ParserService parserService, FileName fileName) { this.parserService = parserService; @@ -68,6 +72,7 @@ namespace ICSharpCode.SharpDevelop.Parser int FindIndexForProject(IProject parentProject) { + Debug.Assert(rwLock.IsReadLockHeld || rwLock.IsUpgradeableReadLockHeld || rwLock.IsWriteLockHeld); if (parentProject == null) return 0; for (int i = 0; i < entries.Count; i++) { @@ -81,7 +86,8 @@ namespace ICSharpCode.SharpDevelop.Parser public void AddOwnerProject(IProject project, bool isLinkedFile) { Debug.Assert(project != null); - lock (this) { + rwLock.EnterWriteLock(); + try { if (FindIndexForProject(project) >= 0) throw new InvalidOperationException("The project alreadys owns the file"); ProjectEntry newEntry = new ProjectEntry(project, null, null); @@ -92,6 +98,8 @@ namespace ICSharpCode.SharpDevelop.Parser } else { entries.Insert(0, newEntry); } + } finally { + rwLock.ExitWriteLock(); } } @@ -100,7 +108,8 @@ namespace ICSharpCode.SharpDevelop.Parser Debug.Assert(project != null); ProjectEntry oldEntry; bool removedLastOwner = false; - lock (this) { + rwLock.EnterWriteLock(); + try { int index = FindIndexForProject(project); if (index < 0) throw new InvalidOperationException("The project does not own the file"); @@ -111,6 +120,8 @@ namespace ICSharpCode.SharpDevelop.Parser } else { entries.RemoveAt(index); } + } finally { + rwLock.ExitWriteLock(); } if (oldEntry.UnresolvedFile != null) { project.OnParseInformationUpdated(new ParseInformationEventArgs(project, oldEntry.UnresolvedFile, null)); @@ -128,6 +139,7 @@ namespace ICSharpCode.SharpDevelop.Parser /// int CompareVersions(ITextSourceVersion newVersion) { + Debug.Assert(rwLock.IsReadLockHeld || rwLock.IsUpgradeableReadLockHeld || rwLock.IsWriteLockHeld); if (currentVersion != null && newVersion != null && currentVersion.BelongsToSameDocumentAs(newVersion)) return currentVersion.CompareAge(newVersion); else @@ -137,7 +149,8 @@ namespace ICSharpCode.SharpDevelop.Parser #region Expire Cache + GetExistingUnresolvedFile + GetCachedParseInformation public void ExpireCache() { - lock (this) { + rwLock.EnterWriteLock(); + try { if (PrimaryProject == null) { parserService.RemoveEntry(this); } else { @@ -148,12 +161,15 @@ namespace ICSharpCode.SharpDevelop.Parser } // force re-parse on next ParseFile() call even if unchanged this.currentVersion = null; + } finally { + rwLock.ExitWriteLock(); } } public IUnresolvedFile GetExistingUnresolvedFile(ITextSourceVersion version, IProject parentProject) { - lock (this) { + rwLock.EnterReadLock(); + try { if (version != null && CompareVersions(version) != 0) { return null; } @@ -161,12 +177,15 @@ namespace ICSharpCode.SharpDevelop.Parser if (index < 0) return null; return entries[index].UnresolvedFile; + } finally { + rwLock.ExitReadLock(); } } public ParseInformation GetCachedParseInformation(ITextSourceVersion version, IProject parentProject) { - lock (this) { + rwLock.EnterReadLock(); + try { if (version != null && CompareVersions(version) != 0) { return null; } @@ -174,6 +193,8 @@ namespace ICSharpCode.SharpDevelop.Parser if (index < 0) return null; return entries[index].CachedParseInformation; + } finally { + rwLock.ExitReadLock(); } } #endregion @@ -218,7 +239,8 @@ namespace ICSharpCode.SharpDevelop.Parser } ProjectEntry result; - lock (this) { + rwLock.EnterUpgradeableReadLock(); + try { int index = FindIndexForProject(parentProject); int versionComparison = CompareVersions(fileContent.Version); if (versionComparison > 0 || index < 0) { @@ -253,24 +275,32 @@ namespace ICSharpCode.SharpDevelop.Parser } // 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 && results[i].NewParseInformation.IsFullParseInformation)) - entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, results[i].NewParseInformation); - else - entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, null); - if (entries[i].Project != null) - entries[i].Project.OnParseInformationUpdated(results[i]); - parserService.RaiseParseInformationUpdated(results[i]); + rwLock.EnterWriteLock(); + try { + currentVersion = fileContent.Version; + for (int i = 0; i < entries.Count; i++) { + if (fullParseInformationRequested || (entries[i].CachedParseInformation != null && results[i].NewParseInformation.IsFullParseInformation)) + entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, results[i].NewParseInformation); + else + entries[i] = new ProjectEntry(entries[i].Project, results[i].NewUnresolvedFile, null); + if (entries[i].Project != null) + entries[i].Project.OnParseInformationUpdated(results[i]); + parserService.RaiseParseInformationUpdated(results[i]); + } + result = entries[index]; + } finally { + rwLock.ExitWriteLock(); } - result = entries[index]; - } // exit lock + } finally { + rwLock.ExitUpgradeableReadLock(); + } parserService.RegisterForCacheExpiry(this); return result; } ParseInformation ParseWithExceptionHandling(ITextSource fileContent, bool fullParseInformationRequested, IProject project, CancellationToken cancellationToken) { + Debug.Assert(rwLock.IsUpgradeableReadLockHeld && !rwLock.IsWriteLockHeld); #if DEBUG if (Debugger.IsAttached) return parser.Parse(fileName, fileContent, fullParseInformationRequested, project, cancellationToken); @@ -285,6 +315,8 @@ namespace ICSharpCode.SharpDevelop.Parser #endregion #region ParseAsync + /// lock object for protecting the runningAsyncParse* fields + object runningAsyncParseLock = new object(); Task runningAsyncParseTask; ITextSourceVersion runningAsyncParseFileContentVersion; IProject runningAsyncParseProject; @@ -324,19 +356,24 @@ namespace ICSharpCode.SharpDevelop.Parser } } Task task; - lock (this) { + lock (runningAsyncParseLock) { 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) { - // Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects) - // If full parse info is requested, ensure we have full parse info. - if (entries[index].UnresolvedFile != null && !(requestFullParseInformation && entries[index].CachedParseInformation == null)) { - // We already have the requested version parsed, just return it: - return Task.FromResult(entries[index]); + rwLock.EnterReadLock(); + try { + int index = FindIndexForProject(parentProject); + int versionComparison = CompareVersions(fileContent.Version); + if (versionComparison == 0 && index >= 0) { + // Ensure we have parse info for the specified project (entry.UnresolvedFile is null for newly registered projects) + // If full parse info is requested, ensure we have full parse info. + if (entries[index].UnresolvedFile != null && !(requestFullParseInformation && entries[index].CachedParseInformation == null)) { + // We already have the requested version parsed, just return it: + return Task.FromResult(entries[index]); + } } + } finally { + rwLock.ExitReadLock(); } // Optimization: // if an equivalent task is already running, return that one instead @@ -356,7 +393,7 @@ namespace ICSharpCode.SharpDevelop.Parser } return DoParse(fileContent, parentProject, requestFullParseInformation, cancellationToken); } finally { - lock (this) { + lock (runningAsyncParseLock) { runningAsyncParseTask = null; runningAsyncParseFileContentVersion = null; runningAsyncParseProject = null; @@ -383,7 +420,8 @@ namespace ICSharpCode.SharpDevelop.Parser throw new ArgumentNullException("unresolvedFile"); FreezableHelper.Freeze(unresolvedFile); var newParseInfo = new ParseInformation(unresolvedFile, null, false); - lock (this) { + rwLock.EnterWriteLock(); + try { int index = FindIndexForProject(project); if (index >= 0) { currentVersion = null; @@ -392,6 +430,8 @@ namespace ICSharpCode.SharpDevelop.Parser project.OnParseInformationUpdated(args); parserService.RaiseParseInformationUpdated(args); } + } finally { + rwLock.ExitWriteLock(); } } } From 9744db9e5abf71d4a3264d8e94461b2ab8d33ab2 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 6 Jul 2014 12:19:49 +0200 Subject: [PATCH 11/11] Adjust StressTest to SD5 API change --- src/Tools/StressTest/StressTest.sln | 12 +++++++----- src/Tools/StressTest/StressTest/StressTest.csproj | 3 --- src/Tools/StressTest/StressTest/UserControl.xaml.cs | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Tools/StressTest/StressTest.sln b/src/Tools/StressTest/StressTest.sln index 390728c9f4..5f54b0427c 100644 --- a/src/Tools/StressTest/StressTest.sln +++ b/src/Tools/StressTest/StressTest.sln @@ -1,7 +1,9 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -# SharpDevelop 4.3 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +# SharpDevelop 5.0 +VisualStudioVersion = 12.0.20827.3 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StressTest", "StressTest\StressTest.csproj", "{30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}" EndProject Global @@ -10,9 +12,9 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Debug|x86.Build.0 = Debug|x86 {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Debug|x86.ActiveCfg = Debug|x86 - {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Release|x86.Build.0 = Release|x86 + {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Debug|x86.Build.0 = Debug|x86 {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Release|x86.ActiveCfg = Release|x86 + {30D10654-A5F5-4AC5-A370-E6DD4D0FAC50}.Release|x86.Build.0 = Release|x86 EndGlobalSection EndGlobal diff --git a/src/Tools/StressTest/StressTest/StressTest.csproj b/src/Tools/StressTest/StressTest/StressTest.csproj index d712b438de..090ba83d63 100644 --- a/src/Tools/StressTest/StressTest/StressTest.csproj +++ b/src/Tools/StressTest/StressTest/StressTest.csproj @@ -98,9 +98,6 @@ - - - /addindir:"$(MsBuildProjectDirectory)\$(OutputPath)" diff --git a/src/Tools/StressTest/StressTest/UserControl.xaml.cs b/src/Tools/StressTest/StressTest/UserControl.xaml.cs index 7946890d57..680d165bcb 100644 --- a/src/Tools/StressTest/StressTest/UserControl.xaml.cs +++ b/src/Tools/StressTest/StressTest/UserControl.xaml.cs @@ -35,6 +35,7 @@ using ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; +using ICSharpCode.SharpDevelop.Workbench; namespace StressTest {