Fixed mixed line breaks in saved code files and tests failing on Windows (#1149)

This commit is contained in:
Paul Koudelka authored and GitHub committed 2026-11-03 14:43:26 +01:00
1 parent 770c16e20a
commit f864359290
4 files changed
+39 -4

No files matched your search

@@ -118,14 +118,18 @@ public static class IContentExtensions
/// <remarks>
/// Markdig hands us the lines of a block without a line break after the last one. Compilers
/// and shells read such a file, but Git marks it, and some tools drop the last line altogether.
/// The lines end with LF as the model wrote them, except where the language needs CR LF.
/// Between the lines, Markdig keeps the line breaks the answer came with: LF, CR LF, or a lone
/// CR, the three that Markdown knows. Every line ends with LF, except where the language needs
/// CR LF, so that the file never mixes two kinds. Only these three count as line breaks, unlike
/// in ReplaceLineEndings: a form feed or a U+2028 may stand inside a string of the code.
/// </remarks>
/// <param name="file">The code block.</param>
/// <returns>The content of the file to write.</returns>
private static string ToSourceCodeFile(MessageFile file)
{
var code = file.Content.EndsWith('\n') ? file.Content : $"{file.Content}\n";
return file.Language.NeedsWindowsLineEndings ? code.ReplaceLineEndings("\r\n") : code;
var text = file.Content.Replace("\r\n", "\n").Replace('\r', '\n');
var code = text.EndsWith('\n') ? text : $"{text}\n";
return file.Language.NeedsWindowsLineEndings ? code.Replace("\n", "\r\n") : code;
}
/// <summary>
@@ -16,6 +16,7 @@
- Fixed configuration plugins being unable to lock the preselected provider, the preselected profile, or the transcription provider to none. An empty value now does this, as the example configuration plugin describes. Older versions of AI Studio ignore the empty value and leave the choice to your users.
- Fixed AI Studio loading none of your organization's configurations when one place, such as the Windows registry, named a configuration with an invalid ID. AI Studio now skips that entry and uses the configurations from the other places.
- Fixed chats being stored although you chose "Store chats manually" or "Disable workspaces". AI Studio now stores a chat only when you click "Save chat", or not at all while workspaces are disabled. In both modes, leaving a chat whose answer is still being written asks you first and stops that answer. Chats outside of workspaces that earlier versions stored this way appear among your disappearing chats once you use workspaces again.
- Fixed saving a code block as a file, where the last line could end differently from all other lines, for example, on Windows. All lines of the file now end the same way.
- Upgraded several libraries to improve security.
- Upgraded Tauri to v2.12.5.
- Upgraded MudBlazor to v9.12.0.
@@ -117,7 +117,8 @@ public sealed class SwotCategoryAnalysisResponseTests
public void TheSystemPromptAssignsExactlyOneCategory(string categoryName, string expectedDescription)
{
var category = Enum.Parse<SwotAnalysisCategory>(categoryName);
var prompt = SwotCategoryAnalysis.BuildSystemPrompt(category, "Answer in German.");
// The prompt takes the line breaks of its source file, which depend on the checkout:
var prompt = SwotCategoryAnalysis.BuildSystemPrompt(category, "Answer in German.").ReplaceLineEndings("\n");
using (Assert.EnterMultipleScope())
{
+29
View File
@@ -251,6 +251,35 @@ public sealed class IContentExtensionsTests
Assert.That(TextWith(string.Empty).ToExportContent(file), Is.EqualTo("print(42)\n"));
}
[TestCase("import sys\r\nprint(sys.argv)")]
[TestCase("import sys\r\nprint(sys.argv)\r\n")]
[TestCase("import sys\rprint(sys.argv)")]
public void CodeNeverMixesLineEndings(string code)
{
var file = new MessageFile(1, Position: 0, string.Empty, FileExportFormat.SOURCE_CODE, SourceCodeLanguages.FromCodeFenceLanguage("python"), code);
Assert.That(TextWith(string.Empty).ToExportContent(file), Is.EqualTo("import sys\nprint(sys.argv)\n"), "Every line ends with LF, whatever line breaks the code came with.");
}
[TestCase("@echo off\necho done")]
[TestCase("@echo off\r\necho done\r\n")]
[TestCase("@echo off\recho done")]
public void ABatchFileNeverMixesLineEndings(string code)
{
var file = new MessageFile(1, Position: 0, string.Empty, FileExportFormat.SOURCE_CODE, SourceCodeLanguages.FromCodeFenceLanguage("bat"), code);
Assert.That(TextWith(string.Empty).ToExportContent(file), Is.EqualTo("@echo off\r\necho done\r\n"), "Every line ends with CR LF, whatever line breaks the code came with.");
}
[TestCase("python", "print('a\fb\u2028c')", "print('a\fb\u2028c')\n")]
[TestCase("bat", "echo a\fb\u2028c", "echo a\fb\u2028c\r\n")]
public void AFormFeedOrALineSeparatorStaysInItsLine(string language, string code, string expected)
{
var file = new MessageFile(1, Position: 0, string.Empty, FileExportFormat.SOURCE_CODE, SourceCodeLanguages.FromCodeFenceLanguage(language), code);
Assert.That(TextWith(string.Empty).ToExportContent(file), Is.EqualTo(expected), "Markdown breaks lines only at LF and CR, and so does the file.");
}
[Test]
public void ABatchFileEndsItsLinesTheWindowsWay()
{