.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

813 lines
22 KiB

//
// CSharpCompletionEngineBase.cs
//
// Author:
// Mike Krüger <mkrueger@xamarin.com>
//
// Copyright (c) 2011 Xamarin Inc. (http://xamarin.com)
//
// 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.Linq;
using System.Text;
using ICSharpCode.NRefactory.CSharp.Resolver;
using ICSharpCode.NRefactory.Editor;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.Semantics;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using ICSharpCode.NRefactory.CSharp.TypeSystem;
namespace ICSharpCode.NRefactory.CSharp.Completion
{
/// <summary>
/// Acts as a common base between code completion and parameter completion.
/// </summary>
public class CSharpCompletionEngineBase
{
protected IDocument document;
protected int offset;
protected TextLocation location;
protected IUnresolvedTypeDefinition currentType;
protected IUnresolvedMember currentMember;
#region Input properties
public CSharpTypeResolveContext ctx { get; private set; }
public IProjectContent ProjectContent { get; private set; }
ICompilation compilation;
protected ICompilation Compilation {
get {
if (compilation == null)
compilation = ProjectContent.Resolve (ctx).Compilation;
return compilation;
}
}
#endregion
protected CSharpCompletionEngineBase(IProjectContent content, ICompletionContextProvider completionContextProvider, CSharpTypeResolveContext ctx)
{
if (content == null)
throw new ArgumentNullException("content");
if (ctx == null)
throw new ArgumentNullException("ctx");
if (completionContextProvider == null)
throw new ArgumentNullException("completionContextProvider");
this.ProjectContent = content;
this.CompletionContextProvider = completionContextProvider;
this.ctx = ctx;
}
public ICompletionContextProvider CompletionContextProvider {
get;
private set;
}
public void SetOffset (int offset)
{
Reset ();
this.offset = offset;
this.location = document.GetLocation (offset);
CompletionContextProvider.GetCurrentMembers (offset, out currentType, out currentMember);
}
public bool GetParameterCompletionCommandOffset (out int cpos)
{
// Start calculating the parameter offset from the beginning of the
// current member, instead of the beginning of the file.
cpos = offset - 1;
var mem = currentMember;
if (mem == null || (mem is IType)) {
return false;
}
int startPos = document.GetOffset (mem.Region.BeginLine, mem.Region.BeginColumn);
int parenDepth = 0;
int chevronDepth = 0;
Stack<int> indexStack = new Stack<int> ();
while (cpos > startPos) {
char c = document.GetCharAt (cpos);
if (c == ')') {
parenDepth++;
}
if (c == '>') {
chevronDepth++;
}
if (c == '}') {
if (indexStack.Count > 0) {
parenDepth = indexStack.Pop ();
} else {
parenDepth = 0;
}
chevronDepth = 0;
}
if (indexStack.Count == 0 && (parenDepth == 0 && c == '(' || chevronDepth == 0 && c == '<')) {
int p = GetCurrentParameterIndex (startPos, cpos + 1);
if (p != -1) {
cpos++;
return true;
} else {
return false;
}
}
if (c == '(') {
parenDepth--;
}
if (c == '<') {
chevronDepth--;
}
if (c == '{') {
indexStack.Push (parenDepth);
chevronDepth = 0;
}
cpos--;
}
return false;
}
public int GetCurrentParameterIndex (int triggerOffset, int endOffset)
{
char lastChar = document.GetCharAt (endOffset - 1);
if (lastChar == '(' || lastChar == '<') {
return 0;
}
var parameter = new Stack<int> ();
var bracketStack = new Stack<Stack<int>> ();
bool inSingleComment = false, inString = false, inVerbatimString = false, inChar = false, inMultiLineComment = false;
for (int i = triggerOffset; i < endOffset; i++) {
char ch = document.GetCharAt (i);
char nextCh = i + 1 < document.TextLength ? document.GetCharAt (i + 1) : '\0';
switch (ch) {
case '{':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
bracketStack.Push (parameter);
parameter = new Stack<int> ();
break;
case '[':
case '(':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
parameter.Push (0);
break;
case '}':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
if (bracketStack.Count > 0) {
parameter = bracketStack.Pop ();
} else {
return -1;
}
break;
case ']':
case ')':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
if (parameter.Count > 0) {
parameter.Pop ();
} else {
return -1;
}
break;
case '<':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
parameter.Push (0);
break;
case '>':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
if (parameter.Count > 0) {
parameter.Pop ();
}
break;
case ',':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
if (parameter.Count > 0) {
parameter.Push (parameter.Pop () + 1);
}
break;
case '/':
if (inString || inChar || inVerbatimString) {
break;
}
if (nextCh == '/') {
i++;
inSingleComment = true;
}
if (nextCh == '*') {
inMultiLineComment = true;
}
break;
case '*':
if (inString || inChar || inVerbatimString || inSingleComment) {
break;
}
if (nextCh == '/') {
i++;
inMultiLineComment = false;
}
break;
case '@':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment) {
break;
}
if (nextCh == '"') {
i++;
inVerbatimString = true;
}
break;
case '\n':
case '\r':
inSingleComment = false;
inString = false;
inChar = false;
break;
case '\\':
if (inString || inChar) {
i++;
}
break;
case '"':
if (inSingleComment || inMultiLineComment || inChar) {
break;
}
if (inVerbatimString) {
if (nextCh == '"') {
i++;
break;
}
inVerbatimString = false;
break;
}
inString = !inString;
break;
case '\'':
if (inSingleComment || inMultiLineComment || inString || inVerbatimString) {
break;
}
inChar = !inChar;
break;
}
}
if (parameter.Count == 0 || bracketStack.Count > 0) {
return -1;
}
return parameter.Pop() + 1;
}
#region Context helper methods
public class MiniLexer
{
readonly string text;
public bool IsFistNonWs = true;
public bool IsInSingleComment = false;
public bool IsInString = false;
public bool IsInVerbatimString = false;
public bool IsInChar = false;
public bool IsInMultiLineComment = false;
public bool IsInPreprocessorDirective = false;
public MiniLexer(string text)
{
this.text = text;
}
public void Parse(Action<char> act = null)
{
Parse(0, text.Length, act);
}
public void Parse(int start, int length, Action<char> act = null)
{
for (int i = start; i < length; i++) {
char ch = text [i];
char nextCh = i + 1 < text.Length ? text [i + 1] : '\0';
switch (ch) {
case '#':
if (IsFistNonWs)
IsInPreprocessorDirective = true;
break;
case '/':
if (IsInString || IsInChar || IsInVerbatimString)
break;
if (nextCh == '/') {
i++;
IsInSingleComment = true;
}
if (nextCh == '*')
IsInMultiLineComment = true;
break;
case '*':
if (IsInString || IsInChar || IsInVerbatimString || IsInSingleComment)
break;
if (nextCh == '/') {
i++;
IsInMultiLineComment = false;
}
break;
case '@':
if (IsInString || IsInChar || IsInVerbatimString || IsInSingleComment || IsInMultiLineComment)
break;
if (nextCh == '"') {
i++;
IsInVerbatimString = true;
}
break;
case '\n':
case '\r':
IsInSingleComment = false;
IsInString = false;
IsInChar = false;
IsFistNonWs = true;
IsInPreprocessorDirective = false;
break;
case '\\':
if (IsInString || IsInChar)
i++;
break;
case '"':
if (IsInSingleComment || IsInMultiLineComment || IsInChar)
break;
if (IsInVerbatimString) {
if (nextCh == '"') {
i++;
break;
}
IsInVerbatimString = false;
break;
}
IsInString = !IsInString;
break;
case '\'':
if (IsInSingleComment || IsInMultiLineComment || IsInString || IsInVerbatimString)
break;
IsInChar = !IsInChar;
break;
}
if (act != null)
act(ch);
IsFistNonWs &= ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}
}
}
protected bool IsInsideCommentStringOrDirective(int offset)
{
var lexer = new MiniLexer(document.Text);
lexer.Parse(0, offset);
return
lexer.IsInSingleComment ||
lexer.IsInString ||
lexer.IsInVerbatimString ||
lexer.IsInChar ||
lexer.IsInMultiLineComment ||
lexer.IsInPreprocessorDirective;
}
protected bool IsInsideCommentStringOrDirective()
{
var text = GetMemberTextToCaret();
var lexer = new MiniLexer(text.Item1);
lexer.Parse();
return
lexer.IsInSingleComment ||
lexer.IsInString ||
lexer.IsInVerbatimString ||
lexer.IsInChar ||
lexer.IsInMultiLineComment ||
lexer.IsInPreprocessorDirective;
}
protected bool IsInsideDocComment ()
{
var text = GetMemberTextToCaret ();
bool inSingleComment = false, inString = false, inVerbatimString = false, inChar = false, inMultiLineComment = false;
bool singleLineIsDoc = false;
for (int i = 0; i < text.Item1.Length - 1; i++) {
char ch = text.Item1 [i];
char nextCh = text.Item1 [i + 1];
switch (ch) {
case '/':
if (inString || inChar || inVerbatimString)
break;
if (nextCh == '/') {
i++;
inSingleComment = true;
singleLineIsDoc = i + 1 < text.Item1.Length && text.Item1 [i + 1] == '/';
if (singleLineIsDoc) {
i++;
}
}
if (nextCh == '*')
inMultiLineComment = true;
break;
case '*':
if (inString || inChar || inVerbatimString || inSingleComment)
break;
if (nextCh == '/') {
i++;
inMultiLineComment = false;
}
break;
case '@':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment)
break;
if (nextCh == '"') {
i++;
inVerbatimString = true;
}
break;
case '\n':
case '\r':
inSingleComment = false;
inString = false;
inChar = false;
break;
case '\\':
if (inString || inChar)
i++;
break;
case '"':
if (inSingleComment || inMultiLineComment || inChar)
break;
if (inVerbatimString) {
if (nextCh == '"') {
i++;
break;
}
inVerbatimString = false;
break;
}
inString = !inString;
break;
case '\'':
if (inSingleComment || inMultiLineComment || inString || inVerbatimString)
break;
inChar = !inChar;
break;
}
}
return inSingleComment && singleLineIsDoc;
}
protected CSharpResolver GetState ()
{
return new CSharpResolver (ctx);
/*var state = new CSharpResolver (ctx);
state.CurrentMember = currentMember;
state.CurrentTypeDefinition = currentType;
state.CurrentUsingScope = CSharpUnresolvedFile.GetUsingScope (location);
if (state.CurrentMember != null) {
var node = Unit.GetNodeAt (location);
if (node == null)
return state;
var navigator = new NodeListResolveVisitorNavigator (new[] { node });
var visitor = new ResolveVisitor (state, CSharpUnresolvedFile, navigator);
Unit.AcceptVisitor (visitor, null);
try {
var newState = visitor.GetResolverStateBefore (node);
if (newState != null)
state = newState;
} catch (Exception) {
}
}
return state;*/
}
#endregion
#region Basic parsing/resolving functions
static Stack<Tuple<char, int>> GetBracketStack (string memberText)
{
var bracketStack = new Stack<Tuple<char, int>> ();
bool inSingleComment = false, inString = false, inVerbatimString = false, inChar = false, inMultiLineComment = false;
for (int i = 0; i < memberText.Length; i++) {
char ch = memberText [i];
char nextCh = i + 1 < memberText.Length ? memberText [i + 1] : '\0';
switch (ch) {
case '(':
case '[':
case '{':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment)
break;
bracketStack.Push (Tuple.Create (ch, i));
break;
case ')':
case ']':
case '}':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment)
break;
if (bracketStack.Count > 0)
bracketStack.Pop ();
break;
case '/':
if (inString || inChar || inVerbatimString)
break;
if (nextCh == '/') {
i++;
inSingleComment = true;
}
if (nextCh == '*')
inMultiLineComment = true;
break;
case '*':
if (inString || inChar || inVerbatimString || inSingleComment)
break;
if (nextCh == '/') {
i++;
inMultiLineComment = false;
}
break;
case '@':
if (inString || inChar || inVerbatimString || inSingleComment || inMultiLineComment)
break;
if (nextCh == '"') {
i++;
inVerbatimString = true;
}
break;
case '\n':
case '\r':
inSingleComment = false;
inString = false;
inChar = false;
break;
case '\\':
if (inString || inChar)
i++;
break;
case '"':
if (inSingleComment || inMultiLineComment || inChar)
break;
if (inVerbatimString) {
if (nextCh == '"') {
i++;
break;
}
inVerbatimString = false;
break;
}
inString = !inString;
break;
case '\'':
if (inSingleComment || inMultiLineComment || inString || inVerbatimString)
break;
inChar = !inChar;
break;
default :
break;
}
}
return bracketStack;
}
public static void AppendMissingClosingBrackets (StringBuilder wrapper, string memberText, bool appendSemicolon)
{
var bracketStack = GetBracketStack (memberText);
bool didAppendSemicolon = !appendSemicolon;
//char lastBracket = '\0';
while (bracketStack.Count > 0) {
var t = bracketStack.Pop ();
switch (t.Item1) {
case '(':
wrapper.Append (')');
if (appendSemicolon)
didAppendSemicolon = false;
//lastBracket = ')';
break;
case '[':
wrapper.Append (']');
if (appendSemicolon)
didAppendSemicolon = false;
//lastBracket = ']';
break;
case '<':
wrapper.Append ('>');
if (appendSemicolon)
didAppendSemicolon = false;
//lastBracket = '>';
break;
case '{':
int o = t.Item2 - 1;
if (!didAppendSemicolon) {
didAppendSemicolon = true;
wrapper.Append (';');
}
bool didAppendCatch = false;
while (o >= "try".Length) {
char ch = memberText [o];
if (!char.IsWhiteSpace (ch)) {
if (ch == 'y' && memberText [o - 1] == 'r' && memberText [o - 2] == 't') {
wrapper.Append ("} catch {}");
didAppendCatch = true;
}
break;
}
o--;
}
if (!didAppendCatch)
wrapper.Append ('}');
break;
}
}
if (!didAppendSemicolon)
wrapper.Append (';');
}
protected SyntaxTree ParseStub(string continuation, bool appendSemicolon = true, string afterContinuation = null)
{
var mt = GetMemberTextToCaret();
if (mt == null) {
return null;
}
string memberText = mt.Item1;
var memberLocation = mt.Item2;
int closingBrackets = 1;
int generatedLines = 0;
var wrapper = new StringBuilder();
bool wrapInClass = memberLocation != new TextLocation(1, 1);
if (wrapInClass) {
wrapper.Append("class Stub {");
wrapper.AppendLine();
closingBrackets++;
generatedLines++;
}
wrapper.Append(memberText);
wrapper.Append(continuation);
AppendMissingClosingBrackets(wrapper, memberText, appendSemicolon);
wrapper.Append(afterContinuation);
if (closingBrackets > 0) {
wrapper.Append(new string('}', closingBrackets));
}
var parser = new CSharpParser ();
foreach (var sym in CompletionContextProvider.ConditionalSymbols)
parser.CompilerSettings.ConditionalSymbols.Add (sym);
parser.InitialLocation = new TextLocation(memberLocation.Line - generatedLines, 1);
var result = parser.Parse(wrapper.ToString ());
return result;
}
// string cachedText = null;
protected virtual void Reset ()
{
// cachedText = null;
}
protected Tuple<string, TextLocation> GetMemberTextToCaret()
{
return CompletionContextProvider.GetMemberTextToCaret(offset, currentType, currentMember);
}
protected ExpressionResult GetInvocationBeforeCursor(bool afterBracket)
{
SyntaxTree baseUnit;
baseUnit = ParseStub("a", false);
var section = baseUnit.GetNodeAt<AttributeSection>(location.Line, location.Column - 2);
var attr = section != null ? section.Attributes.LastOrDefault() : null;
if (attr != null) {
return new ExpressionResult((AstNode)attr, baseUnit);
}
//var memberLocation = currentMember != null ? currentMember.Region.Begin : currentType.Region.Begin;
var mref = baseUnit.GetNodeAt(location.Line, location.Column - 1, n => n is InvocationExpression || n is ObjectCreateExpression);
AstNode expr = null;
if (mref is InvocationExpression) {
expr = ((InvocationExpression)mref).Target;
} else if (mref is ObjectCreateExpression) {
expr = mref;
} else {
baseUnit = ParseStub(")};", false);
mref = baseUnit.GetNodeAt(location.Line, location.Column - 1, n => n is InvocationExpression || n is ObjectCreateExpression);
if (mref is InvocationExpression) {
expr = ((InvocationExpression)mref).Target;
} else if (mref is ObjectCreateExpression) {
expr = mref;
}
}
if (expr == null) {
// work around for missing ';' bug in mcs:
baseUnit = ParseStub("a", true);
section = baseUnit.GetNodeAt<AttributeSection>(location.Line, location.Column - 2);
attr = section != null ? section.Attributes.LastOrDefault() : null;
if (attr != null) {
return new ExpressionResult((AstNode)attr, baseUnit);
}
//var memberLocation = currentMember != null ? currentMember.Region.Begin : currentType.Region.Begin;
mref = baseUnit.GetNodeAt(location.Line, location.Column - 1, n => n is InvocationExpression || n is ObjectCreateExpression);
expr = null;
if (mref is InvocationExpression) {
expr = ((InvocationExpression)mref).Target;
} else if (mref is ObjectCreateExpression) {
expr = mref;
}
}
if (expr == null) {
return null;
}
return new ExpressionResult ((AstNode)expr, baseUnit);
}
public class ExpressionResult
{
public AstNode Node { get; private set; }
public SyntaxTree Unit { get; private set; }
public ExpressionResult (AstNode item2, SyntaxTree item3)
{
this.Node = item2;
this.Unit = item3;
}
public override string ToString ()
{
return string.Format ("[ExpressionResult: Node={0}, Unit={1}]", Node, Unit);
}
}
protected Tuple<ResolveResult, CSharpResolver> ResolveExpression (ExpressionResult tuple)
{
return ResolveExpression (tuple.Node);
}
protected Tuple<ResolveResult, CSharpResolver> ResolveExpression(AstNode expr)
{
if (expr == null) {
return null;
}
AstNode resolveNode;
if (expr is Expression || expr is AstType) {
resolveNode = expr;
} else if (expr is VariableDeclarationStatement) {
resolveNode = ((VariableDeclarationStatement)expr).Type;
} else {
resolveNode = expr;
}
try {
var root = expr.AncestorsAndSelf.FirstOrDefault(n => n is EntityDeclaration || n is SyntaxTree);
if (root == null) {
return null;
}
if (root is Accessor)
root = root.Parent;
var csResolver = CompletionContextProvider.GetResolver (GetState(), root);
var result = csResolver.Resolve(resolveNode);
var state = csResolver.GetResolverStateBefore(resolveNode);
return Tuple.Create(result, state);
} catch (Exception e) {
Console.WriteLine(e);
return null;
}
}
#endregion
}
}