diff --git a/Continuous.Client.Core/ContinuousEnv.MonoDevelop.cs b/Continuous.Client.Core/ContinuousEnv.MonoDevelop.cs index e498ac9..7340758 100644 --- a/Continuous.Client.Core/ContinuousEnv.MonoDevelop.cs +++ b/Continuous.Client.Core/ContinuousEnv.MonoDevelop.cs @@ -6,13 +6,10 @@ #if MONODEVELOP using MonoDevelop.Ide; -using MonoDevelop.Ide.Gui; -using MonoDevelop.Projects; -using MonoDevelop.Refactoring; using Gtk; -using ICSharpCode.NRefactory; -using ICSharpCode.NRefactory.CSharp; -using ICSharpCode.NRefactory.CSharp.Resolver; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; namespace Continuous.Client @@ -38,206 +35,100 @@ protected override void AlertImpl (string format, params object[] args) dialog.Destroy (); } - protected override async Task SetWatchTextAsync (WatchVariable w, List vals) + protected override async Task SetWatchTextAsync (WatchVariable w, List vals) { var doc = IdeApp.Workbench.GetDocument (w.FilePath); if (doc == null) - return; - var ed = doc.Editor; - if (ed == null || !ed.CanEdit (w.FileLine)) - return; - var line = ed.GetLine (w.FileLine); - if (line == null) - return; - var newText = "//" + w.ExplicitExpression + "= " + GetValsText (vals); - var lineText = ed.GetLineText (w.FileLine); - var commentIndex = lineText.IndexOf ("//"); - if (commentIndex < 0) - return; - var commentCol = commentIndex + 1; - if (commentCol != w.FileColumn) - return; - - var existingText = lineText.Substring (commentIndex); - - if (existingText != newText) { - var offset = line.Offset + commentIndex; - var remLen = line.Length - commentIndex; - ed.Remove (offset, remLen); - ed.Insert (offset, newText); - } + return; + var ed = doc.Editor; + if (ed == null || ed.IsReadOnly) + return; + var line = ed.GetLine (w.FileLine); + if (line == null) + return; + var newText = "//" + w.ExplicitExpression + "= " + GetValsText (vals); + var lineText = ed.GetLineText (w.FileLine); + var commentIndex = lineText.IndexOf ("//"); + if (commentIndex < 0) + return; + var commentCol = commentIndex + 1; + if (commentCol != w.FileColumn) + return; + + var existingText = lineText.Substring (commentIndex); + + if (existingText != newText) { + var offset = line.Offset + commentIndex; + var remLen = line.Length - commentIndex; + ed.RemoveText (offset, remLen); + ed.InsertText (offset, newText); + } } - class CSharpTypeDecl : TypeDecl - { - public TypeDeclaration Declaration; - public CSharpAstResolver Resolver; - public override string Name { - get { - return Declaration.Name; - } - } + class CSharpTypeDecl : TypeDecl + { + public ClassDeclarationSyntax Declaration { get; set; } + public SyntaxNode Root { get; set; } + public SemanticModel Model { get; set; } + + public override string Name { + get { + return Declaration.Identifier.Text; + } + } + public override TextLoc StartLocation { get { - var l = Declaration.StartLocation; + var l = Declaration.GetLocation ().GetLineSpan ().StartLinePosition; return new TextLoc { Line = l.Line, - Column = l.Column, - }; + Column = l.Character + }; } - } - public override TextLoc EndLocation { + } + + public override TextLoc EndLocation { get { - var l = Declaration.EndLocation; + var l = Declaration.GetLocation ().GetLineSpan ().EndLinePosition; return new TextLoc { Line = l.Line, - Column = l.Column, - }; + Column = l.Character + }; } - } - public override void SetTypeCode () - { - Set (Document, Declaration, Resolver); - } + } - static Statement GetWatchInstrument (string id, Expression expr) - { - var r = new MemberReferenceExpression ( - new MemberReferenceExpression ( - new MemberReferenceExpression ( - new IdentifierExpression ("Continuous"), "Server"), "WatchStore"), "Record"); - var i = new ExpressionStatement (new InvocationExpression (r, new PrimitiveExpression (id), expr)); - var t = new TryCatchStatement (); - t.TryBlock = new BlockStatement (); - t.TryBlock.Statements.Add (i); - var c = new CatchClause (); - c.Body = new BlockStatement (); - t.CatchClauses.Add (c); - return t; - } - - static string GetCommentlessCode (TypeDeclaration rtypedecl) - { - var t = (TypeDeclaration)rtypedecl.Clone (); - var cs = t.Descendants.OfType ().ToList (); - foreach (var c in cs) { - var m = WatchVariable.CommentContentRe.Match (c.Content); - if (m.Success) { - c.Content = m.Groups[1].Value + m.Groups[2].Value; - } else { - c.Remove (); - } - } - return t.ToString (); - } - - static TypeCode Set (DocumentRef doc, TypeDeclaration rtypedecl, CSharpAstResolver resolver) - { - var rawCode = GetCommentlessCode (rtypedecl); - - var typedecl = (TypeDeclaration)rtypedecl.Clone (); - - var ns = rtypedecl.Parent as NamespaceDeclaration; - var nsName = ns == null ? "" : ns.FullName; - - var name = rtypedecl.Name; - - var usings = - resolver.RootNode.Descendants. - OfType (). - Select (x => x.ToString ().Trim ()). - ToList (); - - // - // Find dependencies - // - var deps = new List (); - foreach (var d in rtypedecl.Descendants.OfType ()) { - deps.Add (d.Identifier); - } - - // - // Find watches and instrument - // - var watches = new List (); - foreach (var d in typedecl.Descendants.OfType ()) { - var endLoc = d.EndLocation; - var p = d.Parent; - if (p == null || p.Parent == null) - continue; - var nc = p.GetNextSibling (x => x is Comment && x.StartLocation.Line == endLoc.Line); - if (nc == null || !nc.ToString ().StartsWith ("//=")) - continue; - var id = Guid.NewGuid ().ToString (); - var instrument = GetWatchInstrument (id, new IdentifierExpression (d.Name)); - p.Parent.InsertChildBefore (nc, instrument, BlockStatement.StatementRole); - watches.Add (new WatchVariable { - Id = id, - Expression = d.Name, - ExplicitExpression = "", - FilePath = doc.FullPath, - FileLine = nc.StartLocation.Line, - FileColumn = nc.StartLocation.Column, - }); - } - foreach (var d in typedecl.Descendants.OfType ()) { - var endLoc = d.EndLocation; - var p = d.Parent; - if (p == null || p.Parent == null) - continue; - var nc = p.GetNextSibling (x => x is Comment && x.StartLocation.Line == endLoc.Line); - if (nc == null || !nc.ToString ().StartsWith ("//=")) - continue; - var id = Guid.NewGuid ().ToString (); - var instrument = GetWatchInstrument (id, (Expression)d.Left.Clone ()); - p.Parent.InsertChildBefore (nc, instrument, BlockStatement.StatementRole); - watches.Add (new WatchVariable { - Id = id, - Expression = d.Left.ToString (), - ExplicitExpression = "", - FilePath = doc.FullPath, - FileLine = nc.StartLocation.Line, - FileColumn = nc.StartLocation.Column, - }); - } - foreach (var d in typedecl.Descendants.OfType ().Where (x => x.CommentType == CommentType.SingleLine)) { - var m = WatchVariable.CommentContentRe.Match (d.Content); - if (!m.Success || string.IsNullOrWhiteSpace (m.Groups [1].Value)) - continue; - - var p = d.Parent as BlockStatement; - if (p == null) - continue; - - var exprText = m.Groups [1].Value.Trim (); - var parser = new CSharpParser(); - var syntaxTree = parser.Parse("class C { void F() { var __r = " + exprText + "; } }"); - - if (syntaxTree.Errors.Count > 0) - continue; - var t = syntaxTree.Members.OfType ().First (); - var expr = t.Descendants.OfType ().First ().Initializer; - - var id = Guid.NewGuid ().ToString (); - var instrument = GetWatchInstrument (id, expr.Clone ()); - p.InsertChildBefore (d, instrument, BlockStatement.StatementRole); - watches.Add (new WatchVariable { - Id = id, - Expression = exprText, - ExplicitExpression = m.Groups[1].Value, - FilePath = doc.FullPath, - FileLine = d.StartLocation.Line, - FileColumn = d.StartLocation.Column, - }); - } - - // - // All done - // - var instrumentedCode = typedecl.ToString (); - - return TypeCode.Set (name, usings, rawCode, instrumentedCode, deps, nsName, watches); - } + public override void SetTypeCode () + { + var name = Name; + + var commentlessCode = String.Join (Environment.NewLine, Declaration.GetText ().Lines.Select (l => l.ToString ())); + + var usings = + Root.DescendantNodes () + .OfType () + .Select (u => u.GetText ().ToString ()) + .ToList (); + + var deps =
 Root
 .DescendantNodes()
 .OfType() + .Select(n => Model.GetSymbolInfo(n)) + .Where(s => s.Symbol.Kind == SymbolKind.NamedType) + .Select(n => n.Symbol.Name) + .Distinct()
 .ToList(); + + var ns =
 Declaration.Ancestors()
 .OfType() + .Select(n => n.Name.GetText().ToString().Trim()) + .FirstOrDefault() ?? ""; + + // create an 'instrumented' instance of the document with watch calls + var rewriter = new WatchExpressionRewriter(Document.FullPath); + var instrumented = rewriter.Visit(Declaration); + var instrumentedCode = instrumented.ToString(); + + // the rewriter collects the WatchVariable definitions as it walks the tree + var watches = rewriter.WatchVariables; + + TypeCode.Set (name, usings, commentlessCode, instrumentedCode, deps, ns, watches); + } } class XamlTypeDecl : TypeDecl @@ -265,57 +156,60 @@ public override void SetTypeCode () } } - protected override async Task GetTopLevelTypeDeclsAsync () - { - var doc = IdeApp.Workbench.ActiveDocument; - Log ("Doc = {0}", doc); - if (doc == null) { - return new TypeDecl[0]; - } - - var ext = doc.FileName.Extension; - - if (ext == ".cs") { - try { - var resolver = await doc.GetSharedResolver (); - var typeDecls = - resolver.RootNode.Descendants. - OfType (). - Where (x => !(x.Parent is TypeDeclaration)). - Select (x => new CSharpTypeDecl { - Document = new DocumentRef (doc.FileName.FullPath), - Declaration = x, - Resolver = resolver, - }); - return typeDecls.ToArray (); - } catch (Exception ex) { - Log (ex); - return new TypeDecl[0]; - } - } - - if (ext == ".xaml") { - var xaml = doc.Editor.Text; - return new TypeDecl[] { - new XamlTypeDecl { - Document = new DocumentRef (doc.FileName.FullPath), - XamlText = xaml, - }, - }; - } - - return new TypeDecl[0]; - } - - protected override async Task GetCursorLocationAsync () - { - var doc = IdeApp.Workbench.ActiveDocument; - if (doc == null) { - return null; - } - - var editLoc = doc.Editor.Caret.Location; - return new TextLoc (editLoc.Line, editLoc.Column); + protected override async Task GetTopLevelTypeDeclsAsync () + { + var doc = IdeApp.Workbench.ActiveDocument; + Log ("Doc = {0}", doc); + if (doc == null) { + return new TypeDecl[0]; + } + + var ext = doc.FileName.Extension; + + if (ext == ".cs") { + try { + + var root = await doc.AnalysisDocument.GetSyntaxRootAsync (); + var model = await doc.AnalysisDocument.GetSemanticModelAsync (); + var typeDecls = + root.DescendantNodes ((arg) => true) + .OfType () + .Select (t => new CSharpTypeDecl { + Document = new DocumentRef (doc.FileName.FullPath), + Declaration = t, + Root = root, + Model = model, + }); + return typeDecls.ToArray (); + + } catch (Exception ex) { + Log (ex); + return new TypeDecl[0]; + } + } + + if (ext == ".xaml") { + var xaml = doc.Editor.Text; + return new TypeDecl[] { + new XamlTypeDecl { + Document = new DocumentRef (doc.FileName.FullPath), + XamlText = xaml, + }, + }; + } + + return new TypeDecl[0]; + } + + protected override async Task GetCursorLocationAsync () + { + var doc = IdeApp.Workbench.ActiveDocument; + if (doc == null) { + return null; + } + + var editLoc = doc.Editor.CaretLocation; + return new TextLoc (editLoc.Line, editLoc.Column); } protected override void MonitorEditorChanges () @@ -358,6 +252,82 @@ protected override async Task GetSelectedTextAsync () return ""; } - } + } + + public class WatchExpressionRewriter : CSharpSyntaxRewriter + { + private string path; + + public WatchExpressionRewriter(string p) + { + path = p; + } + + public List WatchVariables = new List(); + + public override SyntaxNode VisitExpressionStatement(ExpressionStatementSyntax node) + { + // don't handle nodes that aren't assignment or don't have the //= comment + if (!(node.Expression is AssignmentExpressionSyntax && HasWatchComment(node))) + return node; + + var expr = (node.Expression as AssignmentExpressionSyntax).Left; + return AddWatchNode(node, expr); + } + + public override SyntaxNode VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) + { + // don't handle nodes that don't have the //= comment + if (!HasWatchComment(node)) + return node; + + var vn = node.Declaration.Variables.First().Identifier.Text; + var expr = SyntaxFactory.IdentifierName(vn); + + return AddWatchNode(node, expr); + } + + bool HasWatchComment(SyntaxNode node) + { + // expect that only valid node types are passed here + return + node + .GetTrailingTrivia() + .Any(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia && t.ToString().StartsWith("//=")); + } + + SyntaxNode AddWatchNode(StatementSyntax node, ExpressionSyntax expr) + { + var id = Guid.NewGuid().ToString(); + var c = node.GetTrailingTrivia().First(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia && t.ToString().StartsWith("//=")); + var p = c.GetLocation().GetLineSpan().StartLinePosition; + + var wv = new WatchVariable { + Id = id, + Expression = expr.ToString(), + ExplicitExpression = "", + FilePath = path, + FileLine = p.Line + 1, // 0-based index + FileColumn = p.Character + 1, // 0-based index + }; + WatchVariables.Add(wv); + + var wi = GetWatchInstrument(id, expr); + + // creating a block and removing the open/close braces is a bit of a hack but + // lets us replace one node with two... + return + SyntaxFactory + .Block(node, wi) + .WithOpenBraceToken(SyntaxFactory.MissingToken(SyntaxKind.OpenBraceToken)) + .WithCloseBraceToken(SyntaxFactory.MissingToken(SyntaxKind.CloseBraceToken)) + .WithTrailingTrivia(SyntaxFactory.EndOfLine("\r\n")); + } + + StatementSyntax GetWatchInstrument(string id, ExpressionSyntax expr) + { + return SyntaxFactory.ParseStatement($"try {{ Continuous.Server.WatchStore.Record(\"{id}\", {expr}); }} catch {{ }}"); + } + } } #endif diff --git a/Continuous.Client.Core/ContinuousEnv.VisualStudio.cs b/Continuous.Client.Core/ContinuousEnv.VisualStudio.cs index adbc504..94a0a70 100644 --- a/Continuous.Client.Core/ContinuousEnv.VisualStudio.cs +++ b/Continuous.Client.Core/ContinuousEnv.VisualStudio.cs @@ -3,10 +3,10 @@ using System.Text; using System.Threading.Tasks; using System.Linq; -using EnvDTE80; -using System.Threading; - +using System.Threading; + #if VISUALSTUDIO +using EnvDTE80; using EnvDTE; using System.Windows; diff --git a/Continuous.Client.MonoDevelop/Continuous.Client.MonoDevelop.csproj b/Continuous.Client.MonoDevelop/Continuous.Client.MonoDevelop.csproj index 20dec76..6e51c54 100644 --- a/Continuous.Client.MonoDevelop/Continuous.Client.MonoDevelop.csproj +++ b/Continuous.Client.MonoDevelop/Continuous.Client.MonoDevelop.csproj @@ -51,5 +51,58 @@ ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + + + ..\packages\Microsoft.Composition.1.0.27\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + + + ..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll + + + ..\packages\System.Reflection.Metadata.1.2.0\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\packages\Microsoft.CodeAnalysis.Common.1.2.1\lib\net45\Microsoft.CodeAnalysis.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.1.2.1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + + + ..\packages\Microsoft.CodeAnalysis.VisualBasic.1.2.1\lib\net45\Microsoft.CodeAnalysis.VisualBasic.dll + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.2.1\lib\net45\Microsoft.CodeAnalysis.Workspaces.Desktop.dll + + + ..\packages\Microsoft.CodeAnalysis.Workspaces.Common.1.2.1\lib\net45\Microsoft.CodeAnalysis.Workspaces.dll + + + ..\packages\Microsoft.CodeAnalysis.CSharp.Workspaces.1.2.1\lib\net45\Microsoft.CodeAnalysis.CSharp.Workspaces.dll + + + ..\packages\Microsoft.CodeAnalysis.VisualBasic.Workspaces.1.2.1\lib\net45\Microsoft.CodeAnalysis.VisualBasic.Workspaces.dll + + + + + + + + + + + \ No newline at end of file diff --git a/Continuous.Client.MonoDevelop/Pads.cs b/Continuous.Client.MonoDevelop/Pads.cs index 1f25b3d..2c9cc49 100644 --- a/Continuous.Client.MonoDevelop/Pads.cs +++ b/Continuous.Client.MonoDevelop/Pads.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Gtk; +using MonoDevelop.Components; using MonoDevelop.Ide.Gui; namespace Continuous.Client.XamarinStudio @@ -10,26 +11,26 @@ public enum Pads Main, } - public class MainPad : IPadContent + public class MainPad : PadContent { readonly MainPadControl control = new MainPadControl (); #region IPadContent implementation - public void Initialize (IPadWindow window) + protected override void Initialize (IPadWindow window) { control.ShowAll (); } public void RedrawContent () { } - public Widget Control { + public override Control Control { get { return control; } } #endregion #region IDisposable implementation - public void Dispose () + public override void Dispose () { control.Dispose (); } diff --git a/Continuous.Client.MonoDevelop/packages.config b/Continuous.Client.MonoDevelop/packages.config index 2d410a3..feb515e 100644 --- a/Continuous.Client.MonoDevelop/packages.config +++ b/Continuous.Client.MonoDevelop/packages.config @@ -1,5 +1,24 @@  + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addin-project.xml b/addin-project.xml index 90ee7f3..1d04801 100644 --- a/addin-project.xml +++ b/addin-project.xml @@ -1,4 +1,4 @@ - + Continuous.Client.MonoDevelop/bin/Debug/Continuous.Client.MonoDevelop.dll Continuous.Client.MonoDevelop.sln