using System.Text;
using XFESystem.Desktop;
namespace XFESystem.Browser;
public sealed partial class BrowserService
{
private Func<bool>? operationCancellation;
internal void SetOperationCancellation(Func<bool>? requested) => operationCancellation = requested;
private bool OperationCancelled => operationCancellation?.Invoke() == true;
private void CheckOperationCancellation()
{
if (OperationCancelled) throw new OperationCanceledException("The editor operation was cancelled at a resource/script boundary.");
}
/// <summary>Read real model and rendered view state. This neither calls
/// render(), fabricates tokens nor discards the remaining browser tasks.</summary>
internal BrowserDisplayList? ReadyEditorLayout(string source, string path, int width, int height)
{
CheckOperationCancellation();
if (ReadEditorText() != source || runtime is null || document is null) return null;
string language = path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ? "csharp" : "javascript";
if (runtime.ReadString("xfeMonacoEditor.getModel().getLanguageId()") != language) return null;
var lines = document.QuerySelectorAll(".view-lines .view-line");
if (lines.Length == 0) return null;
var expected = source.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
if (lines.Length > expected.Length) return null;
bool visibleSource = false, renderedToken = false;
for (int index = 0; index < lines.Length; index++)
{
// Monaco expands tabs/space placeholders in the view. Compare the
// actual token characters, while the full model readback above
// separately validates every original character and line ending.
string actual = ViewCharacters(lines[index].TextContent);
string original = ViewCharacters(expected[index]);
if (actual != original) return null;
if (actual.Length != 0) visibleSource = true;
if (lines[index].QuerySelectorAll("span[class]").Any(span => span.ClassList.Any(name => name.StartsWith("mtk", StringComparison.Ordinal)))) renderedToken = true;
}
if (!string.IsNullOrWhiteSpace(source) && (!visibleSource || !renderedToken)) return null;
var layout = Layout(new(width, height));
var viewport = new BrowserRect(0, 0, width, height);
bool textPaint = layout.Commands.Any(command => command.Kind == BrowserDrawKind.Text && command.Opacity > 0 &&
!string.IsNullOrWhiteSpace(command.Text) && command.Node?.ParentElement?.Closest(".view-line") is not null &&
command.Bounds.Intersect(viewport) is { Width: > 0, Height: > 0 });
if (!string.IsNullOrWhiteSpace(source) && !textPaint) return null;
return layout;
}
private static string ViewCharacters(string text)
{
var value = new StringBuilder(text.Length);
foreach (char character in text)
if (character is not (' ' or '\t' or '\u00a0' or '\u200b')) value.Append(character);
return value.ToString();
}
}
using System.Text;
using XFESystem.Desktop;
namespace XFESystem.Browser;
public sealed partial class BrowserService
{
private Func<bool>? operationCancellation;
internal void SetOperationCancellation(Func<bool>? requested) => operationCancellation = requested;
private bool OperationCancelled => operationCancellation?.Invoke() == true;
private void CheckOperationCancellation()
{
if (OperationCancelled) throw new OperationCanceledException("The editor operation was cancelled at a resource/script boundary.");
}
/// <summary>Read real model and rendered view state. This neither calls
/// render(), fabricates tokens nor discards the remaining browser tasks.</summary>
internal BrowserDisplayList? ReadyEditorLayout(string source, string path, int width, int height)
{
CheckOperationCancellation();
if (ReadEditorText() != source || runtime is null || document is null) return null;
string language = path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) ? "csharp" : "javascript";
if (runtime.ReadString("xfeMonacoEditor.getModel().getLanguageId()") != language) return null;
var lines = document.QuerySelectorAll(".view-lines .view-line");
if (lines.Length == 0) return null;
var expected = source.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
if (lines.Length > expected.Length) return null;
bool visibleSource = false, renderedToken = false;
for (int index = 0; index < lines.Length; index++)
{
// Monaco expands tabs/space placeholders in the view. Compare the
// actual token characters, while the full model readback above
// separately validates every original character and line ending.
string actual = ViewCharacters(lines[index].TextContent);
string original = ViewCharacters(expected[index]);
if (actual != original) return null;
if (actual.Length != 0) visibleSource = true;
if (lines[index].QuerySelectorAll("span[class]").Any(span => span.ClassList.Any(name => name.StartsWith("mtk", StringComparison.Ordinal)))) renderedToken = true;
}
if (!string.IsNullOrWhiteSpace(source) && (!visibleSource || !renderedToken)) return null;
var layout = Layout(new(width, height));
var viewport = new BrowserRect(0, 0, width, height);
bool textPaint = layout.Commands.Any(command => command.Kind == BrowserDrawKind.Text && command.Opacity > 0 &&
!string.IsNullOrWhiteSpace(command.Text) && command.Node?.ParentElement?.Closest(".view-line") is not null &&
command.Bounds.Intersect(viewport) is { Width: > 0, Height: > 0 });
if (!string.IsNullOrWhiteSpace(source) && !textPaint) return null;
return layout;
}
private static string ViewCharacters(string text)
{
var value = new StringBuilder(text.Length);
foreach (char character in text)
if (character is not (' ' or '\t' or '\u00a0' or '\u200b')) value.Append(character);
return value.ToString();
}
}