diff --git a/CSharpMath.CoreTests/MathListTest.cs b/CSharpMath.CoreTests/MathListTest.cs index bfbb81df..3c4fe9ac 100644 --- a/CSharpMath.CoreTests/MathListTest.cs +++ b/CSharpMath.CoreTests/MathListTest.cs @@ -30,12 +30,6 @@ public void TestAdd() { Assert.Equal(atom2, list[1]); } - [Fact] - public void TestAddErrors() { - var list = new MathList(); - Assert.Throws(() => list.Add(null!)); - } - [Fact] public void TestInsert() { var list = new MathList(); diff --git a/CSharpMath.Editor.Tests/KeyPressTests.cs b/CSharpMath.Editor.Tests/KeyPressTests.cs index c2908744..4b3a7e3c 100644 --- a/CSharpMath.Editor.Tests/KeyPressTests.cs +++ b/CSharpMath.Editor.Tests/KeyPressTests.cs @@ -227,11 +227,27 @@ public void Return(params K[] inputs) => [ Theory, T(@"", K.Backspace, K.Backspace, K.Backspace, K.Backspace, K.Backspace), + T(@"2", K.Backspace, K.Backspace, K.D1, K.Backspace, K.D2), T(@"1", K.D1, K.D2, K.Backspace), T(@"x^2", K.SmallX, K.Power, K.D2, K.D1, K.Backspace), - T(@"y_{3_4}", K.SmallY, K.Subscript, K.D3, K.Subscript, K.Backspace, K.Backspace, K.D4, K.D5, K.Backspace), - T(@"5^■", K.D5, K.Power, K.Iota, K.Kappa, K.SmallEta, K.Backspace, K.Backspace, K.Backspace, K.Backspace), - T(@"\frac{■}{\square }", K.Fraction, K.Backspace), + T(@"5", K.D5, K.Power, K.Iota, K.Kappa, K.SmallEta, K.Backspace, K.Backspace, K.Backspace, K.Backspace), + T(@"", K.Fraction, K.Backspace), + T(@"", K.Power, K.Backspace), + T(@"", K.Subscript, K.Backspace), + T(@"", K.SquareRoot, K.Backspace), + T(@"3", K.CubeRoot, K.Backspace), + T(@"", K.NthRoot, K.Backspace), + T(@"a", K.SmallA, K.Fraction, K.Backspace), + T(@"a", K.SmallA, K.SquareRoot, K.Backspace), + T(@"a3", K.SmallA, K.CubeRoot, K.Backspace), + T(@"a", K.SmallA, K.NthRoot, K.Backspace), + T(@"a", K.SmallA, K.Power, K.Backspace), + T(@"a", K.SmallA, K.Subscript, K.Backspace), + T(@"\square ^■", K.Power, K.Subscript, K.Backspace), + T(@"\square ^■", K.Power, K.Power, K.Backspace), + T(@"\square _■", K.Subscript, K.Power, K.Backspace), + T(@"\square _■", K.Subscript, K.Subscript, K.Backspace), + T(@"y_4", K.SmallY, K.Subscript, K.D3, K.Subscript, K.Backspace, K.Backspace, K.D4, K.D5, K.Backspace), T(@"", K.VerticalBar, K.VerticalBar, K.Backspace, K.Backspace, K.Backspace) ] public void Backspace(string latex, params K[] inputs) => Test(latex, inputs); @@ -239,6 +255,88 @@ public void Return(params K[] inputs) => [ Theory, T(@"", K.Left, K.Left, K.Backspace, K.Backspace, K.Right, K.Right, K.Backspace, K.Backspace, K.Left), + T(@"", K.Power, K.Left, K.Backspace), + T(@"", K.Subscript, K.Left, K.Backspace), + T(@".\frac{\square }{\square }", K.Fraction, K.Left, K.Backspace, K.Decimal), + T(@".\sqrt{\square }", K.SquareRoot, K.Left, K.Backspace, K.Decimal), + T(@".\sqrt[\square ]{\square }", K.NthRoot, K.Left, K.Backspace, K.Decimal), + T(@".\left( \square \right) ", K.BothRoundBrackets, K.Left, K.Backspace, K.Decimal), + T(@".", K.Power, K.Left, K.Backspace, K.Decimal), + T(@".", K.Subscript, K.Left, K.Backspace, K.Decimal), + T(@".\frac{\square }{\square }", K.SmallA, K.Fraction, K.Left, K.Backspace, K.Decimal), + T(@".\sqrt{\square }", K.SmallA, K.SquareRoot, K.Left, K.Backspace, K.Decimal), + T(@".\sqrt[\square ]{\square }", K.SmallA, K.NthRoot, K.Left, K.Backspace, K.Decimal), + T(@".\left( \square \right) ", K.SmallA, K.BothRoundBrackets, K.Left, K.Backspace, K.Decimal), + T(@".^{\square }", K.SmallA, K.Power, K.Left, K.Backspace, K.Decimal), + T(@"._{\square }", K.SmallA, K.Subscript, K.Left, K.Backspace, K.Decimal), + T(@".", K.Fraction, K.Right, K.Backspace, K.Decimal), + T(@".", K.SquareRoot, K.Right, K.Backspace, K.Decimal), + T(@".", K.NthRoot, K.Right, K.Backspace, K.Decimal), + T(@".", K.BothRoundBrackets, K.Right, K.Backspace, K.Decimal), + T(@".", K.Power, K.Right, K.Backspace, K.Decimal), + T(@".", K.Subscript, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.Fraction, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.SquareRoot, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.NthRoot, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.BothRoundBrackets, K.Right, K.Backspace, K.Decimal), + T(@".", K.SmallA, K.Power, K.Right, K.Backspace, K.Decimal), + T(@".", K.SmallA, K.Subscript, K.Right, K.Backspace, K.Decimal), + T(@".", K.Fraction, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.SquareRoot, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.NthRoot, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.BothRoundBrackets, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.Power, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.Subscript, K.Right, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.Fraction, K.Right, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.SquareRoot, K.Right, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.NthRoot, K.Right, K.Right, K.Backspace, K.Decimal), + T(@"a.", K.SmallA, K.BothRoundBrackets, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.SmallA, K.Power, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".", K.SmallA, K.Subscript, K.Right, K.Right, K.Backspace, K.Decimal), + T(@".bcd", K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".bcd", K.SquareRoot, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".bcd", K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".bcd", K.BothRoundBrackets, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".bcd", K.Power, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".bcd", K.Subscript, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.SquareRoot, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.BothRoundBrackets, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.Power, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"a.bcd", K.SmallA, K.Subscript, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"bcd.", K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Right, K.Backspace, K.Decimal), + T(@"bcd.", K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Right, K.Backspace, K.Decimal), + T(@"abcd.", K.SmallA, K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Right, K.Backspace, K.Decimal), + T(@"abcd.", K.SmallA, K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Right, K.Backspace, K.Decimal), + T(@"bcd.", K.Fraction, K.Right, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"bcd.", K.NthRoot, K.Right, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"abcd.", K.SmallA, K.Fraction, K.Right, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"abcd.", K.SmallA, K.NthRoot, K.Right, K.SmallB, K.SmallC, K.SmallD, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"bcd.efg", K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Right, K.SmallE, K.SmallF, K.SmallG, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"bcd.efg", K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Right, K.SmallE, K.SmallF, K.SmallG, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"abcd.efg", K.SmallA, K.Fraction, K.SmallB, K.SmallC, K.SmallD, K.Right, K.SmallE, K.SmallF, K.SmallG, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"abcd.efg", K.SmallA, K.NthRoot, K.SmallB, K.SmallC, K.SmallD, K.Right, K.SmallE, K.SmallF, K.SmallG, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".456", K.Subscript, K.D4, K.D5, K.D6, K.Left, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".789", K.Power, K.D7, K.D8, K.D9, K.Left, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".456789", K.Subscript, K.D4, K.D5, K.D6, K.Right, K.Power, K.D7, K.D8, K.D9, K.Left, K.Left, K.Left, K.Left, K.Left, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@".456789", K.Power, K.D7, K.D8, K.D9, K.Right, K.Subscript, K.D4, K.D5, K.D6, K.Left, K.Left, K.Left, K.Left, K.Backspace, K.Decimal), + T(@"\square ^{\square }", K.Power, K.Right, K.Subscript, K.Backspace), + T(@"\square _{\square }", K.Subscript, K.Right, K.Power, K.Backspace), + T(@"X^{\square }", K.X, K.Power, K.Right, K.Subscript, K.Backspace), + T(@"X_{\square }", K.X, K.Subscript, K.Right, K.Power, K.Backspace), + T(@"\square ^Z", K.Power, K.Z, K.Right, K.Subscript, K.Backspace), + T(@"\square _{\square }Z", K.Subscript, K.Right, K.Power, K.Z, K.Left, K.Backspace), + T(@"X^Z", K.X, K.Power, K.Z, K.Right, K.Subscript, K.Backspace), + T(@"X_{\square }Z", K.X, K.Subscript, K.Right, K.Power, K.Z, K.Left, K.Backspace), + T(@"\square ^{\square }Y", K.Power, K.Right, K.Subscript, K.Y, K.Left, K.Backspace), + T(@"\square _Y", K.Subscript, K.Y, K.Right, K.Power, K.Backspace), + T(@"X^{\square }Y", K.X, K.Power, K.Right, K.Subscript, K.Y, K.Left, K.Backspace), + T(@"X_Y", K.X, K.Subscript, K.Y, K.Right, K.Power, K.Backspace), + T(@"\square ^ZY", K.Power, K.Z, K.Right, K.Subscript, K.Y, K.Left, K.Backspace), + T(@"\square _YZ", K.Subscript, K.Y, K.Right, K.Power, K.Z, K.Left, K.Backspace), + T(@"X^ZY", K.X, K.Power, K.Z, K.Right, K.Subscript, K.Y, K.Left, K.Backspace), + T(@"X_YZ", K.X, K.Subscript, K.Y, K.Right, K.Power, K.Z, K.Left, K.Backspace), T(@"\frac{\square }{3}", K.Slash, K.D3, K.Left, K.Left, K.Backspace, K.Left), T(@"1_3", K.D1, K.D2, K.Subscript, K.D3, K.Left, K.Left, K.Backspace), T(@"1_3^2", K.D1, K.D4, K.Subscript, K.D3, K.Left, K.Left, K.Power, K.D2, K.Left, K.Left, K.Left, K.Left, K.Backspace), @@ -247,7 +345,6 @@ public void Return(params K[] inputs) => T(@"\sqrt[■]{\square }", K.NthRoot, K.SmallA, K.Backspace), T(@"\sqrt{■}", K.SquareRoot, K.SmallA, K.Backspace), T(@"\frac{1}{■}", K.Slash, K.D6, K.Backspace), - T(@"■_5", K.Subscript, K.D5, K.Left, K.Left, K.Backspace, K.X, K.Left, K.Left, K.Left, K.Backspace), T(@"7+1^X", K.D7, K.Plus, K.D1, K.D2, K.Power, K.X, K.Left, K.Left, K.Backspace), T(@"7.^X", K.D7, K.Decimal, K.D1, K.Power, K.X, K.Left, K.Left, K.Backspace), T(@"7+■^X", K.D7, K.Plus, K.D1, K.Power, K.X, K.Left, K.Left, K.Backspace), diff --git a/CSharpMath.Editor/Extensions/MathList.cs b/CSharpMath.Editor/Extensions/MathList.cs index 050f0870..dd5ea9e0 100644 --- a/CSharpMath.Editor/Extensions/MathList.cs +++ b/CSharpMath.Editor/Extensions/MathList.cs @@ -4,10 +4,13 @@ namespace CSharpMath.Editor { using Atom; using Atoms = Atom.Atoms; using Structures; + using System.Linq; + partial class Extensions { + // TODO: document this function. The name sounds is reasonable but the inputs are not transparent. static void InsertAtAtomIndexAndAdvance(this MathList self, int atomIndex, MathAtom atom, ref MathListIndex advance, MathListSubIndexType advanceType) { if (atomIndex < 0 || atomIndex > self.Count) - throw new IndexOutOfRangeException($"Index {atomIndex} is out of bounds for list of size {self.Atoms.Count}"); + throw new IndexOutOfRangeException($"Insertion index {atomIndex} is out of bounds for list of size {self.Atoms.Count}"); // Test for placeholder to the right of index, e.g. \sqrt{‸■} -> \sqrt{2‸} if (atomIndex < self.Count && self[atomIndex] is Atoms.Placeholder placeholder) { atom.Superscript.Append(placeholder.Superscript); @@ -23,8 +26,8 @@ static void InsertAtAtomIndexAndAdvance(this MathList self, int atomIndex, MathA /// Inserts and modifies to advance to the next position. public static void InsertAndAdvance(this MathList self, ref MathListIndex index, MathAtom atom, MathListSubIndexType advanceType) { index ??= MathListIndex.Level0Index(0); - if (index.AtomIndex > self.Atoms.Count) - throw new IndexOutOfRangeException($"Index {index.AtomIndex} is out of bounds for list of size {self.Atoms.Count}"); + if (index.AtomIndex < 0 || index.AtomIndex > self.Atoms.Count) + throw new IndexOutOfRangeException($"Insertion index {index.AtomIndex} is out of bounds for list of size {self.Atoms.Count}"); switch (index.SubIndexType) { case MathListSubIndexType.None: self.InsertAtAtomIndexAndAdvance(index.AtomIndex, atom, ref index, advanceType); @@ -79,14 +82,94 @@ public static void InsertAndAdvance(this MathList self, ref MathListIndex index, throw new SubIndexTypeMismatchException(index); } } + // TODO document this function + public static MathListIndex? PreviousOrBeforeWholeList(MathListIndex index) { + return + index.SubIndexType switch + { + // TODO: remove the index.SubIndex.AtomIndex == -1 possibility as this is not valid + MathListSubIndexType.None => index.AtomIndex > -1 ? MathListIndex.Level0Index(index.AtomIndex - 1) : null, + _ => + (index.SubIndex == null) ? null : + PreviousOrBeforeWholeList(index.SubIndex) is MathListIndex prevSubIndex + ? MathListIndex.IndexAtLocation(index.AtomIndex, index.SubIndexType, prevSubIndex) : null, + }; + } + // TODO: document this function + public static void RemoveAt(this MathList self, MathListIndex index) { + // TODO: document this function + static bool IsBeforeSubList(MathListIndex index) { + // TODO: remove the index.SubIndex.AtomIndex == -1 condition as this is not valid + return (index.SubIndex != null && index.SubIndex.AtomIndex == -1) && index.SubIndexType == MathListSubIndexType.None; + } + // TODO: document this function + // From github conv so far: + // "atom is a MathAtom directly contained in self that contains(index.SubIndex.AtomIndex > -1) + // is(index.SubIndex.AtomIndex = -1) the atom to remove." + void RemoveAtInnerList(TAtom atom, int innerListIndex) where TAtom : MathAtom, IMathListContainer { + if (index.SubIndex is null) throw new InvalidCodePathException($"{nameof(index.SubIndex)} should exist"); + if (IsBeforeSubList(index)) { + index.ReplaceWith( + index.LevelDown() + ?? throw new InvalidCodePathException($"{nameof(index.SubIndex)} is not null but {nameof(index.LevelDown)} is null")); + self.RemoveAt(index); + MathListIndex tempIndex = index; + int i = 0; + foreach (var innerList in atom.InnerLists) + if (!(innerList.Count == 1 && innerList[0] is Atoms.Placeholder)) + if (i++ < innerListIndex) { + foreach (var inner in innerList) + self.InsertAndAdvance(ref index, inner, MathListSubIndexType.None); + tempIndex = index; + } + else + foreach (var inner in innerList) + self.InsertAndAdvance(ref tempIndex, inner, MathListSubIndexType.None); + if(index.SubIndexType != MathListSubIndexType.None && tempIndex.AtomIndex == 0 // We deleted an atom only consisting of placeholders + || atom.Superscript.Count > 0 || atom.Subscript.Count > 0) + self.InsertAndAdvance(ref tempIndex, LaTeXSettings.Placeholder, MathListSubIndexType.None); + if(atom.Superscript.Count > 0) self[tempIndex.AtomIndex - 1].Superscript.Append(atom.Superscript); + if(atom.Subscript.Count > 0) self[tempIndex.AtomIndex - 1].Subscript.Append(atom.Subscript); + } else atom.InnerLists.ElementAt(innerListIndex).RemoveAt(index.SubIndex); + } + // TODO: document this function + void RemoveAtInnerScript(ref MathListIndex index, MathAtom atom, bool superscript) { + if (index.SubIndex is null) throw new InvalidCodePathException($"{nameof(index.SubIndex)} should exist"); + var script = superscript ? atom.Superscript : atom.Subscript; + if (IsBeforeSubList(index)) { + index.ReplaceWith( + index.LevelDown() + ?? throw new InvalidCodePathException($"{nameof(index.SubIndex)} is not null but {nameof(index.LevelDown)} is null")); + if (atom is Atoms.Placeholder && (superscript ? atom.Subscript : atom.Superscript).Count == 0) + self.RemoveAt(index.AtomIndex); + else index.ReplaceWith(index.Next); + var tempIndex = index; + if (!(script.Count == 1 && script[0] is Atoms.Placeholder)) + foreach (var inner in script) + self.InsertAndAdvance(ref tempIndex, inner, MathListSubIndexType.None); + script.Clear(); + } else script.RemoveAt(index.SubIndex); + } - public static void RemoveAt(this MathList self, ref MathListIndex index) { - index ??= MathListIndex.Level0Index(0); - if (index.AtomIndex > self.Atoms.Count) - throw new IndexOutOfRangeException($"Index {index.AtomIndex} is out of bounds for list of size {self.Atoms.Count}"); + if (index.AtomIndex < -1 || index.AtomIndex >= self.Atoms.Count) + throw new IndexOutOfRangeException($"Deletion index {index.AtomIndex} is out of bounds for list of size {self.Atoms.Count}"); switch (index.SubIndexType) { case MathListSubIndexType.None: - self.RemoveAt(index.AtomIndex); + // TODO: remove the index.SubIndex.AtomIndex == -1 condition as this is not valid + if (index.AtomIndex == -1) { + index.ReplaceWith(index.Next); + if (self.Atoms[index.AtomIndex] is Atoms.Placeholder { Superscript: var super, Subscript: var sub }) { + self.RemoveAt(index.AtomIndex); + var tempIndex = index; + if (!(sub.Count == 1 && sub[0] is Atoms.Placeholder)) + foreach (var s in sub) + self.InsertAndAdvance(ref tempIndex, s, MathListSubIndexType.None); + if (!(super.Count == 1 && super[0] is Atoms.Placeholder)) + foreach (var s in super) + self.InsertAndAdvance(ref tempIndex, s, MathListSubIndexType.None); + } + } else + self.RemoveAt(index.AtomIndex); break; case var _ when index.SubIndex is null: throw new InvalidCodePathException("index.SubIndex is null despite non-None subindex type"); @@ -113,9 +196,10 @@ public static void RemoveAt(this MathList self, ref MathListIndex index) { previous.Subscript.Append(currentAtom.Subscript); self.RemoveAt(index.AtomIndex); // it was in the nucleus and we removed it, get out of the nucleus and get in the nucleus of the previous one. - index = downIndex.Previous is MathListIndex downPrev + index.ReplaceWith( + downIndex.Previous is MathListIndex downPrev ? downPrev.LevelUpWithSubIndex(MathListSubIndexType.BetweenBaseAndScripts, MathListIndex.Level0Index(1)) - : downIndex; + : downIndex); break; } // insert placeholder since we couldn't place the scripts in previous atom @@ -123,42 +207,44 @@ public static void RemoveAt(this MathList self, ref MathListIndex index) { insertionAtom.Subscript.Append(currentAtom.Subscript); insertionAtom.Superscript.Append(currentAtom.Superscript); self.RemoveAt(index.AtomIndex); - index = downIndex; + index.ReplaceWith(downIndex); self.InsertAndAdvance(ref index, insertionAtom, MathListSubIndexType.None); - index = index.Previous ?? throw new InvalidCodePathException("Cannot go back after insertion?"); + index.ReplaceWith(index.Previous ?? throw new InvalidCodePathException("Cannot go back after insertion?")); return; case MathListSubIndexType.Radicand: case MathListSubIndexType.Degree: if (!(self.Atoms[index.AtomIndex] is Atoms.Radical radical)) throw new SubIndexTypeMismatchException(typeof(Atoms.Radical), index); if (index.SubIndexType == MathListSubIndexType.Degree) - radical.Degree.RemoveAt(ref index.SubIndex); - else radical.Radicand.RemoveAt(ref index.SubIndex); + RemoveAtInnerList(radical, 0); + else + RemoveAtInnerList(radical, 1); break; case MathListSubIndexType.Numerator: case MathListSubIndexType.Denominator: if (!(self.Atoms[index.AtomIndex] is Atoms.Fraction frac)) throw new SubIndexTypeMismatchException(typeof(Atoms.Fraction), index); if (index.SubIndexType == MathListSubIndexType.Numerator) - frac.Numerator.RemoveAt(ref index.SubIndex); - else frac.Denominator.RemoveAt(ref index.SubIndex); + RemoveAtInnerList(frac, 0); + else + RemoveAtInnerList(frac, 1); break; case MathListSubIndexType.Subscript: var current = self.Atoms[index.AtomIndex]; if (current.Subscript.IsEmpty()) throw new SubIndexTypeMismatchException(index); - current.Subscript.RemoveAt(ref index.SubIndex); + RemoveAtInnerScript(ref index, current, false); break; case MathListSubIndexType.Superscript: current = self.Atoms[index.AtomIndex]; if (current.Superscript.IsEmpty()) throw new SubIndexTypeMismatchException(index); - current.Superscript.RemoveAt(ref index.SubIndex); + RemoveAtInnerScript(ref index, current, true); break; case MathListSubIndexType.Inner: if (!(self.Atoms[index.AtomIndex] is Atoms.Inner inner)) throw new SubIndexTypeMismatchException(typeof(Atoms.Inner), index); - inner.InnerList.RemoveAt(ref index.SubIndex); + RemoveAtInnerList(inner, 0); break; default: throw new SubIndexTypeMismatchException(index); @@ -167,7 +253,7 @@ public static void RemoveAt(this MathList self, ref MathListIndex index) { // We have deleted to the beginning of the line and it is not the outermost line if (self.AtomAt(index) is null) { self.InsertAndAdvance(ref index, LaTeXSettings.Placeholder, MathListSubIndexType.None); - index = index.Previous ?? throw new InvalidCodePathException("Cannot go back after insertion?"); ; + index.ReplaceWith(index.Previous ?? throw new InvalidCodePathException("Cannot go back after insertion?")); } } } diff --git a/CSharpMath.Editor/MathKeyboard.cs b/CSharpMath.Editor/MathKeyboard.cs index 104aaec8..1d89be37 100644 --- a/CSharpMath.Editor/MathKeyboard.cs +++ b/CSharpMath.Editor/MathKeyboard.cs @@ -87,7 +87,7 @@ public MathListIndex InsertionIndex { public LineStyle LineStyle { get; set; } public Color SelectColor { get; set; } public virtual RectangleF Measure => Display?.DisplayBounds() ?? RectangleF.Empty; - public bool HasText => MathList?.Atoms?.Count > 0; + public bool HasText => MathList.Atoms.Count > 0; public void RecreateDisplayFromMathList() { var position = Display?.Position ?? default; Display = Typesetter.CreateLine(MathList, Font, Context, LineStyle); @@ -389,9 +389,9 @@ void MoveCursorRight() { void DeleteBackwards() { // delete the last atom from the list - if (HasText && _insertionIndex.Previous is MathListIndex previous) { + if (HasText && (Extensions.PreviousOrBeforeWholeList(_insertionIndex)) is MathListIndex previous) { _insertionIndex = previous; - MathList.RemoveAt(ref _insertionIndex); + MathList.RemoveAt(_insertionIndex); } } diff --git a/CSharpMath.Editor/MathListIndex.cs b/CSharpMath.Editor/MathListIndex.cs index cb880cd3..3eb0b896 100644 --- a/CSharpMath.Editor/MathListIndex.cs +++ b/CSharpMath.Editor/MathListIndex.cs @@ -22,7 +22,7 @@ public enum MathListSubIndexType : byte { } /** -* An index that points to a particular character in the MathList. The index is a LinkedList that represents +* An index that points to a particular atom in the MathList. The index is a LinkedList that represents * a path from the beginning of the MathList to reach a particular atom in the list. The next node of the path * is represented by the subIndex. The path terminates when the subIndex is nil. * @@ -43,8 +43,14 @@ private MathListIndex() { } public int AtomIndex { get; set; } ///The type of subindex, e.g. superscript, numerator etc. public MathListSubIndexType SubIndexType { get; set; } + ///The index into the sublist. public MathListIndex? SubIndex; + public void ReplaceWith(MathListIndex replacement) { + AtomIndex = replacement.AtomIndex; + SubIndexType = replacement.SubIndexType; + SubIndex = replacement.SubIndex; + } /** Factory function to create a `MathListIndex` with no subindexes. The index of the atom that the `MathListIndex` points at. @@ -91,18 +97,15 @@ public MathListIndex LevelUpWithSubIndex(MathListSubIndexType type, MathListInde ///Returns true if any of the subIndexes of this index have the given type. public bool HasSubIndexOfType(MathListSubIndexType subIndexType) => - SubIndexType == subIndexType ? true : - SubIndex != null ? SubIndex.HasSubIndexOfType(subIndexType) : false; + SubIndexType == subIndexType || (SubIndex != null && SubIndex.HasSubIndexOfType(subIndexType)); public bool AtSameLevel(MathListIndex other) => - SubIndexType != other.SubIndexType ? false : + SubIndexType == other.SubIndexType && // No subindexes, they are at the same level. - SubIndexType == MathListSubIndexType.None ? true : + (SubIndexType == MathListSubIndexType.None || // the subindexes are used in different atoms - AtomIndex != other.AtomIndex ? false : - SubIndex != null && other.SubIndex != null ? SubIndex.AtSameLevel(other.SubIndex) : - // No subindexes, they are at the same level. - true; + (AtomIndex == other.AtomIndex && + (SubIndex == null || other.SubIndex == null || SubIndex.AtSameLevel(other.SubIndex)))); public int FinalIndex => SubIndexType is MathListSubIndexType.None || SubIndex is null ? AtomIndex : SubIndex.FinalIndex; @@ -120,9 +123,9 @@ SubIndex is null ? $@"[{AtomIndex}, {SubIndexType}:{SubIndex.ToString().Trim('[', ']')}]"; public bool EqualsToIndex(MathListIndex index) => - index is null || AtomIndex != index.AtomIndex || SubIndexType != index.SubIndexType ? false : - SubIndex != null && index.SubIndex != null ? SubIndex.EqualsToIndex(index.SubIndex) : - index.SubIndex == null; + !(index is null) && AtomIndex == index.AtomIndex && SubIndexType == index.SubIndexType && + (SubIndex != null && index.SubIndex != null ? SubIndex.EqualsToIndex(index.SubIndex) : + index.SubIndex == null); public override bool Equals(object obj) => obj is MathListIndex index && EqualsToIndex(index); diff --git a/CSharpMath/Atom/Atoms/Accent.cs b/CSharpMath/Atom/Atoms/Accent.cs index 95735aac..6a5b5600 100644 --- a/CSharpMath/Atom/Atoms/Accent.cs +++ b/CSharpMath/Atom/Atoms/Accent.cs @@ -18,8 +18,8 @@ protected override MathAtom CloneInside(bool finalize) => new Accent(Nucleus, InnerList.Clone(finalize)); public override bool ScriptsAllowed => true; public bool EqualsAccent(Accent other) => - EqualsAtom(other) && InnerList.NullCheckingStructuralEquality(other?.InnerList); - public override bool Equals(object obj) => obj is Accent a ? EqualsAccent(a) : false; + EqualsAtom(other) && InnerList.Equals(other.InnerList); + public override bool Equals(object obj) => obj is Accent a && EqualsAccent(a); public override int GetHashCode() => (base.GetHashCode(), InnerList).GetHashCode(); } } \ No newline at end of file diff --git a/CSharpMath/Atom/Atoms/Fraction.cs b/CSharpMath/Atom/Atoms/Fraction.cs index 2a28d515..a7371d7a 100644 --- a/CSharpMath/Atom/Atoms/Fraction.cs +++ b/CSharpMath/Atom/Atoms/Fraction.cs @@ -29,8 +29,8 @@ protected override MathAtom CloneInside(bool finalize) => public override bool Equals(object obj) => obj is Fraction f && EqualsFraction(f); public bool EqualsFraction(Fraction other) => EqualsAtom(other) - && Numerator.NullCheckingStructuralEquality(other.Numerator) - && Denominator.NullCheckingStructuralEquality(other.Denominator) + && Numerator.Equals(other.Numerator) + && Denominator.Equals(other.Denominator) && LeftDelimiter == other.LeftDelimiter && RightDelimiter == other.RightDelimiter; public override int GetHashCode() => diff --git a/CSharpMath/Atom/Atoms/Inner.cs b/CSharpMath/Atom/Atoms/Inner.cs index 05026ebd..56bcca51 100644 --- a/CSharpMath/Atom/Atoms/Inner.cs +++ b/CSharpMath/Atom/Atoms/Inner.cs @@ -16,10 +16,10 @@ protected override MathAtom CloneInside(bool finalize) => new Inner(LeftBoundary, InnerList.Clone(finalize), RightBoundary); public bool EqualsInner(Inner otherInner) => EqualsAtom(otherInner) - && InnerList.NullCheckingStructuralEquality(otherInner.InnerList) - && LeftBoundary.NullCheckingStructuralEquality(otherInner.LeftBoundary) - && RightBoundary.NullCheckingStructuralEquality(otherInner.RightBoundary); - public override bool Equals(object obj) => obj is Inner i ? EqualsInner(i) : false; + && InnerList.Equals(otherInner.InnerList) + && LeftBoundary.Equals(otherInner.LeftBoundary) + && RightBoundary.Equals(otherInner.RightBoundary); + public override bool Equals(object obj) => obj is Inner i && EqualsInner(i); public override int GetHashCode() => (base.GetHashCode(), InnerList, LeftBoundary, RightBoundary).GetHashCode(); public override string DebugString => diff --git a/CSharpMath/Atom/Atoms/Overline.cs b/CSharpMath/Atom/Atoms/Overline.cs index de7c7f67..65152fe3 100644 --- a/CSharpMath/Atom/Atoms/Overline.cs +++ b/CSharpMath/Atom/Atoms/Overline.cs @@ -16,8 +16,8 @@ protected override MathAtom CloneInside(bool finalize) => .AppendInBracesOrLiteralNull(InnerList?.DebugString) .ToString(); public bool EqualsOverline(Overline other) => - EqualsAtom(other) && InnerList.NullCheckingStructuralEquality(other?.InnerList); - public override bool Equals(object obj) => obj is Overline o ? EqualsOverline(o) : false; + EqualsAtom(other) && InnerList.Equals(other.InnerList); + public override bool Equals(object obj) => obj is Overline o && EqualsOverline(o); public override int GetHashCode() => (base.GetHashCode(), InnerList).GetHashCode(); } diff --git a/CSharpMath/Atom/Atoms/RaiseBox.cs b/CSharpMath/Atom/Atoms/RaiseBox.cs index 2c839516..5f3e5cd4 100644 --- a/CSharpMath/Atom/Atoms/RaiseBox.cs +++ b/CSharpMath/Atom/Atoms/RaiseBox.cs @@ -13,7 +13,7 @@ protected override MathAtom CloneInside(bool finalize) => public override int GetHashCode() => (base.GetHashCode(), Raise, InnerList).GetHashCode(); public override bool Equals(object obj) => - obj is RaiseBox r ? EqualsAtom(r) && Raise == r.Raise - && InnerList.NullCheckingStructuralEquality(r.InnerList) : false; + obj is RaiseBox r && EqualsAtom(r) && Raise == r.Raise + && InnerList.Equals(r.InnerList); } } \ No newline at end of file diff --git a/CSharpMath/Atom/IMathObject.cs b/CSharpMath/Atom/IMathObject.cs index 217ec576..d5ef2ef8 100644 --- a/CSharpMath/Atom/IMathObject.cs +++ b/CSharpMath/Atom/IMathObject.cs @@ -6,12 +6,4 @@ public interface IMathObject { public interface IMathListContainer : IMathObject { System.Collections.Generic.IEnumerable InnerLists { get; } } -} -namespace CSharpMath { - using Atom; - partial class Extensions { - /// Safe to call, even if one or both are null. Returns true if both are null. - public static bool NullCheckingStructuralEquality(this IMathObject? obj1, IMathObject? obj2) => - obj1 == null ? obj2 == null : obj2 == null ? false : obj1.Equals(obj2); - } } \ No newline at end of file diff --git a/CSharpMath/Atom/MathAtom.cs b/CSharpMath/Atom/MathAtom.cs index 9426bac5..b9db6ec1 100644 --- a/CSharpMath/Atom/MathAtom.cs +++ b/CSharpMath/Atom/MathAtom.cs @@ -85,8 +85,8 @@ public bool EqualsAtom(MathAtom otherAtom) => GetType() == otherAtom.GetType() && //IndexRange == otherAtom.IndexRange && //FontStyle == otherAtom.FontStyle && - Superscript.NullCheckingStructuralEquality(otherAtom.Superscript) && - Subscript.NullCheckingStructuralEquality(otherAtom.Subscript); + Superscript.Equals(otherAtom.Superscript) && + Subscript.Equals(otherAtom.Subscript); public override bool Equals(object obj) => obj is MathAtom a && EqualsAtom(a); bool IEquatable.Equals(MathAtom otherAtom) => EqualsAtom(otherAtom); public override int GetHashCode() => (Superscript, Subscript, Nucleus).GetHashCode(); diff --git a/CSharpMath/Atom/MathList.cs b/CSharpMath/Atom/MathList.cs index a1f0e2f0..9fa3aab6 100644 --- a/CSharpMath/Atom/MathList.cs +++ b/CSharpMath/Atom/MathList.cs @@ -5,7 +5,7 @@ namespace CSharpMath.Atom { using Atoms; #pragma warning disable CA1710 // Identifiers should have correct suffix - // WTF CA1710, you want types inheriting IList to have the Collection suffix? + // WTF CA1710, you want types implementing IList to have the Collection suffix? class DisabledMathList : MathList { internal DisabledMathList() { } public override void Add(MathAtom item) => throw new InvalidOperationException("Scripts are not allowed!"); @@ -111,14 +111,11 @@ public MathList Clone(bool finalize) { public virtual void Append(IEnumerable list) => Atoms.AddRange(list); public void RemoveAtoms(int index, int count) => Atoms.RemoveRange(index, count); public bool EqualsList(MathList otherList) { - if (otherList == null) { - return false; - } if (otherList.Count != Count) { return false; } for (int i = 0; i < Count; i++) { - if (!this[i].NullCheckingStructuralEquality(otherList[i])) { + if (!this[i].Equals(otherList[i])) { return false; } } @@ -132,13 +129,11 @@ public override int GetHashCode() => IEnumerator IEnumerable.GetEnumerator() => Atoms.GetEnumerator(); public int IndexOf(MathAtom item) => Atoms.IndexOf(item); public void Insert(int index, MathAtom item) { - if (item != null) Atoms.Insert(index, item); - else throw new ArgumentNullException(nameof(item), "MathList cannot contain null."); + Atoms.Insert(index, item); } public void RemoveAt(int index) => Atoms.RemoveAt(index); public virtual void Add(MathAtom item) { - if (item != null) Atoms.Add(item); - else throw new ArgumentNullException(nameof(item), "MathList cannot contain null."); + Atoms.Add(item); } public void Clear() => Atoms.Clear(); public bool Contains(MathAtom item) => Atoms.Contains(item);