diff --git a/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts b/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts index 68d6a949..a6e6d7d7 100644 --- a/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts +++ b/src/Apps/NetPad.Apps.App/App/src/core/@domain/api.ts @@ -2669,12 +2669,19 @@ export interface IAppDependencyCheckResult { isSupportedDotNetEfToolInstalled: boolean; } +/** An implementation of semantic versioning (https://semver.org) */ export class SemanticVersion implements ISemanticVersion { + /** The major version number, never negative. */ major!: number; + /** The minor version number, never negative. */ minor!: number; + /** The patch version, -1 if not specified. */ patch!: number; + /** PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. */ preReleaseLabel?: string | undefined; + /** BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. */ buildLabel?: string | undefined; + /** String representation. */ string!: string; constructor(data?: ISemanticVersion) { @@ -2723,12 +2730,19 @@ export class SemanticVersion implements ISemanticVersion { } } +/** An implementation of semantic versioning (https://semver.org) */ export interface ISemanticVersion { + /** The major version number, never negative. */ major: number; + /** The minor version number, never negative. */ minor: number; + /** The patch version, -1 if not specified. */ patch: number; + /** PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. */ preReleaseLabel?: string | undefined; + /** BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. */ buildLabel?: string | undefined; + /** String representation. */ string: string; } @@ -3188,6 +3202,7 @@ export class SyntaxNodeOrTokenSlim implements ISyntaxNodeOrTokenSlim { isToken!: boolean; isNode!: boolean; kind!: SyntaxKind; + type!: string; span!: LinePositionSpan; isMissing!: boolean; valueText?: string | undefined; @@ -3215,6 +3230,7 @@ export class SyntaxNodeOrTokenSlim implements ISyntaxNodeOrTokenSlim { this.isToken = _data["isToken"]; this.isNode = _data["isNode"]; this.kind = _data["kind"]; + this.type = _data["type"]; this.span = _data["span"] ? LinePositionSpan.fromJS(_data["span"]) : new LinePositionSpan(); this.isMissing = _data["isMissing"]; this.valueText = _data["valueText"]; @@ -3248,6 +3264,7 @@ export class SyntaxNodeOrTokenSlim implements ISyntaxNodeOrTokenSlim { data["isToken"] = this.isToken; data["isNode"] = this.isNode; data["kind"] = this.kind; + data["type"] = this.type; data["span"] = this.span ? this.span.toJSON() : undefined; data["isMissing"] = this.isMissing; data["valueText"] = this.valueText; @@ -3281,6 +3298,7 @@ export interface ISyntaxNodeOrTokenSlim { isToken: boolean; isNode: boolean; kind: SyntaxKind; + type: string; span: LinePositionSpan; isMissing: boolean; valueText?: string | undefined; @@ -4346,7 +4364,6 @@ export interface ICreateScriptDto { export class RunOptionsDto implements IRunOptionsDto { specificCodeToRun?: string | undefined; - additionalCode?: SourceCodeDto[] | undefined; constructor(data?: IRunOptionsDto) { if (data) { @@ -4360,11 +4377,6 @@ export class RunOptionsDto implements IRunOptionsDto { init(_data?: any) { if (_data) { this.specificCodeToRun = _data["specificCodeToRun"]; - if (Array.isArray(_data["additionalCode"])) { - this.additionalCode = [] as any; - for (let item of _data["additionalCode"]) - this.additionalCode!.push(SourceCodeDto.fromJS(item)); - } } } @@ -4378,11 +4390,6 @@ export class RunOptionsDto implements IRunOptionsDto { toJSON(data?: any) { data = typeof data === 'object' ? data : {}; data["specificCodeToRun"] = this.specificCodeToRun; - if (Array.isArray(this.additionalCode)) { - data["additionalCode"] = []; - for (let item of this.additionalCode) - data["additionalCode"].push(item.toJSON()); - } return data; } @@ -4396,62 +4403,6 @@ export class RunOptionsDto implements IRunOptionsDto { export interface IRunOptionsDto { specificCodeToRun?: string | undefined; - additionalCode?: SourceCodeDto[] | undefined; -} - -export class SourceCodeDto implements ISourceCodeDto { - usings?: string[] | undefined; - code?: string | undefined; - - constructor(data?: ISourceCodeDto) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - if (Array.isArray(_data["usings"])) { - this.usings = [] as any; - for (let item of _data["usings"]) - this.usings!.push(item); - } - this.code = _data["code"]; - } - } - - static fromJS(data: any): SourceCodeDto { - data = typeof data === 'object' ? data : {}; - let result = new SourceCodeDto(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (Array.isArray(this.usings)) { - data["usings"] = []; - for (let item of this.usings) - data["usings"].push(item); - } - data["code"] = this.code; - return data; - } - - clone(): SourceCodeDto { - const json = this.toJSON(); - let result = new SourceCodeDto(); - result.init(json); - return result; - } -} - -export interface ISourceCodeDto { - usings?: string[] | undefined; - code?: string | undefined; } export type OptimizationLevel = "Debug" | "Release"; @@ -5163,6 +5114,7 @@ export interface IKeyboardShortcutConfiguration { export type KeyCode = "Unknown" | "Backspace" | "Tab" | "Enter" | "ShiftLeft" | "ShiftRight" | "ControlLeft" | "ControlRight" | "AltLeft" | "AltRight" | "Pause" | "CapsLock" | "Escape" | "Space" | "PageUp" | "PageDown" | "End" | "Home" | "ArrowLeft" | "ArrowUp" | "ArrowRight" | "ArrowDown" | "PrintScreen" | "Insert" | "Delete" | "Digit0" | "Digit1" | "Digit2" | "Digit3" | "Digit4" | "Digit5" | "Digit6" | "Digit7" | "Digit8" | "Digit9" | "KeyA" | "KeyB" | "KeyC" | "KeyD" | "KeyE" | "KeyF" | "KeyG" | "KeyH" | "KeyI" | "KeyJ" | "KeyK" | "KeyL" | "KeyM" | "KeyN" | "KeyO" | "KeyP" | "KeyQ" | "KeyR" | "KeyS" | "KeyT" | "KeyU" | "KeyV" | "KeyW" | "KeyX" | "KeyY" | "KeyZ" | "MetaLeft" | "MetaRight" | "ContextMenu" | "Numpad0" | "Numpad1" | "Numpad2" | "Numpad3" | "Numpad4" | "Numpad5" | "Numpad6" | "Numpad7" | "Numpad8" | "Numpad9" | "NumpadMultiply" | "NumpadAdd" | "NumpadSubtract" | "NumpadDecimal" | "NumpadDivide" | "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | "F11" | "F12" | "NumLock" | "ScrollLock" | "Semicolon" | "Equal" | "Comma" | "Minus" | "Period" | "Slash" | "Backquote" | "BracketLeft" | "Backslash" | "BracketRight" | "Quote"; +/** This should be moved to the OmniSharp plugin */ export class OmniSharpOptions implements IOmniSharpOptions { enabled!: boolean; executablePath?: string | undefined; @@ -5227,6 +5179,7 @@ export class OmniSharpOptions implements IOmniSharpOptions { } } +/** This should be moved to the OmniSharp plugin */ export interface IOmniSharpOptions { enabled: boolean; executablePath?: string | undefined; @@ -5713,8 +5666,11 @@ export interface IErrorResult { details?: string | undefined; } +/** A base class for all script output */ export abstract class ScriptOutput implements IScriptOutput { + /** The body of the output. */ body?: any | undefined; + /** The order this output was emitted. A value of 0 indicates no order. */ order!: number; constructor(data?: IScriptOutput) { @@ -5750,11 +5706,15 @@ export abstract class ScriptOutput implements IScriptOutput { } } +/** A base class for all script output */ export interface IScriptOutput { + /** The body of the output. */ body?: any | undefined; + /** The order this output was emitted. A value of 0 indicates no order. */ order: number; } +/** A base class for script output with an HTML-formatted string as the body. */ export abstract class HtmlScriptOutput extends ScriptOutput implements IHtmlScriptOutput { body?: string | undefined; @@ -5786,10 +5746,12 @@ export abstract class HtmlScriptOutput extends ScriptOutput implements IHtmlScri } } +/** A base class for script output with an HTML-formatted string as the body. */ export interface IHtmlScriptOutput extends IScriptOutput { body?: string | undefined; } +/** Results script output represented as HTML. */ export class HtmlResultsScriptOutput extends HtmlScriptOutput implements IHtmlResultsScriptOutput { constructor(data?: IHtmlResultsScriptOutput) { @@ -5821,9 +5783,11 @@ export class HtmlResultsScriptOutput extends HtmlScriptOutput implements IHtmlRe } } +/** Results script output represented as HTML. */ export interface IHtmlResultsScriptOutput extends IHtmlScriptOutput { } +/** Error script output represented as HTML. */ export class HtmlErrorScriptOutput extends HtmlScriptOutput implements IHtmlErrorScriptOutput { constructor(data?: IHtmlErrorScriptOutput) { @@ -5855,9 +5819,11 @@ export class HtmlErrorScriptOutput extends HtmlScriptOutput implements IHtmlErro } } +/** Error script output represented as HTML. */ export interface IHtmlErrorScriptOutput extends IHtmlScriptOutput { } +/** Raw script output represented as HTML. */ export class HtmlRawScriptOutput extends HtmlScriptOutput implements IHtmlRawScriptOutput { constructor(data?: IHtmlRawScriptOutput) { @@ -5889,9 +5855,11 @@ export class HtmlRawScriptOutput extends HtmlScriptOutput implements IHtmlRawScr } } +/** Raw script output represented as HTML. */ export interface IHtmlRawScriptOutput extends IHtmlScriptOutput { } +/** SQL script output represented as HTML. */ export class HtmlSqlScriptOutput extends HtmlScriptOutput implements IHtmlSqlScriptOutput { constructor(data?: IHtmlSqlScriptOutput) { @@ -5923,6 +5891,7 @@ export class HtmlSqlScriptOutput extends HtmlScriptOutput implements IHtmlSqlScr } } +/** SQL script output represented as HTML. */ export interface IHtmlSqlScriptOutput extends IHtmlScriptOutput { } @@ -6018,11 +5987,17 @@ export interface IAppStatusMessagePublishedEvent { message: AppStatusMessage; } +/** Represents a status change in the application. */ export class AppStatusMessage implements IAppStatusMessage { + /** The ID of the script this message relates to. */ scriptId?: string | undefined; + /** The text of this message. */ text!: string; + /** The priority of this message. */ priority!: AppStatusMessagePriority; + /** Whether this status message should be persistant or it should clear out after a timeout. */ persistant!: boolean; + /** The DateTime of when this message was created. */ createdDate!: Date; constructor(data?: IAppStatusMessage) { @@ -6069,11 +6044,17 @@ export class AppStatusMessage implements IAppStatusMessage { } } +/** Represents a status change in the application. */ export interface IAppStatusMessage { + /** The ID of the script this message relates to. */ scriptId?: string | undefined; + /** The text of this message. */ text: string; + /** The priority of this message. */ priority: AppStatusMessagePriority; + /** Whether this status message should be persistant or it should clear out after a timeout. */ persistant: boolean; + /** The DateTime of when this message was created. */ createdDate: Date; } diff --git a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/code-pane/syntax-tree-view/syntax-tree-view.html b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/code-pane/syntax-tree-view/syntax-tree-view.html index b5ebb80a..f4455e78 100644 --- a/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/code-pane/syntax-tree-view/syntax-tree-view.html +++ b/src/Apps/NetPad.Apps.App/App/src/windows/main/panes/code-pane/syntax-tree-view/syntax-tree-view.html @@ -37,7 +37,10 @@ - ${syntaxNode.kind} + + ${syntaxNode.kind} + ParseScaffoldedSourceFileAsync(FileInfo var syntaxTreeRoot = CSharpSyntaxTree.ParseText(scaffoldedCode).GetRoot(); var nodes = syntaxTreeRoot.DescendantNodes().ToArray(); - var usings = new HashSet(); - - foreach (var usingDirective in nodes.OfType()) - { - var ns = scaffoldedCode.Substring(usingDirective.Span.Start, usingDirective.Span.Length) - .Split(' ')[1] - .TrimEnd(';'); - - usings.Add(ns); - } + var usings = nodes + .OfType() + .Select(u => string.Join( + ' ', + u.NormalizeWhitespace().ChildNodes().Select(x => x.ToFullString())) + ) + .ToArray(); var classDeclaration = nodes.OfType().Single(); var classCode = scaffoldedCode.Substring(classDeclaration.Span.Start, classDeclaration.Span.Length); diff --git a/src/Core/NetPad.Runtime/CodeAnalysis/CodeAnalysisService.cs b/src/Core/NetPad.Runtime/CodeAnalysis/CodeAnalysisService.cs index a1da7e60..cef3ca2f 100644 --- a/src/Core/NetPad.Runtime/CodeAnalysis/CodeAnalysisService.cs +++ b/src/Core/NetPad.Runtime/CodeAnalysis/CodeAnalysisService.cs @@ -50,7 +50,13 @@ private static SyntaxNodeOrTokenSlim Build(SyntaxNodeOrToken nodeOrToken, Cancel var kind = nodeOrToken.Kind(); - var element = new SyntaxNodeOrTokenSlim(nodeOrToken.IsToken, nodeOrToken.IsNode, kind, nodeOrToken.GetLocation()!.GetLineSpan().Span, nodeOrToken.IsMissing); + var element = new SyntaxNodeOrTokenSlim( + nodeOrToken.IsToken, + nodeOrToken.IsNode, + kind, + nodeOrToken.IsNode ? nodeOrToken.AsNode()!.GetType().Name : string.Empty, + nodeOrToken.GetLocation()!.GetLineSpan().Span, + nodeOrToken.IsMissing); if (nodeOrToken.IsToken) { @@ -78,12 +84,11 @@ private static SyntaxNodeOrTokenSlim Build(SyntaxNodeOrToken nodeOrToken, Cancel return element; } - private static SyntaxTriviaSlim ToTrivia(Microsoft.CodeAnalysis.SyntaxTrivia trivia) + private static SyntaxTriviaSlim ToTrivia(SyntaxTrivia trivia) { var kind = trivia.Kind(); - string? displayValue;// = kind.ToString();//.Token.ValueText; //TriviaDisplayValues.GetValueOrDefault(t.Kind()); - if (!TriviaDisplayValues.TryGetValue(trivia.Kind(), out displayValue)) + if (!_triviaDisplayValues.TryGetValue(trivia.Kind(), out var displayValue)) { if (trivia.IsKind(SyntaxKind.WhitespaceTrivia)) { @@ -99,7 +104,7 @@ private static SyntaxTriviaSlim ToTrivia(Microsoft.CodeAnalysis.SyntaxTrivia tri return new SyntaxTriviaSlim(kind, trivia.GetLocation()!.GetLineSpan().Span, displayValue.Truncate(50, true)); } - private static Dictionary TriviaDisplayValues = new Dictionary + private static readonly Dictionary _triviaDisplayValues = new Dictionary { [SyntaxKind.SemicolonToken] = ";", [SyntaxKind.EndOfLineTrivia] = "\\n", diff --git a/src/Core/NetPad.Runtime/CodeAnalysis/SyntaxNodeOrTokenSlim.cs b/src/Core/NetPad.Runtime/CodeAnalysis/SyntaxNodeOrTokenSlim.cs index 15cc5c9d..96e83b69 100644 --- a/src/Core/NetPad.Runtime/CodeAnalysis/SyntaxNodeOrTokenSlim.cs +++ b/src/Core/NetPad.Runtime/CodeAnalysis/SyntaxNodeOrTokenSlim.cs @@ -3,11 +3,18 @@ namespace NetPad.CodeAnalysis; -public class SyntaxNodeOrTokenSlim(bool isToken, bool isNode, SyntaxKind kind, LinePositionSpan span, bool isMissing) +public class SyntaxNodeOrTokenSlim( + bool isToken, + bool isNode, + SyntaxKind kind, + string type, + LinePositionSpan span, + bool isMissing) { public bool IsToken { get; } = isToken; public bool IsNode { get; } = isNode; public SyntaxKind Kind { get; } = kind; + public string Type { get; } = type; public LinePositionSpan Span { get; } = span; public bool IsMissing { get; } = isMissing; public string? ValueText { get; private set; } diff --git a/src/Core/NetPad.Runtime/DotNet/SourceCode.cs b/src/Core/NetPad.Runtime/DotNet/SourceCode.cs index c19694f6..fb186a22 100644 --- a/src/Core/NetPad.Runtime/DotNet/SourceCode.cs +++ b/src/Core/NetPad.Runtime/DotNet/SourceCode.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Json.Serialization; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -73,37 +74,24 @@ public string ToCodeString(bool useGlobalNotation = false) public static SourceCode Parse(string text) { - var syntaxTreeRoot = CSharpSyntaxTree.ParseText(text).GetRoot(); - var nodes = syntaxTreeRoot.DescendantNodes().ToArray(); - - var usings = new HashSet(); - var usingSpans = new List<(int startIndex, int length)>(); - - foreach (var usingDirective in nodes.OfType()) - { - int startIndex = usingDirective.Span.Start; - int length = usingDirective.Span.Length; - - usingSpans.Add((startIndex, length)); - - var ns = text.Substring(startIndex, length) - .Split(' ').Skip(1).JoinToString(" ") - .TrimEnd(';'); - - usings.Add(ns); - } - - string code; - - if (!usings.Any()) - { - code = text; - } - else - { - usingSpans = usingSpans.OrderBy(s => s.startIndex).ToList(); - code = text.RemoveRanges(usingSpans); - } + var root = CSharpSyntaxTree.ParseText(text).GetRoot(); + + var usingDirectives = root + .DescendantNodes() + .OfType() + .ToArray(); + + var usings = usingDirectives + .Select(u => string.Join( + ' ', + u.NormalizeWhitespace().ChildNodes().Select(x => x.ToFullString())) + ) + .ToArray(); + + var code = root + .RemoveNodes(usingDirectives, SyntaxRemoveOptions.KeepNoTrivia)? + .NormalizeWhitespace() + .ToFullString(); return new SourceCode(code, usings); }