Skip to content

Commit fd7218f

Browse files
committed
Feature: Add Avalonia-based desktop app for CodeIngest.
1 parent b0599db commit fd7218f

16 files changed

+618
-207
lines changed

CodeIngest.Desktop/App.axaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Application xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
4+
xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
5+
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
6+
x:Class="CodeIngest.Desktop.App"
7+
RequestedThemeVariant="Default">
8+
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
9+
<Application.Styles>
10+
<FluentTheme />
11+
<themes:MaterialTheme BaseTheme="Dark" PrimaryColor="BlueGrey" SecondaryColor="Lime" />
12+
<avalonia:MaterialIconStyles />
13+
<dialogHostAvalonia:DialogHostStyles />
14+
15+
<Style Selector="Button">
16+
<Setter Property="Focusable" Value="False" />
17+
</Style>
18+
<Style Selector="ToggleButton">
19+
<Setter Property="Focusable" Value="False" />
20+
</Style>
21+
</Application.Styles>
22+
</Application>

CodeIngest.Desktop/App.axaml.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Code authored by Dean Edis (DeanTheCoder).
2+
// Anyone is free to copy, modify, use, compile, or distribute this software,
3+
// either in source code form or as a compiled binary, for any non-commercial
4+
// purpose.
5+
//
6+
// If you modify the code, please retain this copyright header,
7+
// and consider contributing back to the repository or letting us know
8+
// about your modifications. Your contributions are valued!
9+
//
10+
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
11+
using Avalonia;
12+
using Avalonia.Controls.ApplicationLifetimes;
13+
using Avalonia.Markup.Xaml;
14+
using CSharp.Core.UI;
15+
16+
namespace CodeIngest.Desktop;
17+
18+
public class App : Application
19+
{
20+
public override void Initialize() =>
21+
AvaloniaXamlLoader.Load(this);
22+
23+
public override void OnFrameworkInitializationCompleted()
24+
{
25+
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
26+
{
27+
desktop.MainWindow = new MainWindow
28+
{
29+
DataContext = new MainViewModel(DialogService.Instance)
30+
};
31+
}
32+
33+
base.OnFrameworkInitializationCompleted();
34+
}
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>WinExe</OutputType>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<Nullable>disable</Nullable>
6+
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
7+
<ApplicationManifest>app.manifest</ApplicationManifest>
8+
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
9+
<Company>Dean Edis (DeanTheCoder)</Company>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<PackageReference Include="Avalonia" Version="11.1.3" />
14+
<PackageReference Include="Avalonia.Desktop" Version="11.1.3" />
15+
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.3" />
16+
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.3" />
17+
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
18+
<PackageReference Include="Avalonia.Diagnostics" Version="11.1.3">
19+
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
20+
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
21+
</PackageReference>
22+
<PackageReference Include="Material.Avalonia" Version="3.10.2" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\CodeIngestLib\CodeIngestLib.csproj" />
27+
<ProjectReference Include="..\DTC.Core\CSharp.Core\CSharp.Core.csproj" />
28+
</ItemGroup>
29+
</Project>

CodeIngest.Desktop/MainViewModel.cs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Code authored by Dean Edis (DeanTheCoder).
2+
// Anyone is free to copy, modify, use, compile, or distribute this software,
3+
// either in source code form or as a compiled binary, for any non-commercial
4+
// purpose.
5+
//
6+
// If you modify the code, please retain this copyright header,
7+
// and consider contributing back to the repository or letting us know
8+
// about your modifications. Your contributions are valued!
9+
//
10+
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
11+
using System;
12+
using System.IO;
13+
using System.Linq;
14+
using System.Threading.Tasks;
15+
using CodeIngestLib;
16+
using CSharp.Core.Extensions;
17+
using CSharp.Core.UI;
18+
using CSharp.Core.ViewModels;
19+
using Material.Icons;
20+
21+
namespace CodeIngest.Desktop;
22+
23+
public class SelectedFoldersProvider : ISelectedItemsProvider<DirectoryInfo>
24+
{
25+
private DirectoryInfo[] m_selectedItems;
26+
27+
public void SetSelectedItems(DirectoryInfo[] items) =>
28+
m_selectedItems = items;
29+
30+
public DirectoryInfo[] GetSelectedItems() =>
31+
m_selectedItems ?? [];
32+
}
33+
34+
public class MainViewModel : ViewModelBase
35+
{
36+
private readonly IDialogService m_dialogService;
37+
private DirectoryInfo m_driveRoot = new DirectoryInfo(Environment.CurrentDirectory);
38+
private bool m_isCSharp = true;
39+
private bool m_isCppNoHeaders;
40+
private bool m_isCppWithHeaders;
41+
private bool m_includeMarkdown;
42+
private bool m_excludeImports = true;
43+
private bool m_useFullPaths;
44+
private bool m_excludeComments = true;
45+
46+
public DirectoryInfo DriveRoot
47+
{
48+
get => m_driveRoot;
49+
set => SetField(ref m_driveRoot, value);
50+
}
51+
52+
public SelectedFoldersProvider SelectedFolders { get; } = new SelectedFoldersProvider();
53+
54+
public bool IsCSharp
55+
{
56+
get => m_isCSharp;
57+
set => SetField(ref m_isCSharp, value);
58+
}
59+
60+
public bool IsCppNoHeaders
61+
{
62+
get => m_isCppNoHeaders;
63+
set => SetField(ref m_isCppNoHeaders, value);
64+
}
65+
66+
public bool IsCppWithHeaders
67+
{
68+
get => m_isCppWithHeaders;
69+
set => SetField(ref m_isCppWithHeaders, value);
70+
}
71+
72+
public bool ExcludeImports
73+
{
74+
get => m_excludeImports;
75+
set => SetField(ref m_excludeImports, value);
76+
}
77+
78+
public bool ExcludeComments
79+
{
80+
get => m_excludeComments;
81+
set => SetField(ref m_excludeComments, value);
82+
}
83+
84+
public bool IncludeMarkdown
85+
{
86+
get => m_includeMarkdown;
87+
set => SetField(ref m_includeMarkdown, value);
88+
}
89+
90+
public bool UseFullPaths
91+
{
92+
get => m_useFullPaths;
93+
set => SetField(ref m_useFullPaths, value);
94+
}
95+
96+
public async Task SelectRoot()
97+
{
98+
var rootFolder = await m_dialogService.SelectFolderAsync("Select a folder to scan for code.");
99+
if (rootFolder != null)
100+
DriveRoot = rootFolder;
101+
}
102+
103+
public MainViewModel(IDialogService dialogService = null)
104+
{
105+
m_dialogService = dialogService ?? DialogService.Instance;
106+
}
107+
108+
public async Task RunIngest()
109+
{
110+
var selectedFolders = SelectedFolders?.GetSelectedItems();
111+
if (selectedFolders?.Any() != true)
112+
return; // Nothing to do.
113+
114+
string[] filterExtensions;
115+
if (IsCppNoHeaders)
116+
filterExtensions = [".cpp"];
117+
else if (IsCppWithHeaders)
118+
filterExtensions = [".cpp", ".h"];
119+
else
120+
filterExtensions = [".cs"];
121+
122+
var outputFile = await m_dialogService.ShowFileSaveAsync("Save output file as...", "CodeIngest", "Code File", filterExtensions);
123+
if (outputFile == null)
124+
return; // User cancelled.
125+
126+
var options = new IngestOptions
127+
{
128+
ExcludeImports = ExcludeImports,
129+
UseFullPaths = UseFullPaths,
130+
StripComments = ExcludeComments
131+
};
132+
133+
options.FilePatterns.Clear();
134+
if (IsCSharp)
135+
{
136+
options.FilePatterns.Add("*.cs");
137+
} else if (IsCppNoHeaders)
138+
{
139+
options.FilePatterns.Add("*.cpp");
140+
} else if (IsCppWithHeaders)
141+
{
142+
options.FilePatterns.Add("*.h");
143+
options.FilePatterns.Add("*.cpp");
144+
}
145+
146+
if (IncludeMarkdown)
147+
options.FilePatterns.Add("*.md");
148+
149+
var ingester = new Ingester(options);
150+
var result = ingester.Run(selectedFolders, outputFile);
151+
if (!result.HasValue)
152+
{
153+
m_dialogService.ShowMessage("Failed to generate code file.", "Please check your file permissions and try again.", MaterialIconKind.Error);
154+
return;
155+
}
156+
157+
m_dialogService.ShowMessage("Code file generated successfully.", $@"{result.Value.FileCount:N0} files produced {result.Value.OutputBytes.ToSize()} of output.");
158+
}
159+
}

CodeIngest.Desktop/MainWindow.axaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<Window xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:ui="clr-namespace:CSharp.Core.UI;assembly=CSharp.Core"
6+
xmlns:viewModels="clr-namespace:CodeIngest.Desktop"
7+
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
8+
xmlns:dialogHostAvalonia="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
9+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
10+
x:Class="CodeIngest.Desktop.MainWindow"
11+
x:DataType="viewModels:MainViewModel"
12+
Title="Code Ingest"
13+
MinWidth="640" MinHeight="480">
14+
<Design.DataContext>
15+
<viewModels:MainViewModel />
16+
</Design.DataContext>
17+
18+
<dialogHostAvalonia:DialogHost OverlayBackground="Black">
19+
<Grid ColumnDefinitions="*,Auto">
20+
<Grid.Styles>
21+
<Style Selector="Border">
22+
<Setter Property="CornerRadius" Value="2"/>
23+
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignBody}" />
24+
</Style>
25+
<Style Selector="Button">
26+
<Setter Property="Foreground" Value="{DynamicResource MaterialDesignBody}" />
27+
</Style>
28+
<Style Selector="avalonia|MaterialIcon">
29+
<Setter Property="Foreground" Value="{DynamicResource MaterialDesignBody}" />
30+
<Setter Property="Width" Value="24" />
31+
<Setter Property="Height" Value="24" />
32+
</Style>
33+
</Grid.Styles>
34+
35+
<Border BorderThickness="1" Margin="8">
36+
<ui:FolderTree SelectedItemsProvider="{Binding SelectedFolders}"
37+
Location="{Binding DriveRoot}"
38+
Margin="8"/>
39+
</Border>
40+
41+
<StackPanel Grid.Column="1" Margin="0,8,8,8">
42+
<Button Command="{Binding SelectRoot}">
43+
<StackPanel Orientation="Horizontal">
44+
<avalonia:MaterialIcon Kind="FolderOutline" />
45+
<TextBlock Text="Select Root" Margin="8,0,0,0" VerticalAlignment="Center"/>
46+
</StackPanel>
47+
</Button>
48+
<Button Command="{Binding RunIngest}" Margin="0,4,0,0">
49+
<StackPanel Orientation="Horizontal">
50+
<avalonia:MaterialIcon Kind="Export" />
51+
<TextBlock Text="Ingest" Margin="8,0,0,0" VerticalAlignment="Center"/>
52+
</StackPanel>
53+
</Button>
54+
55+
<Border BorderThickness="1" Margin="0,16,0,0">
56+
<StackPanel>
57+
<!-- ReSharper disable once Xaml.StyleClassNotFound -->
58+
<TextBlock Text="Options" Classes="Overline" Margin="4"/>
59+
<StackPanel Margin="8">
60+
<CheckBox Content="Export Full Paths" IsChecked="{Binding UseFullPaths}"/>
61+
62+
<RadioButton Content="C#" Margin="0,16,0,0" IsChecked="{Binding IsCSharp}"/>
63+
<RadioButton Content="C/C++ (No Headers)" IsChecked="{Binding IsCppNoHeaders}" />
64+
<RadioButton Content="C/C++ (With Headers)" IsChecked="{Binding IsCppWithHeaders}" />
65+
66+
<CheckBox Content="Exclude Imports" Margin="0,16,0,0"
67+
ToolTip.Tip="Remove using/#include/etc statements from the output."
68+
IsChecked="{Binding ExcludeImports}" />
69+
<CheckBox Content="Exclude comments"
70+
ToolTip.Tip="Remove // and /*-style comments from the output."
71+
IsChecked="{Binding ExcludeComments}" />
72+
<CheckBox Content="Include Markdown"
73+
ToolTip.Tip="Include .md files in the output."
74+
IsChecked="{Binding IncludeMarkdown}" />
75+
</StackPanel>
76+
</StackPanel>
77+
</Border>
78+
</StackPanel>
79+
</Grid>
80+
</dialogHostAvalonia:DialogHost>
81+
</Window>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Code authored by Dean Edis (DeanTheCoder).
2+
// Anyone is free to copy, modify, use, compile, or distribute this software,
3+
// either in source code form or as a compiled binary, for any non-commercial
4+
// purpose.
5+
//
6+
// If you modify the code, please retain this copyright header,
7+
// and consider contributing back to the repository or letting us know
8+
// about your modifications. Your contributions are valued!
9+
//
10+
// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND.
11+
using Avalonia.Controls;
12+
13+
namespace CodeIngest.Desktop;
14+
15+
public partial class MainWindow : Window
16+
{
17+
public MainWindow()
18+
{
19+
InitializeComponent();
20+
}
21+
}

0 commit comments

Comments
 (0)