Add support for preview of pages and view without XAML
This commit is contained in:
Родитель
5ad8c1d888
Коммит
fa71d6864d
|
@ -3,6 +3,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace XAMLator.Client
|
||||
{
|
||||
|
@ -23,34 +24,33 @@ namespace XAMLator.Client
|
|||
SemanticModel semanticModel)
|
||||
{
|
||||
XAMLDocument xamlDocument = null;
|
||||
FormsViewClassDeclaration xamlClass = null;
|
||||
bool created;
|
||||
FormsViewClassDeclaration formsViewClass = null;
|
||||
|
||||
// FIXME: Support any kind of types, not just Xamarin.Forms views
|
||||
if (!fileName.EndsWith(".xaml") && !fileName.EndsWith(".xaml.cs"))
|
||||
if (!fileName.EndsWith(".xaml") && !fileName.EndsWith(".xaml.cs") && !fileName.EndsWith(".cs"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if we have already an instance of the class declaration for that file
|
||||
if (!FormsViewClassDeclaration.TryGetByFileName(fileName, out xamlClass))
|
||||
if (!FormsViewClassDeclaration.TryGetByFileName(fileName, out formsViewClass))
|
||||
{
|
||||
if (fileName.EndsWith(".xaml"))
|
||||
{
|
||||
xamlDocument = XAMLDocument.Parse(fileName, text);
|
||||
// Check if we have an instance of class by namespace
|
||||
if (!FormsViewClassDeclaration.TryGetByFullNamespace(xamlDocument.Type, out xamlClass))
|
||||
if (!FormsViewClassDeclaration.TryGetByFullNamespace(xamlDocument.Type, out formsViewClass))
|
||||
{
|
||||
xamlClass = await CreateFromXaml(xamlDocument);
|
||||
formsViewClass = await CreateFromXaml(xamlDocument);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xamlClass = await CreateFromCodeBehind(fileName, syntaxTree, semanticModel);
|
||||
formsViewClass = await CreateFromCodeBehind(fileName, syntaxTree, semanticModel);
|
||||
}
|
||||
}
|
||||
|
||||
if (xamlClass == null)
|
||||
if (formsViewClass == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -59,19 +59,19 @@ namespace XAMLator.Client
|
|||
if (fileName.EndsWith(".xaml") && xamlDocument == null)
|
||||
{
|
||||
xamlDocument = XAMLDocument.Parse(fileName, text);
|
||||
await xamlClass.UpdateXaml(xamlDocument);
|
||||
await formsViewClass.UpdateXaml(xamlDocument);
|
||||
}
|
||||
// The document is code behind
|
||||
if (fileName.EndsWith(".xaml.cs"))
|
||||
// The document is code behind or a view without XAML
|
||||
if (fileName.EndsWith(".cs"))
|
||||
{
|
||||
var classDeclaration = FormsViewClassDeclaration.FindClass(syntaxTree, xamlClass.ClassName);
|
||||
if (xamlClass.NeedsClassInitialization)
|
||||
var classDeclaration = FormsViewClassDeclaration.FindClass(syntaxTree, formsViewClass.ClassName);
|
||||
if (formsViewClass.NeedsClassInitialization)
|
||||
{
|
||||
xamlClass.FillClassInfo(classDeclaration, semanticModel);
|
||||
formsViewClass.FillClassInfo(classDeclaration, semanticModel);
|
||||
}
|
||||
xamlClass.UpdateCode(classDeclaration, semanticModel);
|
||||
formsViewClass.UpdateCode(classDeclaration, semanticModel);
|
||||
}
|
||||
return xamlClass;
|
||||
return formsViewClass;
|
||||
}
|
||||
|
||||
static async Task<FormsViewClassDeclaration> CreateFromXaml(XAMLDocument xamlDocument)
|
||||
|
@ -92,8 +92,12 @@ namespace XAMLator.Client
|
|||
SyntaxTree syntaxTree, SemanticModel semanticModel)
|
||||
|
||||
{
|
||||
string xaml = null, xamlFilePath = null, codeBehindFilePath = null;
|
||||
XAMLDocument xamlDocument;
|
||||
string xaml = null;
|
||||
string xamlFilePath = null;
|
||||
string codeBehindFilePath = null;
|
||||
string className = null;
|
||||
ClassDeclarationSyntax classDeclaration;
|
||||
XAMLDocument xamlDocument = null;
|
||||
|
||||
codeBehindFilePath = fileName;
|
||||
var xamlCandidate = fileName.Substring(0, fileName.Length - 3);
|
||||
|
@ -104,16 +108,34 @@ namespace XAMLator.Client
|
|||
}
|
||||
|
||||
// FIXME: Handle XF views without XAML
|
||||
// Parse the XAML file
|
||||
xamlDocument = XAMLDocument.Parse(xamlFilePath, xaml);
|
||||
|
||||
var className = xamlDocument.Type.Split('.').Last();
|
||||
var classDeclaration = FormsViewClassDeclaration.FindClass(syntaxTree, className);
|
||||
var xamlClass = new FormsViewClassDeclaration(classDeclaration, semanticModel,
|
||||
codeBehindFilePath, xamlDocument);
|
||||
|
||||
await xamlClass.UpdateXaml(xamlDocument);
|
||||
return xamlClass;
|
||||
if (xamlFilePath != null)
|
||||
{
|
||||
// Parse the XAML file
|
||||
xamlDocument = XAMLDocument.Parse(xamlFilePath, xaml);
|
||||
className = xamlDocument.Type.Split('.').Last();
|
||||
classDeclaration = FormsViewClassDeclaration.FindClass(syntaxTree, className);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
classDeclaration = FormsViewClassDeclaration.FindFormsViewClass(syntaxTree, semanticModel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Exception(ex);
|
||||
Log.Error("The class is not a Xamarin.Forms View or Page");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var formsClass = new FormsViewClassDeclaration(classDeclaration, semanticModel,
|
||||
codeBehindFilePath, xamlDocument);
|
||||
|
||||
if (xamlDocument != null)
|
||||
{
|
||||
await formsClass.UpdateXaml(xamlDocument);
|
||||
}
|
||||
return formsClass;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,7 +96,10 @@ namespace XAMLator.Client
|
|||
string codeBehindFilePath, XAMLDocument xaml)
|
||||
{
|
||||
this.codeBehindFilePath = codeBehindFilePath;
|
||||
this.xamlFilePath = xaml.FilePath;
|
||||
if (xaml != null)
|
||||
{
|
||||
xamlFilePath = xaml.FilePath;
|
||||
}
|
||||
StyleSheets = new Dictionary<string, string>();
|
||||
UpdateCode(classDeclarationSyntax, model);
|
||||
classesCache.Add(this);
|
||||
|
@ -111,7 +114,10 @@ namespace XAMLator.Client
|
|||
public FormsViewClassDeclaration(string codeBehindFilePath, XAMLDocument xaml)
|
||||
{
|
||||
this.codeBehindFilePath = codeBehindFilePath;
|
||||
this.xamlFilePath = xaml.FilePath;
|
||||
if (xaml != null)
|
||||
{
|
||||
xamlFilePath = xaml.FilePath;
|
||||
}
|
||||
StyleSheets = new Dictionary<string, string>();
|
||||
Namespace = xaml.Type.Substring(0, xaml.Type.LastIndexOf('.'));
|
||||
ClassName = xaml.Type.Split('.').Last();
|
||||
|
@ -479,5 +485,28 @@ namespace XAMLator.Client
|
|||
.OfType<ClassDeclarationSyntax>()
|
||||
.Single(c => c.Identifier.Text == className);
|
||||
}
|
||||
|
||||
internal static ClassDeclarationSyntax FindFormsViewClass(SyntaxTree syntaxTree, SemanticModel model)
|
||||
{
|
||||
return syntaxTree.GetRoot()
|
||||
.DescendantNodes()
|
||||
.OfType<ClassDeclarationSyntax>()
|
||||
.First(c => IsViewOrPage(model.GetDeclaredSymbol(c)));
|
||||
}
|
||||
|
||||
static bool IsViewOrPage(INamedTypeSymbol type)
|
||||
{
|
||||
do
|
||||
{
|
||||
var name = type.ToString();
|
||||
if (name == "Xamarin.Forms.Page" || name == "Xamarin.Forms.View" ||
|
||||
name == "Xamarin.Forms.ContentPage" || name == "Xamarin.Forms.ContentView")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
type = type.BaseType;
|
||||
} while (type != null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,16 +68,19 @@ namespace XAMLator.Server
|
|||
|
||||
protected virtual Page CreateViewFromResult(EvalResult res)
|
||||
{
|
||||
if (res.HasResult)
|
||||
Page page;
|
||||
if (!res.HasResult)
|
||||
{
|
||||
return res.Result as Page;
|
||||
res.Result = TypeActivator(res.ResultType);
|
||||
}
|
||||
var result = TypeActivator(res.ResultType);
|
||||
Page page = result as Page;
|
||||
if (page == null && result is View view)
|
||||
if (res.Result is View view)
|
||||
{
|
||||
page = new ContentPage { Content = view };
|
||||
}
|
||||
else
|
||||
{
|
||||
page = res.Result as Page;
|
||||
}
|
||||
if (page != null)
|
||||
{
|
||||
if (viewModelsMapping.TryGetValue(res.ResultType, out object viewModel))
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
using Xamarin.Forms;
|
||||
|
||||
namespace XAMLator.Server.Tests
|
||||
{
|
||||
public class NoXAMLTestContentPage : ContentPage
|
||||
{
|
||||
public NoXAMLTestContentPage()
|
||||
{
|
||||
Content = new StackLayout
|
||||
{
|
||||
Children = {
|
||||
new Label { Text = "Hello ContentPage" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using Xamarin.Forms;
|
||||
|
||||
namespace XAMLator.Server.Tests
|
||||
{
|
||||
public class NoXAMLTestContentView : ContentView
|
||||
{
|
||||
public NoXAMLTestContentView()
|
||||
{
|
||||
Content = new Label { Text = "Hello ContentView" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace XAMLator.Server.Tests
|
||||
{
|
||||
public class NoXamlTestInheritanceContentView : NoXAMLTestContentView
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,9 @@ namespace XAMLator.Server.Tests
|
|||
CopyFile(TestProjectDir, "TestPage.xaml", tempDir);
|
||||
CopyFile(TestProjectDir, "TestPage.xaml.cs", tempDir);
|
||||
CopyFile(TestAutogenDir, "TestPage.xaml.g.cs", tempDir);
|
||||
CopyFile(TestProjectDir, "NoXAMLTestContentPage.cs", tempDir);
|
||||
CopyFile(TestProjectDir, "NoXAMLTestContentView.cs", tempDir);
|
||||
CopyFile(TestProjectDir, "NoXAMLTestInheritanceContentView.cs", tempDir);
|
||||
workspace = new AdhocWorkspace();
|
||||
var solution = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default);
|
||||
workspace.AddSolution(solution);
|
||||
|
@ -29,6 +32,13 @@ namespace XAMLator.Server.Tests
|
|||
AddDocument(testProject, tempDir, "TestPage.xaml.cs");
|
||||
AddDocument(testProject, tempDir, "TestPage.xaml");
|
||||
AddDocument(testProject, tempDir, "TestPage.xaml.g.cs");
|
||||
AddDocument(testProject, tempDir, "NoXAMLTestContentPage.cs");
|
||||
AddDocument(testProject, tempDir, "NoXAMLTestContentView.cs");
|
||||
AddDocument(testProject, tempDir, "NoXAMLTestInheritanceContentView.cs");
|
||||
// Define a stub type for ContentPage and ConentView so they resolve to a type and not to an ErrorType
|
||||
// as the Xamarin.Forms types are not defined in this workspae
|
||||
AddDocument(testProject, tempDir, "ContentPage.cs", @"namespace Xamarin.Forms { class ContentPage: Page {}}");
|
||||
AddDocument(testProject, tempDir, "ContentPage.cs", @"namespace Xamarin.Forms { class ContentView: View {}}");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -65,13 +75,23 @@ namespace XAMLator.Server.Tests
|
|||
}
|
||||
|
||||
Document AddDocument(Project testProject, string dir, string fileName)
|
||||
{
|
||||
return AddDocument(testProject, dir, fileName, ReadFile(dir, fileName));
|
||||
}
|
||||
|
||||
Document AddDocument(Project testProject, string dir, string fileName, string text)
|
||||
{
|
||||
return AddDocument(testProject, dir, fileName, SourceText.From(text));
|
||||
}
|
||||
|
||||
Document AddDocument(Project testProject, string dir, string fileName, SourceText content)
|
||||
{
|
||||
DocumentInfo documentInfo = DocumentInfo.Create(
|
||||
DocumentId.CreateNewId(testProject.Id),
|
||||
fileName,
|
||||
new List<string> { dir },
|
||||
SourceCodeKind.Regular,
|
||||
TextLoader.From(TextAndVersion.Create(ReadFile(dir, fileName), VersionStamp.Create())),
|
||||
TextLoader.From(TextAndVersion.Create(content, VersionStamp.Create())),
|
||||
Path.Combine(dir, fileName));
|
||||
return workspace.AddDocument(documentInfo);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,61 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace XAMLator.Server.Tests
|
||||
{
|
||||
public class ViewWithoutXAMLPreviewFeature
|
||||
[TestFixture]
|
||||
public class ViewWithoutXAMLPreviewFeature : XAMLatorFeatureBase
|
||||
{
|
||||
public ViewWithoutXAMLPreviewFeature()
|
||||
[Test]
|
||||
public async Task The_user_changes_a_content_page_class_without_xaml_and_its_previewed()
|
||||
{
|
||||
await When_the_code_changes("NoXAMLTestContentPage.cs", @"
|
||||
using Xamarin.Forms;
|
||||
namespace XAMLator.Server.Tests {
|
||||
public class NoXAMLTestContentPage : ContentPage {
|
||||
public NoXAMLTestContentPage() {
|
||||
Content = new Label { Text = ""Hello ContentPage"" };
|
||||
}
|
||||
}
|
||||
}");
|
||||
var label = (previewer.PreviewedPage as ContentPage).Content as Label;
|
||||
Assert.AreEqual(PreviewState.Preview, previewer.State);
|
||||
Assert.AreEqual("Hello ContentPage", label.Text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task The_user_changes_a_conent_view_class_without_xaml_and_its_previewed()
|
||||
{
|
||||
await When_the_code_changes("NoXAMLTestContentView.cs", @"
|
||||
using Xamarin.Forms;
|
||||
namespace XAMLator.Server.Tests {
|
||||
public class NoXAMLTestContentView : ContentView {
|
||||
public NoXAMLTestContentView() {
|
||||
Content = new Label { Text = ""Hello!"" };
|
||||
}
|
||||
}
|
||||
}");
|
||||
var label = ((previewer.PreviewedPage as ContentPage).Content as ContentView).Content as Label;
|
||||
Assert.AreEqual(PreviewState.Preview, previewer.State);
|
||||
Assert.AreEqual("Hello!", label.Text);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task The_user_changes_a_view_page_class_with_inheritance_without_xaml_and_its_previewed()
|
||||
{
|
||||
await When_the_code_changes("NoXAMLTestInheritanceContentView.cs", @"
|
||||
using Xamarin.Forms;
|
||||
namespace XAMLator.Server.Tests {
|
||||
public class NoXAMLTestInheritanceContentView : NoXAMLTestContentView {
|
||||
public NoXAMLTestInheritanceContentView() {
|
||||
}
|
||||
}
|
||||
}");
|
||||
var label = ((previewer.PreviewedPage as ContentPage).Content as ContentView).Content as Label;
|
||||
Assert.AreEqual(PreviewState.Preview, previewer.State);
|
||||
Assert.AreEqual("Hello ContentView", label.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -212,6 +212,9 @@
|
|||
<Compile Include="XAMLatorFeatureBase.cs" />
|
||||
<Compile Include="UsageFeature.cs" />
|
||||
<Compile Include="CSSUpdateFeature.cs" />
|
||||
<Compile Include="NoXAMLTestContentPage.cs" />
|
||||
<Compile Include="NoXAMLTestContentView.cs" />
|
||||
<Compile Include="NoXamlTestInheritanceContentView.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="TestPage.xaml">
|
||||
|
|
Загрузка…
Ссылка в новой задаче