diff --git a/MApplication/ConsoleWindowView.cs b/MApplication/ConsoleWindowView.cs index 8d2c140..7867c29 100644 --- a/MApplication/ConsoleWindowView.cs +++ b/MApplication/ConsoleWindowView.cs @@ -20,6 +20,11 @@ namespace MApplication public int Height { get; } + public void HideCursor() + { + Console.CursorVisible = false; + } + public void MoveCursorTo(int column, int line) { Console.CursorLeft = StartingColumn + column; @@ -39,6 +44,11 @@ namespace MApplication } } + public void ShowCursor() + { + Console.CursorVisible = true; + } + public void WriteText(string s) { Console.Write(s); diff --git a/MApplication/DisplayLine.cs b/MApplication/DisplayLine.cs index fb2d43c..ac2a5c3 100644 --- a/MApplication/DisplayLine.cs +++ b/MApplication/DisplayLine.cs @@ -1,14 +1,18 @@ using System.Collections.Immutable; +using System.Linq; namespace MApplication { internal class DisplayLine { - public ImmutableList Chunks { get; } - public DisplayLine(ImmutableList chunks) { Chunks = chunks; + Width = chunks.Sum(c => c.Width); } + + public ImmutableList Chunks { get; } + + public int Width { get; } } } diff --git a/MApplication/DisplayTextViewPort.cs b/MApplication/DisplayTextViewPort.cs index 1a1128a..f7cd411 100644 --- a/MApplication/DisplayTextViewPort.cs +++ b/MApplication/DisplayTextViewPort.cs @@ -9,13 +9,17 @@ namespace MApplication int width, int height, int startingColumn = 0, - int startingLine = 0) + int startingLine = 0, + int cursorRelativeColumn = 0, + int cursorRelativeLine = 0) { Text = text; Width = width; Height = height; StartingColumn = startingColumn; StartingLine = startingLine; + CursorRelativeColumn = cursorRelativeColumn; + CursorRelativeLine = cursorRelativeLine; } public DisplayText Text { get; } @@ -28,8 +32,138 @@ namespace MApplication public int StartingLine { get; } + public int CursorRelativeColumn { get; } + + public int CursorRelativeLine { get; } + + public int CursorAbsoluteColumn => CursorRelativeColumn + StartingColumn; + + public int CursorAbsoluteLine => CursorRelativeLine + StartingLine; + + public int CurrentLineWidth => Text.Lines[CursorAbsoluteLine].Width; + + public DisplayTextViewPort CursorLeft(out bool needsRedraw) + { + if (CursorRelativeColumn == 0) + { + return ShiftLeft(out needsRedraw); + } + else + { + return With(out needsRedraw, cursorRelativeColumn: CursorRelativeColumn - 1); + } + } + + public DisplayTextViewPort CursorRight(out bool needsRedraw) + { + if (CursorRelativeColumn == Width - 1) + { + return ShiftRight(out needsRedraw); + } + else + { + return With(out needsRedraw, cursorRelativeColumn: CursorRelativeColumn + 1); + } + } + + private DisplayTextViewPort SnapToLine(out bool needsRedraw) + { + if (CursorAbsoluteColumn > CurrentLineWidth) + { + needsRedraw = true; + var toSubtract = CursorAbsoluteColumn - CurrentLineWidth; + if (toSubtract < CursorRelativeColumn) + { + return With(out var _, cursorRelativeColumn: CursorRelativeColumn - toSubtract); + } + + return With( + out var _, + startingColumn: StartingColumn - (toSubtract - CursorRelativeColumn), + cursorRelativeColumn: 0); + } + else + { + needsRedraw = false; + return this; + } + } + + public DisplayTextViewPort CursorUp(out bool needsRedraw) + { + bool changed1; + var result1 = CursorRelativeLine switch + { + 0 => ShiftUp(out changed1), + _ => With(out changed1, cursorRelativeLine: CursorRelativeLine - 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 => ShiftDown(out changed1), + _ => With(out changed1, cursorRelativeLine: CursorRelativeLine + 1), + }; + var result = result1.SnapToLine(out var changed2); + needsRedraw = changed1 || changed2; + return result; + } + + public DisplayTextViewPort CursorHome(out bool needsRedraw) + { + return With(out needsRedraw, startingColumn: 0, cursorRelativeColumn: 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, cursorRelativeColumn: CursorRelativeColumn + toAdd); + } + + return With( + out needsRedraw, + startingColumn: StartingColumn + CursorRelativeColumn + toAdd - Width + 1, + cursorRelativeColumn: Width - 1); + } + + public DisplayTextViewPort ShiftLeft(out bool needsRedraw) + { + return With(out needsRedraw, startingColumn: Math.Max(StartingColumn - 1, 0)); + } + + public DisplayTextViewPort ShiftRight(out bool needsRedraw) + { + return With(out needsRedraw, startingColumn: StartingColumn + 1); + } + + public DisplayTextViewPort ShiftUp(out bool needsRedraw) + { + return With(out needsRedraw, startingLine: Math.Max(StartingLine - 1, 0)); + } + + public DisplayTextViewPort ShiftDown(out bool needsRedraw) + { + return With(out needsRedraw, startingLine: StartingLine + 1); + } + public void RenderTo(IOutputView view) { + view.HideCursor(); for (var lineNumber = StartingLine; lineNumber < StartingLine + Height; lineNumber++) { view.MoveCursorTo(0, lineNumber - StartingLine); @@ -63,32 +197,45 @@ namespace MApplication 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? startingLine = null, + int? cursorRelativeColumn = null, + int? cursorRelativeLine = null) { var widthValue = width ?? Width; var heightValue = height ?? Height; var startingColumnValue = startingColumn ?? StartingColumn; var startingLineValue = startingLine ?? StartingLine; + var cursorRelativeColumnValue = cursorRelativeColumn ?? CursorRelativeColumn; + var cursorRelativeLineValue = cursorRelativeLine ?? CursorRelativeLine; if (widthValue == Width && heightValue == Height && startingColumnValue == StartingColumn && - startingLineValue == StartingLine) + startingLineValue == StartingLine && + cursorRelativeColumnValue == CursorRelativeColumn && + cursorRelativeLineValue == CursorRelativeLine) { + changed = false; return this; } + changed = true; return new DisplayTextViewPort( text: Text, width: widthValue, height: heightValue, startingColumn: startingColumnValue, - startingLine: startingLineValue); + startingLine: startingLineValue, + cursorRelativeColumn: cursorRelativeColumnValue, + cursorRelativeLine: cursorRelativeLineValue); } } } diff --git a/MApplication/IOutputView.cs b/MApplication/IOutputView.cs index e9ce462..ae97e05 100644 --- a/MApplication/IOutputView.cs +++ b/MApplication/IOutputView.cs @@ -5,6 +5,8 @@ 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/Program.cs b/MApplication/Program.cs index 5c456a4..4bdac12 100644 --- a/MApplication/Program.cs +++ b/MApplication/Program.cs @@ -4,6 +4,80 @@ 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); + switch (key.Key) + { + case ConsoleKey.LeftArrow: + viewPort = viewPort.CursorLeft(out needsRedraw); + break; + + case ConsoleKey.RightArrow: + viewPort = viewPort.CursorRight(out needsRedraw); + break; + + case ConsoleKey.UpArrow: + viewPort = viewPort.CursorUp(out needsRedraw); + break; + + case ConsoleKey.DownArrow: + viewPort = viewPort.CursorDown(out needsRedraw); + break; + + case ConsoleKey.Home: + viewPort = viewPort.CursorHome(out needsRedraw); + break; + + case ConsoleKey.End: + viewPort = viewPort.CursorEnd(out needsRedraw); + break; + } + } + } + + } + class Program { private static readonly string BaseDirectory; @@ -24,20 +98,6 @@ namespace MApplication } } - 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; - } - private static Style GetStyle() { return new Style( @@ -57,56 +117,6 @@ namespace MApplication Console.Write(chunk.Text.ToString()); } - private static void PrintLine(DisplayLine line) - { - foreach (var chunk in line.Chunks) - { - PrintChunk(chunk); - } - } - - static void RenderFile(string fileName) - { - var tree = GetTree(fileName); - var text = CodeProcessor.GetText(tree); - var viewPort = new DisplayTextViewPort( - text: text, - width: 80, - height: 24); - - var targetWidth = 80; - var targetHeight = 24; - var outputViewPort = new ConsoleWindowView( - startingColumn: (Console.WindowWidth - targetWidth) / 2, - startingLine: (Console.WindowHeight - targetHeight) / 2, - width: targetWidth, - height: targetHeight); - - while (true) - { - viewPort.RenderTo(outputViewPort); - var key = Console.ReadKey(intercept: true); - switch (key.Key) - { - case ConsoleKey.LeftArrow: - viewPort = viewPort.With(startingColumn: Math.Max(viewPort.StartingColumn - 1, 0)); - break; - - case ConsoleKey.RightArrow: - viewPort = viewPort.With(startingColumn: viewPort.StartingColumn + 1); - break; - - case ConsoleKey.UpArrow: - viewPort = viewPort.With(startingLine: Math.Max(viewPort.StartingLine - 1, 0)); - break; - - case ConsoleKey.DownArrow: - viewPort = viewPort.With(startingLine: viewPort.StartingLine + 1); - break; - } - } - } - static void Main(string[] args) { var oldStyle = GetStyle(); @@ -115,15 +125,15 @@ namespace MApplication "datatypes", "@table", "table.m"); - Console.CursorVisible = false; - RenderFile(fileName); - Console.CursorVisible = true; - - //foreach (var line in text.Lines) - //{ - // PrintLine(line); - // Console.WriteLine(); - //} + 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); }