@ -5,8 +5,11 @@
// <version>$Revision: $</version>
// <version>$Revision: $</version>
// </file>
// </file>
using System ;
using System ;
using System.Diagnostics ;
using System.Linq ;
using ICSharpCode.NRefactory ;
using ICSharpCode.NRefactory ;
using ICSharpCode.NRefactory.Ast ;
using ICSharpCode.NRefactory.Ast ;
using ICSharpCode.NRefactory.Visitors ;
using ICSharpCode.SharpDevelop.Dom ;
using ICSharpCode.SharpDevelop.Dom ;
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver ;
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver ;
using ICSharpCode.SharpDevelop.Editor ;
using ICSharpCode.SharpDevelop.Editor ;
@ -21,7 +24,32 @@ namespace ICSharpCode.SharpDevelop.Refactoring
public class EditorContext
public class EditorContext
{
{
public ITextEditor Editor { get ; private set ; }
public ITextEditor Editor { get ; private set ; }
SnippetParser SnippetParser { get ; set ; }
int CaretLine { get ; set ; }
int CaretColumn { get ; set ; }
/// <summary>
/// Language independent.
/// </summary>
public ExpressionResult CurrentExpression { get ; private set ; }
/// <summary>
/// Language independent.
/// </summary>
public ResolveResult CurrentSymbol { get ; private set ; }
public IDocumentLine CurrentLine { get ; private set ; }
/// <summary>
/// Only available for C# and VB.
/// </summary>
public INode CurrentLineAST { get ; private set ; }
/// <summary>
/// Only available for C# and VB.
/// </summary>
public INode CurrentMemberAST { get ; private set ; }
/// <summary>
/// Only available for C# and VB.
/// </summary>
public INode CurrentElement { get ; private set ; }
NRefactoryResolver Resolver { get ; set ; }
NRefactoryResolver Resolver { get ; set ; }
public EditorContext ( ITextEditor editor )
public EditorContext ( ITextEditor editor )
@ -29,81 +57,155 @@ namespace ICSharpCode.SharpDevelop.Refactoring
if ( editor = = null )
if ( editor = = null )
throw new ArgumentNullException ( "editor" ) ;
throw new ArgumentNullException ( "editor" ) ;
this . Editor = editor ;
this . Editor = editor ;
this . SnippetParser = GetSnippetParser ( editor ) ;
this . CaretLine = editor . Caret . Line ;
this . Resolver = GetResolver ( editor ) ;
this . CaretColumn = editor . Caret . Column ;
if ( CaretColumn > 1 & & editor . Document . GetText ( editor . Document . PositionToOffset ( CaretLine , CaretColumn - 1 ) , 1 ) = = ";" ) {
// If caret is just after ';', pretend that caret is before ';'
// (works well e.g. for this.Foo();(*caret*) - we want to get "this.Foo()")
// This is equivalent to pretending that ; don't exist, and actually is not such a bad idea.
CaretColumn - = 1 ;
}
Stopwatch sw = new Stopwatch ( ) ;
sw . Start ( ) ;
this . CurrentExpression = GetExpressionAtCaret ( editor ) ;
this . CurrentSymbol = ResolveExpression ( editor ) ;
long elapsedResolveMs = sw . ElapsedMilliseconds ;
this . CurrentLine = editor . Document . GetLine ( CaretLine ) ;
this . CurrentLineAST = GetCurrentLineAst ( this . CurrentLine , editor ) ;
this . CurrentMemberAST = GetCurrentMemberAST ( editor ) ;
this . CurrentElement = FindInnermostNodeAtLocation ( this . CurrentMemberAST , new Location ( CaretColumn , CaretLine ) ) ;
// ICSharpCode.Core.LoggingService.Debug(string.Format(
// @"
// Context actions (elapsed {5} ms ({6} ms in Resolver)):
// ExprAtCaret: {0}
// ----------------------
// SymbolAtCaret: {1}
// ----------------------
// CurrentLineAST: {2}
// ----------------------
// AstNodeAtCaret: {3}
// ----------------------
// CurrentMemberAST: {4}
// ----------------------",
// CurrentExpression, CurrentSymbol, CurrentLineAST, CurrentElement, CurrentMemberAST == null ? "" : CurrentMemberAST.ToString().TakeStartEllipsis(100),
// sw.ElapsedMilliseconds, elapsedResolveMs));
}
}
// TODO make all reference types cached ResolveResult? - implement own Nullable<T>
public TNode GetCurrentElement < TNode > ( ) where TNode : class , INode
ResolveResult symbolUnderCaret ;
public ResolveResult SymbolUnderCaret
{
{
get
if ( this . CurrentElement is TNode )
{
return ( TNode ) this . CurrentElement ;
if ( symbolUnderCaret ! = null )
return null ;
return symbolUnderCaret ;
// workaround so that Resolve works when the caret is placed also at the end of the word
symbolUnderCaret = ParserService . Resolve ( Editor . Caret . Line , Editor . Caret . Column > 1 ? Editor . Caret . Column - 1 : 1 , Editor . Document , Editor . FileName ) ;
if ( symbolUnderCaret = = null )
symbolUnderCaret = ParserService . Resolve ( Editor . Caret . Line , Editor . Caret . Column , Editor . Document , Editor . FileName ) ;
return symbolUnderCaret ;
}
}
}
IDocumentLine currentLine ;
public TNode GetContainingElement < TNode > ( ) where TNode : class , INode
public IDocumentLine CurrentLine
{
{
get
var node = this . CurrentElement ;
while ( node ! = null )
{
{
if ( currentLine ! = null )
if ( node is TNode )
return currentLine ;
return ( TNode ) node ;
try
node = node . Parent ;
{
return ( currentLine = Editor . Document . GetLine ( Editor . Caret . Line ) ) ;
}
catch
{
return null ;
}
}
}
return null ;
}
}
INode currentLineAST ;
// public TNode GetInnerElement<TNode>() where TNode : class, INode
public INode CurrentLineAST
// {
// var findChildVisitor = new FindOutermostNodeVisitor<TNode>();
// this.CurrentElement.AcceptVisitor(findChildVisitor, null);
// return findChildVisitor.FoundNode;
// }
INode FindInnermostNodeAtLocation ( INode memberDecl , Location position )
{
{
get
if ( memberDecl = = null )
{
return null ;
if ( currentLineAST ! = null )
if ( memberDecl is MethodDeclaration ) {
return currentLineAST ;
return FindInnermostNodeInBlock ( ( ( MethodDeclaration ) memberDecl ) . Body , position ) ;
if ( this . SnippetParser = = null | | this . CurrentLine = = null )
} else if ( memberDecl is PropertyDeclaration ) {
return nul l ;
var propertyDecl = ( PropertyDeclaration ) memberDec l;
try {
if ( proper tyDecl . HasGetRegion & & position > = p ropert yDecl . GetRegion . StartLocation & & position < = propertyDecl . GetRegion . EndLocation ) {
return ( currentLineAST = SnippetParser . Parse ( this . CurrentLine . Text ) ) ;
return FindInnermostNodeInBlock ( propertyDecl . GetRegion . Block , position ) ;
}
}
catch {
if ( propertyDecl . HasSetRegion & & position > = propertyDecl . SetRegion . StartLocation & & position < = propertyDecl . SetRegion . EndLocation ) {
return null ;
return FindInnermostNodeInBlock ( propertyDecl . SetRegion . Block , position ) ;
}
}
}
}
return null ;
}
}
INode currentMemberAST ;
INode FindInnermostNodeInBlock ( BlockStatement node , Location position )
public INode CurrentMemberAST
{
{
get
if ( node = = null )
return null ;
var findInnermostVisitor = new FindInnermostNodeVisitor ( position ) ;
node . AcceptVisitor ( findInnermostVisitor , null ) ;
return findInnermostVisitor . InnermostNode ;
}
class FindInnermostNodeVisitor : NodeTrackingAstVisitor
{
public Location CaretLocation { get ; private set ; }
public INode InnermostNode { get ; private set ; }
public FindInnermostNodeVisitor ( Location caretLocation )
{
{
if ( Resolver = = null )
this . CaretLocation = caretLocation ;
return null ;
}
if ( currentMemberAST ! = null )
return currentMemberAST ;
protected override void BeginVisit ( INode node )
try {
{
Resolver . Initialize ( ParserService . GetParseInformation ( Editor . FileName ) , Editor . Caret . Line , Editor . Caret . Column ) ;
if ( node . StartLocation < = CaretLocation & & node . EndLocation > = CaretLocation ) {
return ( currentMemberAST = Resolver . ParseCurrentMember ( Editor . Document . Text ) ) ;
// the node visited last will be the innermost
}
this . InnermostNode = node ;
catch {
return null ;
}
}
base . BeginVisit ( node ) ;
}
}
}
}
// class FindOutermostNodeVisitor<TNode> : NodeTrackingAstVisitor where TNode : class, INode
// {
// public TNode FoundNode { get; private set; }
//
// protected override void BeginVisit(INode node)
// {
// if (node is TNode && FoundNode == null) {
// FoundNode = (TNode)node;
// }
// base.BeginVisit(node);
// }
// }
ResolveResult ResolveExpression ( ITextEditor editor )
{
return ParserService . Resolve ( this . CurrentExpression , CaretLine , CaretColumn , editor . FileName , editor . Document . Text ) ;
}
ExpressionResult GetExpressionAtCaret ( ITextEditor editor )
{
ExpressionResult expr = ParserService . FindFullExpression ( CaretLine , CaretColumn , editor . Document , editor . FileName ) ;
// if no expression, look one character back (works better with method calls - Foo()(*caret*))
if ( string . IsNullOrWhiteSpace ( expr . Expression ) & & CaretColumn > 1 )
expr = ParserService . FindFullExpression ( CaretLine , CaretColumn - 1 , editor . Document , editor . FileName ) ;
return expr ;
}
INode GetCurrentLineAst ( IDocumentLine currentLine , ITextEditor editor )
{
if ( currentLine = = null )
return null ;
var snippetParser = GetSnippetParser ( editor ) ;
return snippetParser . Parse ( currentLine . Text ) ;
}
SnippetParser GetSnippetParser ( ITextEditor editor )
SnippetParser GetSnippetParser ( ITextEditor editor )
{
{
var lang = GetEditorLanguage ( editor ) ;
var lang = GetEditorLanguage ( editor ) ;
@ -113,29 +215,42 @@ namespace ICSharpCode.SharpDevelop.Refactoring
return null ;
return null ;
}
}
NRefactoryResolver GetResolver ( ITextEditor editor )
SupportedLanguage ? GetEditorLanguage ( ITextEditor editor )
{
{
if ( editor = = null | | editor . Language = = null )
if ( editor = = null | | editor . Language = = null )
return null ;
return null ;
try
if ( editor . Language . Properties = = LanguageProperties . CSharp )
{
return SupportedLanguage . CSharp ;
return new NRefactoryResolver ( editor . Language . Properties ) ;
if ( editor . Language . Properties = = LanguageProperties . VBNet )
return SupportedLanguage . VBNet ;
return null ;
}
INode GetCurrentMemberAST ( ITextEditor editor )
{
try {
var resolver = GetNRefactoryResolver ( editor ) ;
resolver . Initialize ( ParserService . GetParseInformation ( editor . FileName ) , CaretLine , CaretColumn ) ;
return resolver . ParseCurrentMember ( editor . Document . Text ) ;
}
}
catch ( NotSupportedException )
catch {
{
return null ;
return null ;
}
}
}
}
SupportedLanguage ? GetEditorLanguage ( ITextEditor editor )
NRefactoryResolver GetNRefactoryResolver ( ITextEditor editor )
{
{
if ( editor = = null | | editor . Language = = null )
if ( editor = = null | | editor . Language = = null )
return null ;
return null ;
if ( editor . Language . Properties = = LanguageProperties . CSharp )
try
return SupportedLanguage . CSharp ;
{
if ( editor . Language . Properties = = LanguageProperties . VBNet )
return new NRefactoryResolver ( editor . Language . Properties ) ;
return SupportedLanguage . VBNet ;
}
return null ;
catch ( NotSupportedException )
{
return null ;
}
}
}
}
}
}
}