From b64747f524d826620c2bef87b2aab82cd540de9f Mon Sep 17 00:00:00 2001 From: KuiperZone Date: Sun, 23 Apr 2023 02:00:21 +0100 Subject: [PATCH] Version 1.2.0 --- AvantGarde.Test/AvantGarde.Test.csproj | 1 + AvantGarde.Test/Internal/TestUtilBase.cs | 6 +- AvantGarde.Test/Projects/DotnetProjectTest.cs | 67 ++++- AvantGarde.Test/Projects/NodeItemTest.cs | 41 ++- AvantGarde.pupnet.conf | 31 ++- AvantGarde/Loading/RemoteLoader.cs | 2 +- AvantGarde/Projects/DotnetProject.cs | 241 ++++++++++++++++-- AvantGarde/Projects/DotnetSolution.cs | 14 +- AvantGarde/Projects/NodeItem.cs | 71 ++++-- .../ViewModels/PreviewOptionsViewModel.cs | 1 - AvantGarde/Views/AvantWindow.cs | 16 ++ AvantGarde/Views/MainWindow.axaml.cs | 7 +- AvantGarde/Views/MessageBox.axaml | 1 - AvantGarde/Views/MessageBox.axaml.cs | 7 +- AvantGarde/Views/PreviewPane.axaml | 7 +- AvantGarde/Views/PreviewPane.axaml.cs | 55 ++-- AvantGarde/Views/SolutionWindow.axaml.cs | 1 + AvantGarde/Views/XamlCodeControl.axaml.cs | 1 + CHANGES | 7 +- Deploy/AvantGarde.metainfo.xml | 17 ++ 20 files changed, 467 insertions(+), 127 deletions(-) diff --git a/AvantGarde.Test/AvantGarde.Test.csproj b/AvantGarde.Test/AvantGarde.Test.csproj index 1ce7e9e..3fd3e68 100644 --- a/AvantGarde.Test/AvantGarde.Test.csproj +++ b/AvantGarde.Test/AvantGarde.Test.csproj @@ -5,6 +5,7 @@ false enable false + None diff --git a/AvantGarde.Test/Internal/TestUtilBase.cs b/AvantGarde.Test/Internal/TestUtilBase.cs index 51b49e4..5156468 100755 --- a/AvantGarde.Test/Internal/TestUtilBase.cs +++ b/AvantGarde.Test/Internal/TestUtilBase.cs @@ -16,12 +16,8 @@ // with Avant Garde. If not, see . // ----------------------------------------------------------------------------- -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Text; -using System.Threading; using Xunit.Abstractions; namespace AvantGarde.Test.Internal @@ -285,7 +281,7 @@ protected virtual void Dispose(bool _) { try { - Directory.Delete(_scratch, true); + Directory.Delete(_scratch, true); } catch { diff --git a/AvantGarde.Test/Projects/DotnetProjectTest.cs b/AvantGarde.Test/Projects/DotnetProjectTest.cs index 9f85f3a..fb4147b 100644 --- a/AvantGarde.Test/Projects/DotnetProjectTest.cs +++ b/AvantGarde.Test/Projects/DotnetProjectTest.cs @@ -16,8 +16,6 @@ // with Avant Garde. If not, see . // ----------------------------------------------------------------------------- -using System; -using System.IO; using AvantGarde.Test.Internal; using Xunit; using Xunit.Abstractions; @@ -90,7 +88,7 @@ public void Refresh_Updates() // Change framework path = CreateFileContent("Name.Test.csproj", ProjectNet6); - // Not exist + // Assembly not exist because we switch to .NET6 code = item.GetHashCode(); Assert.True(item.Refresh()); Assert.NotEqual(code, item.GetHashCode()); @@ -129,5 +127,68 @@ public void Refresh_Updates() Assert.Equal("net6.0", item.TargetFramework); Assert.True(item.AssemblyPath?.Exists == true); } + + [Fact] + public void Refresh_Artifacts() + { + var solDir = Path.Combine(Scratch, "Solution"); + var projDir = Path.Combine(solDir, "Name.Test"); + Directory.CreateDirectory(projDir); + + var path = CreateFileContent(Path.Combine(projDir, "Name.Test.csproj"), ProjectNet6); + var item = new DotnetProject(path); + + Assert.True(item.Refresh()); + Assert.Equal("net6.0", item.TargetFramework); + Assert.False(item.AssemblyPath?.Exists == true); + + // Now put in dummy solution ABOVE project (where artifacts live) + CreateFileContent(Path.Combine(solDir, "Name.Test.sln"), "Dummy"); + + item.Refresh(); + Assert.Equal("net6.0", item.TargetFramework); + Assert.False(item.AssemblyPath?.Exists == true); + + // Create assembly under artifacts + // debug_net8.0/projectName.dll + // .artifacts/bin/debug/projectName.dll + // .artifacts/bin/[projectName]/debug_net8.0/projectName.dll + var artDir = Path.Combine(solDir, ".artifacts", "bin", "debug"); + Directory.CreateDirectory(artDir); + path = CreateFileContent(Path.Combine(artDir, "Name.Test.dll"), "Dummy"); + + // Exists now + item.Refresh(); + Assert.True(item.AssemblyPath?.Exists == true); + + // New location + Directory.Delete(artDir, true); + artDir = Path.Combine(solDir, ".artifacts", "bin", "debug_net6.0"); + + Directory.CreateDirectory(artDir); + path = CreateFileContent(Path.Combine(artDir, "Name.Test.dll"), "Dummy"); + + item.Refresh(); + Assert.True(item.AssemblyPath?.Exists == true); + + // New location + Directory.Delete(artDir, true); + artDir = Path.Combine(solDir, ".artifacts", "bin", "Name.Test", "debug_net6.0"); + Directory.CreateDirectory(artDir); + path = CreateFileContent(Path.Combine(artDir, "Name.Test.dll"), "Dummy"); + + item.Refresh(); + Assert.True(item.AssemblyPath?.Exists == true); + + // New location + Directory.Delete(artDir, true); + artDir = Path.Combine(solDir, ".artifacts/bin/Name.Test/debug/net6.0"); + Directory.CreateDirectory(artDir); + path = CreateFileContent(Path.Combine(artDir, "Name.Test.dll"), "Dummy"); + + item.Refresh(); + Assert.True(item.AssemblyPath?.Exists == true); + } + } } \ No newline at end of file diff --git a/AvantGarde.Test/Projects/NodeItemTest.cs b/AvantGarde.Test/Projects/NodeItemTest.cs index 7438bdf..6292d7c 100644 --- a/AvantGarde.Test/Projects/NodeItemTest.cs +++ b/AvantGarde.Test/Projects/NodeItemTest.cs @@ -16,8 +16,6 @@ // with Avant Garde. If not, see . // ----------------------------------------------------------------------------- -using System; -using System.IO; using AvantGarde.Test.Internal; using Xunit; using Xunit.Abstractions; @@ -193,24 +191,39 @@ public void FilePattern_FiltersOnPattern() } [Fact] - public void FindFirst_FindsName() + public void FindDirectory_FindsName() { - var temp = CreateNewScratch(); - var sub = Directory.CreateDirectory(temp + "sub") + "/"; + var temp = CreateNewScratch() + "sub"; + var sub = Directory.CreateDirectory(temp) + "/"; CreateFileContent(sub + "Text1.txt", "Hello World"); var item = new NodeItem(Scratch, PathKind.Directory); item.Refresh(); - Assert.Null(item.FindFirst("NotExist", true)); - Assert.Null(item.FindFirst("Text1.txt", false)); + Assert.Null(item.FindDirectory("NotExist")); + Assert.Null(item.FindDirectory("Text1.txt")); + + Assert.Equal(temp, item.FindDirectory(temp)?.FullName); + Assert.Equal(temp, item.FindDirectory(temp.ToUpperInvariant(), StringComparison.InvariantCultureIgnoreCase)?.FullName); + } + + [Fact] + public void FindFile_FindsName() + { + var temp = CreateNewScratch() + "sub"; + var sub = Directory.CreateDirectory(temp) + "/"; + CreateFileContent(sub + "Text1.txt", "Hello World"); + + var item = new NodeItem(Scratch, PathKind.Directory); + item.Refresh(); - Assert.Equal("Text1.txt", item.FindFirst("Text1.txt", true)?.Name); - Assert.Equal("Text1.txt", item.FindFirst("TEXT1.txt", true, StringComparison.OrdinalIgnoreCase)?.Name); + Assert.Null(item.FindFile("NotExist")); + Assert.Equal("Text1.txt", item.FindFile("Text1.txt")?.Name); + Assert.Equal("Text1.txt", item.FindFile("TEXT1.txt", StringComparison.OrdinalIgnoreCase)?.Name); } [Fact] - public void FindExact_FindsPath() + public void FindNode_FindsPath() { var temp = CreateNewScratch(); var sub = Directory.CreateDirectory(temp + "sub") + "/"; @@ -219,11 +232,11 @@ public void FindExact_FindsPath() var item = new NodeItem(Scratch, PathKind.Directory); item.Refresh(); - Assert.Null(item.FindExact("NotExist")); - Assert.Null(item.FindExact("")); + Assert.Null(item.FindNode("NotExist")); + Assert.Null(item.FindNode("")); - Assert.Equal("Text1.txt", item.FindExact(path)?.Name); - Assert.Equal("Text1.txt", item.FindExact(path.ToUpperInvariant(), StringComparison.OrdinalIgnoreCase)?.Name); + Assert.Equal("Text1.txt", item.FindNode(path)?.Name); + Assert.Equal("Text1.txt", item.FindNode(path.ToUpperInvariant(), StringComparison.OrdinalIgnoreCase)?.Name); } } diff --git a/AvantGarde.pupnet.conf b/AvantGarde.pupnet.conf index 4f60769..65af300 100644 --- a/AvantGarde.pupnet.conf +++ b/AvantGarde.pupnet.conf @@ -1,10 +1,10 @@ -# PUPNET DEPLOY 1.0.1 +# PUPNET DEPLOY: 1.3.0 # APP PREAMBLE AppBaseName = AvantGarde AppFriendlyName = Avant Garde AppId = zone.kuiper.AvantGarde -AppVersionRelease = 1.1.0[1] +AppVersionRelease = 1.2.0[1] AppShortSummary = A cross-platform XAML Previewer for the Avalonia .NET Framework AppLicenseId = GPL-3.0-or-later AppLicenseFile = LICENSE @@ -22,7 +22,7 @@ DesktopTerminal = false DesktopFile = Deploy/AvantGarde.desktop StartCommand = PrimeCategory = Development -MetaFile = Deploy/AvantGarde.metainfo.xml +MetaFile = Deploy/AvantGarde.metainfo.xml IconFiles = """ Deploy/AvantGarde.ico Deploy/AvantGarde.svg @@ -62,8 +62,33 @@ FlatpakFinishArgs = """ """ FlatpakBuilderArgs = +# RPM OPTIONS +RpmAutoReq = false +RpmAutoProv = true +RpmRequires = """ + krb5-libs + libicu + openssl-libs + zlib +""" + +# DEBIAN OPTIONS +DebianRecommends = """ + libc6 + libgcc1 + libgcc-s1 + libgssapi-krb5-2 + libicu + libssl + libstdc++6 + libunwind + zlib1g +""" + # WINDOWS SETUP OPTIONS +SetupAdminInstall = false SetupCommandPrompt = SetupMinWindowsVersion = 10 SetupSignTool = +SetupSuffixOutput = SetupVersionOutput = false \ No newline at end of file diff --git a/AvantGarde/Loading/RemoteLoader.cs b/AvantGarde/Loading/RemoteLoader.cs index c572d93..3daa80c 100644 --- a/AvantGarde/Loading/RemoteLoader.cs +++ b/AvantGarde/Loading/RemoteLoader.cs @@ -173,7 +173,7 @@ public static PathItem FindDesignerHost(string? version) node.Properties.FilePatterns = DotnetHostName; node.Refresh(); - return node.FindFirst(DotnetHostName, true, StringComparison.OrdinalIgnoreCase) ?? + return node.FindFile(DotnetHostName, StringComparison.OrdinalIgnoreCase) ?? throw new FileNotFoundException($"Unable to locate {DotnetHostName} for version {version}"); } diff --git a/AvantGarde/Projects/DotnetProject.cs b/AvantGarde/Projects/DotnetProject.cs index b390f87..7519c34 100644 --- a/AvantGarde/Projects/DotnetProject.cs +++ b/AvantGarde/Projects/DotnetProject.cs @@ -17,6 +17,7 @@ // ----------------------------------------------------------------------------- using System.Diagnostics; +using System.Runtime.InteropServices; using System.Xml.Linq; namespace AvantGarde.Projects @@ -35,6 +36,43 @@ public sealed class DotnetProject : PathItem private bool _refreshed; private bool _customOverride; + static DotnetProject() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + RuntimeId = "win-x64"; + } + else + if (RuntimeInformation.OSArchitecture == Architecture.Arm64) + { + RuntimeId = "win-arm64"; + } + } + else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + RuntimeId = "linux-x64"; + } + else + if (RuntimeInformation.OSArchitecture == Architecture.Arm64) + { + RuntimeId = "linux-arm64"; + } + } + else + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + if (RuntimeInformation.OSArchitecture == Architecture.X64) + { + RuntimeId = "osx-x64"; + } + } + } + /// Constructor with "csproj" file path and . If null, a default /// instance will be created. A call to is needed after construction. public DotnetProject(string path, DotnetSolution? solution = null) @@ -48,9 +86,13 @@ public DotnetProject(string path, DotnetSolution? solution = null) // Solution must present before Contents Contents = new NodeItem(ParentDirectory, PathKind.Directory, this); _hashCode = base.GetHashCode(); - } + /// + /// Gets the default expected runtime ID (i.e. "linux-x64"), if known. + /// + public static string? RuntimeId { get; } + /// /// Gets the project name. Same as , but lacks the extension. /// @@ -357,44 +399,195 @@ private static string GetAvaloniaVersion(XElement? root) private PathItem? FindTargetAssembly(string? framework, BuildKind build) { - if (!string.IsNullOrEmpty(framework)) + Debug.WriteLine($"{nameof(FindTargetAssembly)} {framework}, {build}"); + + if (string.IsNullOrEmpty(framework)) + { + Debug.WriteLine("Failed - framework is null or empty"); + return null; + } + + PathItem? item0 = null; + + // Find within traditional project; + PathItem? item1 = FindAssemblyUnderRoot(Contents, ProjectName, framework, true, build); + Debug.WriteLine($"Traditional assembly found: {item1 != null}, {item1?.FullName}"); + + // Locate common artifacts file + var artifacts = FindArtifactsDirectory(Contents); + + if (artifacts != null) { - var assemblyName = ProjectName + ".dll"; + Debug.WriteLine("Find under artifacts directory"); + item0 = FindAssemblyUnderRoot(artifacts, ProjectName, framework, false, build); + Debug.WriteLine($"Artifact assembly found: {item1 != null}, {item1?.FullName}"); + } + + if (item0 != null && item1 != null) + { + Debug.WriteLine("Compare timestamps"); + return item0.LastUtc > item1.LastUtc ? item0 : item1; + } - var temp = new NodeItem(Contents.GetDirectoryInfo()); - temp.Properties.FilePatterns = assemblyName; + return item1 != null ? item1 : item0; + } - temp.Refresh(); + private static PathItem? FindAssemblyUnderRoot(NodeItem root, string project, string framework, bool mandatoryFramework, BuildKind build) + { + // Locate assembly path somewhere within a build directory tree. + // We accommodate traditional structures under the project bin directory, and also the + // new single "artifacts" directory for .NET8. See below which describes both structures: + // https://github.com/dotnet/designs/blob/simplify-output-paths-2/accepted/2023/simplify-output-paths.md + + // These are valid trees: (square brackets indicate optional) + // project/bin/Debug/[net7.0]/[linux-x64]/projectName.dll + // .artifacts/bin/debug/projectName.dll + // .artifacts/bin/[projectName]/debug_net8.0/projectName.dll + + string assemblyName = project + ".dll"; + Debug.WriteLine($"{nameof(FindAssemblyUnderRoot)} under {root}"); + Debug.WriteLine($"{nameof(project)} {project}"); + Debug.WriteLine($"{nameof(framework)} {framework}"); + Debug.WriteLine($"{nameof(build)} {build}"); + Debug.WriteLine($"{nameof(RuntimeId)} {RuntimeId}"); + + // Make a copy + var node = new NodeItem(root.GetDirectoryInfo()); + node.Properties.FilePatterns = assemblyName; + node.Refresh(); + + // Presence of "bin" mandatory + Debug.WriteLine("Look for mandatory bin"); + node = node.FindDirectory("bin", StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Bin: {node?.FullName}"); + + if (node == null) + { + Debug.WriteLine("Failed - bin directory not found"); + return null; + } - // Presence of "bin" preferred - NodeItem node = temp; - var dir = temp.FindFirst("bin", false, StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Look for optional project {project}"); + var temp = node.FindDirectory(project, StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Project: {temp?.FullName}"); - if (dir != null) + if (temp != null) + { + Debug.WriteLine("Project found OK"); + node = temp; + } + + // Combined debug_net8.0 (artifacts only) + var pivot = build + "_" + framework; + Debug.WriteLine($"Look for pivot configuration/framework {pivot}"); + temp = node.FindDirectory(pivot, StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Pivot: {temp?.FullName}"); + + if (temp == null) + { + Debug.WriteLine($"Look for mandatory configuration {build}"); + temp = node.FindDirectory(build.ToString(), StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Configuration: {temp?.FullName}"); + + if (temp == null) { - node = dir; - Debug.WriteLine("Bin directory found"); + Debug.WriteLine($"Failed - could not find configuration {build}"); + return null; } - // We need debug/net5.0 etc - dir = node.FindFirst(build.ToString(), false, StringComparison.OrdinalIgnoreCase); + // OK + node = temp; + + Debug.WriteLine($"Look for optional framework {framework}"); + temp = node.FindDirectory(framework, StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Framework: {temp?.FullName}"); - if (dir != null) + if (temp == null && mandatoryFramework) { - Debug.WriteLine("Build Debug/Release directory found"); - dir = dir.FindFirst(framework, false, StringComparison.OrdinalIgnoreCase); + Debug.WriteLine($"Failed - could not find configuration {framework}"); + return null; + } - if (dir != null) - { - Debug.WriteLine("Framework directory found"); - return dir.FindFirst(assemblyName, true, StringComparison.OrdinalIgnoreCase); - } + if (temp != null) + { + Debug.WriteLine("Framework found OK"); + node = temp; + } + } + else + { + Debug.WriteLine($"Combined {pivot} found OK"); + node = temp; + } + if (RuntimeId != null) + { + // linux-x64 or win-x64 etc. + Debug.WriteLine($"Look for optional runtime {RuntimeId}"); + temp = node.FindDirectory(RuntimeId, StringComparison.OrdinalIgnoreCase); + + if (temp != null) + { + Debug.WriteLine("RuntimeId found OK"); + node = temp; } } - return null; + Debug.WriteLine("Finally look for manadory assembly file"); + return node.FindFile(assemblyName, StringComparison.OrdinalIgnoreCase); } - } + private static NodeItem? FindArtifactsDirectory(PathItem dir, int max = 5) + { + int count = 0; + Debug.WriteLine($"{nameof(FindArtifactsDirectory)} upwards from {dir}"); + + EnumerationOptions opts = new(); + opts.IgnoreInaccessible = true; + opts.RecurseSubdirectories = false; + opts.ReturnSpecialDirectories = true; + opts.AttributesToSkip = FileAttributes.System; + opts.MatchType = MatchType.Simple; + opts.MatchCasing = MatchCasing.PlatformDefault; + + while (true) + { + Debug.WriteLine($"Iterate {dir}"); + var artifacts = Path.Combine(dir.FullName, ".artifacts"); + + if (Directory.Exists(artifacts)) + { + Debug.WriteLine($"Found {artifacts}"); + return new NodeItem(artifacts, PathKind.Directory); + } + + if (++count < max && dir.Exists && dir.ParentDirectory.Length != 0) + { + // Terminate upward traversal on encountering either Directory.Build.props or *.sln file. + var props = Path.Combine(dir.FullName, "Directory.Build.props"); + + if (File.Exists(props)) + { + Debug.WriteLine("Found Directory.Build.props - terminate here"); + return null; + } + + var sol = dir.GetDirectoryInfo().EnumerateFiles("*.sln", opts); + + if (sol.Count() != 0) + { + Debug.WriteLine("Found *.sln file - terminate here"); + return null; + } + + dir = new NodeItem(dir.ParentDirectory, PathKind.Directory); + continue; + } + + Debug.WriteLine("Failed return null"); + return null; + } + } + + } } diff --git a/AvantGarde/Projects/DotnetSolution.cs b/AvantGarde/Projects/DotnetSolution.cs index 58b4f2d..b62cd1d 100644 --- a/AvantGarde/Projects/DotnetSolution.cs +++ b/AvantGarde/Projects/DotnetSolution.cs @@ -141,19 +141,7 @@ public override bool Refresh() { foreach (var project in Projects.Values) { - if (project.Name.Equals(name, PathItem.PlatformComparison) || project.FullName.Equals(name, PathItem.PlatformComparison)) - { - return project; - } - - var item = project.Contents.FindExact(name); - - if (item != null) - { - return item; - } - - item = project.Contents.FindFirst(name, true) ?? project.Contents.FindFirst(name, false); + var item = project.Contents.FindFile(name) ?? project.Contents.FindDirectory(name); if (item != null) { diff --git a/AvantGarde/Projects/NodeItem.cs b/AvantGarde/Projects/NodeItem.cs index 42b3b39..94d45ca 100644 --- a/AvantGarde/Projects/NodeItem.cs +++ b/AvantGarde/Projects/NodeItem.cs @@ -151,35 +151,69 @@ public override DateTime LastUtc } /// - /// Finds the first node with a matching name. If files is true, only files are found. If false, only directories. + /// Finds the first file node using either full or leaf name with default case sensitivity. /// - public NodeItem? FindFirst(string name, bool files, StringComparison comparison) + public NodeItem? FindFile(string name) { - return FindInternal(CleanPath(name), files, comparison); + return FindFile(name, int.MaxValue, PathItem.PlatformComparison); } /// - /// Overload with platform default case sensitivity. + /// Finds the first file node using either full or leaf name. /// - public NodeItem? FindFirst(string name, bool files) + public NodeItem? FindFile(string name, StringComparison comparison) { - return FindInternal(CleanPath(name), files, PathItem.PlatformComparison); + return FindFile(name, int.MaxValue, comparison); } /// - /// Finds a node with matching full name. + /// Finds the first file node using either full or leaf name. A depth value of 1 will find items only the top-level directory. /// - public NodeItem? FindExact(string fullName, StringComparison comparison) + public NodeItem? FindFile(string name, int depth, StringComparison comparison) { - return FindInternal(CleanPath(fullName), null, comparison); + return FindInternal(CleanPath(name), false, depth, comparison); } /// - /// Overload with platform default case sensitivity. + /// Finds the first directory node using either full or leaf name with default case sensitivity /// - public NodeItem? FindExact(string fullName) + public NodeItem? FindDirectory(string name) { - return FindInternal(CleanPath(fullName), null, PathItem.PlatformComparison); + return FindDirectory(name, int.MaxValue, PathItem.PlatformComparison); + } + + /// + /// Finds the first directory node using either full or leaf name. + /// + public NodeItem? FindDirectory(string name, StringComparison comparison) + { + return FindDirectory(name, int.MaxValue, comparison); + } + + /// + /// Finds the first directory node using either full or leaf name. A depth value of 1 will find items only the top-level directory. + /// + public NodeItem? FindDirectory(string name, int depth, StringComparison comparison) + { + return FindInternal(CleanPath(name), true, depth, comparison); + } + + /// + /// Finds a node with matching full name. Unlike and , + /// this routine can find either a file or directory, but requires the full path name. + /// + public NodeItem? FindNode(string fullName, StringComparison comparison) + { + return FindInternal(CleanPath(fullName), null, int.MaxValue, comparison); + } + + /// + /// Finds a node with matching full name with default case sensitivity. Unlike and + /// , this routine can find either a file or directory, but requires the full path name. + /// + public NodeItem? FindNode(string fullName) + { + return FindInternal(CleanPath(fullName), null, int.MaxValue, PathItem.PlatformComparison); } /// @@ -347,23 +381,26 @@ private void ToString(StringBuilder sb, int indent) } } - private NodeItem? FindInternal(string name, bool? isFile, StringComparison comparison) + private NodeItem? FindInternal(string name, bool? isDirectory, int depth, StringComparison comparison) { - if (isFile == null && FullName.Equals(name, comparison)) + if (isDirectory == null && FullName.Equals(name, comparison)) { + // Full name match return this; } - if (isFile != null && IsDirectory != isFile && Name.Equals(name, comparison)) + if (isDirectory != null && IsDirectory == isDirectory && + (Name.Equals(name, comparison) || FullName.Equals(name, comparison))) { + // Name only match return this; } - if (_contents != null) + if (_contents != null && depth > 0) { for (int n = _contents.Count - 1; n > -1; --n) { - var found = _contents[n].FindInternal(name, isFile, comparison); + var found = _contents[n].FindInternal(name, isDirectory, depth - 1, comparison); if (found != null) { diff --git a/AvantGarde/ViewModels/PreviewOptionsViewModel.cs b/AvantGarde/ViewModels/PreviewOptionsViewModel.cs index d3fa234..5054b59 100644 --- a/AvantGarde/ViewModels/PreviewOptionsViewModel.cs +++ b/AvantGarde/ViewModels/PreviewOptionsViewModel.cs @@ -50,7 +50,6 @@ public class PreviewOptionsViewModel : AvantViewModel public PreviewOptionsViewModel() { - _scaleItems.Add("25%"); _scaleItems.Add("50%"); _scaleItems.Add("67%"); diff --git a/AvantGarde/Views/AvantWindow.cs b/AvantGarde/Views/AvantWindow.cs index 8ac7173..9e16d64 100644 --- a/AvantGarde/Views/AvantWindow.cs +++ b/AvantGarde/Views/AvantWindow.cs @@ -44,6 +44,22 @@ public AvantWindow(T model) /// protected T Model; + /// + /// Returns nominal width (without font-size scaling). + /// + public double DescaledWidth + { + get { return Width / GlobalModel.Global.Scale; } + } + + /// + /// Returns nominal height (without font-size scaling). + /// + public double DescaledHeight + { + get { return Height / GlobalModel.Global.Scale; } + } + /// /// Called by this class. Can be overridden, but call base. /// diff --git a/AvantGarde/Views/MainWindow.axaml.cs b/AvantGarde/Views/MainWindow.axaml.cs index d1d87a3..d82ae2a 100644 --- a/AvantGarde/Views/MainWindow.axaml.cs +++ b/AvantGarde/Views/MainWindow.axaml.cs @@ -436,14 +436,14 @@ private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEvent case nameof(Window.Width): if (WindowState != WindowState.Maximized && App.Settings.Width != Width) { - App.Settings.Width = Width; + App.Settings.Width = DescaledWidth; _writeSettingsFlag = true; } break; case nameof(Window.Height): if (WindowState != WindowState.Maximized && App.Settings.Height != Height) { - App.Settings.Height = Height; + App.Settings.Height = DescaledHeight; _writeSettingsFlag = true; } break; @@ -544,9 +544,10 @@ private void RefreshTimerHandler(object? sender, EventArgs e) if (_writeSettingsFlag) { Debug.WriteLine("Write settings"); - Debug.WriteLine("WRITE WIDTH: " + App.Settings.Width); _writeSettingsFlag = false; +#if !DEBUG App.Settings.Write(); +#endif } } catch (Exception x) diff --git a/AvantGarde/Views/MessageBox.axaml b/AvantGarde/Views/MessageBox.axaml index fba2125..ffc7def 100644 --- a/AvantGarde/Views/MessageBox.axaml +++ b/AvantGarde/Views/MessageBox.axaml @@ -6,7 +6,6 @@ x:Class="AvantGarde.Views.MessageBox" MaxWidth="800" MaxHeight="800" - CanResize="False" Title="Title" WindowStartupLocation="CenterOwner"> diff --git a/AvantGarde/Views/MessageBox.axaml.cs b/AvantGarde/Views/MessageBox.axaml.cs index 62b83d5..f7ef3d4 100644 --- a/AvantGarde/Views/MessageBox.axaml.cs +++ b/AvantGarde/Views/MessageBox.axaml.cs @@ -35,7 +35,7 @@ public partial class MessageBox : AvantWindow /// public MessageBox() { - AvaloniaXamlLoader.Load(this); + InitializeComponent(); Title = App.Current?.Name ?? string.Empty; } @@ -120,8 +120,6 @@ public static Task ShowDialog(Window owner, Exception error, bool? s protected override void OnOpened(EventArgs e) { - base.OnOpened(e); - if (Buttons.HasFlag(BoxButtons.Ok)) { AddButton("Ok", BoxButtons.Ok); @@ -158,7 +156,8 @@ protected override void OnOpened(EventArgs e) } this.SizeToContent = SizeToContent.WidthAndHeight; - this.SetCenterFix(); + base.OnOpened(e); + this.CanResize = false; } private void AddButton(string caption, BoxButtons rslt) diff --git a/AvantGarde/Views/PreviewPane.axaml b/AvantGarde/Views/PreviewPane.axaml index d9d7cee..4019bba 100644 --- a/AvantGarde/Views/PreviewPane.axaml +++ b/AvantGarde/Views/PreviewPane.axaml @@ -8,11 +8,9 @@ xmlns:vm="using:AvantGarde.ViewModels" x:DataType="vm:PreviewPaneViewModel" - x:CompileBindings="False" - x:Class="AvantGarde.Views.PreviewPane"> - + @@ -52,12 +50,11 @@ /> diff --git a/AvantGarde/Views/PreviewPane.axaml.cs b/AvantGarde/Views/PreviewPane.axaml.cs index 02793a0..359066a 100644 --- a/AvantGarde/Views/PreviewPane.axaml.cs +++ b/AvantGarde/Views/PreviewPane.axaml.cs @@ -19,7 +19,6 @@ using System.Diagnostics; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Markup.Xaml; using Avalonia.Threading; using AvantGarde.Loading; using AvantGarde.Projects; @@ -34,11 +33,6 @@ namespace AvantGarde.Views /// public partial class PreviewPane : UserControl { - private readonly CodeTextBox _plainText; - private readonly PreviewControl _previewControl; - private readonly Grid _xamlGrid; - private readonly XamlCodeControl _xamlCode; - private readonly DispatcherTimer _timer; private readonly PreviewPaneViewModel _model = new(); private int _caretIndex = int.MinValue; @@ -47,20 +41,14 @@ public PreviewPane() { _model.Owner = this; DataContext = _model; - AvaloniaXamlLoader.Load(this); + InitializeComponent(); _model.LoadFlagChecked += LoadFlagCheckedHandler; _model.ScaleChanged += ScaleChangedHandler; - _previewControl = this.FindOrThrow("PreviewControl"); - _previewControl.PointerEventOccurred += PointerEventHandler; - _previewControl.GotoClick += GotoClickHander; - - _plainText = this.FindOrThrow("PlainTextBox"); - _xamlGrid = this.FindOrThrow("XamlGrid"); - _xamlCode = this.FindOrThrow("XamlCode"); - - _xamlCode.IsVisible = false; + XamlCode.IsVisible = false; + PreviewControl.PointerEventOccurred += PointerEventHandler; + PreviewControl.GotoClick += GotoClickHander; _timer = new(TimeSpan.FromMilliseconds(100), DispatcherPriority.Normal, TimerHandler); _timer.Start(); @@ -93,8 +81,8 @@ public PreviewPane() /// public PreviewWindowTheme WindowTheme { - get { return _previewControl.WindowTheme; } - set { _previewControl.WindowTheme = value; } + get { return PreviewControl.WindowTheme; } + set { PreviewControl.WindowTheme = value; } } /// @@ -169,8 +157,8 @@ public bool IsXamlViewable /// public string? OutputText { - get { return _xamlCode.OutputText; } - set { _xamlCode.OutputText = value; } + get { return XamlCode.OutputText; } + set { XamlCode.OutputText = value; } } /// @@ -235,7 +223,7 @@ public bool Update(PreviewPayload? payload) _model.HasContent = payload.Source != null || payload.Text != null; _model.IsXamlViewable = payload.ItemKind == PathKind.Xaml; _model.IsPlainTextViewable = payload.Text != null && payload.Source == null && !_model.IsXamlViewable; - _previewControl.Update(payload, ScaleFactor); + PreviewControl.Update(payload, ScaleFactor); } else { @@ -243,15 +231,18 @@ public bool Update(PreviewPayload? payload) _model.HasContent = false; _model.IsXamlViewable = false; _model.IsPlainTextViewable = false; - _previewControl.Update(null, ScaleFactor); + PreviewControl.Update(null, ScaleFactor); } if (_model.IsPlainTextViewable) { - _plainText.Text = payload?.Text; + PlainTextBox.Text = payload?.Text; } - if (_xamlCode.Update(payload)) + // We are setting this here because XAML binding not working for unknown reason + XamlCode.IsVisible = _model.IsXamlViewable; + + if (XamlCode.Update(payload)) { ResetSplitter(); } @@ -261,7 +252,7 @@ public bool Update(PreviewPayload? payload) private RowDefinition GetSplitRow() { - var row = _xamlGrid.RowDefinitions[2]; + var row = XamlGrid.RowDefinitions[2]; if (row != null) { @@ -323,7 +314,7 @@ private void GotoClickHander(PreviewError error) if (_model.IsXamlViewable) { IsXamlViewOpen = true; - _xamlCode.SetCaretPos(error.LineNum, error.LinePos); + XamlCode.SetCaretPos(error.LineNum, error.LinePos); } } @@ -331,25 +322,25 @@ private void TimerHandler(object? sender, EventArgs e) { // Update caret position. // Not sure of a better way to do this other than a timer. - if (_plainText.IsVisible) + if (PlainTextBox.IsVisible) { - var idx = _plainText.CaretIndex; + var idx = PlainTextBox.CaretIndex; if (_caretIndex != idx) { _caretIndex = idx; - _model.CaretText = _plainText.GetCaretLabel(); + _model.CaretText = PlainTextBox.GetCaretLabel(); } } else - if (_xamlCode.IsVisible) + if (XamlCode.IsVisible) { - var idx = _xamlCode.GetCaretIndex(); + var idx = XamlCode.GetCaretIndex(); if (_caretIndex != idx) { _caretIndex = idx; - _model.CaretText = _xamlCode.GetCaretLabel(); + _model.CaretText = XamlCode.GetCaretLabel(); } } else diff --git a/AvantGarde/Views/SolutionWindow.axaml.cs b/AvantGarde/Views/SolutionWindow.axaml.cs index 9756c6f..ee4d2e6 100644 --- a/AvantGarde/Views/SolutionWindow.axaml.cs +++ b/AvantGarde/Views/SolutionWindow.axaml.cs @@ -90,6 +90,7 @@ private void OkClickHandler(object? sender, RoutedEventArgs e) { if (Properties != null) { + // Accept warning - check needed for Avalonia 11 if (_depthUpDown.Value != null) { Properties.SearchDepth = (int)_depthUpDown.Value; diff --git a/AvantGarde/Views/XamlCodeControl.axaml.cs b/AvantGarde/Views/XamlCodeControl.axaml.cs index ebf81bb..e4858b0 100644 --- a/AvantGarde/Views/XamlCodeControl.axaml.cs +++ b/AvantGarde/Views/XamlCodeControl.axaml.cs @@ -77,6 +77,7 @@ public bool Update(PreviewPayload? payload) HasXaml = payload?.ItemKind == PathKind.Xaml; _model.CodeText = payload?.Text; OutputText = payload?.Output; + return HasXaml != temp; } diff --git a/CHANGES b/CHANGES index 4206b0a..c1496a2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,13 @@ -+ NEXT ++ VERSION 1.2.0 +- Added detection of application build output in the new '.artifacts' directory that will come into play with .NET8. - Added solution directory tree traverse to find the Directory.Packages.props file when AvaloniaVersion is null. This is needed if the version is defined only globally, and not overridden in the csproj. - Added check for the presence of the PackageVersion tag. This is needed since the Directory.Packages.props could be the only place where the Version attribute is defined. - Added check for the presence of the VersionOverride attribute. This is needed since the version used by a project could still be different from the global one, at least in theory. - Increased height of SettingsWindow. +- RPM and DEB packages now specify full .NET dependencies. +- Bugfix: XAML code text and debug output was hidden (fixes a bug introduced in 1.1.0). +- Bugfix: Error in persisting size of main window between launches. +- Bugfix: Crash when trying open recent but non-existing solution file. + VERSION 1.1.0 - Added new "application font" preference diff --git a/Deploy/AvantGarde.metainfo.xml b/Deploy/AvantGarde.metainfo.xml index 3ac7cbd..6af96ec 100644 --- a/Deploy/AvantGarde.metainfo.xml +++ b/Deploy/AvantGarde.metainfo.xml @@ -43,6 +43,23 @@ + + + +
    +
  • Added detection of application build output in the new '.artifacts' directory that will come into play with .NET8.
  • +
  • Added solution directory tree traverse to find the Directory.Packages.props file when AvaloniaVersion is null. This is needed if the version is defined only globally, and not overridden in the csproj.
  • +
  • Added check for the presence of the PackageVersion tag. This is needed since the Directory.Packages.props could be the only place where the Version attribute is defined.
  • +
  • Added check for the presence of the VersionOverride attribute. This is needed since the version used by a project could still be different from the global one, at least in theory.
  • +
  • Increased height of SettingsWindow.
  • +
  • RPM and DEB packages now specify full .NET dependencies.
  • +
  • Bugfix: XAML code text and debug output was hidden (fixes a bug introduced in 1.1.0).
  • +
  • Bugfix: Error in persisting size of main window between launches.
  • +
  • Bugfix: Crash when trying open recent but non-existing solution file.
  • +
+
+
+