diff --git a/MApplication/CodeProcessor.cs b/MApplication/CodeProcessor.cs new file mode 100644 index 0000000..ec73813 --- /dev/null +++ b/MApplication/CodeProcessor.cs @@ -0,0 +1,14 @@ +using Parser; + +namespace MApplication +{ + internal static class CodeProcessor + { + public static DisplayText GetText(SyntaxTree tree) + { + var visitor = new ColoringVisitor(StyleSchemeFactory.GetDefaultScheme()); + visitor.Visit(tree.Root); + return new DisplayText(visitor.GetLines()); + } + } +} diff --git a/MApplication/ColoringVisitor.cs b/MApplication/ColoringVisitor.cs new file mode 100644 index 0000000..16bce5a --- /dev/null +++ b/MApplication/ColoringVisitor.cs @@ -0,0 +1,461 @@ +using Parser; +using Parser.Internal; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace MApplication +{ + internal class ColoringVisitor : SyntaxWalker + { + private readonly ImmutableList.Builder Builder; + private ImmutableList.Builder CurrentLineBuilder; + private readonly StyleScheme _scheme; + private int _currentLine; + + public ColoringVisitor(StyleScheme scheme) + { + Builder = ImmutableList.CreateBuilder(); + CurrentLineBuilder = ImmutableList.CreateBuilder(); + _currentLine = 0; + _scheme = scheme; + } + + public override void VisitToken(SyntaxToken token) + { + ProcessTriviaList(token.LeadingTrivia); + CurrentLineBuilder.Add(new DisplayLineChunk(token.Text.AsMemory(), _scheme.DefaultToken)); + ProcessTriviaList (token.TrailingTrivia); + } + + private void ProcessTriviaList(IReadOnlyList triviaList) + { + foreach (var trivia in triviaList) + { + ProcessTrivia(trivia); + } + } + + private void FinishLine() + { + Builder.Add(new DisplayLine(CurrentLineBuilder.ToImmutable())); + CurrentLineBuilder.Clear(); + _currentLine++; + } + + private void ProcessTrivia(SyntaxTrivia trivia) + { + if (trivia.Text.Contains('\n')) + { + var first = true; + foreach (var part in trivia.Text.Split('\n')) + { + if (first) + { + first = false; + } else + { + FinishLine(); + } + CurrentLineBuilder.Add(new DisplayLineChunk(part.AsMemory(), _scheme.Trivia)); + } + } + else + { + CurrentLineBuilder.Add(new DisplayLineChunk(trivia.Text.AsMemory(), _scheme.Trivia)); + } + } + + public void AddToken(SyntaxToken token, Style style) + { + ProcessTriviaList(token.LeadingTrivia); + CurrentLineBuilder.Add(new DisplayLineChunk(token.Text.AsMemory(), style)); + ProcessTriviaList(token.TrailingTrivia); + } + + public override void VisitFile(FileSyntaxNode node) + { + Visit(node.StatementList); + AddToken(node.EndOfFile, _scheme.Keyword); + } + + public override void VisitBaseClassList(BaseClassListSyntaxNode node) + { + AddToken(node.LessSign, _scheme.Punctuation); + Visit(node.BaseClasses); + } + + public override void VisitClassDeclaration(ClassDeclarationSyntaxNode node) + { + AddToken(node.ClassdefKeyword, _scheme.Keyword); + Visit(node.Attributes); + Visit(node.ClassName); + Visit(node.BaseClassList); + Visit(node.Nodes); + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitList(SyntaxNodeOrTokenList list) + { + foreach (var nodeOrToken in list) + { + if (nodeOrToken.IsToken) + { + var token = nodeOrToken.AsToken(); + if (token.Kind == TokenKind.IdentifierToken) + { + AddToken(token, _scheme.Identifier); + } + else if (SyntaxFacts.IsBracket(token.Kind)) + { + AddToken(token, _scheme.Bracket); + } + else + { + AddToken(token, _scheme.Punctuation); + } + } + else + { + Visit(nodeOrToken.AsNode()); + } + } + } + + public override void VisitMethodsList(MethodsListSyntaxNode node) + { + AddToken(node.MethodsKeyword, _scheme.Keyword); + Visit(node.Attributes); + Visit(node.Methods); + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitPropertiesList(PropertiesListSyntaxNode node) + { + AddToken(node.PropertiesKeyword, _scheme.Keyword); + Visit(node.Attributes); + Visit(node.Properties); + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitMethodDefinition(MethodDefinitionSyntaxNode node) + { + AddToken(node.FunctionKeyword, _scheme.Keyword); + Visit(node.OutputDescription); + Visit(node.InputDescription); + Visit(node.Commas); + Visit(node.Body); + Visit(node.EndKeyword); + } + + public override void VisitEndKeyword(EndKeywordSyntaxNode node) + { + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitFunctionDeclaration(FunctionDeclarationSyntaxNode node) + { + AddToken(node.FunctionKeyword, _scheme.Keyword); + Visit(node.OutputDescription); + AddToken(node.Name, _scheme.Identifier); + Visit(node.InputDescription); + Visit(node.Commas); + Visit(node.Body); + Visit(node.EndKeyword); + } + + public override void VisitIfStatement(IfStatementSyntaxNode node) + { + AddToken(node.IfKeyword, _scheme.ControlKeyword); + Visit(node.Condition); + Visit(node.OptionalCommas); + Visit(node.Body); + Visit(node.ElseifClauses); + Visit(node.ElseClause); + AddToken(node.EndKeyword, _scheme.ControlKeyword); + } + + public override void VisitElseClause(ElseClause node) + { + AddToken(node.ElseKeyword, _scheme.ControlKeyword); + Visit(node.Body); + } + + public override void VisitElseifClause(ElseifClause node) + { + AddToken(node.ElseifKeyword, _scheme.ControlKeyword); + Visit(node.Condition); + Visit(node.Body); + } + + public override void VisitAbstractMethodDeclaration(AbstractMethodDeclarationSyntaxNode node) + { + Visit(node.OutputDescription); + Visit(node.Name); + Visit(node.InputDescription); + } + + public override void VisitAssignmentExpression(AssignmentExpressionSyntaxNode node) + { + Visit(node.Lhs); + AddToken(node.AssignmentSign, _scheme.Operator); + Visit(node.Rhs); + } + + public override void VisitExpressionStatement(ExpressionStatementSyntaxNode node) + { + Visit(node.Expression); + } + + public override void VisitArrayLiteralExpression(ArrayLiteralExpressionSyntaxNode node) + { + AddToken(node.OpeningSquareBracket, _scheme.Bracket); + Visit(node.Nodes); + AddToken(node.ClosingSquareBracket, _scheme.Bracket); + } + + public override void VisitCellArrayLiteralExpression(CellArrayLiteralExpressionSyntaxNode node) + { + AddToken(node.OpeningBrace, _scheme.Bracket); + Visit(node.Nodes); + AddToken(node.ClosingBrace, _scheme.Bracket); + } + + public override void VisitIdentifierName(IdentifierNameSyntaxNode node) + { + AddToken(node.Name, _scheme.Identifier); + } + + public override void VisitForStatement(ForStatementSyntaxNode node) + { + AddToken(node.ForKeyword, _scheme.ControlKeyword); + Visit(node.Assignment); + Visit(node.OptionalCommas); + Visit(node.Body); + AddToken(node.EndKeyword, _scheme.ControlKeyword); + } + + public override void VisitSwitchStatement(SwitchStatementSyntaxNode node) + { + AddToken(node.SwitchKeyword, _scheme.ControlKeyword); + Visit(node.SwitchExpression); + Visit(node.OptionalCommas); + Visit(node.Cases); + AddToken(node.EndKeyword, _scheme.ControlKeyword); + } + + public override void VisitWhileStatement(WhileStatementSyntaxNode node) + { + AddToken(node.WhileKeyword, _scheme.ControlKeyword); + Visit(node.Condition); + Visit(node.OptionalCommas); + Visit(node.Body); + AddToken(node.EndKeyword, _scheme.ControlKeyword); + } + + public override void VisitUnquotedStringLiteral(UnquotedStringLiteralSyntaxNode node) + { + AddToken(node.StringToken, _scheme.UnquotedStringLiteral); + } + + public override void VisitStringLiteral(StringLiteralSyntaxNode node) + { + AddToken(node.StringToken, _scheme.StringLiteral); + } + + public override void VisitBinaryOperationExpression(BinaryOperationExpressionSyntaxNode node) + { + Visit(node.Lhs); + AddToken(node.Operation, _scheme.Operator); + Visit(node.Rhs); + } + + public override void VisitFunctionCallExpression(FunctionCallExpressionSyntaxNode node) + { + Visit(node.FunctionName); + AddToken(node.OpeningBracket, _scheme.Bracket); + Visit(node.Nodes); + AddToken(node.ClosingBracket, _scheme.Bracket); + } + + public override void VisitSwitchCase(SwitchCaseSyntaxNode node) + { + AddToken(node.CaseKeyword, _scheme.ControlKeyword); + Visit(node.CaseIdentifier); + Visit(node.OptionalCommas); + Visit(node.Body); + } + + public override void VisitCatchClause(CatchClauseSyntaxNode node) + { + AddToken(node.CatchKeyword, _scheme.ControlKeyword); + Visit(node.CatchBody); + } + + public override void VisitTryCatchStatement(TryCatchStatementSyntaxNode node) + { + AddToken(node.TryKeyword, _scheme.ControlKeyword); + Visit(node.TryBody); + Visit(node.CatchClause); + AddToken(node.EndKeyword, _scheme.ControlKeyword); + } + + public override void VisitCommandExpression(CommandExpressionSyntaxNode node) + { + Visit(node.CommandName); + Visit(node.Arguments); + } + + public override void VisitNumberLiteral(NumberLiteralSyntaxNode node) + { + AddToken(node.Number, _scheme.NumberLiteral); + } + + public override void VisitUnaryPrefixOperationExpression(UnaryPrefixOperationExpressionSyntaxNode node) + { + AddToken(node.Operation, _scheme.Operator); + Visit(node.Operand); + } + + public override void VisitUnaryPostixOperationExpression(UnaryPostixOperationExpressionSyntaxNode node) + { + Visit(node.Operand); + AddToken(node.Operation, _scheme.Operator); + } + + public override void VisitBaseClassInvokation(BaseClassInvokationSyntaxNode node) + { + Visit(node.MethodName); + AddToken(node.AtSign, _scheme.Operator); + Visit(node.BaseClassNameAndArguments); + } + + public override void VisitAttributeAssignment(AttributeAssignmentSyntaxNode node) + { + AddToken(node.AssignmentSign, _scheme.Operator); + Visit(node.Value); + } + + public override void VisitAttribute(AttributeSyntaxNode node) + { + Visit(node.Name); + Visit(node.Assignment); + } + + public override void VisitAttributeList(AttributeListSyntaxNode node) + { + AddToken(node.OpeningBracket, _scheme.Bracket); + Visit(node.Nodes); + AddToken(node.ClosingBracket, _scheme.Bracket); + } + + public override void VisitCellArrayElementAccessExpression(CellArrayElementAccessExpressionSyntaxNode node) + { + Visit(node.Expression); + AddToken(node.OpeningBrace, _scheme.Bracket); + Visit(node.Nodes); + AddToken(node.ClosingBrace, _scheme.Bracket); + } + + public override void VisitCompoundName(CompoundNameSyntaxNode node) + { + Visit(node.Nodes); + } + + public override void VisitDoubleQuotedStringLiteral(DoubleQuotedStringLiteralSyntaxNode node) + { + AddToken(node.StringToken, _scheme.StringLiteral); + } + + public override void VisitEmptyExpression(EmptyExpressionSyntaxNode node) + { + } + + public override void VisitEmptyStatement(EmptyStatementSyntaxNode node) + { + AddToken(node.Semicolon, _scheme.Punctuation); + } + + public override void VisitEnumerationList(EnumerationListSyntaxNode node) + { + AddToken(node.EnumerationKeyword, _scheme.Keyword); + Visit(node.Attributes); + Visit(node.Items); + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitEventsList(EventsListSyntaxNode node) + { + AddToken(node.EventsKeyword, _scheme.Keyword); + Visit(node.Attributes); + Visit(node.Events); + AddToken(node.EndKeyword, _scheme.Keyword); + } + + public override void VisitEnumerationItemValue(EnumerationItemValueSyntaxNode node) + { + AddToken(node.OpeningBracket, _scheme.Punctuation); + Visit(node.Values); + AddToken(node.ClosingBracket, _scheme.Punctuation); + } + + public override void VisitEnumerationItem(EnumerationItemSyntaxNode node) + { + Visit(node.Name); + Visit(node.Values); + Visit(node.Commas); + } + + public override void VisitFunctionInputDescription(FunctionInputDescriptionSyntaxNode node) + { + AddToken(node.OpeningBracket, _scheme.Bracket); + Visit(node.ParameterList); + AddToken(node.ClosingBracket, _scheme.Bracket); + } + + public override void VisitFunctionOutputDescription(FunctionOutputDescriptionSyntaxNode node) + { + Visit(node.OutputList); + AddToken(node.AssignmentSign, _scheme.Operator); + } + + public override void VisitIndirectMemberAccess(IndirectMemberAccessSyntaxNode node) + { + AddToken(node.OpeningBracket, _scheme.Bracket); + Visit(node.Expression); + AddToken(node.ClosingBracket, _scheme.Bracket); + } + + public override void VisitLambda(LambdaSyntaxNode node) + { + AddToken(node.AtSign, _scheme.Operator); + Visit(node.Input); + Visit(node.Body); + } + + public override void VisitNamedFunctionHandle(NamedFunctionHandleSyntaxNode node) + { + AddToken(node.AtSign, _scheme.Operator); + Visit(node.FunctionName); + } + + public override void VisitMemberAccess(MemberAccessSyntaxNode node) + { + Visit(node.LeftOperand); + AddToken(node.Dot, _scheme.Operator); + Visit(node.RightOperand); + } + + public override void VisitParenthesizedExpression(ParenthesizedExpressionSyntaxNode node) + { + AddToken(node.OpeningBracket, _scheme.Operator); + Visit(node.Expression); + AddToken(node.ClosingBracket, _scheme.Operator); + } + + public ImmutableList GetLines() + { + return Builder.ToImmutable(); + } + } +} diff --git a/MApplication/ConsoleWindowView.cs b/MApplication/ConsoleWindowView.cs new file mode 100644 index 0000000..7867c29 --- /dev/null +++ b/MApplication/ConsoleWindowView.cs @@ -0,0 +1,57 @@ +using System; + +namespace MApplication +{ + internal class ConsoleWindowView : IOutputView + { + public ConsoleWindowView(int startingColumn, int startingLine, int width, int height) + { + StartingColumn = startingColumn; + StartingLine = startingLine; + Width = width; + Height = height; + } + + public int StartingColumn { get; } + + public int StartingLine { get; } + + public int Width { get; } + + public int Height { get; } + + public void HideCursor() + { + Console.CursorVisible = false; + } + + public void MoveCursorTo(int column, int line) + { + Console.CursorLeft = StartingColumn + column; + Console.CursorTop = StartingLine + line; + } + + public void SetStyle(Style style) + { + if (Console.ForegroundColor != style.ForegroundColor) + { + Console.ForegroundColor = style.ForegroundColor; + } + + if (Console.BackgroundColor != style.BackgroundColor) + { + Console.BackgroundColor = style.BackgroundColor; + } + } + + public void ShowCursor() + { + Console.CursorVisible = true; + } + + public void WriteText(string s) + { + Console.Write(s); + } + } +} diff --git a/MApplication/DisplayLine.cs b/MApplication/DisplayLine.cs new file mode 100644 index 0000000..ac2a5c3 --- /dev/null +++ b/MApplication/DisplayLine.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; +using System.Linq; + +namespace MApplication +{ + internal class DisplayLine + { + public DisplayLine(ImmutableList chunks) + { + Chunks = chunks; + Width = chunks.Sum(c => c.Width); + } + + public ImmutableList Chunks { get; } + + public int Width { get; } + } +} diff --git a/MApplication/DisplayLineChunk.cs b/MApplication/DisplayLineChunk.cs new file mode 100644 index 0000000..eb650a3 --- /dev/null +++ b/MApplication/DisplayLineChunk.cs @@ -0,0 +1,19 @@ +using System; + +namespace MApplication +{ + internal class DisplayLineChunk + { + public DisplayLineChunk(ReadOnlyMemory text, Style style) + { + Text = text; + Style = style; + } + + public ReadOnlyMemory Text { get; } + + public Style Style { get; } + + public int Width => Text.Length; + } +} diff --git a/MApplication/DisplayText.cs b/MApplication/DisplayText.cs new file mode 100644 index 0000000..49743fa --- /dev/null +++ b/MApplication/DisplayText.cs @@ -0,0 +1,14 @@ +using System.Collections.Immutable; + +namespace MApplication +{ + internal class DisplayText + { + public DisplayText(ImmutableList lines) + { + Lines = lines; + } + + public ImmutableList Lines { get; } + } +} diff --git a/MApplication/DisplayTextViewPort.cs b/MApplication/DisplayTextViewPort.cs new file mode 100644 index 0000000..68f37fb --- /dev/null +++ b/MApplication/DisplayTextViewPort.cs @@ -0,0 +1,286 @@ +using System; + +namespace MApplication +{ + internal class DisplayTextViewPort + { + public DisplayTextViewPort( + DisplayText text, + int width, + int height, + int startingColumn = 0, + int startingLine = 0, + int cursorAbsoluteColumn = 0, + int cursorAbsoluteLine = 0, + int virtualCursorAbsoluteColumn = 0) + { + Text = text; + Width = width; + Height = height; + StartingColumn = startingColumn; + StartingLine = startingLine; + CursorAbsoluteColumn = cursorAbsoluteColumn; + CursorAbsoluteLine = cursorAbsoluteLine; + VirtualCursorAbsoluteColumn = virtualCursorAbsoluteColumn; + } + + public DisplayText Text { get; } + + public int Width { get; } + + public int Height { get; } + + public int StartingColumn { get; } + + public int StartingLine { get; } + + public int CursorRelativeColumn => CursorAbsoluteColumn - StartingColumn; + + public int CursorRelativeLine => CursorAbsoluteLine - StartingLine; + + public int CursorAbsoluteColumn { get; } + + public int CursorAbsoluteLine { get; } + + public int VirtualCursorAbsoluteColumn { get; } + + public int CurrentLineWidth => Text.Lines[CursorAbsoluteLine].Width; + + public DisplayTextViewPort CursorLeft(out bool needsRedraw) + { + var newCursorAbsoluteColumn = Math.Max(CursorAbsoluteColumn - 1, 0); + if (CursorRelativeColumn == 0) + { + return With( + out needsRedraw, + cursorAbsoluteColumn: newCursorAbsoluteColumn, + virtualCursorAbsoluteColumn: newCursorAbsoluteColumn, + startingColumn: Math.Max(StartingColumn - 1, 0)); + } + else + { + return With( + out needsRedraw, + cursorAbsoluteColumn: newCursorAbsoluteColumn, + virtualCursorAbsoluteColumn: newCursorAbsoluteColumn); + } + } + + public DisplayTextViewPort CursorRight(out bool needsRedraw) + { + var newCursorAbsoluteColumn = Math.Min(CursorAbsoluteColumn + 1, CurrentLineWidth); + if (CursorRelativeColumn == Width - 1) + { + return With( + out needsRedraw, + cursorAbsoluteColumn: newCursorAbsoluteColumn, + virtualCursorAbsoluteColumn: newCursorAbsoluteColumn, + startingColumn: StartingColumn + 1); + } + else + { + return With( + out needsRedraw, + cursorAbsoluteColumn: newCursorAbsoluteColumn, + virtualCursorAbsoluteColumn: newCursorAbsoluteColumn); + } + } + + private DisplayTextViewPort SnapToLine(out bool needsRedraw) + { + var cursorAbsoluteColumn = Math.Min(VirtualCursorAbsoluteColumn, CurrentLineWidth); + if (cursorAbsoluteColumn < StartingColumn) + { + return With( + out needsRedraw, + startingColumn: cursorAbsoluteColumn, + cursorAbsoluteColumn: cursorAbsoluteColumn); + } + + if (cursorAbsoluteColumn > StartingColumn + Width - 1) + { + return With( + out needsRedraw, + startingColumn: cursorAbsoluteColumn - Width + 1, + cursorAbsoluteColumn: cursorAbsoluteColumn); + } + + return With( + out needsRedraw, + cursorAbsoluteColumn: cursorAbsoluteColumn); + } + + public DisplayTextViewPort CursorUp(out bool needsRedraw) + { + bool changed1; + var result1 = CursorRelativeLine switch + { + 0 => With( + out changed1, + startingLine: Math.Max(StartingLine - 1, 0), + cursorAbsoluteLine: Math.Max(StartingLine - 1, 0)), + _ => With(out changed1, cursorAbsoluteLine: CursorAbsoluteLine - 1), + }; + var result = result1.SnapToLine(out var changed2); + needsRedraw = changed1 || changed2; + return result; + } + + public DisplayTextViewPort CursorDown(out bool needsRedraw) + { + bool changed1; + var result1 = CursorRelativeLine switch + { + _ when CursorRelativeLine == Height - 1 => + With( + out changed1, + startingLine: Math.Min(CursorAbsoluteLine + 1, Text.Lines.Count - 1) - Height + 1, + cursorAbsoluteLine: Math.Min(CursorAbsoluteLine + 1, Text.Lines.Count - 1)), + _ => With( + out changed1, + cursorAbsoluteLine: CursorAbsoluteLine + 1), + }; + var result = result1.SnapToLine(out var changed2); + needsRedraw = changed1 || changed2; + return result; + } + + internal DisplayTextViewPort PageDown(out bool needsRedraw) + { + var result1 = With( + out var changed1, + startingLine: Math.Min(StartingLine + Height, Text.Lines.Count - 1), + cursorAbsoluteLine: Math.Min(CursorAbsoluteLine + Height, Text.Lines.Count - 1)); + var result = result1.SnapToLine(out var changed2); + needsRedraw = changed1 || changed2; + return result; + } + + internal DisplayTextViewPort PageUp(out bool needsRedraw) + { + var result1 = With( + out var changed1, + startingLine: Math.Max(StartingLine - Height, 0), + cursorAbsoluteLine: Math.Max(CursorAbsoluteLine - Height, 0)); + var result = result1.SnapToLine(out var changed2); + needsRedraw = changed1 || changed2; + return result; + } + + public DisplayTextViewPort CursorHome(out bool needsRedraw) + { + return With( + out needsRedraw, + startingColumn: 0, + cursorAbsoluteColumn: 0, + virtualCursorAbsoluteColumn: 0); + } + + public DisplayTextViewPort CursorEnd(out bool needsRedraw) + { + var lineWidth = Text.Lines[CursorAbsoluteLine].Width; + var toAdd = lineWidth - CursorAbsoluteColumn; + if (toAdd == 0) + { + needsRedraw = false; + return this; + } + + if (CursorRelativeColumn + toAdd < Width) + { + return With( + out needsRedraw, + cursorAbsoluteColumn: CursorAbsoluteColumn + toAdd, + virtualCursorAbsoluteColumn: CursorAbsoluteColumn + toAdd); + } + + return With( + out needsRedraw, + startingColumn: lineWidth - Width + 1, + cursorAbsoluteColumn: lineWidth, + virtualCursorAbsoluteColumn: lineWidth); + } + + public void RenderTo(IOutputView view) + { + view.HideCursor(); + for (var lineNumber = StartingLine; lineNumber < StartingLine + Height; lineNumber++) + { + view.MoveCursorTo(0, lineNumber - StartingLine); + if (lineNumber >= Text.Lines.Count) + { + view.WriteText(new string(' ', Width)); + continue; + } + + var line = Text.Lines[lineNumber]; + var startsIn = StartingColumn; + foreach (var chunk in line.Chunks) + { + var left = Math.Max(0, startsIn); + var right = Math.Min(chunk.Width, startsIn + Width); + if (left < right) + { + view.SetStyle(chunk.Style); + view.WriteText(chunk.Text[left..right].ToString()); + } + + startsIn -= chunk.Width; + if (startsIn + Width <= 0) + { + break; + } + } + + if (startsIn + Width > 0) + { + var numberOfSpaces = Math.Min(startsIn + Width, Width); + view.WriteText(new string(' ', numberOfSpaces)); + } + } + view.MoveCursorTo(column: CursorRelativeColumn, line: CursorRelativeLine); + view.ShowCursor(); + } + + public DisplayTextViewPort With( + out bool changed, + int? width = null, + int? height = null, + int? startingColumn = null, + int? startingLine = null, + int? cursorAbsoluteColumn = null, + int? cursorAbsoluteLine = null, + int? virtualCursorAbsoluteColumn = null) + { + var widthValue = width ?? Width; + var heightValue = height ?? Height; + var startingColumnValue = startingColumn ?? StartingColumn; + var startingLineValue = startingLine ?? StartingLine; + var cursorAbsoluteColumnValue = cursorAbsoluteColumn ?? CursorAbsoluteColumn; + var cursorAbsoluteLineValue = cursorAbsoluteLine ?? CursorAbsoluteLine; + var virtualCursorAbsoluteColumnValue = virtualCursorAbsoluteColumn ?? VirtualCursorAbsoluteColumn; + if (widthValue == Width && + heightValue == Height && + startingColumnValue == StartingColumn && + startingLineValue == StartingLine && + cursorAbsoluteColumnValue == CursorAbsoluteColumn && + cursorAbsoluteLineValue == CursorAbsoluteLine && + virtualCursorAbsoluteColumnValue == VirtualCursorAbsoluteColumn) + { + changed = false; + return this; + } + + changed = true; + return new DisplayTextViewPort( + text: Text, + width: widthValue, + height: heightValue, + startingColumn: startingColumnValue, + startingLine: startingLineValue, + cursorAbsoluteColumn: cursorAbsoluteColumnValue, + cursorAbsoluteLine: cursorAbsoluteLineValue, + virtualCursorAbsoluteColumn: virtualCursorAbsoluteColumnValue); + } + } +} diff --git a/MApplication/IOutputView.cs b/MApplication/IOutputView.cs new file mode 100644 index 0000000..ae97e05 --- /dev/null +++ b/MApplication/IOutputView.cs @@ -0,0 +1,13 @@ +namespace MApplication +{ + internal interface IOutputView + { + int Width { get; } + int Height { get; } + void MoveCursorTo(int column, int line); + void ShowCursor(); + void HideCursor(); + void SetStyle(Style style); + void WriteText(string s); + } +} diff --git a/MApplication/MApplication.csproj b/MApplication/MApplication.csproj new file mode 100644 index 0000000..a4e6d65 --- /dev/null +++ b/MApplication/MApplication.csproj @@ -0,0 +1,14 @@ + + + + Exe + net5.0 + enable + preview + + + + + + + diff --git a/MApplication/Program.cs b/MApplication/Program.cs new file mode 100644 index 0000000..f1a1843 --- /dev/null +++ b/MApplication/Program.cs @@ -0,0 +1,130 @@ +using Parser; +using System; +using System.IO; + +namespace MApplication +{ + internal class FileRenderer + { + private readonly IOutputView _outputView; + + public FileRenderer(IOutputView outputView) + { + _outputView = outputView; + } + + private static MParser CreateParser(ITextWindow window) + { + return new MParser(window); + } + + private static SyntaxTree GetTree(string fileName) + { + var text = File.ReadAllText(fileName); + var window = new TextWindowWithNull(text, fileName); + var parser = CreateParser(window); + var tree = parser.Parse(); + return tree; + } + + public void RenderFile(string fileName) + { + var tree = GetTree(fileName); + var text = CodeProcessor.GetText(tree); + var viewPort = new DisplayTextViewPort( + text: text, + width: 80, + height: 24); + + var needsRedraw = true; + while (true) + { + if (needsRedraw) + { + viewPort.RenderTo(_outputView); + } + + _outputView.MoveCursorTo(viewPort.CursorRelativeColumn, viewPort.CursorRelativeLine); + + var key = Console.ReadKey(intercept: true); + viewPort = key.Key switch + { + ConsoleKey.LeftArrow => viewPort.CursorLeft(out needsRedraw), + ConsoleKey.RightArrow => viewPort.CursorRight(out needsRedraw), + ConsoleKey.UpArrow => viewPort.CursorUp(out needsRedraw), + ConsoleKey.DownArrow => viewPort.CursorDown(out needsRedraw), + ConsoleKey.Home => viewPort.CursorHome(out needsRedraw), + ConsoleKey.End => viewPort.CursorEnd(out needsRedraw), + ConsoleKey.PageUp => viewPort.PageUp(out needsRedraw), + ConsoleKey.PageDown => viewPort.PageDown(out needsRedraw), + _ => viewPort, + }; + if (key.Key == ConsoleKey.Q) + { + return; + } + } + } + } + + class Program + { + private static readonly string BaseDirectory; + private const string BaseDirectoryMacOs = @"/Applications/MATLAB_R2017b.app/toolbox/matlab/"; + private const string BaseDirectoryWindows = @"D:\Program Files\MATLAB\R2018a\toolbox\matlab\"; + + static Program() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + case PlatformID.Unix: + BaseDirectory = BaseDirectoryMacOs; + break; + default: + BaseDirectory = BaseDirectoryWindows; + break; + } + } + + private static Style GetStyle() + { + return new Style( + foregroundColor: Console.ForegroundColor, + backgroundColor: Console.BackgroundColor); + } + + private static void SetStyle(Style style) + { + Console.BackgroundColor = style.BackgroundColor; + Console.ForegroundColor = style.ForegroundColor; + } + + private static void PrintChunk(DisplayLineChunk chunk) + { + SetStyle(chunk.Style); + Console.Write(chunk.Text.ToString()); + } + + static void Main(string[] args) + { + var oldStyle = GetStyle(); + var fileName = Path.Combine( + BaseDirectory, + "datatypes", + "@table", + "table.m"); + var targetWidth = 80; + var targetHeight = 24; + var outputViewPort = new ConsoleWindowView( + startingColumn: (Console.WindowWidth - targetWidth) / 2, + startingLine: (Console.WindowHeight - targetHeight) / 2, + width: targetWidth, + height: targetHeight); + var renderer = new FileRenderer(outputViewPort); + renderer.RenderFile(fileName); + + SetStyle(oldStyle); + } + } +} diff --git a/MApplication/Style.cs b/MApplication/Style.cs new file mode 100644 index 0000000..b92eadb --- /dev/null +++ b/MApplication/Style.cs @@ -0,0 +1,31 @@ +using System; + +namespace MApplication +{ + internal class Style + { + public Style( + ConsoleColor foregroundColor, + ConsoleColor backgroundColor) + { + ForegroundColor = foregroundColor; + BackgroundColor = backgroundColor; + } + + public ConsoleColor ForegroundColor { get; } + + public ConsoleColor BackgroundColor { get; } + + public static Style Color(ConsoleColor foregroundColor) + { + return new Style(foregroundColor, ConsoleColor.Black); + } + + public static Style ColorWithBackGround( + ConsoleColor foregroundColor, + ConsoleColor backgroundColor) + { + return new Style(foregroundColor, backgroundColor); + } + } +} diff --git a/MApplication/StyleScheme.cs b/MApplication/StyleScheme.cs new file mode 100644 index 0000000..55cb0da --- /dev/null +++ b/MApplication/StyleScheme.cs @@ -0,0 +1,53 @@ +namespace MApplication +{ + internal class StyleScheme + { + public StyleScheme( + Style defaultToken, + Style keyword, + Style controlKeyword, + Style trivia, + Style punctuation, + Style @operator, + Style identifier, + Style unquotedStringLiteral, + Style stringLiteral, + Style numberLiteral, + Style bracket) + { + DefaultToken = defaultToken; + Keyword = keyword; + ControlKeyword = controlKeyword; + Trivia = trivia; + Punctuation = punctuation; + Operator = @operator; + Identifier = identifier; + UnquotedStringLiteral = unquotedStringLiteral; + StringLiteral = stringLiteral; + NumberLiteral = numberLiteral; + Bracket = bracket; + } + + public Style DefaultToken { get; } + + public Style Keyword { get; } + + public Style ControlKeyword { get; } + + public Style Trivia { get; } + + public Style Punctuation { get; } + + public Style Operator { get; } + + public Style Identifier { get; } + + public Style UnquotedStringLiteral { get; } + + public Style StringLiteral { get; } + + public Style NumberLiteral { get; } + + public Style Bracket { get; } + } +} diff --git a/MApplication/StyleSchemeFactory.cs b/MApplication/StyleSchemeFactory.cs new file mode 100644 index 0000000..0304158 --- /dev/null +++ b/MApplication/StyleSchemeFactory.cs @@ -0,0 +1,27 @@ +using System; + +namespace MApplication +{ + internal static class StyleSchemeFactory + { + private static StyleScheme _defaultScheme; + + static StyleSchemeFactory() + { + _defaultScheme = new StyleScheme( + defaultToken: Style.Color(ConsoleColor.Gray), + keyword: Style.Color(ConsoleColor.Green), + controlKeyword: Style.Color(ConsoleColor.Yellow), + trivia: Style.Color(ConsoleColor.DarkGray), + punctuation: Style.Color(ConsoleColor.DarkBlue), + @operator: Style.Color(ConsoleColor.Cyan), + identifier: Style.Color(ConsoleColor.White), + unquotedStringLiteral: Style.Color(ConsoleColor.Blue), + stringLiteral: Style.Color(ConsoleColor.Magenta), + numberLiteral: Style.Color(ConsoleColor.DarkGreen), + bracket: Style.Color(ConsoleColor.DarkYellow)); + } + + public static StyleScheme GetDefaultScheme() => _defaultScheme; + } +} diff --git a/Solution.sln b/Solution.sln index ed306a0..b48c4d5 100644 --- a/Solution.sln +++ b/Solution.sln @@ -19,7 +19,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repl", "Repl\Repl.csproj", "{8FEDFE5D-3320-418F-88A6-09C1B51C4441}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repl", "Repl\Repl.csproj", "{8FEDFE5D-3320-418F-88A6-09C1B51C4441}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MApplication", "MApplication\MApplication.csproj", "{A7EE271C-8822-43EA-BA13-5D6D5DC5B581}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,6 +53,10 @@ Global {8FEDFE5D-3320-418F-88A6-09C1B51C4441}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FEDFE5D-3320-418F-88A6-09C1B51C4441}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FEDFE5D-3320-418F-88A6-09C1B51C4441}.Release|Any CPU.Build.0 = Release|Any CPU + {A7EE271C-8822-43EA-BA13-5D6D5DC5B581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7EE271C-8822-43EA-BA13-5D6D5DC5B581}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7EE271C-8822-43EA-BA13-5D6D5DC5B581}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7EE271C-8822-43EA-BA13-5D6D5DC5B581}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE