diff --git a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin index a0219c4c68..d9056d6151 100644 --- a/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin +++ b/src/AddIns/Debugger/Debugger.AddIn/Debugger.AddIn.addin @@ -162,19 +162,6 @@ - - - - - - - - - + ChooseEncodingDialog.xaml Code @@ -87,6 +88,7 @@ + diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CaretReferencesRenderer.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CaretReferencesRenderer.cs new file mode 100644 index 0000000000..4607b5708b --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CaretReferencesRenderer.cs @@ -0,0 +1,123 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Collections.Generic; +using System.Windows.Threading; + +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; + +namespace ICSharpCode.AvalonEdit.AddIn +{ + /// + /// Description of CaretReferencesRenderer. + /// + public class CaretReferencesRenderer + { + /// + /// Delays the highlighting after the caret position changes, so that Find references does not get called too often. + /// + DispatcherTimer delayTimer; + const int delayMilliseconds = 1000; + DispatcherTimer delayMoveTimer; + const int delayMoveMilliseconds = 100; + + CodeEditorView editorView; + ITextEditor Editor { get { return editorView.Adapter; } } + + ExpressionHighlightRenderer highlightRenderer; + ResolveResult lastResolveResult; + + public CaretReferencesRenderer(CodeEditorView editorView) + { + this.editorView = editorView; + this.highlightRenderer = new ExpressionHighlightRenderer(this.editorView.TextArea.TextView); + this.delayTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(delayMilliseconds) }; + this.delayTimer.Stop(); + this.delayTimer.Tick += TimerTick; + this.delayMoveTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(delayMoveMilliseconds) }; + this.delayMoveTimer.Stop(); + this.delayMoveTimer.Tick += TimerMoveTick; + this.editorView.TextArea.Caret.PositionChanged += CaretPositionChanged; + } + + void TimerTick(object sender, EventArgs e) + { + this.delayTimer.Stop(); + LoggingService.Info("tick"); + // almost the same as DebuggerService.HandleToolTipRequest + var referencesToBeHighlighted = GetReferencesInCurrentFile(this.lastResolveResult); + this.highlightRenderer.SetHighlight(referencesToBeHighlighted); + } + + void TimerMoveTick(object sender, EventArgs e) + { + LoggingService.Debug("move"); + this.delayMoveTimer.Stop(); + this.delayTimer.Stop(); + var resolveResult = GetExpressionUnderCaret(); + if (resolveResult == null) { + this.lastResolveResult = resolveResult; + this.highlightRenderer.ClearHighlight(); + return; + } + // caret is over symbol and that symbol is different from the last time + if (!SameResolveResult(resolveResult, lastResolveResult)) + { + this.lastResolveResult = resolveResult; + this.highlightRenderer.ClearHighlight(); + this.delayTimer.Start(); + } + } + + /// + /// In the current document, highlights all references to the expression + /// which is currently under the caret (local variable, class, property). + /// This gets called on every caret position change, so quite often. + /// + void CaretPositionChanged(object sender, EventArgs e) + { + this.delayMoveTimer.Stop(); + this.delayMoveTimer.Start(); + } + + /// + /// Resolves the current expression under caret. + /// This gets called on every caret position change, so quite often. + /// + ResolveResult GetExpressionUnderCaret() + { + if (string.IsNullOrEmpty(Editor.FileName) || ParserService.LoadSolutionProjectsThreadRunning) + return null; + int line = Editor.Caret.Position.Line; + int column = Editor.Caret.Position.Column; + return ParserService.Resolve(line, column, Editor.Document, Editor.FileName); + } + + /// + /// Finds references to resolved expression in the current file. + /// + List GetReferencesInCurrentFile(ResolveResult resolveResult) + { + var references = RefactoringService.FindReferencesLocal(resolveResult, Editor.FileName, null); + if (references == null || references.Count == 0) + return null; + return references; + } + + /// + /// Returns true if the 2 ResolveResults refer to the same symbol. + /// + bool SameResolveResult(ResolveResult resolveResult, ResolveResult resolveResult2) + { + return false; + } + } +} diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs index d4cf259340..0b706ed66d 100755 --- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditorView.cs @@ -41,6 +41,7 @@ namespace ICSharpCode.AvalonEdit.AddIn public ITextEditor Adapter { get; set; } BracketHighlightRenderer bracketRenderer; + CaretReferencesRenderer caretReferencesRenderer; public CodeEditorView() { @@ -48,19 +49,16 @@ namespace ICSharpCode.AvalonEdit.AddIn UpdateCustomizedHighlighting(); - bracketRenderer = new BracketHighlightRenderer(this.TextArea.TextView); + this.bracketRenderer = new BracketHighlightRenderer(this.TextArea.TextView); + this.caretReferencesRenderer = new CaretReferencesRenderer(this); this.MouseHover += TextEditorMouseHover; this.MouseHoverStopped += TextEditorMouseHoverStopped; this.MouseLeave += TextEditorMouseLeave; this.TextArea.TextView.MouseDown += TextViewMouseDown; - this.TextArea.Caret.PositionChanged += CaretPositionChanged; + this.TextArea.Caret.PositionChanged += HighlightBrackets; - var editingKeyBindings = this.TextArea.DefaultInputHandler.Editing.InputBindings.OfType(); - var tabBinding = editingKeyBindings.Single(b => b.Key == Key.Tab && b.Modifiers == ModifierKeys.None); - this.TextArea.DefaultInputHandler.Editing.InputBindings.Remove(tabBinding); - var newTabBinding = new KeyBinding(new CustomTabCommand(this, tabBinding.Command), tabBinding.Key, tabBinding.Modifiers); - this.TextArea.DefaultInputHandler.Editing.InputBindings.Add(newTabBinding); + SetUpTabSnippetHandler(); } protected override string FileName { @@ -71,13 +69,16 @@ namespace ICSharpCode.AvalonEdit.AddIn { base.OnOptionChanged(e); if (e.PropertyName == "HighlightBrackets") - CaretPositionChanged(null, e); + HighlightBrackets(null, e); else if (e.PropertyName == "EnableFolding") UpdateParseInformation(); } #region CaretPositionChanged - Bracket Highlighting - void CaretPositionChanged(object sender, EventArgs e) + /// + /// Highlights matching brackets. + /// + void HighlightBrackets(object sender, EventArgs e) { if (CodeEditorOptions.Instance.HighlightBrackets) { /* @@ -97,6 +98,15 @@ namespace ICSharpCode.AvalonEdit.AddIn #endregion #region Custom Tab command (code snippet expansion) + void SetUpTabSnippetHandler() + { + var editingKeyBindings = this.TextArea.DefaultInputHandler.Editing.InputBindings.OfType(); + var tabBinding = editingKeyBindings.Single(b => b.Key == Key.Tab && b.Modifiers == ModifierKeys.None); + this.TextArea.DefaultInputHandler.Editing.InputBindings.Remove(tabBinding); + var newTabBinding = new KeyBinding(new CustomTabCommand(this, tabBinding.Command), tabBinding.Key, tabBinding.Modifiers); + this.TextArea.DefaultInputHandler.Editing.InputBindings.Add(newTabBinding); + } + sealed class CustomTabCommand : ICommand { CodeEditorView editor; @@ -202,7 +212,7 @@ namespace ICSharpCode.AvalonEdit.AddIn ITextMarker markerWithToolTip = markersAtOffset.FirstOrDefault(marker => marker.ToolTip != null); if (markerWithToolTip != null) { - args.ShowToolTip(markerWithToolTip.ToolTip); + args.SetToolTip(markerWithToolTip.ToolTip); } } diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExpressionHighlightRenderer.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExpressionHighlightRenderer.cs new file mode 100644 index 0000000000..648cc68650 --- /dev/null +++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/ExpressionHighlightRenderer.cs @@ -0,0 +1,80 @@ +// +// +// +// +// $Revision: $ +// +using System; +using System.Diagnostics; +using System.Windows.Media; +using System.Collections.Generic; + +using ICSharpCode.AvalonEdit.Document; +using ICSharpCode.AvalonEdit.Rendering; +using ICSharpCode.SharpDevelop.Editor; +using ICSharpCode.SharpDevelop.Refactoring; + +namespace ICSharpCode.AvalonEdit.AddIn +{ + /// + /// Highlights expressions (references to expression under current caret). + /// + public class ExpressionHighlightRenderer : IBackgroundRenderer + { + List renderedReferences; + Pen borderPen; + Brush backgroundBrush; + TextView textView; + + public void SetHighlight(List renderedReferences) + { + if (this.renderedReferences != renderedReferences) { + this.renderedReferences = renderedReferences; + textView.InvalidateLayer(this.Layer); + } + } + + public void ClearHighlight() + { + this.SetHighlight(null); + } + + public ExpressionHighlightRenderer(TextView textView) + { + if (textView == null) + throw new ArgumentNullException("textView"); + //this.borderPen = new Pen(new SolidColorBrush(Color.FromRgb(70, 230, 70)), 1); + this.borderPen = new Pen(Brushes.Transparent, 1); + this.borderPen.Freeze(); + this.backgroundBrush = new SolidColorBrush(Color.FromArgb(120, 60, 255, 60)); + this.backgroundBrush.Freeze(); + this.textView = textView; + this.textView.BackgroundRenderers.Add(this); + } + + public KnownLayer Layer { + get { + return KnownLayer.Selection; + } + } + + public void Draw(TextView textView, DrawingContext drawingContext) + { + if (this.renderedReferences == null) + return; + BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); + builder.CornerRadius = 1; + builder.AlignToMiddleOfPixels = true; + foreach (var reference in this.renderedReferences) { + builder.AddSegment(textView, new TextSegment() { + StartOffset = reference.Offset, + Length = reference.Length }); + builder.CloseFigure(); + } + Geometry geometry = builder.CreateGeometry(); + if (geometry != null) { + drawingContext.DrawGeometry(backgroundBrush, borderPen, geometry); + } + } + } +} diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs index 156486f716..a1960add72 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs @@ -31,7 +31,7 @@ namespace Hornung.ResourceToolkit.ToolTips ResourceResolveResult result = ResourceResolverService.Resolve(e.Editor.FileName, doc, logicPos.Y - 1, logicPos.X - 1, null); if (result != null && result.ResourceFileContent != null) { - e.ShowToolTip(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key)); + e.SetToolTip(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key)); } } diff --git a/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs index 533393d091..35e714e2ef 100644 --- a/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs +++ b/src/AddIns/Misc/SharpRefactoring/Project/Src/SwitchBodySnippetElement.cs @@ -184,7 +184,7 @@ namespace SharpRefactoring /// Assuming that interactive mode of 'switch' snippet has currently finished in context, /// returns the switch condition that user entered. /// - string GetSwitchConditionText(InsertionContext context, out int startOffset) + string GetSwitchConditionText(InsertionContext context, out int conditionEndOffset) { var snippetActiveElements = context.ActiveElements.ToList(); if (snippetActiveElements.Count == 0) @@ -194,17 +194,17 @@ namespace SharpRefactoring throw new InvalidOperationException("Switch snippet condition should be " + typeof(IReplaceableActiveElement).Name); if (switchConditionElement.Segment == null) throw new InvalidOperationException("Swith condition should have a start offset"); - startOffset = switchConditionElement.Segment.EndOffset - 1; + conditionEndOffset = switchConditionElement.Segment.EndOffset - 1; return switchConditionElement.Text; } /// /// Resolves the Dom.IReturnType of expression ending at offset, ie. the switch condition expression. /// - IReturnType ResolveConditionType(string conditionExpression, int offset) + IReturnType ResolveConditionType(string conditionExpression, int conditionEndOffset) { ExpressionResult expressionResult = new ExpressionResult(conditionExpression); - Location location = this.Editor.Document.OffsetToPosition(offset); + Location location = this.Editor.Document.OffsetToPosition(conditionEndOffset); var result = ParserService.Resolve(expressionResult, location.Line, location.Column, this.Editor.FileName, this.Editor.Document.Text); return result.ResolvedType; } diff --git a/src/Main/Base/Project/Src/Editor/ToolTipRequestEventArgs.cs b/src/Main/Base/Project/Src/Editor/ToolTipRequestEventArgs.cs index e192ec7dfc..495ec920cb 100644 --- a/src/Main/Base/Project/Src/Editor/ToolTipRequestEventArgs.cs +++ b/src/Main/Base/Project/Src/Editor/ToolTipRequestEventArgs.cs @@ -38,9 +38,9 @@ namespace ICSharpCode.SharpDevelop.Editor public object ContentToShow { get; set; } /// - /// Shows the tool tip. + /// Sets the tooltip to be shown. /// - public void ShowToolTip(object content) + public void SetToolTip(object content) { if (content == null) throw new ArgumentNullException("content"); diff --git a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs index 71973927fb..614bcd6146 100644 --- a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs +++ b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs @@ -246,9 +246,9 @@ namespace ICSharpCode.SharpDevelop.Debugging #region Tool tips /// /// Gets debugger tooltip information for the specified position. - /// A descriptive text for the element or a DebuggerGridControl + /// A descriptive string for the element or a DebuggerTooltipControl /// showing its current value (when in debugging mode) can be returned - /// through the ToolTipInfo object. + /// through the ToolTipRequestEventArgs.SetTooltip() method. /// internal static void HandleToolTipRequest(ToolTipRequestEventArgs e) { @@ -276,15 +276,15 @@ namespace ICSharpCode.SharpDevelop.Debugging } if (toolTipText != null) { if (debuggerCanShowValue && currentDebugger != null) { - e.ShowToolTip(currentDebugger.GetTooltipControl(expressionResult.Expression)); + e.SetToolTip(currentDebugger.GetTooltipControl(expressionResult.Expression)); } else { - e.ShowToolTip(toolTipText); + e.SetToolTip(toolTipText); } } } else { #if DEBUG if (Control.ModifierKeys == Keys.Control) { - e.ShowToolTip("no expr: " + expressionResult.ToString()); + e.SetToolTip("no expr: " + expressionResult.ToString()); } #endif } diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs index b66524bd00..591b574928 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParserService.cs @@ -182,7 +182,7 @@ namespace ICSharpCode.SharpDevelop } #endregion - #region GetParser / ExpressionFinder /etc. + #region GetParser / ExpressionFinder / Resolve / etc. static readonly string[] DefaultTaskListTokens = {"HACK", "TODO", "UNDONE", "FIXME"}; /// @@ -238,6 +238,9 @@ namespace ICSharpCode.SharpDevelop return null; } + /// + /// Resolves given ExpressionResult. + /// public static ResolveResult Resolve(ExpressionResult expressionResult, int caretLineNumber, int caretColumn, string fileName, string fileContent) @@ -252,6 +255,27 @@ namespace ICSharpCode.SharpDevelop } return null; } + + /// + /// Resolves expression at given position. + /// That is, finds ExpressionResult at that position and + /// calls the overload Resolve(ExpressionResult,...). + /// + public static ResolveResult Resolve(int caretLine, int caretColumn, IDocument document, string fileName) + { + IExpressionFinder expressionFinder = GetExpressionFinder(fileName); + if (expressionFinder == null) + return null; + if (caretColumn > document.GetLine(caretLine).Length) + return null; + string documentText = document.Text; + var expressionResult = expressionFinder.FindFullExpression(documentText, document.PositionToOffset(caretLine, caretColumn)); + string expression = (expressionResult.Expression ?? "").Trim(); + if (expression.Length > 0) { + return Resolve(expressionResult, caretLine, caretColumn, fileName, documentText); + } else + return null; + } #endregion #region GetParseableFileContent diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs index 9eedc19290..75cdbb8267 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs @@ -88,54 +88,78 @@ namespace ICSharpCode.SharpDevelop.Refactoring /// Find all references to the specified member. /// public static List FindReferences(IMember member, IProgressMonitor progressMonitor) + { + return FindReferences(member, null, progressMonitor); + } + + static List FindReferences(IMember member, string fileName, IProgressMonitor progressMonitor) { if (member == null) throw new ArgumentNullException("member"); - return RunFindReferences(member.DeclaringType, member, false, progressMonitor); + return RunFindReferences(member.DeclaringType, member, fileName, progressMonitor); } /// /// Find all references to the specified class. /// public static List FindReferences(IClass @class, IProgressMonitor progressMonitor) + { + return FindReferences(@class, null, progressMonitor); + } + + static List FindReferences(IClass @class, string fileName, IProgressMonitor progressMonitor) { if (@class == null) throw new ArgumentNullException("class"); - return RunFindReferences(@class, null, false, progressMonitor); + return RunFindReferences(@class, null, fileName, progressMonitor); } /// /// Find all references to the resolved entity. /// public static List FindReferences(ResolveResult entity, IProgressMonitor progressMonitor) + { + return FindReferences(entity, null, progressMonitor); + } + + /// + /// Finds all references to the resolved entity, only in the file where the entity was resolved. + /// + public static List FindReferencesLocal(ResolveResult entity, string fileName, IProgressMonitor progressMonitor) + { + return FindReferences(entity, fileName, progressMonitor); + } + + static List FindReferences(ResolveResult entity, string fileName, IProgressMonitor progressMonitor) { if (entity == null) throw new ArgumentNullException("entity"); if (entity is LocalResolveResult) { - return RunFindReferences(entity.CallingClass, (entity as LocalResolveResult).Field, true, progressMonitor); + return RunFindReferences(entity.CallingClass, (entity as LocalResolveResult).Field, + entity.CallingClass.CompilationUnit.FileName, progressMonitor); } else if (entity is TypeResolveResult) { - return FindReferences((entity as TypeResolveResult).ResolvedClass, progressMonitor); + return FindReferences((entity as TypeResolveResult).ResolvedClass, fileName, progressMonitor); } else if (entity is MemberResolveResult) { - return FindReferences((entity as MemberResolveResult).ResolvedMember, progressMonitor); + return FindReferences((entity as MemberResolveResult).ResolvedMember, fileName, progressMonitor); } else if (entity is MethodGroupResolveResult) { IMethod method = (entity as MethodGroupResolveResult).GetMethodIfSingleOverload(); if (method != null) { - return FindReferences(method, progressMonitor); + return FindReferences(method, fileName, progressMonitor); } } else if (entity is MixedResolveResult) { - return FindReferences((entity as MixedResolveResult).PrimaryResult, progressMonitor); + return FindReferences((entity as MixedResolveResult).PrimaryResult, fileName, progressMonitor); } return null; } /// /// This method can be used in three modes: - /// 1. Find references to classes (parentClass = targetClass, member = null, isLocal = false) - /// 2. Find references to members (parentClass = parent, member = member, isLocal = false) - /// 3. Find references to local variables (parentClass = parent, member = local var as field, isLocal = true) + /// 1. Find references to classes (parentClass = targetClass, member = null, fileName = null) + /// 2. Find references to members (parentClass = parent, member = member, fileName = null) + /// 3. Find references to local variables (parentClass = parent, member = local var as field, fileName = parent.CompilationUnit.FileName) /// static List RunFindReferences(IClass ownerClass, IMember member, - bool isLocal, + string fileName, IProgressMonitor progressMonitor) { if (ParserService.LoadSolutionProjectsThreadRunning) { @@ -145,10 +169,12 @@ namespace ICSharpCode.SharpDevelop.Refactoring return null; } List files; - if (isLocal) { + if (!string.IsNullOrEmpty(fileName)) { + // search just in given file files = new List(); - files.Add(FindItem(ownerClass.CompilationUnit.FileName)); + files.Add(FindItem(fileName)); } else { + // search in all possible files ownerClass = ownerClass.GetCompoundClass(); files = GetPossibleFiles(ownerClass, member); } @@ -169,7 +195,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring ITextBuffer content = entry.GetContent(); if (content != null) { - AddReferences(references, ownerClass, member, isLocal, entry.FileName, content.Text); + AddReferences(references, ownerClass, member, entry.FileName, content.Text); } } @@ -181,7 +207,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring /// static void AddReferences(List list, IClass parentClass, IMember member, - bool isLocal, string fileName, string fileContent) { TextFinder textFinder; // the class used to find the position to resolve