diff --git a/.ai-guidelines.md b/.ai-guidelines.md
new file mode 100644
index 0000000..82b8c08
--- /dev/null
+++ b/.ai-guidelines.md
@@ -0,0 +1,51 @@
+# pgLabII AI Assistant Guidelines
+
+## Project Context
+This is a .NET 8/C# 13 Avalonia cross-platform application for document management.
+
+### Architecture Overview
+- **Main Project**: pgLabII (Avalonia UI)
+- **Platform Projects**: pgLabII.Desktop
+- **Utility Project**: pgLabII.PgUtils
+- **Core Components**: DocumentSession, DocumentSessionFactory, LocalDb
+
+## Coding Standards
+
+### C# Guidelines
+- Use C# 13 features and modern .NET patterns
+- Prefer primary constructors for dependency injection
+- Use `var` for obvious types, explicit types for clarity
+- Implement proper async/await patterns for I/O operations
+- Use nullable reference types consistently
+- Prefer "target-typed new"
+- Prefer "list initializer" over new
+
+### Avalonia-Specific
+- Follow MVVM pattern strictly
+- ViewModels should inherit from appropriate base classes
+- Use ReactiveUI patterns where applicable
+- Make use of annotations to generate bindable properties
+- Implement proper data binding
+- Consider cross-platform UI constraints
+
+### Project Patterns
+- Services should use dependency injection
+- Use the DocumentSession pattern for data operations
+- Integrate with LocalDb for persistence
+- Implement proper error handling and logging
+- Consider performance for document operations
+- Use FluentResults.Result for expected errors
+
+### Architecture Rules
+- Keep platform-specific code in respective platform projects
+- Shared business logic in main pgLabII project
+- Utilities in pgLabII.PgUtils
+- Follow clean architecture principles
+
+## Code Review Focus Areas
+1. Memory management for document operations
+2. Cross-platform compatibility
+3. Proper async patterns
+4. Error handling and user feedback
+5. Performance considerations for large documents
+6. UI responsiveness
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 94ea5dc..6a883b3 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -6,16 +6,19 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -23,19 +26,16 @@
+
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
\ No newline at end of file
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..2b2be0c
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,12 @@
+# pgLabII development roadmap
+
+pgLabII is a developer tool for postgresql databases. Its goal is to allow for querying the database and also editing row data.
+It also is going to give some user friendly views of the database structure.
+
+## UI overview
+
+There is a window for managing the configuration data of connections to actual database instances.
+When a connection is opened a window specific for that database instance is openen. Within this window
+the user can open many tabs to query the database and inspects it's tables, indexes etc.
+
+
diff --git a/pgLabII.Android/Icon.png b/pgLabII.Android/Icon.png
deleted file mode 100644
index 41a2a61..0000000
Binary files a/pgLabII.Android/Icon.png and /dev/null differ
diff --git a/pgLabII.Android/MainActivity.cs b/pgLabII.Android/MainActivity.cs
deleted file mode 100644
index e1efa06..0000000
--- a/pgLabII.Android/MainActivity.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Android.App;
-using Android.Content.PM;
-using Avalonia;
-using Avalonia.Android;
-using Avalonia.ReactiveUI;
-
-namespace pgLabII.Android;
-
-[Activity(
- Label = "pgLabII.Android",
- Theme = "@style/MyTheme.NoActionBar",
- Icon = "@drawable/icon",
- MainLauncher = true,
- ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
-public class MainActivity : AvaloniaMainActivity
-{
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder)
- .WithInterFont()
- .UseReactiveUI();
- }
-}
diff --git a/pgLabII.Android/Properties/AndroidManifest.xml b/pgLabII.Android/Properties/AndroidManifest.xml
deleted file mode 100644
index a77e007..0000000
--- a/pgLabII.Android/Properties/AndroidManifest.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/pgLabII.Android/Resources/AboutResources.txt b/pgLabII.Android/Resources/AboutResources.txt
deleted file mode 100644
index c2bca97..0000000
--- a/pgLabII.Android/Resources/AboutResources.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-Images, layout descriptions, binary blobs and string dictionaries can be included
-in your application as resource files. Various Android APIs are designed to
-operate on the resource IDs instead of dealing with images, strings or binary blobs
-directly.
-
-For example, a sample Android app that contains a user interface layout (main.axml),
-an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
-would keep its resources in the "Resources" directory of the application:
-
-Resources/
- drawable/
- icon.png
-
- layout/
- main.axml
-
- values/
- strings.xml
-
-In order to get the build system to recognize Android resources, set the build action to
-"AndroidResource". The native Android APIs do not operate directly with filenames, but
-instead operate on resource IDs. When you compile an Android application that uses resources,
-the build system will package the resources for distribution and generate a class called "R"
-(this is an Android convention) that contains the tokens for each one of the resources
-included. For example, for the above Resources layout, this is what the R class would expose:
-
-public class R {
- public class drawable {
- public const int icon = 0x123;
- }
-
- public class layout {
- public const int main = 0x456;
- }
-
- public class strings {
- public const int first_string = 0xabc;
- public const int second_string = 0xbcd;
- }
-}
-
-You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
-to reference the layout/main.axml file, or R.strings.first_string to reference the first
-string in the dictionary file values/strings.xml.
\ No newline at end of file
diff --git a/pgLabII.Android/Resources/drawable-night-v31/avalonia_anim.xml b/pgLabII.Android/Resources/drawable-night-v31/avalonia_anim.xml
deleted file mode 100644
index dde4b5a..0000000
--- a/pgLabII.Android/Resources/drawable-night-v31/avalonia_anim.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pgLabII.Android/Resources/drawable-v31/avalonia_anim.xml b/pgLabII.Android/Resources/drawable-v31/avalonia_anim.xml
deleted file mode 100644
index 94f27d9..0000000
--- a/pgLabII.Android/Resources/drawable-v31/avalonia_anim.xml
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pgLabII.Android/Resources/drawable/splash_screen.xml b/pgLabII.Android/Resources/drawable/splash_screen.xml
deleted file mode 100644
index 2e920b4..0000000
--- a/pgLabII.Android/Resources/drawable/splash_screen.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
diff --git a/pgLabII.Android/Resources/values-night/colors.xml b/pgLabII.Android/Resources/values-night/colors.xml
deleted file mode 100644
index 3d47b6f..0000000
--- a/pgLabII.Android/Resources/values-night/colors.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #212121
-
diff --git a/pgLabII.Android/Resources/values-v31/styles.xml b/pgLabII.Android/Resources/values-v31/styles.xml
deleted file mode 100644
index d5ecec4..0000000
--- a/pgLabII.Android/Resources/values-v31/styles.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/pgLabII.Android/Resources/values/colors.xml b/pgLabII.Android/Resources/values/colors.xml
deleted file mode 100644
index 59279d5..0000000
--- a/pgLabII.Android/Resources/values/colors.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #FFFFFF
-
diff --git a/pgLabII.Android/Resources/values/styles.xml b/pgLabII.Android/Resources/values/styles.xml
deleted file mode 100644
index 6e534de..0000000
--- a/pgLabII.Android/Resources/values/styles.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
diff --git a/pgLabII.Android/pgLabII.Android.csproj b/pgLabII.Android/pgLabII.Android.csproj
deleted file mode 100644
index 562450e..0000000
--- a/pgLabII.Android/pgLabII.Android.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
- Exe
- net8.0-android
- 21
- enable
- com.CompanyName.pgLabII
- 1
- 1.0
- apk
- false
-
-
-
-
- Resources\drawable\Icon.png
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pgLabII.Desktop/pgLabII.Desktop.csproj b/pgLabII.Desktop/pgLabII.Desktop.csproj
index b997c85..9002229 100644
--- a/pgLabII.Desktop/pgLabII.Desktop.csproj
+++ b/pgLabII.Desktop/pgLabII.Desktop.csproj
@@ -3,7 +3,7 @@
WinExe
- net8.0
+ net9.0
enable
true
AnyCPU;x64
diff --git a/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringParserTests.cs b/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringParserTests.cs
index 34a4440..d953ffc 100644
--- a/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringParserTests.cs
+++ b/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringParserTests.cs
@@ -14,7 +14,8 @@ public class PqConnectionStringParserTests
tokenizer
.AddString(kw)
.AddEquals()
- .AddString(val);
+ .AddString(val)
+ .AddEof();
}
[Fact]
diff --git a/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringTokenizerTests.cs b/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringTokenizerTests.cs
index 7ec407e..5cc6954 100644
--- a/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringTokenizerTests.cs
+++ b/pgLabII.PgUtils.Tests/ConnectionStrings/PqConnectionStringTokenizerTests.cs
@@ -1,4 +1,5 @@
using pgLabII.PgUtils.ConnectionStrings;
+using pgLabII.PgUtils.Tests.ConnectionStrings.Util;
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
@@ -11,18 +12,18 @@ public class PqConnectionStringTokenizerTests
public void GetKeyword_Success(string input, string expected)
{
PqConnectionStringTokenizer subject = new(input);
-
- Assert.Equal(expected, subject.GetKeyword());
+ var result = subject.GetKeyword();
+ ResultAssert.Success(result, expected);
}
[Theory]
[InlineData("=")]
[InlineData("")]
[InlineData(" ")]
- public void GetKeyword_Throws(string input)
+ public void GetKeyword_Errors(string input)
{
PqConnectionStringTokenizer subject = new(input);
- Assert.Throws(() => subject.GetKeyword());
+ ResultAssert.Failed(subject.GetKeyword());
}
[Theory]
@@ -36,7 +37,7 @@ public class PqConnectionStringTokenizerTests
{
PqConnectionStringTokenizer subject = new(input);
- Assert.Equal(expected, subject.Eof);
+ Assert.Equal(expected, subject.IsEof);
}
[Theory]
@@ -46,7 +47,7 @@ public class PqConnectionStringTokenizerTests
public void ConsumeEquals_Success(string input)
{
PqConnectionStringTokenizer subject = new(input);
- subject.ConsumeEquals();
+ ResultAssert.Success(subject.ConsumeEquals());
}
[Theory]
@@ -57,7 +58,8 @@ public class PqConnectionStringTokenizerTests
public void ConsumeEquals_Throws(string input)
{
PqConnectionStringTokenizer subject = new(input);
- Assert.Throws(() => subject.ConsumeEquals());
+ var result = subject.ConsumeEquals();
+ ResultAssert.Failed(result);
}
[Theory]
@@ -69,7 +71,8 @@ public class PqConnectionStringTokenizerTests
public void GetValue_Success(string input, string expected)
{
PqConnectionStringTokenizer subject = new(input);
- Assert.Equal(expected, subject.GetValue());
+ var result = subject.GetValue();
+ ResultAssert.Success(result, expected);
}
[Theory]
@@ -80,6 +83,7 @@ public class PqConnectionStringTokenizerTests
public void GetValue_Throws(string input)
{
PqConnectionStringTokenizer subject = new(input);
- Assert.Throws(() => subject.GetValue());
+ var result = subject.GetValue();
+ ResultAssert.Failed(result);
}
}
diff --git a/pgLabII.PgUtils.Tests/ConnectionStrings/Util/ResultAssert.cs b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/ResultAssert.cs
new file mode 100644
index 0000000..252d675
--- /dev/null
+++ b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/ResultAssert.cs
@@ -0,0 +1,38 @@
+using FluentResults;
+
+namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
+
+public static class ResultAssert
+{
+ public static void Success(Result result)
+ {
+ Assert.True(result.IsSuccess);
+ }
+
+ public static void Success(Result result)
+ {
+ Assert.True(result.IsSuccess);
+ }
+
+ public static void Success(Result result, T expected)
+ {
+ Assert.True(result.IsSuccess);
+ Assert.Equal(expected, result.Value);
+ }
+
+ public static void Success(Result result, Action assert)
+ {
+ Assert.True(result.IsSuccess);
+ assert(result.Value);
+ }
+
+ public static void Failed(Result result)
+ {
+ Assert.True(result.IsFailed);
+ }
+
+ public static void Failed(Result result)
+ {
+ Assert.True(result.IsFailed);
+ }
+}
diff --git a/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizer.cs b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizer.cs
index f3922e2..93e6789 100644
--- a/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizer.cs
+++ b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizer.cs
@@ -1,84 +1,93 @@
-using pgLabII.PgUtils.ConnectionStrings;
+using FluentResults;
+using pgLabII.PgUtils.ConnectionStrings;
+using OneOf;
namespace pgLabII.PgUtils.Tests.ConnectionStrings;
+using Elem = OneOf;
+
internal class UnitTestTokenizer : IPqConnectionStringTokenizer
{
- private readonly struct Elem(PqToken result, object? output)
- {
- public readonly PqToken Result = result;
- public readonly object? Output = output;
- }
-
- private readonly List _tokens = [];
+ private readonly List _tokens = new();
private int _position = 0;
-
- public UnitTestTokenizer AddString(string? output)
+ public UnitTestTokenizer AddString(string output)
{
- _tokens.Add(new(PqToken.String, output));
+ _tokens.Add(output);
return this;
}
- public UnitTestTokenizer AddException(Exception? output)
+ public UnitTestTokenizer AddError(Error error)
{
- _tokens.Add(new(PqToken.Exception, output));
+ _tokens.Add(error);
return this;
}
public UnitTestTokenizer AddEquals()
{
- _tokens.Add(new(PqToken.Equals, null));
+ _tokens.Add(PqToken.Equals);
return this;
}
+ public void AddEof()
+ {
+ _tokens.Add(PqToken.Eof);
+ }
+
// note we do no whitespace at end tests here
- public bool Eof => _position >= _tokens.Count;
-
- public string GetKeyword()
+ public bool IsEof
{
- EnsureNotEof();
- var elem = Consume();
- if (elem.Result == PqToken.String)
- return (string)elem.Output!;
-
- throw new Exception("Unexpected call to GetKeyword");
+ get
+ {
+ EnsureNotEol();
+ return _tokens[_position].IsT0 && _tokens[_position].AsT0 == PqToken.Eof;
+ }
}
-
- public void ConsumeEquals()
+ public Result GetKeyword()
{
- EnsureNotEof();
+ EnsureNotEol();
var elem = Consume();
- if (elem.Result == PqToken.Equals)
- return;
-
- throw new Exception("Unexpected call to ConsumeEquals");
+ return elem.Match>(
+ token => throw new Exception("Unexpected call to GetKeyword"),
+ str => str,
+ error => Result.Fail(error));
}
- public string GetValue()
+ public Result ConsumeEquals()
{
- EnsureNotEof();
+ EnsureNotEol();
var elem = Consume();
- if (elem.Result == PqToken.String)
- return (string)elem.Output!;
- throw new Exception("Unexpected call to GetValue");
+ return elem.Match(
+ token =>
+ {
+ if (token != PqToken.Equals)
+ throw new Exception("Unexpected call to GetKeyword");
+ return Result.Ok();
+ },
+ str => throw new Exception("Unexpected call to ConsumeEquals"),
+ error => Result.Fail(error));
+ }
+
+ public Result GetValue()
+ {
+ EnsureNotEol();
+ var elem = Consume();
+ return elem.Match>(
+ token => throw new Exception("Unexpected call to GetValue"),
+ str => str,
+ error => Result.Fail(error));
}
private Elem Consume()
{
- var elem = _tokens[_position++];
- if (elem.Result == PqToken.Exception)
- {
- throw (Exception)elem.Output!;
- }
- return elem;
+ return _tokens[_position++];
}
- private void EnsureNotEof()
+ private void EnsureNotEol()
{
- if (Eof)
- throw new Exception("unexpected eof in test, wrong parser call?");
+ if (_position >= _tokens.Count)
+ throw new Exception("unexpected end of list in test, wrong parser call?");
}
}
diff --git a/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizerTests.cs b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizerTests.cs
index 6bcb053..8c85e5f 100644
--- a/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizerTests.cs
+++ b/pgLabII.PgUtils.Tests/ConnectionStrings/Util/UnitTestTokenizerTests.cs
@@ -1,27 +1,40 @@
-namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
+using FluentResults;
+
+namespace pgLabII.PgUtils.Tests.ConnectionStrings.Util;
public class UnitTestTokenizerTests
{
private readonly UnitTestTokenizer _sut = new();
[Fact]
- public void Eof_True()
+ public void IsEof_Throws()
{
- Assert.True(_sut.Eof);
+ Assert.Throws(() =>
+ {
+ bool _ = _sut.IsEof;
+ });
+ }
+
+ [Fact]
+ public void IsEof_True()
+ {
+ _sut.AddEof();
+ Assert.True(_sut.IsEof);
}
[Fact]
public void Eof_False()
{
_sut.AddString("a");
- Assert.False(_sut.Eof);
+ Assert.False(_sut.IsEof);
}
[Fact]
public void GetKeyword_Success()
{
_sut.AddString("a");
- Assert.Equal("a", _sut.GetKeyword());
+ var result = _sut.GetKeyword();
+ ResultAssert.Success(result, "a");
}
[Fact]
@@ -34,15 +47,17 @@ public class UnitTestTokenizerTests
[Fact]
public void GetKeyword_SimulatesException()
{
- _sut.AddException(new ArgumentNullException());
- Assert.Throws(() => _sut.GetKeyword());
+ _sut.AddError(new("test"));
+ var result = _sut.GetKeyword();
+ ResultAssert.Failed(result);
}
[Fact]
public void GetValue_Success()
{
_sut.AddString("a");
- Assert.Equal("a", _sut.GetValue());
+ var result = _sut.GetValue();
+ ResultAssert.Success(result, "a");
}
[Fact]
@@ -55,15 +70,17 @@ public class UnitTestTokenizerTests
[Fact]
public void GetValue_SimulatesException()
{
- _sut.AddException(new ArgumentNullException());
- Assert.Throws(() => _sut.GetValue());
+ _sut.AddError(new("test"));
+ var result = _sut.GetValue();
+ ResultAssert.Failed(result);
}
[Fact]
public void ConsumeEquals_Success()
{
_sut.AddEquals();
- _sut.ConsumeEquals();
+ var result = _sut.ConsumeEquals();
+ ResultAssert.Success(result);
}
[Fact]
diff --git a/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj b/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj
index a99e569..6c549da 100644
--- a/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj
+++ b/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj
@@ -11,6 +11,7 @@
+
diff --git a/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj.DotSettings b/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj.DotSettings
new file mode 100644
index 0000000..1a62760
--- /dev/null
+++ b/pgLabII.PgUtils.Tests/pgLabII.PgUtils.Tests.csproj.DotSettings
@@ -0,0 +1,3 @@
+
+ True
+ True
\ No newline at end of file
diff --git a/pgLabII.PgUtils/ConnectionStrings/Pq/IPqConnectionStringTokenizer.cs b/pgLabII.PgUtils/ConnectionStrings/Pq/IPqConnectionStringTokenizer.cs
index 98c42a6..346a9a6 100644
--- a/pgLabII.PgUtils/ConnectionStrings/Pq/IPqConnectionStringTokenizer.cs
+++ b/pgLabII.PgUtils/ConnectionStrings/Pq/IPqConnectionStringTokenizer.cs
@@ -1,9 +1,11 @@
-namespace pgLabII.PgUtils.ConnectionStrings;
+using FluentResults;
+
+namespace pgLabII.PgUtils.ConnectionStrings;
public interface IPqConnectionStringTokenizer
{
- bool Eof { get; }
- string GetKeyword();
- void ConsumeEquals();
- string GetValue();
+ bool IsEof { get; }
+ Result GetKeyword();
+ Result ConsumeEquals();
+ Result GetValue();
}
diff --git a/pgLabII.PgUtils/ConnectionStrings/Pq/KeywordMapping.cs b/pgLabII.PgUtils/ConnectionStrings/Pq/KeywordMapping.cs
deleted file mode 100644
index 995134e..0000000
--- a/pgLabII.PgUtils/ConnectionStrings/Pq/KeywordMapping.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace pgLabII.PgUtils.ConnectionStrings.Pq;
-
-enum Keyword
-{
- Host,
- HostAddr,
- Port,
- DatabaseName,
- UserName,
- Password,
-}
-
diff --git a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParser.cs b/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParser.cs
index 1cd51a8..cb83332 100644
--- a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParser.cs
+++ b/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParser.cs
@@ -1,8 +1,12 @@
using System.Collections.ObjectModel;
+using FluentResults;
using Npgsql;
namespace pgLabII.PgUtils.ConnectionStrings;
+///
+/// Parser for converting a libpq style connection string into a dictionary of key value pairs
+///
public ref struct PqConnectionStringParser
{
// Note possible keywords
@@ -51,64 +55,38 @@ public ref struct PqConnectionStringParser
).Parse();
}
- private readonly IPqConnectionStringTokenizer tokenizer;
- private readonly Dictionary result = new();
+ private readonly IPqConnectionStringTokenizer _tokenizer;
+ private readonly Dictionary _result = new();
public PqConnectionStringParser(IPqConnectionStringTokenizer tokenizer)
{
- this.tokenizer = tokenizer;
+ this._tokenizer = tokenizer;
}
public IDictionary Parse()
{
- result.Clear();
+ _result.Clear();
- while (!tokenizer.Eof)
+ while (!_tokenizer.IsEof)
ParsePair();
- return result;
+ return _result;
}
- private void ParsePair()
+ private Result ParsePair()
{
- string kw = tokenizer.GetKeyword();
- tokenizer.ConsumeEquals();
- string v = tokenizer.GetValue();
- result.Add(kw, v);
- //switch (kw)
- //{
- // case "host":
- // case "hostaddr":
- // result.Host = v.ToString();
- // break;
- // case "port":
- // result.Port = int.Parse(v);
- // break;
- // case "dbname":
- // result.Database = v.ToString();
- // break;
- // case "user":
- // result.Username = v.ToString();
- // break;
- // case "password":
- // result.Password = v.ToString();
- // break;
- // case "connect_timeout":
- // result.Timeout = int.Parse(v);
- // break;
- // case "application_name":
- // result.ApplicationName = v.ToString();
- // break;
- // case "options":
- // result.Options = v.ToString();
- // break;
- // case "sslmode":
- // result.SslMode = ToSslMode(v);
- // break;
- // default:
- // // Todo what do we do with values we do not support/recognize?
- // break;
- //}
+ var kwResult = _tokenizer.GetKeyword();
+ if (kwResult.IsFailed)
+ return kwResult.ToResult();
+ var result = _tokenizer.ConsumeEquals();
+ if (result.IsFailed)
+ return result;
+ var valResult = _tokenizer.GetValue();
+ if (valResult.IsFailed)
+ return valResult.ToResult();
+
+ _result.Add(kwResult.Value, valResult.Value);
+ return Result.Ok();
}
private SslMode ToSslMode(ReadOnlySpan v)
diff --git a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParserException.cs b/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParserException.cs
deleted file mode 100644
index 0a80ba6..0000000
--- a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringParserException.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace pgLabII.PgUtils.ConnectionStrings;
-
-public class PqConnectionStringParserException : Exception
-{
- public PqConnectionStringParserException()
- {
- }
-
- public PqConnectionStringParserException(string? message) : base(message)
- {
- }
-
- public PqConnectionStringParserException(string? message, Exception? innerException) : base(message, innerException)
- {
- }
-}
diff --git a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringTokenizer.cs b/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringTokenizer.cs
index b8d7107..fd46bb8 100644
--- a/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringTokenizer.cs
+++ b/pgLabII.PgUtils/ConnectionStrings/Pq/PqConnectionStringTokenizer.cs
@@ -1,4 +1,5 @@
using System.Text;
+using FluentResults;
using static System.Net.Mime.MediaTypeNames;
namespace pgLabII.PgUtils.ConnectionStrings;
@@ -8,7 +9,7 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
private readonly string input;
private int position = 0;
- public bool Eof
+ public bool IsEof
{
get
{
@@ -23,37 +24,38 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
position = 0;
}
- public string GetKeyword()
+ public Result GetKeyword()
{
- if (Eof)
- throw new PqConnectionStringParserException($"Unexpected end of file was expecting a keyword at position {position}");
+ if (IsEof)
+ return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
return GetString(forKeyword: true);
}
- public void ConsumeEquals()
+ public Result ConsumeEquals()
{
ConsumeWhitespace();
if (position < input.Length && input[position] == '=')
{
position++;
+ return Result.Ok();
}
else
- throw new PqConnectionStringParserException($"Was expecting '=' after keyword at position {position}");
+ return Result.Fail($"Was expecting '=' after keyword at position {position}");
}
- public string GetValue()
+ public Result GetValue()
{
- if (Eof)
- throw new PqConnectionStringParserException($"Unexpected end of file was expecting a keyword at position {position}");
+ if (IsEof)
+ return Result.Fail($"Unexpected end of file was expecting a keyword at position {position}");
return GetString(forKeyword: false);
}
- private string GetString(bool forKeyword)
+ private Result GetString(bool forKeyword)
{
if (forKeyword && input[position] == '=')
- throw new PqConnectionStringParserException($"Unexpected '=' was expecting keyword at position {position}");
+ return Result.Fail($"Unexpected '=' was expecting keyword at position {position}");
if (input[position] == '\'')
return ParseQuotedText();
@@ -75,7 +77,7 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
return input.Substring(start, position - start);
}
- private string ParseQuotedText()
+ private Result ParseQuotedText()
{
bool escape = false;
StringBuilder sb = new();
@@ -93,7 +95,7 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
escape = false;
break;
default:
- throw new PqConnectionStringParserException($"Invalid escape sequence at position {position}");
+ return Result.Fail($"Invalid escape sequence at position {position}");
}
}
else
@@ -113,6 +115,6 @@ public class PqConnectionStringTokenizer : IPqConnectionStringTokenizer
}
}
}
- throw new PqConnectionStringParserException($"Missing end quote on value starting at {start}");
+ return Result.Fail($"Missing end quote on value starting at {start}");
}
}
diff --git a/pgLabII.PgUtils/pgLabII.PgUtils.csproj b/pgLabII.PgUtils/pgLabII.PgUtils.csproj
index d44f26c..f70bcc6 100644
--- a/pgLabII.PgUtils/pgLabII.PgUtils.csproj
+++ b/pgLabII.PgUtils/pgLabII.PgUtils.csproj
@@ -8,6 +8,7 @@
+
diff --git a/pgLabII.iOS/AppDelegate.cs b/pgLabII.iOS/AppDelegate.cs
deleted file mode 100644
index 2e21b1f..0000000
--- a/pgLabII.iOS/AppDelegate.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Foundation;
-using UIKit;
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.iOS;
-using Avalonia.Media;
-using Avalonia.ReactiveUI;
-
-namespace pgLabII.iOS;
-
-// The UIApplicationDelegate for the application. This class is responsible for launching the
-// User Interface of the application, as well as listening (and optionally responding) to
-// application events from iOS.
-[Register("AppDelegate")]
-#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
-public partial class AppDelegate : AvaloniaAppDelegate
-#pragma warning restore CA1711 // Identifiers should not have incorrect suffix
-{
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder)
- .WithInterFont()
- .UseReactiveUI();
- }
-}
diff --git a/pgLabII.iOS/Entitlements.plist b/pgLabII.iOS/Entitlements.plist
deleted file mode 100644
index 0c67376..0000000
--- a/pgLabII.iOS/Entitlements.plist
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/pgLabII.iOS/Info.plist b/pgLabII.iOS/Info.plist
deleted file mode 100644
index 9ef8ec5..0000000
--- a/pgLabII.iOS/Info.plist
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
- CFBundleDisplayName
- pgLabII
- CFBundleIdentifier
- companyName.pgLabII
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1.0
- LSRequiresIPhoneOS
-
- MinimumOSVersion
- 13.0
- UIDeviceFamily
-
- 1
- 2
-
- UILaunchStoryboardName
- LaunchScreen
- UIRequiredDeviceCapabilities
-
- armv7
-
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
-
-
diff --git a/pgLabII.iOS/Main.cs b/pgLabII.iOS/Main.cs
deleted file mode 100644
index 01ff99a..0000000
--- a/pgLabII.iOS/Main.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using UIKit;
-
-namespace pgLabII.iOS;
-
-public class Application
-{
- // This is the main entry point of the application.
- static void Main(string[] args)
- {
- // if you want to use a different Application Delegate class from "AppDelegate"
- // you can specify it here.
- UIApplication.Main(args, null, typeof(AppDelegate));
- }
-}
diff --git a/pgLabII.iOS/Resources/LaunchScreen.xib b/pgLabII.iOS/Resources/LaunchScreen.xib
deleted file mode 100644
index f610a15..0000000
--- a/pgLabII.iOS/Resources/LaunchScreen.xib
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/pgLabII.iOS/pgLabII.iOS.csproj b/pgLabII.iOS/pgLabII.iOS.csproj
deleted file mode 100644
index 2e94356..0000000
--- a/pgLabII.iOS/pgLabII.iOS.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- Exe
- net8.0-ios
- 13.0
- enable
-
-
-
-
-
-
-
-
-
-
diff --git a/pgLabII/App.axaml b/pgLabII/App.axaml
index 9af82b4..ef1df69 100644
--- a/pgLabII/App.axaml
+++ b/pgLabII/App.axaml
@@ -14,6 +14,7 @@
+
\ No newline at end of file
diff --git a/pgLabII/Contracts/IEditHistoryManager.cs b/pgLabII/Contracts/IEditHistoryManager.cs
new file mode 100644
index 0000000..d93ddf8
--- /dev/null
+++ b/pgLabII/Contracts/IEditHistoryManager.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using pgLabII.Model;
+using pgLabII.Views.Controls;
+
+namespace pgLabII;
+
+public interface IEditHistoryManager
+{
+ void AddEdit(int offset, string insertedText, string removedText);
+ void FlushBuffer();
+ void SaveToDatabase();
+ IReadOnlyList GetHistory();
+}
diff --git a/pgLabII/EditHistoryManager/EditBuffer.cs b/pgLabII/EditHistoryManager/EditBuffer.cs
new file mode 100644
index 0000000..60e8f54
--- /dev/null
+++ b/pgLabII/EditHistoryManager/EditBuffer.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Text;
+
+namespace pgLabII;
+
+public class EditBuffer
+{
+ public int CurrentOffset { get; set; }
+ public StringBuilder InsertedText { get; set; } = new();
+ public StringBuilder RemovedText { get; set; } = new();
+ public DateTime LastEdit { get; set; }
+}
diff --git a/pgLabII/EditHistoryManager/EditHistoryManager.cs b/pgLabII/EditHistoryManager/EditHistoryManager.cs
new file mode 100644
index 0000000..45ba53d
--- /dev/null
+++ b/pgLabII/EditHistoryManager/EditHistoryManager.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Timers;
+using pgLabII.Infra;
+using pgLabII.Model;
+
+namespace pgLabII;
+
+public class EditHistoryManager : IEditHistoryManager
+{
+ private readonly LocalDb _db;
+ private readonly Document _document;
+ private readonly List _pendingEdits = new();
+ private EditBuffer? _currentBuffer;
+ private const int BufferTimeoutMs = 500;
+ private readonly Timer _idleFlushTimer;
+ private readonly object _sync = new object();
+
+ public EditHistoryManager(LocalDb db, Document document)
+ {
+ _db = db;
+ _document = document;
+
+ _idleFlushTimer = new Timer(BufferTimeoutMs)
+ {
+ AutoReset = false,
+ Enabled = false
+ };
+ _idleFlushTimer.Elapsed += OnIdleFlushTimerElapsed;
+ }
+
+ public void Dispose()
+ {
+ _idleFlushTimer.Elapsed -= OnIdleFlushTimerElapsed;
+ _idleFlushTimer.Dispose();
+ // Final flush on dispose to avoid losing last edits
+ lock (_sync)
+ {
+ FlushBuffer();
+ if (_pendingEdits.Count > 0)
+ {
+ SaveToDatabase();
+ }
+ }
+ }
+
+ public void AddEdit(int offset, string insertedText, string removedText)
+ {
+ var now = DateTime.UtcNow;
+ bool isSimpleEdit = (insertedText.Length <= 1 && removedText.Length <= 1);
+
+ if (TryCombineWithBuffer(offset, insertedText, removedText, now, isSimpleEdit))
+ {
+ RestartIdleTimer();
+ return;
+ }
+
+ FlushBuffer();
+ CreateNewBuffer(offset, insertedText, removedText, now);
+ RestartIdleTimer();
+ }
+
+ public void FlushBuffer()
+ {
+ if (_currentBuffer == null) return;
+
+ var edit = new EditHistoryEntry
+ {
+ DocumentId = _document.Id,
+ Offset = _currentBuffer.CurrentOffset,
+ InsertedText = _currentBuffer.InsertedText.ToString(),
+ RemovedText = _currentBuffer.RemovedText.ToString(),
+ Timestamp = DateTime.UtcNow
+ };
+
+ _pendingEdits.Add(edit);
+ _currentBuffer = null;
+
+ if (_pendingEdits.Count >= 10)
+ {
+ SaveToDatabase();
+ }
+ }
+
+ public void SaveToDatabase()
+ {
+ _db.EditHistory.AddRange(_pendingEdits);
+ _db.SaveChanges();
+ _pendingEdits.Clear();
+ }
+
+ public IReadOnlyList GetHistory() => _pendingEdits.AsReadOnly();
+
+ private bool TryCombineWithBuffer(int offset, string insertedText, string removedText, DateTime now, bool isSimpleEdit)
+ {
+ // Try to combine with current buffer if it's a simple edit
+ if (_currentBuffer != null &&
+ isSimpleEdit &&
+ (now - _currentBuffer.LastEdit).TotalMilliseconds < BufferTimeoutMs)
+ {
+ // For consecutive inserts
+ if (insertedText.Length == 1 &&
+ offset == _currentBuffer.CurrentOffset + _currentBuffer.InsertedText.Length)
+ {
+ _currentBuffer.InsertedText.Append(insertedText);
+ _currentBuffer.LastEdit = now;
+ return true;
+ }
+
+ // For consecutive deletes or backspaces
+ if (removedText.Length == 1 &&
+ (offset == _currentBuffer.CurrentOffset - 1 || // Backspace
+ offset == _currentBuffer.CurrentOffset)) // Delete
+ {
+ if (offset < _currentBuffer.CurrentOffset)
+ {
+ _currentBuffer.RemovedText.Insert(0, removedText);
+ _currentBuffer.CurrentOffset = offset;
+ }
+ else
+ {
+ _currentBuffer.RemovedText.Append(removedText);
+ }
+ _currentBuffer.LastEdit = now;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void CreateNewBuffer(int offset, string insertedText, string removedText, DateTime now)
+ {
+ _currentBuffer = new EditBuffer
+ {
+ CurrentOffset = offset,
+ LastEdit = now
+ };
+ _currentBuffer.InsertedText.Append(insertedText);
+ _currentBuffer.RemovedText.Append(removedText);
+ }
+
+ private void RestartIdleTimer()
+ {
+ _idleFlushTimer.Stop();
+ _idleFlushTimer.Interval = BufferTimeoutMs;
+ _idleFlushTimer.Start();
+ }
+
+ private void OnIdleFlushTimerElapsed(object? sender, ElapsedEventArgs e)
+ {
+ lock (_sync)
+ {
+ // On inactivity, flush any buffered edit and persist remaining pending edits.
+ FlushBuffer();
+ if (_pendingEdits.Count > 0)
+ {
+ SaveToDatabase();
+ }
+ }
+ }
+
+}
diff --git a/pgLabII/EditHistoryManager/EditOperation.cs b/pgLabII/EditHistoryManager/EditOperation.cs
new file mode 100644
index 0000000..b0fb10c
--- /dev/null
+++ b/pgLabII/EditHistoryManager/EditOperation.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace pgLabII;
+
+public class EditOperation
+{
+ public required int Offset { get; set; }
+ public required string InsertedText { get; set; }
+ public required string RemovedText { get; set; }
+ public DateTime Timestamp { get; set; }
+}
diff --git a/pgLabII/Infra/LocalDb.cs b/pgLabII/Infra/LocalDb.cs
index cb4c492..7dd09b4 100644
--- a/pgLabII/Infra/LocalDb.cs
+++ b/pgLabII/Infra/LocalDb.cs
@@ -6,10 +6,12 @@ using pgLabII.Model;
namespace pgLabII.Infra;
-internal class LocalDb : DbContext
+public class LocalDb : DbContext
{
public DbSet ServerConfigurations => Set();
-
+ public DbSet Documents => Set();
+ public DbSet EditHistory => Set();
+
public string DbPath { get; }
public LocalDb()
@@ -26,9 +28,10 @@ internal class LocalDb : DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
-
-
new ServerConfigurationEntityConfiguration().Configure(modelBuilder.Entity());
+ new ServerUserEntityConfiguration().Configure(modelBuilder.Entity());
+ new DocumentEntityConfiguration().Configure(modelBuilder.Entity());
+ new EditHistoryEntityConfiguration().Configure(modelBuilder.Entity());
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
@@ -56,3 +59,26 @@ public class ServerUserEntityConfiguration : IEntityTypeConfiguration e.Id);
}
}
+
+public class DocumentEntityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder b)
+ {
+ b.HasKey(e => e.Id);
+ b.Property(e => e.OriginalFilename).IsRequired();
+ b.Property(e => e.BaseCopyFilename).IsRequired();
+ b.HasMany(e => e.EditHistory)
+ .WithOne(e => e.Document)
+ .HasForeignKey(e => e.DocumentId);
+ }
+}
+
+public class EditHistoryEntityConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder b)
+ {
+ b.HasKey(e => e.Id);
+ b.Property(e => e.Timestamp).IsRequired();
+ b.HasIndex(e => new { e.DocumentId, e.Timestamp});
+ }
+}
diff --git a/pgLabII/Model/Document.cs b/pgLabII/Model/Document.cs
new file mode 100644
index 0000000..01e3f62
--- /dev/null
+++ b/pgLabII/Model/Document.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+
+namespace pgLabII.Model;
+
+public class Document
+{
+ public int Id { get; set; }
+ public required string OriginalFilename { get; set; }
+ public required string BaseCopyFilename { get; set; }
+ public DateTime Created { get; set; }
+ public DateTime LastModified { get; set; }
+
+ public List EditHistory { get; set; } = [];
+}
diff --git a/pgLabII/Model/EditHistoryEntry.cs b/pgLabII/Model/EditHistoryEntry.cs
new file mode 100644
index 0000000..750d2c9
--- /dev/null
+++ b/pgLabII/Model/EditHistoryEntry.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace pgLabII.Model;
+
+public class EditHistoryEntry
+{
+ public int Id { get; set; }
+ public int DocumentId { get; set; }
+ public Document Document { get; set; } = null!;
+ public DateTime Timestamp { get; set; }
+ public int Offset { get; set; }
+ public string InsertedText { get; set; } = string.Empty;
+ public string RemovedText { get; set; } = string.Empty;
+}
diff --git a/pgLabII/ServiceCollectionExtensions.cs b/pgLabII/ServiceCollectionExtensions.cs
index aab7efc..babfa67 100644
--- a/pgLabII/ServiceCollectionExtensions.cs
+++ b/pgLabII/ServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@
using pgLabII.Infra;
using pgLabII.ViewModels;
using pgLabII.Views;
+using pgLabII.Views.Controls;
namespace pgLabII;
@@ -13,6 +14,8 @@ internal static class ServiceCollectionExtensions
collection.AddTransient();
collection.AddTransient();
collection.AddScoped();
+ collection.AddTransient();
+ collection.AddTransient();
}
}
diff --git a/pgLabII/Services/DocumentSession.cs b/pgLabII/Services/DocumentSession.cs
new file mode 100644
index 0000000..221d479
--- /dev/null
+++ b/pgLabII/Services/DocumentSession.cs
@@ -0,0 +1,6 @@
+namespace pgLabII.Services;
+
+public class DocumentSession
+{
+
+}
diff --git a/pgLabII/Services/DocumentSessionFactory.cs b/pgLabII/Services/DocumentSessionFactory.cs
new file mode 100644
index 0000000..a16640b
--- /dev/null
+++ b/pgLabII/Services/DocumentSessionFactory.cs
@@ -0,0 +1,18 @@
+using System;
+using pgLabII.Infra;
+
+namespace pgLabII.Services;
+
+public class DocumentSessionFactory(
+ LocalDb localDb)
+{
+ public DocumentSession CreateNew()
+ {
+ throw new NotImplementedException();
+ }
+
+ public DocumentSession Open(string path)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/pgLabII/ViewModels/CodeEditorViewModel.cs b/pgLabII/ViewModels/CodeEditorViewModel.cs
new file mode 100644
index 0000000..082233b
--- /dev/null
+++ b/pgLabII/ViewModels/CodeEditorViewModel.cs
@@ -0,0 +1,10 @@
+using AvaloniaEdit.Document;
+using ReactiveUI.SourceGenerators;
+
+namespace pgLabII.ViewModels;
+
+public partial class CodeEditorViewModel : ViewModelBase
+{
+ [Reactive] private TextDocument _document = new();
+
+}
diff --git a/pgLabII/Views/Controls/CodeEditorView.axaml b/pgLabII/Views/Controls/CodeEditorView.axaml
new file mode 100644
index 0000000..9e63fa7
--- /dev/null
+++ b/pgLabII/Views/Controls/CodeEditorView.axaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/pgLabII/Views/Controls/CodeEditorView.axaml.cs b/pgLabII/Views/Controls/CodeEditorView.axaml.cs
new file mode 100644
index 0000000..c74919f
--- /dev/null
+++ b/pgLabII/Views/Controls/CodeEditorView.axaml.cs
@@ -0,0 +1,50 @@
+using System.IO;
+using Avalonia;
+using Avalonia.Controls;
+using AvaloniaEdit.Document;
+using AvaloniaEdit.TextMate;
+using TextMateSharp.Grammars;
+
+namespace pgLabII.Views.Controls;
+
+public partial class CodeEditorView : UserControl
+{
+ private TextMate.Installation? _textMate;
+ private readonly IEditHistoryManager _editHistoryManager = new EditHistoryManager(
+ new(),
+ new()
+ {
+ OriginalFilename = "",
+ BaseCopyFilename = "",
+ });
+
+ public CodeEditorView()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ var registryOptions = new RegistryOptions(ThemeName.DarkPlus);
+ _textMate = Editor.InstallTextMate(registryOptions);
+ _textMate.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".sql").Id));
+
+ Editor.Document.Changed += DocumentChanged;
+ }
+
+
+ private void OnSaveClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ // Example save logic
+ File.WriteAllText("final.sql", Editor.Text);
+ }
+
+
+ private void DocumentChanged(object? sender, DocumentChangeEventArgs e)
+ {
+ _editHistoryManager.AddEdit(e.Offset, e.InsertedText.Text, e.RemovedText.Text);
+ }
+}
+
diff --git a/pgLabII/Views/ServerListView.axaml.cs b/pgLabII/Views/ServerListView.axaml.cs
index d2d8eeb..eb5c023 100644
--- a/pgLabII/Views/ServerListView.axaml.cs
+++ b/pgLabII/Views/ServerListView.axaml.cs
@@ -8,6 +8,7 @@ public partial class ServerListView : UserControl
public ServerListView()
{
//DataContext = new ServerListViewModel();
+
InitializeComponent();
}
diff --git a/pgLabII/Views/SingleDatabaseWindow.axaml b/pgLabII/Views/SingleDatabaseWindow.axaml
index cd0641c..3f3549b 100644
--- a/pgLabII/Views/SingleDatabaseWindow.axaml
+++ b/pgLabII/Views/SingleDatabaseWindow.axaml
@@ -3,6 +3,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:pgLabII.ViewModels"
+ xmlns:controls="clr-namespace:pgLabII.Views.Controls"
+ xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="pgLabII.Views.SingleDatabaseWindow"
Title="SingleDatabaseWindow"
@@ -45,11 +47,12 @@
-
-
-
-
-
+
+
+
+
+
+
diff --git a/pgLabII/pgLabII.csproj b/pgLabII/pgLabII.csproj
index 5c03e58..f2c36f9 100644
--- a/pgLabII/pgLabII.csproj
+++ b/pgLabII/pgLabII.csproj
@@ -1,6 +1,6 @@
- net8.0
+ net9.0
enable
latest
true
@@ -13,6 +13,7 @@
+
@@ -22,6 +23,8 @@
All
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/pgLabII/pgLabII.csproj.DotSettings b/pgLabII/pgLabII.csproj.DotSettings
index 127787e..c7563b2 100644
--- a/pgLabII/pgLabII.csproj.DotSettings
+++ b/pgLabII/pgLabII.csproj.DotSettings
@@ -1,2 +1,3 @@
- True
\ No newline at end of file
+ True
+ False
\ No newline at end of file