diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a30d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,398 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml diff --git a/App.config b/App.config deleted file mode 100644 index fad249e..0000000 --- a/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..bb46346 --- /dev/null +++ b/App.xaml @@ -0,0 +1,4 @@ + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..0bc4dd6 --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,62 @@ +using Autofac; +using charposition.Services; +using System; +using System.Data; +using System.Windows; + +namespace charposition; + +/// +/// Interaction logic for App.xaml +/// +public partial class App : Application +{ + private void Application_Startup(object sender, StartupEventArgs e) + { + // Default: dummy data + string text = DummyData.Text; + string title = DummyData.Title; + string semantics = DummyData.Semantics; + string error = string.Empty; + + // Services + ContainerBuilder services = new(); + services.RegisterType().As(); + services.RegisterType().As(); + services.RegisterType().SingleInstance(); + services.RegisterType(); + + var container = services.Build(); + + var model = container.Resolve(); + + // read args + var fileReader = container.Resolve(); + if (e.Args.Length > 0) + { + text = string.Empty; + semantics = string.Empty; + try + { + title = e.Args[0]; + text = fileReader.ReadAllText(e.Args[0]); + semantics = e.Args.Length == 1 ? string.Empty : fileReader.ReadAllText(e.Args[1]); + } + catch (Exception ex) + { + error = ex.Message; + } + } + + model.LoadData(text, semantics); + + if (error != string.Empty) + { + model.ErrorMessage = error; + } + + var window = container.Resolve(); + window.Title = title; + window.Show(); + } +} diff --git a/Controls/Character.cs b/Controls/Character.cs new file mode 100644 index 0000000..ee99508 --- /dev/null +++ b/Controls/Character.cs @@ -0,0 +1,62 @@ +using System.Windows.Controls; +using System.Windows.Media; + +namespace charposition.Controls; + +public class Character : Canvas +{ + public int Line { get; set; } + public int Column { get; set; } + + public int CharIndex + { + set + { + this.Index.Text = value.ToString(); + } + } + public char CharCode + { + set + { + string display = DisplayCharacter(value); + this.Label.Text = display; + this.Label.Foreground = display != value.ToString() ? Brushes.Gray : Brushes.Black; + } + } + + private TextBlock Label { get; } + private TextBlock Index { get; } + + public Character() + { + this.Label = new() + { + HorizontalAlignment = System.Windows.HorizontalAlignment.Left, + VerticalAlignment = System.Windows.VerticalAlignment.Top, + }; + this.Children.Add(this.Label); + Canvas.SetTop(this.Label, 2); + Canvas.SetLeft(this.Label, 8); + + this.Index = new() + { + Foreground = Brushes.Gray, + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, + VerticalAlignment = System.Windows.VerticalAlignment.Bottom, + FontSize = 10 + }; + this.Children.Add(this.Index); + Canvas.SetBottom(this.Index, 2); + Canvas.SetRight(this.Index, 2); + } + + private static string DisplayCharacter(char character) => + character switch + { + '\r' => "\\r", + '\n' => "\\n", + '\t' => "\\t", + _ => character.ToString(), + }; +} diff --git a/Controls/CharsCanvas.cs b/Controls/CharsCanvas.cs new file mode 100644 index 0000000..e9c7e21 --- /dev/null +++ b/Controls/CharsCanvas.cs @@ -0,0 +1,355 @@ +using charposition.ParserModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace charposition.Controls; + +public class CharsCanvas : UserControl +{ + public event EventHandler? HoveredCharChanged; + + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); + + public int MaxLineLength + { + get => (int)GetValue(MaxLineLengthProperty); + set => SetValue(MaxLineLengthProperty, value); + } + public static readonly DependencyProperty MaxLineLengthProperty = + DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsCanvas), new PropertyMetadata(1, Redraw)); + + public IEnumerable> LineChars + { + get => (IEnumerable>)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(IEnumerable>), typeof(CharsCanvas), new PropertyMetadata(null, OnLineCharsChanged)); + + static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is ObservableCollection> coll && d is CharsCanvas ctrl) + { + coll.CollectionChanged += (object? sender, NotifyCollectionChangedEventArgs e) => ctrl.Draw(); + } + } + + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(CharsCanvas), new PropertyMetadata(null, Redraw)); + + private readonly ScrollViewer scrollViewer; + + public CharsCanvas(ScrollViewer scrollViewer) + { + this.Background = Brushes.Transparent; + + this.Canvas = new Canvas(); + this.Content = this.Canvas; + + Canvas.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + Canvas.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.SizeChanged += (s, e) => this.Draw(); + + this.HighlightColumn = new() + { + Width = CharsView.CellWidth, + Fill = CharsView.HighlightBrush + }; + this.HighlightLine = new() + { + Height = CharsView.CellHeight, + Fill = CharsView.HighlightBrush + }; + + this.HighlightLine.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + this.HighlightColumn.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.scrollViewer = scrollViewer; + this.scrollViewer.ScrollChanged += (s, e) => this.Draw(); + } + + protected override Size MeasureOverride(Size constraint) => + new(this.MaxLineLength * CharsView.CellWidth, this.LineCount * CharsView.CellHeight); + + protected override void OnMouseMove(MouseEventArgs e) + { + var mouseOverChar = this.Characters.Find(c => c.IsMouseOver); + this.HighlightCharacter(mouseOverChar); + } + + internal void HighlightCharacter(Character? character) + { + if (this.HighlightedCharacter == character) + { + return; + } + + this.HoveredCharChanged?.Invoke(this, new CharChangedArgs + { + Column = character?.Column, + Line = character?.Line + }); + + if (this.HighlightedCharacter != null) + { + this.UpdateCharacterSelection(this.HighlightedCharacter); + } + + this.HighlightedCharacter = character; + if (character == null) + { + this.Canvas.Children.Remove(this.HighlightColumn); + this.Canvas.Children.Remove(this.HighlightLine); + return; + } + + character.Background = Brushes.Lime; + Canvas.SetLeft(this.HighlightColumn, character.Column * CharsView.CellWidth); + Canvas.SetTop(this.HighlightLine, character.Line * CharsView.CellHeight); + if (!this.Canvas.Children.Contains(this.HighlightColumn)) + { + this.Canvas.Children.Insert(0, this.HighlightColumn); + } + if (!this.Canvas.Children.Contains(this.HighlightLine)) + { + this.Canvas.Children.Insert(0, this.HighlightLine); + } + } + + private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => + ((CharsCanvas)d).Draw(); + + private void Draw() + { + // Calculate visible columns + this.visibleLines = (int)Math.Ceiling(this.scrollViewer.ViewportHeight / CharsView.CellHeight) + 2; + this.visibleColumns = (int)Math.Ceiling(this.scrollViewer.ViewportWidth / CharsView.CellWidth) + 2; + + // Update columns + this.startRow = (int)Math.Max(0, Math.Floor(this.scrollViewer.VerticalOffset / CharsView.CellHeight) - 1); + this.startCol = (int)Math.Max(0, Math.Floor(this.scrollViewer.HorizontalOffset / CharsView.CellWidth) - 1); + + this.UpdateRowLines(); + this.UpdateColumnLines(); + this.UpdateCharacters(); + } + + private void UpdateCharacters() + { + int charIndex = 0; + int rowIndex = 0; + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + { + var endCol = Math.Min(lineChars.Count, this.startCol + this.visibleColumns); + var col = startCol; + foreach(var c in lineChars.Skip(this.startCol).Take(endCol)) + { + if (this.Characters.Count <= charIndex) + { + var @char = new Character() + { + Width = CharsView.CellWidth, + Height = CharsView.CellHeight, + Background = Brushes.Transparent, + }; + this.Canvas.Children.Add(@char); + this.Characters.Add(@char); + } + + var character = this.Characters[charIndex]; + character.CharIndex = c.Key; + character.CharCode = c.Value; + character.Line = rowIndex + this.startRow; + character.Column = col; + this.UpdateCharacterSelection(character); + Canvas.SetTop(character, character.Line * CharsView.CellHeight); + Canvas.SetLeft(character, col * CharsView.CellWidth); + + charIndex++; + col++; + } + rowIndex++; + } + + // Cleanup unneeded chars + if (this.Characters.Count > charIndex + 100) + { + while (this.Characters.Count > charIndex) + { + this.Canvas.Children.Remove(this.Characters[^1]); + this.Characters.RemoveAt(this.Characters.Count - 1); + } + } + } + + private void UpdateCharacterSelection(Character character) + { + bool isSelected = false; + if (this.SelectedSpan?.Start != null && this.SelectedSpan?.End != null) + { + int row = character.Line + 1; + if (row == this.SelectedSpan.Start[0]) + { + isSelected = character.Column + 1 >= this.SelectedSpan.Start[1]; + } + else if (row > this.SelectedSpan.Start[0] && character.Line < this.SelectedSpan.End[0]) + { + isSelected = true; + } + else if (row == this.SelectedSpan.End[0]) + { + isSelected = character.Column + 1 <= this.SelectedSpan.End[1]; + } + } + + character.Background = isSelected ? CharsView.SelectionBrush : Brushes.Transparent; + } + + private void UpdateColumnLines() + { + int rowIndex = 0; + int colIndex = 0; + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + { + var endCol = Math.Min(lineChars.Count, this.startCol + this.visibleColumns); + for (int i = this.startCol; i <= endCol; i++) + { + if (this.ColumnLines.Count <= colIndex) + { + var line = new Line + { + Stroke = CharsView.LineBrush, + StrokeThickness = 0.5, + }; + this.Canvas.Children.Add(line); + this.ColumnLines.Add(line); + } + var columnLine = this.ColumnLines[colIndex++]; + + columnLine.X1 = columnLine.X2 = (i * CharsView.CellWidth); + + int row = this.startRow + rowIndex; + columnLine.Y1 = (row * CharsView.CellHeight); + columnLine.Y2 = ((row + 1) * CharsView.CellHeight); + } + + rowIndex++; + } + + // Cleanup unneeded columns + if (this.ColumnLines.Count > colIndex + 100) + { + while (this.ColumnLines.Count > colIndex) + { + this.Canvas.Children.Remove(this.ColumnLines[^1]); + this.ColumnLines.RemoveAt(this.ColumnLines.Count - 1); + } + } + } + + private void UpdateRowLines() + { + // Cleanup unneeded lines + if (this.RowLines.Count > this.visibleLines + 25) + { + while (this.RowLines.Count > this.visibleLines) + { + this.Canvas.Children.Remove(this.RowLines[^1]); + this.RowLines.RemoveAt(this.RowLines.Count - 1); + } + } + + // Create new lines + while (this.RowLines.Count < this.visibleLines) + { + var line = new Line + { + Stroke = CharsView.LineBrush, + StrokeThickness = 0.5, + X1 = 0 + }; + this.Canvas.Children.Add(line); + this.RowLines.Add(line); + } + + // Adjust line lengths + int lastLineLength = 0; + int rowIndex = 0; + foreach (var lineChars in this.LineChars.Skip(this.startRow).Take(this.visibleLines)) + { + if (lastLineLength == 0) + { + rowIndex++; + lastLineLength = lineChars.Count; + continue; + } + + int length = Math.Max(lastLineLength, lineChars.Count); + UpdateRowLineLength(rowIndex, this.startRow + rowIndex, length); + lastLineLength = lineChars.Count; + rowIndex++; + } + + UpdateRowLineLength(rowIndex, this.startRow + rowIndex, lastLineLength); + } + + private void UpdateRowLineLength(int rowIndex, int lineNo, int length) + { + if (rowIndex < 1 || rowIndex >= this.RowLines.Count) + { + return; + } + + var line = this.RowLines[rowIndex - 1]; + line.Y1 = lineNo * CharsView.CellHeight; + line.Y2 = lineNo * CharsView.CellHeight; + line.X2 = length * CharsView.CellWidth; + } + + private Canvas Canvas { get; } + + private List RowLines { get; } = new(); + private List ColumnLines { get; } = new(); + + private List Characters { get; } = new(); + private Character? HighlightedCharacter { get; set; } + + private Rectangle HighlightLine { get; } + private Rectangle HighlightColumn { get; } + + private int visibleLines; + private int visibleColumns; + private int startRow; + private int startCol; + + public class CharChangedArgs : EventArgs + { + public int? Line { get; set; } + public int? Column { get; set; } + } +} diff --git a/Controls/CharsView.cs b/Controls/CharsView.cs new file mode 100644 index 0000000..d522bd1 --- /dev/null +++ b/Controls/CharsView.cs @@ -0,0 +1,269 @@ +using charposition.Converters; +using charposition.ParserModel; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace charposition.Controls; + +public class CharsView : UserControl +{ + internal const int CellWidth = 24; + internal const int CellHeight = 32; + + internal static readonly Brush LineBrush = Brushes.Gray; + internal static readonly Brush LabelBrush = Brushes.Gray; + internal static readonly Brush HighlightBrush = new SolidColorBrush(Color.FromRgb(230, 255, 236)); + internal static readonly Brush SelectionBrush = new SolidColorBrush(Color.FromArgb(100, 192, 232, 250)); + + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(CharsView), new PropertyMetadata(1, Redraw)); + + public int MaxLineLength + { + get => (int)GetValue(MaxLineLengthProperty); + set => SetValue(MaxLineLengthProperty, value); + } + public static readonly DependencyProperty MaxLineLengthProperty = + DependencyProperty.Register("MaxLineLength", typeof(int), typeof(CharsView), new PropertyMetadata(1, Redraw)); + + public IEnumerable LineChars + { + get => (IEnumerable)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(IEnumerable), typeof(CharsView), new PropertyMetadata(null, OnLineCharsChanged)); + + static void OnLineCharsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is ObservableCollection coll && d is CharsView ctrl) + { + coll.CollectionChanged += (object? sender, NotifyCollectionChangedEventArgs e) => ctrl.Draw(); + } + } + + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(CharsView), new PropertyMetadata(null)); + + public CharsView() + { + this.Background = Brushes.Transparent; + + this.Canvas = new Canvas(); + this.Content = this.Canvas; + + Canvas.SetBinding(WidthProperty, + new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this }); + Canvas.SetBinding(HeightProperty, + new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this }); + + this.SizeChanged += (s, e) => this.Draw(); + + this.HorizontalLine = new Line + { + Stroke = LineBrush, + StrokeThickness = 1, + X1 = CellWidth, + Y1 = CellWidth, + Y2 = CellWidth + }; + this.Canvas.Children.Add(this.HorizontalLine); + + this.VerticalLine = new Line + { + Stroke = LineBrush, + StrokeThickness = 1, + X1 = CellWidth, + X2 = CellWidth, + Y1 = CellWidth + }; + this.Canvas.Children.Add(this.VerticalLine); + + this.ScrollViewer = new() + { + HorizontalScrollBarVisibility = ScrollBarVisibility.Visible, + VerticalScrollBarVisibility = ScrollBarVisibility.Visible + }; + this.ScrollViewer.ScrollChanged += (s, e) => this.Draw(); + Canvas.SetTop(this.ScrollViewer, CellWidth + 1); + Canvas.SetLeft(this.ScrollViewer, CellWidth + 1); + ScrollViewer.SetBinding(WidthProperty, + new Binding + { + Path = new PropertyPath(ActualWidthProperty), + Source = this, + Converter = new SizeAdjustmentConverter { Adjustment = -1 * CellWidth } + }); + ScrollViewer.SetBinding(HeightProperty, + new Binding + { + Path = new PropertyPath(ActualHeightProperty), + Source = this, + Converter = new SizeAdjustmentConverter { Adjustment = -1 * CellWidth } + }); + this.Canvas.Children.Add(this.ScrollViewer); + + this.CharsCanvas = new(this.ScrollViewer); + this.CharsCanvas.HoveredCharChanged += (s, e) => HighlightCharacter(e.Line, e.Column); + this.ScrollViewer.Content = this.CharsCanvas; + + CharsCanvas.SetBinding(CharsCanvas.LineCharsProperty, + new Binding { Path = new PropertyPath(LineCharsProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.MaxLineLengthProperty, + new Binding { Path = new PropertyPath(MaxLineLengthProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.LineCountProperty, + new Binding { Path = new PropertyPath(LineCountProperty), Source = this }); + CharsCanvas.SetBinding(CharsCanvas.SelectedSpanProperty, + new Binding { Path = new PropertyPath(SelectedSpanProperty), Source = this }); + + this.HighlightColumn = new() + { + Width = CellWidth, + Height = CellWidth, + Fill = HighlightBrush, + Visibility = Visibility.Collapsed + }; + this.Canvas.Children.Add(this.HighlightColumn); + this.HighlightLine = new() + { + Width = CellWidth, + Height = CellHeight, + Fill = HighlightBrush, + Visibility = Visibility.Collapsed + }; + this.Canvas.Children.Add(this.HighlightLine); + } + + private void HighlightCharacter(int? line, int? column) + { + if (line.HasValue) + { + this.HighlightLine.Visibility = Visibility.Visible; + Canvas.SetTop(this.HighlightLine, CellWidth + (line.Value * CellHeight) - this.ScrollViewer.VerticalOffset); + } + else + { + this.HighlightLine.Visibility = Visibility.Collapsed; + } + + if (column.HasValue) + { + this.HighlightColumn.Visibility = Visibility.Visible; + Canvas.SetLeft(this.HighlightColumn, ((column.Value + 1) * CellWidth) - this.ScrollViewer.HorizontalOffset); + } + else + { + this.HighlightColumn.Visibility = Visibility.Collapsed; + } + } + + private static void Redraw(DependencyObject d, DependencyPropertyChangedEventArgs e) => + ((CharsView)d).Draw(); + + private void Draw() + { + this.UpdateBorder(); + this.UpdateLineNumbers(); + this.UpdateColumnNumbers(); + } + + private void UpdateColumnNumbers() + { + while (this.ColumnLabels.Count > this.MaxLineLength) + { + this.Canvas.Children.Remove(this.ColumnLabels[^1]); + this.ColumnLabels.RemoveAt(this.ColumnLabels.Count - 1); + } + while (this.ColumnLabels.Count < this.MaxLineLength) + { + var label = new TextBlock + { + Text = (this.ColumnLabels.Count + 1).ToString(), + Foreground = LabelBrush, + FontSize = 12, + Width = CellWidth, + Height = CellHeight, + TextAlignment = TextAlignment.Center + }; + this.Canvas.Children.Add(label); + this.ColumnLabels.Add(label); + Canvas.SetTop(label, 0); + } + + for (int i = 0; i < this.ColumnLabels.Count; i++) + { + var label = this.ColumnLabels[i]; + double left = ((i + 1) * CellWidth) - this.ScrollViewer.HorizontalOffset; + Canvas.SetLeft(label, left); + label.Visibility = left >= CellWidth / 2 ? Visibility.Visible : Visibility.Hidden; + } + } + + private void UpdateLineNumbers() + { + while (this.LineNoLabels.Count > this.LineCount) + { + this.Canvas.Children.Remove(this.LineNoLabels[^1]); + this.LineNoLabels.RemoveAt(this.LineNoLabels.Count - 1); + } + while (this.LineNoLabels.Count < this.LineCount) + { + var label = new TextBlock + { + Text = (this.LineNoLabels.Count + 1).ToString(), + Foreground = LabelBrush, + FontSize = 12, + Width = CellWidth, + Height = CellHeight, + TextAlignment = TextAlignment.Center + }; + this.Canvas.Children.Add(label); + this.LineNoLabels.Add(label); + Canvas.SetLeft(label, 0); + } + + for (int i = 0; i < this.LineNoLabels.Count; i++) + { + var label = this.LineNoLabels[i]; + double top = ((i + 1) * CellHeight) - this.ScrollViewer.VerticalOffset; + Canvas.SetTop(label, top); + label.Visibility = top >= CellWidth / 2 ? Visibility.Visible : Visibility.Hidden; + } + } + + private void UpdateBorder() + { + this.HorizontalLine.X2 = this.ActualWidth; + this.VerticalLine.Y2 = this.ActualHeight; + } + + private Canvas Canvas { get; } + private Line HorizontalLine { get; } + private Line VerticalLine { get; } + + private List LineNoLabels { get; } = new(); + private List ColumnLabels { get; } = new(); + + private ScrollViewer ScrollViewer { get; } + private CharsCanvas CharsCanvas { get; } + + private Rectangle HighlightLine { get; } + private Rectangle HighlightColumn { get; } +} diff --git a/Converters/NotNullVisibilityConverter.cs b/Converters/NotNullVisibilityConverter.cs new file mode 100644 index 0000000..f50201e --- /dev/null +++ b/Converters/NotNullVisibilityConverter.cs @@ -0,0 +1,15 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace charposition.Converters; + +public class NotNullVisibilityConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value != null ? Visibility.Visible : Visibility.Collapsed; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + throw new NotImplementedException(); +} diff --git a/Converters/SizeAdjustmentConverter.cs b/Converters/SizeAdjustmentConverter.cs new file mode 100644 index 0000000..e1f98a2 --- /dev/null +++ b/Converters/SizeAdjustmentConverter.cs @@ -0,0 +1,16 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace charposition.Converters; + +public class SizeAdjustmentConverter : IValueConverter +{ + public double Adjustment { get; set; } + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value is double size ? size + Adjustment : value; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + value is double size ? size - Adjustment : value; +} diff --git a/DummyData.cs b/DummyData.cs new file mode 100644 index 0000000..6d0ba49 --- /dev/null +++ b/DummyData.cs @@ -0,0 +1,46 @@ +namespace charposition; + +internal static class DummyData +{ + public const string Title = "Sample code"; + + public const string Text = +@"class Socket +{ + void Connect(string server) + { + SocketLibrary.Connect(mSocket, server); + } + + void Disconnect() + { + SocketLibrary.Disconnect(mSocket); + } +}"; + + public const string Semantics = +@"--- +type: file +name: FooSocket.csharp +locationSpan : {start: [1, 0], end: [12, 1]} +footerSpan : [0,-1] +parsingErrorsDetected : false +children: + + - type : class + name : Socket + locationSpan : {start: [1, 0], end: [12, 1]} + headerSpan : [0, 16] + footerSpan : [186, 186] + children : + + - type : method + name : Connect + locationSpan : {start: [3, 0], end: [7,2]} + span : [17, 109] + + - type : method + name : Disconnect + locationSpan : {start: [8,0], end: [11,6]} + span : [110, 185]"; +} diff --git a/Form1.Designer.cs b/Form1.Designer.cs deleted file mode 100644 index 463bbef..0000000 --- a/Form1.Designer.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace charposition -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.SuspendLayout(); - // - // pictureBox1 - // - this.pictureBox1.Location = new System.Drawing.Point(0, 0); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(100, 50); - this.pictureBox1.TabIndex = 0; - this.pictureBox1.TabStop = false; - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.AutoScroll = true; - this.BackColor = System.Drawing.Color.White; - this.ClientSize = new System.Drawing.Size(862, 385); - this.Controls.Add(this.pictureBox1); - this.Name = "Form1"; - this.Text = "Form1"; - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.PictureBox pictureBox1; - - } -} - diff --git a/Form1.cs b/Form1.cs deleted file mode 100644 index d3637ce..0000000 --- a/Form1.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Drawing; -using System.Windows.Forms; - -namespace charposition -{ - public partial class Form1 : Form - { - public Form1(string title, string text) - { - InitializeComponent(); - - this.Text = title; - - Bitmap bmp = RenderFile.Draw(text); - - pictureBox1.Width = bmp.Width; - pictureBox1.Height = bmp.Height; - pictureBox1.Left = 0; - pictureBox1.Top = 0; - pictureBox1.Image = bmp; - } - } -} diff --git a/Form1.resx b/Form1.resx deleted file mode 100644 index 29dcb1b..0000000 --- a/Form1.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/MainWindow.xaml b/MainWindow.xaml new file mode 100644 index 0000000..51d8a75 --- /dev/null +++ b/MainWindow.xaml @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs new file mode 100644 index 0000000..3425421 --- /dev/null +++ b/MainWindow.xaml.cs @@ -0,0 +1,32 @@ +using charposition.ParserModel; +using System.Windows; +using System.Windows.Controls; + +namespace charposition; + +/// +/// Interaction logic for MainWindow.xaml +/// +public partial class MainWindow : Window +{ + public MainWindow(MainWindowModel model) + { + DataContext = model; + InitializeComponent(); + } + + private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + if (sender is not TreeView tree || this.DataContext is not MainWindowModel model) + { + return; + } + + model.SelectedSpan = tree.SelectedItem switch + { + FileNode file => file.LocationSpan, + ChildNode node => node.LocationSpan, + _ => null, + }; + } +} diff --git a/MainWindowModel.cs b/MainWindowModel.cs new file mode 100644 index 0000000..e9c8962 --- /dev/null +++ b/MainWindowModel.cs @@ -0,0 +1,139 @@ +using charposition.ParserModel; +using charposition.Services; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Windows; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace charposition; + +public class MainWindowModel : DependencyObject +{ + public int MaximumLineLength + { + get => (int)GetValue(MaximumLineLengthProperty); + set => SetValue(MaximumLineLengthProperty, value); + } + public static readonly DependencyProperty MaximumLineLengthProperty = + DependencyProperty.Register("MaximumLineLength", typeof(int), typeof(MainWindowModel), new PropertyMetadata(0)); + + public int LineCount + { + get => (int)GetValue(LineCountProperty); + set => SetValue(LineCountProperty, value); + } + public static readonly DependencyProperty LineCountProperty = + DependencyProperty.Register("LineCount", typeof(int), typeof(MainWindowModel), new PropertyMetadata(0)); + + public FileNode? SemanticFile + { + get => (FileNode?)GetValue(SemanticFileProperty); + set => SetValue(SemanticFileProperty, value); + } + public static readonly DependencyProperty SemanticFileProperty = + DependencyProperty.Register("SemanticFile", typeof(FileNode), typeof(MainWindowModel), new PropertyMetadata(null)); + + public string? ErrorMessage + { + get => (string?)GetValue(ErrorMessageProperty); + set => SetValue(ErrorMessageProperty, value); + } + public static readonly DependencyProperty ErrorMessageProperty = + DependencyProperty.Register("ErrorMessage", typeof(string), typeof(MainWindowModel), new PropertyMetadata(null)); + + public LocationSpan? SelectedSpan + { + get => (LocationSpan?)GetValue(SelectedSpanProperty); + set => SetValue(SelectedSpanProperty, value); + } + public static readonly DependencyProperty SelectedSpanProperty = + DependencyProperty.Register("SelectedSpan", typeof(LocationSpan), typeof(MainWindowModel), new PropertyMetadata(null)); + + public ObservableCollection Semantics { get; } = new(); + + public ObservableCollection> LineChars + { + get => (ObservableCollection>)GetValue(LineCharsProperty); + set => SetValue(LineCharsProperty, value); + } + public static readonly DependencyProperty LineCharsProperty = + DependencyProperty.Register("LineChars", typeof(ObservableCollection>), typeof(MainWindowModel), + new PropertyMetadata(new ObservableCollection>())); + + private readonly ILineSplitter lineSplitter; + + public MainWindowModel(ILineSplitter lineSplitter) + { + this.lineSplitter = lineSplitter; + } + + public void LoadData(string text, string semantics) + { + this.Clear(); + this.LoadText(text); + this.LoadSemantics(semantics); + } + + private void LoadSemantics(string semantics) + { + if (string.IsNullOrEmpty(semantics)) + { + return; + } + + try + { + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + this.SemanticFile = deserializer.Deserialize(new StringReader(semantics)); + this.Semantics.Add(this.SemanticFile); + } + catch (Exception ex) + { + this.ErrorMessage = ex.Message; + if (ex.InnerException != null) + { + this.ErrorMessage += $": {ex.InnerException.Message}"; + } + } + } + + private void LoadText(string text) + { + int lineCount = 0; + int maxLineLen = 0; + int charIndex = 0; + List> lines = new(); + foreach (char[] lineChars in lineSplitter.SplitLines(text)) + { + lineCount++; + maxLineLen = Math.Max(maxLineLen, lineChars.Length); + + Dictionary line = new(); + foreach(char c in lineChars) + { + line[charIndex++] = c; + } + lines.Add(line); + } + + this.LineCount = lineCount; + this.MaximumLineLength = maxLineLen; + this.LineChars = new ObservableCollection>(lines); + } + + private void Clear() + { + this.ErrorMessage = null; + this.SemanticFile = null; + this.Semantics.Clear(); + this.MaximumLineLength = 0; + this.LineCount = 0; + this.LineChars.Clear(); + } +} diff --git a/ParserModel/ChildNode.cs b/ParserModel/ChildNode.cs new file mode 100644 index 0000000..e8b5e7e --- /dev/null +++ b/ParserModel/ChildNode.cs @@ -0,0 +1,12 @@ +namespace charposition.ParserModel; + +public class ChildNode +{ + public string? Type { get; set; } + public string? Name { get; set; } + public LocationSpan? LocationSpan { get; set; } + public int[]? Span { get; set; } + public int[]? HeaderSpan { get; set; } + public int[]? FooterSpan { get; set; } + public ChildNode[]? Children { get; set; } +} diff --git a/ParserModel/FileNode.cs b/ParserModel/FileNode.cs new file mode 100644 index 0000000..c673219 --- /dev/null +++ b/ParserModel/FileNode.cs @@ -0,0 +1,13 @@ +namespace charposition.ParserModel; + +public class FileNode +{ + public string? Type { get; set; } + public string? Name { get; set; } + public LocationSpan? LocationSpan { get; set; } + public int[]? Span { get; set; } + public int[]? FooterSpan { get; set; } + public bool ParsingErrorsDetected { get; set; } + public int[]? ParsingErrors { get; set; } + public ChildNode[]? Children { get; set; } +} diff --git a/ParserModel/LocationSpan.cs b/ParserModel/LocationSpan.cs new file mode 100644 index 0000000..f1d6f56 --- /dev/null +++ b/ParserModel/LocationSpan.cs @@ -0,0 +1,7 @@ +namespace charposition.ParserModel; + +public class LocationSpan +{ + public int[]? Start { get; set; } + public int[]? End { get; set; } +} diff --git a/Program.cs b/Program.cs deleted file mode 100644 index 4181c1f..0000000 --- a/Program.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.IO; -using System.Windows.Forms; - -namespace charposition -{ - static class Program - { - [STAThread] - static void Main(string[] args) - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - - string text = -@"class Socket -{ - void Connect(string server) - { - SocketLibrary.Connect(mSocket, server); - } - - void Disconnect() - { - SocketLibrary.Disconnect(mSocket); - } -}"; - - string title = "Sample code"; - - if (args.Length > 0) - { - text = File.ReadAllText(args[0]); - title = args[0]; - } - - Application.Run(new Form1(title, text)); - } - } -} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs deleted file mode 100644 index 050835b..0000000 --- a/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("charposition")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("charposition")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("97110a2b-dd77-4e0e-bb10-dc44a64be4b8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs deleted file mode 100644 index 425ec3d..0000000 --- a/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace charposition.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("charposition.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/Properties/Resources.resx b/Properties/Resources.resx deleted file mode 100644 index ffecec8..0000000 --- a/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs deleted file mode 100644 index 37cf637..0000000 --- a/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace charposition.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/Properties/Settings.settings b/Properties/Settings.settings deleted file mode 100644 index abf36c5..0000000 --- a/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..e711530 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "charposition": { + "commandName": "Project" + }, + "Profile 1": { + "commandName": "Project", + "commandLineArgs": "E:\\WEEK70\\PRGS\\aadd.prg C:\\Users\\buchfink\\AppData\\Local\\Temp\\tmpD.yaml" + } + } +} \ No newline at end of file diff --git a/RenderFile.cs b/RenderFile.cs deleted file mode 100644 index 7d8e54d..0000000 --- a/RenderFile.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -using System.Drawing.Text; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace charposition -{ - static class RenderFile - { - static internal Bitmap Draw(string text) - { - Size maxTextSize = GetTextSizeInLinesAndCols(text); - - Font codeFont = new Font("Consolas", 14.0f); - Size codeTextSize = TextRenderer.MeasureText("Z", codeFont); - - int xSpace = codeTextSize.Width + 2; - int ySpace = codeTextSize.Height + 10; - - int xInitialMargin = xSpace; - int yInitialMargin = ySpace; - - var bitmap = new Bitmap( - maxTextSize.Width * xSpace + xInitialMargin +1 , - maxTextSize.Height * ySpace + yInitialMargin + 1, - PixelFormat.Format32bppArgb); - - var g = Graphics.FromImage(bitmap); - - g.SmoothingMode = SmoothingMode.AntiAlias; - g.TextRenderingHint = TextRenderingHint.AntiAlias; - - RenderNumbers numbers = new RenderNumbers(); - numbers.RenderColumns(g, maxTextSize.Width, xSpace, xInitialMargin); - numbers.RenderLines(g, maxTextSize.Height, ySpace, yInitialMargin); - - Font numberFont = new Font("Consolas", 7.0f); - Font eolFont = new Font("Consolas", 11.0f); - - var eolBrush = new SolidBrush(Color.Gray); - - var brush = new SolidBrush(Color.Navy); - var pen = new Pen(brush); - - var linePen = new Pen(new SolidBrush(Color.Gray)); - - Size numberTextSize = TextRenderer.MeasureText("Z", numberFont); - - int x = xInitialMargin; - int y = yInitialMargin; - - for (int pos = 0; pos < text.Length; ++pos) - { - string charToDraw = text[pos].ToString(); - - Font currentFont = codeFont; - Brush currentBrush = brush; - - if (text[pos] == '\r') - { - charToDraw = "\\r"; - currentFont = eolFont; - currentBrush = eolBrush; - } - - if (text[pos] == '\n') - { - charToDraw = "\\n"; - currentFont = eolFont; - currentBrush = eolBrush; - } - - g.DrawString(charToDraw, currentFont, currentBrush, new PointF(x, y)); - - g.DrawRectangle(linePen, new Rectangle( - x, y, - xSpace, - ySpace)); - - string posString = pos.ToString(); - - g.DrawString(posString, numberFont, eolBrush, - new PointF( - x + xSpace - TextRenderer.MeasureText(posString, numberFont).Width, - y + ySpace - numberTextSize.Height)); - - x += xSpace; - - if (text[pos] == '\n') - { - y += ySpace; - x = xInitialMargin; - } - } - - return bitmap; - } - - static Size GetTextSizeInLinesAndCols(string text) - { - int lines = 1; - int maxCols = 0; - - int lineWidth = 0; - - for (int i = 0; i < text.Length; ++i) - { - ++lineWidth; - - if (text[i] == '\n') - { - ++lines; - - if (maxCols < lineWidth) - maxCols = lineWidth; - - lineWidth = 0; - } - } - - return new Size(maxCols, lines); - } - - class RenderNumbers - { - internal void RenderColumns( - Graphics g, - int columns, - int xSpace, - int xInitialMargin) - { - int x = xInitialMargin; - int y = 10; - - for (int i = 1; i <= columns; ++i) - { - Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - - g.DrawString(i.ToString(), mNumberFont, mGrayBrush, - new PointF( - x + xSpace / 2 - (numberTextSize.Width / 2), - y) - ); - - x += xSpace; - } - } - - internal void RenderLines( - Graphics g, - int lines, - int ySpace, - int yInitialMargin) - { - int x = 5; - int y = yInitialMargin; - - for (int i = 1; i <= lines; ++i) - { - Size numberTextSize = TextRenderer.MeasureText(i.ToString(), mNumberFont); - - g.DrawString(i.ToString(), mNumberFont, mGrayBrush, - new PointF( - x, - y + ySpace / 2 - (numberTextSize.Height / 2)) - ); - - y += ySpace; - } - } - - Font mNumberFont = new Font("Consolas", 9.0f); - Brush mGrayBrush = new SolidBrush(Color.Gray); - } - } -} diff --git a/Services/FileReader.cs b/Services/FileReader.cs new file mode 100644 index 0000000..ed6f581 --- /dev/null +++ b/Services/FileReader.cs @@ -0,0 +1,8 @@ +using System.IO; + +namespace charposition.Services; + +internal class FileReader : IFileReader +{ + public string ReadAllText(string path) => File.ReadAllText(path); +} diff --git a/Services/IFileReader.cs b/Services/IFileReader.cs new file mode 100644 index 0000000..e7f548a --- /dev/null +++ b/Services/IFileReader.cs @@ -0,0 +1,6 @@ +namespace charposition.Services; + +public interface IFileReader +{ + string ReadAllText(string path); +} diff --git a/Services/ILineSplitter.cs b/Services/ILineSplitter.cs new file mode 100644 index 0000000..b37f003 --- /dev/null +++ b/Services/ILineSplitter.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace charposition.Services; + +public interface ILineSplitter +{ + IEnumerable SplitLines(string text); +} diff --git a/Services/LineSplitter.cs b/Services/LineSplitter.cs new file mode 100644 index 0000000..872ddc5 --- /dev/null +++ b/Services/LineSplitter.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace charposition.Services; + +internal class LineSplitter : ILineSplitter +{ + public IEnumerable SplitLines(string text) + { + int index; + while ((index = text.IndexOf('\n')) > -1) + { + yield return text[..(index + 1)].ToCharArray(); + text = text[(index + 1)..]; + } + yield return text.ToCharArray(); + } +} diff --git a/charposition.csproj b/charposition.csproj index 28fa6bd..1c8ac02 100644 --- a/charposition.csproj +++ b/charposition.csproj @@ -1,89 +1,16 @@ - - - + - Debug - AnyCPU - {AF4CC8D5-70C5-44EF-B1D1-A2EF3FC24B04} + net7.0-windows WinExe - Properties - charposition - charposition - v4.5 - 512 + enable + true + 1.0.0.0 + CharPosition + CharPosition + CharPosition (c) 2023 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - Form - - - Form1.cs - - - - - - Form1.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - + + - - \ No newline at end of file diff --git a/screenshot/charpositionscreenshot.png b/screenshot/charpositionscreenshot.png index d50d903..89062f1 100644 Binary files a/screenshot/charpositionscreenshot.png and b/screenshot/charpositionscreenshot.png differ