mirror of https://github.com/icsharpcode/ILSpy.git
288 changed files with 12169 additions and 7392 deletions
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
public class UndoStackTests |
||||
{ |
||||
[Test] |
||||
public void ContinueUndoGroup() |
||||
{ |
||||
var doc = new TextDocument(); |
||||
doc.Insert(0, "a"); |
||||
doc.UndoStack.StartContinuedUndoGroup(); |
||||
doc.Insert(1, "b"); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.Undo(); |
||||
Assert.AreEqual("", doc.Text); |
||||
} |
||||
|
||||
[Test] |
||||
public void ContinueEmptyUndoGroup() |
||||
{ |
||||
var doc = new TextDocument(); |
||||
doc.Insert(0, "a"); |
||||
doc.UndoStack.StartUndoGroup(); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.StartContinuedUndoGroup(); |
||||
doc.Insert(1, "b"); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.Undo(); |
||||
Assert.AreEqual("a", doc.Text); |
||||
} |
||||
|
||||
[Test] |
||||
public void ContinueEmptyUndoGroup_WithOptionalEntries() |
||||
{ |
||||
var doc = new TextDocument(); |
||||
doc.Insert(0, "a"); |
||||
doc.UndoStack.StartUndoGroup(); |
||||
doc.UndoStack.PushOptional(new StubUndoableAction()); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.StartContinuedUndoGroup(); |
||||
doc.Insert(1, "b"); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.Undo(); |
||||
Assert.AreEqual("a", doc.Text); |
||||
} |
||||
|
||||
[Test] |
||||
public void EmptyContinuationGroup() |
||||
{ |
||||
var doc = new TextDocument(); |
||||
doc.Insert(0, "a"); |
||||
doc.UndoStack.StartContinuedUndoGroup(); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.StartContinuedUndoGroup(); |
||||
doc.Insert(1, "b"); |
||||
doc.UndoStack.EndUndoGroup(); |
||||
doc.UndoStack.Undo(); |
||||
Assert.AreEqual("", doc.Text); |
||||
} |
||||
|
||||
class StubUndoableAction : IUndoableOperation |
||||
{ |
||||
public void Undo() |
||||
{ |
||||
} |
||||
|
||||
public void Redo() |
||||
{ |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,182 @@
@@ -0,0 +1,182 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.NRefactory.Editor; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Highlighting |
||||
{ |
||||
[TestFixture] |
||||
public class HighlightedLineMergeTests |
||||
{ |
||||
IDocument document = new TextDocument(new string(' ', 20)); |
||||
|
||||
[Test] |
||||
public void SimpleMerge1() |
||||
{ |
||||
HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1)); |
||||
baseLine.Sections.Add(MakeSection(0, 1, "B")); |
||||
|
||||
HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1)); |
||||
additionalLine.Sections.Add(MakeSection(0, 2, "A")); |
||||
|
||||
baseLine.MergeWith(additionalLine); |
||||
// The additional section gets split up so that it fits into the tree structure
|
||||
Assert.That(baseLine.Sections, Is.EqualTo( |
||||
new[] { |
||||
MakeSection(0, 1, "B"), |
||||
MakeSection(0, 1, "A"), |
||||
MakeSection(1, 2, "A") |
||||
}).Using(new SectionComparer())); |
||||
} |
||||
|
||||
[Test] |
||||
public void SimpleMerge2() |
||||
{ |
||||
HighlightedLine baseLine = new HighlightedLine(document, document.GetLineByNumber(1)); |
||||
baseLine.Sections.Add(MakeSection(0, 1, "B")); |
||||
baseLine.Sections.Add(MakeSection(0, 1, "BN")); |
||||
|
||||
HighlightedLine additionalLine = new HighlightedLine(document, document.GetLineByNumber(1)); |
||||
additionalLine.Sections.Add(MakeSection(0, 2, "A")); |
||||
|
||||
baseLine.MergeWith(additionalLine); |
||||
// The additional section gets split up so that it fits into the tree structure
|
||||
Assert.That(baseLine.Sections, Is.EqualTo( |
||||
new[] { |
||||
MakeSection(0, 1, "B"), |
||||
MakeSection(0, 1, "BN"), |
||||
MakeSection(0, 1, "A"), |
||||
MakeSection(1, 2, "A") |
||||
}).Using(new SectionComparer())); |
||||
} |
||||
|
||||
HighlightedSection MakeSection(int start, int end, string name) |
||||
{ |
||||
return new HighlightedSection { Offset = start, Length = end - start, Color = new HighlightingColor { Name = name }}; |
||||
} |
||||
|
||||
class SectionComparer : IEqualityComparer<HighlightedSection> |
||||
{ |
||||
public bool Equals(HighlightedSection a, HighlightedSection b) |
||||
{ |
||||
return a.Offset == b.Offset && a.Length == b.Length && a.Color.Name == b.Color.Name; |
||||
} |
||||
|
||||
public int GetHashCode(HighlightedSection obj) |
||||
{ |
||||
return obj.Offset; |
||||
} |
||||
} |
||||
|
||||
#region Automatic Test
|
||||
/* |
||||
const int combinations = 6 * 3 * 4 * 3 * 3 * 4; |
||||
HighlightingColor[] baseLineColors = { |
||||
new HighlightingColor { Name = "Base-A" }, |
||||
new HighlightingColor { Name = "Base-B" }, |
||||
new HighlightingColor { Name = "Base-N" }, |
||||
new HighlightingColor { Name = "Base-C" } |
||||
}; |
||||
HighlightingColor[] additionalLineColors = { |
||||
new HighlightingColor { Name = "Add-A" }, |
||||
new HighlightingColor { Name = "Add-B" }, |
||||
new HighlightingColor { Name = "Add-N" }, |
||||
new HighlightingColor { Name = "Add-C" } |
||||
}; |
||||
|
||||
HighlightedLine BuildHighlightedLine(int num, HighlightingColor[] colors) |
||||
{ |
||||
// We are build a HighlightedLine with 4 segments:
|
||||
// A B C (top-level) and N nested within B.
|
||||
// These are the integers controlling the generating process:
|
||||
|
||||
int aStart = GetNum(ref num, 5); // start offset of A
|
||||
int aLength = GetNum(ref num, 2); // length of A
|
||||
|
||||
int bDistance = GetNum(ref num, 3); // distance from start of B to end of A
|
||||
int bStart = aStart + aLength + bDistance; |
||||
int nDistance = GetNum(ref num, 2); // distance from start of B to start of N, range 0-2
|
||||
int nLength = GetNum(ref num, 2); // length of N
|
||||
int bEndDistance = GetNum(ref num, 2); // distance from end of N to end of B
|
||||
int bLength = nDistance + nLength + bEndDistance; |
||||
|
||||
int cDistance = GetNum(ref num, 3); // distance from end of B to start of C
|
||||
int cStart = bStart + bLength + cDistance; |
||||
int cLength = 1; |
||||
Assert.AreEqual(0, num); |
||||
|
||||
var documentLine = document.GetLineByNumber(1); |
||||
HighlightedLine line = new HighlightedLine(document, documentLine); |
||||
line.Sections.Add(new HighlightedSection { Offset = aStart, Length = aLength, Color = colors[0] }); |
||||
line.Sections.Add(new HighlightedSection { Offset = bStart, Length = bLength, Color = colors[1] }); |
||||
line.Sections.Add(new HighlightedSection { Offset = bStart + nDistance, Length = nLength, Color = colors[2] }); |
||||
line.Sections.Add(new HighlightedSection { Offset = cStart, Length = cLength, Color = colors[3] }); |
||||
|
||||
return line; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets a number between 0 and max (inclusive)
|
||||
/// </summary>
|
||||
int GetNum(ref int num, int max) |
||||
{ |
||||
int result = num % (max+1); |
||||
num = num / (max + 1); |
||||
return result; |
||||
} |
||||
|
||||
[Test] |
||||
public void TestAll() |
||||
{ |
||||
for (int c1 = 0; c1 < combinations; c1++) { |
||||
HighlightedLine line1 = BuildHighlightedLine(c1, additionalLineColors); |
||||
for (int c2 = 0; c2 < combinations; c2++) { |
||||
HighlightedLine line2 = BuildHighlightedLine(c2, baseLineColors); |
||||
HighlightingColor[] expectedPerCharColors = new HighlightingColor[document.TextLength]; |
||||
ApplyColors(expectedPerCharColors, line2); |
||||
ApplyColors(expectedPerCharColors, line1); |
||||
try { |
||||
line2.MergeWith(line1); |
||||
} catch (InvalidOperationException ex) { |
||||
throw new InvalidOperationException(string.Format("Error for c1 = {0}, c2 = {1}", c1, c2), ex); |
||||
} |
||||
|
||||
HighlightingColor[] actualPerCharColors = new HighlightingColor[document.TextLength]; |
||||
ApplyColors(actualPerCharColors, line2); |
||||
Assert.AreEqual(expectedPerCharColors, actualPerCharColors, string.Format("c1 = {0}, c2 = {1}", c1, c2)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ApplyColors(HighlightingColor[] perCharColors, HighlightedLine line) |
||||
{ |
||||
foreach (var section in line.Sections) { |
||||
for (int i = 0; i < section.Length; i++) { |
||||
perCharColors[section.Offset + i] = section.Color; |
||||
} |
||||
} |
||||
} |
||||
*/ |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Threading; |
||||
using System.Windows; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.AvalonEdit |
||||
{ |
||||
[TestFixture] |
||||
public class MultipleUIThreads |
||||
{ |
||||
Exception error; |
||||
|
||||
[Test] |
||||
public void CreateEditorInstancesOnMultipleUIThreads() |
||||
{ |
||||
Thread t1 = new Thread(new ThreadStart(Run)); |
||||
Thread t2 = new Thread(new ThreadStart(Run)); |
||||
t1.SetApartmentState(ApartmentState.STA); |
||||
t2.SetApartmentState(ApartmentState.STA); |
||||
t1.Start(); |
||||
t2.Start(); |
||||
t1.Join(); |
||||
t2.Join(); |
||||
if (error != null) |
||||
throw new InvalidOperationException(error.Message, error); |
||||
} |
||||
|
||||
[STAThread] |
||||
void Run() |
||||
{ |
||||
try { |
||||
var window = new Window(); |
||||
window.Content = new TextEditor(); |
||||
window.ShowActivated = false; |
||||
window.Show(); |
||||
} catch (Exception ex) { |
||||
error = ex; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,223 +0,0 @@
@@ -1,223 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Text; |
||||
|
||||
using ICSharpCode.AvalonEdit.Xml; |
||||
using ICSharpCode.SharpZipLib.Zip; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Xml |
||||
{ |
||||
class TestFile |
||||
{ |
||||
public string Name { get; set; } |
||||
public string Content { get; set; } |
||||
public string Canonical { get; set; } |
||||
public string Description { get; set; } |
||||
} |
||||
|
||||
[TestFixture] |
||||
public class ParserTests |
||||
{ |
||||
readonly string zipFileName = @"XmlParser\W3C.zip"; |
||||
|
||||
List<TestFile> xmlFiles = new List<TestFile>(); |
||||
|
||||
[TestFixtureSetUp] |
||||
public void OpenZipFile() |
||||
{ |
||||
ZipFile zipFile = new ZipFile(zipFileName); |
||||
|
||||
Dictionary<string, TestFile> xmlFiles = new Dictionary<string, TestFile>(); |
||||
|
||||
// Decompress XML files
|
||||
foreach(ZipEntry zipEntry in zipFile.Cast<ZipEntry>().Where(zip => zip.IsFile && zip.Name.EndsWith(".xml"))) { |
||||
Stream stream = zipFile.GetInputStream(zipEntry); |
||||
string content = new StreamReader(stream).ReadToEnd(); |
||||
xmlFiles.Add(zipEntry.Name, new TestFile { Name = zipEntry.Name, Content = content }); |
||||
} |
||||
// Add descriptions
|
||||
foreach(TestFile metaData in xmlFiles.Values.Where(f => f.Name.StartsWith("ibm/ibm_oasis"))) { |
||||
var doc = System.Xml.Linq.XDocument.Parse(metaData.Content); |
||||
foreach(var testElem in doc.Descendants("TEST")) { |
||||
string uri = "ibm/" + testElem.Attribute("URI").Value; |
||||
string description = testElem.Value.Replace("\n ", "\n").TrimStart('\n'); |
||||
if (xmlFiles.ContainsKey(uri)) |
||||
xmlFiles[uri].Description = description; |
||||
} |
||||
} |
||||
// Copy canonical forms
|
||||
foreach(TestFile canonical in xmlFiles.Values.Where(f => f.Name.Contains("/out/"))) { |
||||
string uri = canonical.Name.Replace("/out/", "/"); |
||||
if (xmlFiles.ContainsKey(uri)) |
||||
xmlFiles[uri].Canonical = canonical.Content; |
||||
} |
||||
// Copy resuts to field
|
||||
this.xmlFiles.AddRange(xmlFiles.Values.Where(f => !f.Name.Contains("/out/"))); |
||||
} |
||||
|
||||
IEnumerable<TestFile> GetXmlFilesStartingWith(string directory) |
||||
{ |
||||
return xmlFiles.Where(f => f.Name.StartsWith(directory)); |
||||
} |
||||
|
||||
[Test] |
||||
public void W3C_Valid() |
||||
{ |
||||
string[] exclude = { |
||||
// NAME in DTD infoset
|
||||
"ibm02v01", "ibm03v01", "ibm85v01", "ibm86v01", "ibm87v01", "ibm88v01", "ibm89v01", |
||||
}; |
||||
TestFiles(GetXmlFilesStartingWith("ibm/valid/"), true, exclude); |
||||
} |
||||
|
||||
[Test] |
||||
public void W3C_Invalid() |
||||
{ |
||||
string[] exclude = { |
||||
// Default attribute value
|
||||
"ibm56i03", |
||||
}; |
||||
TestFiles(GetXmlFilesStartingWith("ibm/invalid/"), true, exclude); |
||||
} |
||||
|
||||
[Test] |
||||
public void W3C_NotWellformed() |
||||
{ |
||||
string[] exclude = { |
||||
// XML declaration well formed
|
||||
"ibm23n", "ibm24n", "ibm26n01", "ibm32n", "ibm80n06", "ibm81n01", "ibm81n02", "ibm81n03", "ibm81n04", "ibm81n05", "ibm81n06", "ibm81n07", "ibm81n08", "ibm81n09", |
||||
// Invalid chars in a comment - do we care?
|
||||
"ibm02n", |
||||
// Invalid char ref - do we care?
|
||||
"ibm66n12", "ibm66n13", "ibm66n14", "ibm66n15", |
||||
// DTD in wrong location
|
||||
"ibm27n01", "ibm43n", |
||||
// Entity refs depending on DTD
|
||||
"ibm41n10", "ibm41n11", "ibm41n12", "ibm41n13", "ibm41n14", "ibm68n04", "ibm68n06", "ibm68n07", "ibm68n08", "ibm68n09", "ibm68n10", |
||||
// DTD Related tests
|
||||
"ibm09n01", "ibm09n02", "ibm13n01", "ibm13n02", "ibm13n03", "ibm28n01", "ibm28n02", "ibm28n03", "ibm29n01", "ibm29n03", "ibm29n04", "ibm29n07", "ibm30n01", "ibm31n01", "ibm45n01", "ibm45n02", "ibm45n03", "ibm45n04", "ibm45n05", "ibm45n06", "ibm46n01", "ibm46n02", "ibm46n03", "ibm46n04", |
||||
"ibm46n05", "ibm47n01", "ibm47n02", "ibm47n03", "ibm47n04", "ibm47n05", "ibm47n06", "ibm48n01", "ibm48n02", "ibm48n03", "ibm48n04", "ibm48n05", "ibm48n06", "ibm48n07", "ibm49n01", "ibm49n02", "ibm49n03", "ibm49n04", "ibm49n05", "ibm49n06", "ibm50n01", "ibm50n02", "ibm50n03", "ibm50n04", |
||||
"ibm50n05", "ibm50n06", "ibm50n07", "ibm51n01", "ibm51n02", "ibm51n03", "ibm51n04", "ibm51n05", "ibm51n06", "ibm51n07", "ibm52n01", "ibm52n02", "ibm52n03", "ibm53n01", "ibm53n02", "ibm53n03", "ibm53n04", "ibm53n05", "ibm53n06", "ibm53n07", "ibm53n08", "ibm54n01", "ibm54n02", "ibm55n01", |
||||
"ibm55n02", "ibm55n03", "ibm56n01", "ibm56n02", "ibm56n03", "ibm56n04", "ibm56n05", "ibm56n06", "ibm56n07", "ibm57n01", "ibm58n01", "ibm58n02", "ibm58n03", "ibm58n04", "ibm58n05", "ibm58n06", "ibm58n07", "ibm58n08", "ibm59n01", "ibm59n02", "ibm59n03", "ibm59n04", "ibm59n05", "ibm59n06", |
||||
"ibm60n01", "ibm60n02", "ibm60n03", "ibm60n04", "ibm60n05", "ibm60n06", "ibm60n07", "ibm60n08", "ibm61n01", "ibm62n01", "ibm62n02", "ibm62n03", "ibm62n04", "ibm62n05", "ibm62n06", "ibm62n07", "ibm62n08", "ibm63n01", "ibm63n02", "ibm63n03", "ibm63n04", "ibm63n05", "ibm63n06", "ibm63n07", |
||||
"ibm64n01", "ibm64n02", "ibm64n03", "ibm65n01", "ibm65n02", "ibm66n01", "ibm66n03", "ibm66n05", "ibm66n07", "ibm66n09", "ibm66n11", "ibm69n01", "ibm69n02", "ibm69n03", "ibm69n04", "ibm69n05", "ibm69n06", "ibm69n07", "ibm70n01", "ibm71n01", "ibm71n02", "ibm71n03", "ibm71n04", "ibm71n05", |
||||
"ibm72n01", "ibm72n02", "ibm72n03", "ibm72n04", "ibm72n05", "ibm72n06", "ibm72n09", "ibm73n01", "ibm73n03", "ibm74n01", "ibm75n01", "ibm75n02", "ibm75n03", "ibm75n04", "ibm75n05", "ibm75n06", "ibm75n07", "ibm75n08", "ibm75n09", "ibm75n10", "ibm75n11", "ibm75n12", "ibm75n13", "ibm76n01", |
||||
"ibm76n02", "ibm76n03", "ibm76n04", "ibm76n05", "ibm76n06", "ibm76n07", "ibm77n01", "ibm77n02", "ibm77n03", "ibm77n04", "ibm78n01", "ibm78n02", "ibm79n01", "ibm79n02", "ibm82n01", "ibm82n02", "ibm82n03", "ibm82n04", "ibm82n08", "ibm83n01", "ibm83n03", "ibm83n04", "ibm83n05", "ibm83n06", |
||||
// No idea what this is
|
||||
"misc/432gewf", "ibm28an01", |
||||
}; |
||||
TestFiles(GetXmlFilesStartingWith("ibm/not-wf/"), false, exclude); |
||||
} |
||||
|
||||
StringBuilder errorOutput; |
||||
|
||||
void TestFiles(IEnumerable<TestFile> files, bool areWellFormed, string[] exclude) |
||||
{ |
||||
errorOutput = new StringBuilder(); |
||||
int testsRun = 0; |
||||
int ignored = 0; |
||||
foreach (TestFile file in files) { |
||||
if (exclude.Any(exc => file.Name.Contains(exc))) { |
||||
ignored++; |
||||
} else { |
||||
testsRun++; |
||||
TestFile(file, areWellFormed); |
||||
} |
||||
} |
||||
if (testsRun == 0) { |
||||
Assert.Fail("Test files not found"); |
||||
} |
||||
if (errorOutput.Length > 0) { |
||||
// Can not output ]]> otherwise nuint will crash
|
||||
Assert.Fail(errorOutput.Replace("]]>", "]]~NUNIT~>").ToString()); |
||||
} |
||||
} |
||||
|
||||
/// <remarks>
|
||||
/// If using DTD, canonical representation is not checked
|
||||
/// If using DTD, uknown entiry references are not error
|
||||
/// </remarks>
|
||||
bool TestFile(TestFile testFile, bool isWellFormed) |
||||
{ |
||||
bool passed = true; |
||||
|
||||
string content = testFile.Content; |
||||
Debug.WriteLine("Testing " + testFile.Name + "..."); |
||||
AXmlParser parser = new AXmlParser(); |
||||
|
||||
bool usingDTD = content.Contains("<!DOCTYPE") && (content.Contains("<!ENTITY") || content.Contains(" SYSTEM ")); |
||||
if (usingDTD) |
||||
parser.UnknownEntityReferenceIsError = false; |
||||
|
||||
AXmlDocument document; |
||||
|
||||
parser.Lock.EnterWriteLock(); |
||||
try { |
||||
document = parser.Parse(content, null); |
||||
} finally { |
||||
parser.Lock.ExitWriteLock(); |
||||
} |
||||
|
||||
string printed = PrettyPrintAXmlVisitor.PrettyPrint(document); |
||||
if (content != printed) { |
||||
errorOutput.AppendFormat("Output of pretty printed XML for \"{0}\" does not match the original.\n", testFile.Name); |
||||
errorOutput.AppendFormat("Pretty printed:\n{0}\n", Indent(printed)); |
||||
passed = false; |
||||
} |
||||
|
||||
if (isWellFormed && !usingDTD) { |
||||
string canonicalPrint = CanonicalPrintAXmlVisitor.Print(document); |
||||
if (testFile.Canonical != null) { |
||||
if (testFile.Canonical != canonicalPrint) { |
||||
errorOutput.AppendFormat("Canonical XML for \"{0}\" does not match the excpected.\n", testFile.Name); |
||||
errorOutput.AppendFormat("Expected:\n{0}\n", Indent(testFile.Canonical)); |
||||
errorOutput.AppendFormat("Seen:\n{0}\n", Indent(canonicalPrint)); |
||||
passed = false; |
||||
} |
||||
} else { |
||||
errorOutput.AppendFormat("Can not find canonical output for \"{0}\"", testFile.Name); |
||||
errorOutput.AppendFormat("Suggested canonical output:\n{0}\n", Indent(canonicalPrint)); |
||||
passed = false; |
||||
} |
||||
} |
||||
|
||||
bool hasErrors = document.SyntaxErrors.FirstOrDefault() != null; |
||||
if (isWellFormed && hasErrors) { |
||||
errorOutput.AppendFormat("Syntax error(s) in well formed file \"{0}\":\n", testFile.Name); |
||||
foreach (var error in document.SyntaxErrors) { |
||||
string followingText = content.Substring(error.StartOffset, Math.Min(10, content.Length - error.StartOffset)); |
||||
errorOutput.AppendFormat("Error ({0}-{1}): {2} (followed by \"{3}\")\n", error.StartOffset, error.EndOffset, error.Message, followingText); |
||||
} |
||||
passed = false; |
||||
} |
||||
|
||||
if (!isWellFormed && !hasErrors) { |
||||
errorOutput.AppendFormat("No syntax errors reported for mallformed file \"{0}\"\n", testFile.Name); |
||||
passed = false; |
||||
} |
||||
|
||||
// Epilog
|
||||
if (!passed) { |
||||
if (testFile.Description != null) { |
||||
errorOutput.AppendFormat("Test description:\n{0}\n", Indent(testFile.Description)); |
||||
} |
||||
errorOutput.AppendFormat("File content:\n{0}\n", Indent(content)); |
||||
errorOutput.AppendLine(); |
||||
} |
||||
|
||||
return passed; |
||||
} |
||||
|
||||
string Indent(string text) |
||||
{ |
||||
return " " + text.TrimEnd().Replace("\n", "\n "); |
||||
} |
||||
} |
||||
} |
@ -1,115 +0,0 @@
@@ -1,115 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Xml; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Xml |
||||
{ |
||||
[TestFixture] |
||||
public class TextReplacementTests |
||||
{ |
||||
#region Test Data
|
||||
string initialDocumentText = @"<UserControl x:Class='ICSharpCode.Profiler.Controls.TimeLineCell'
|
||||
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
|
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
<Grid> |
||||
|
||||
</Grid> |
||||
</UserControl>";
|
||||
|
||||
string finalDocumentText = @"<UserControl x:Class='ICSharpCode.Profiler.Controls.TimeLineCell'
|
||||
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
|
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
<Grid> |
||||
<Grid.RowDefinitions> |
||||
<RowDefinition Height='20' /> |
||||
<RowDefinition Height='20' /> |
||||
<RowDefinition Height='Auto' /> |
||||
</Grid.RowDefinitions> |
||||
<StackPanel Orientation='Horizontal'> |
||||
<TextBlock Text='Test' /> |
||||
</StackPanel> |
||||
<local:TimeLineControl x:Name='t1' Grid.Row='1' /> |
||||
<TextBlock Grid.Row='2' Text='Test' /> |
||||
</Grid> |
||||
</UserControl>";
|
||||
|
||||
int offset = @"<UserControl x:Class='ICSharpCode.Profiler.Controls.TimeLineCell'
|
||||
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
|
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
||||
".Length;
|
||||
|
||||
string original = @" <Grid>
|
||||
|
||||
</Grid>";
|
||||
|
||||
string replacement = @" <Grid>
|
||||
<Grid.RowDefinitions> |
||||
<RowDefinition Height='20' /> |
||||
<RowDefinition Height='20' /> |
||||
<RowDefinition Height='Auto' /> |
||||
</Grid.RowDefinitions> |
||||
<StackPanel Orientation='Horizontal'> |
||||
<TextBlock Text='Test' /> |
||||
</StackPanel> |
||||
<local:TimeLineControl x:Name='t1' Grid.Row='1' /> |
||||
<TextBlock Grid.Row='2' Text='Test' /> |
||||
</Grid>";
|
||||
#endregion
|
||||
|
||||
[Test] |
||||
public void ReplacementTest1() |
||||
{ |
||||
/* |
||||
* REPRODUCTION STEPS |
||||
* |
||||
* 1. Run XmlDOM project |
||||
* 2. paste text from initialDocumentText (see Test Data region) |
||||
* 3. select lines 4 to 6 |
||||
* 4. replace with replacement (see Test Data region) |
||||
* 5. exception thrown: |
||||
* ICSharpCode.AvalonEdit.Xml.InternalException : Assertion failed: cached elements must not have zero length |
||||
* at ICSharpCode.AvalonEdit.Xml.AXmlParser.Assert(Boolean condition, String message) |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\Xml\AXmlParser.cs:line 121 |
||||
* at ICSharpCode.AvalonEdit.Xml.TagReader.TryReadFromCacheOrNew[T](T& res, Predicate`1 condition) |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\Xml\TagReader.cs:line 39 |
||||
* at ICSharpCode.AvalonEdit.Xml.TagReader.<ReadText>d__12.MoveNext() |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\Xml\TagReader.cs:line 456 |
||||
* at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection) |
||||
* at System.Collections.Generic.List`1.AddRange(IEnumerable`1 collection) |
||||
* at ICSharpCode.AvalonEdit.Xml.TagReader.ReadAllTags() |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\Xml\TagReader.cs:line 73 |
||||
* at ICSharpCode.AvalonEdit.Xml.AXmlParser.Parse(String input, IEnumerable`1 changesSinceLastParse) |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\Xml\AXmlParser.cs:line 161 |
||||
* at ICSharpCode.AvalonEdit.Tests.XmlParser.TextReplacementTests.RunTest() |
||||
* in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit.Tests\XmlParser\TextReplacementTests.cs:line 114 |
||||
* at ICSharpCode.AvalonEdit.Tests.XmlParser.TextReplacementTests.TestMethod( |
||||
* ) in c:\Projects\SharpDevelop\4.0\SharpDevelop\src\Libraries\AvalonEdit\ICSharpCode.AvalonEdit.Tests\XmlParser\TextReplacementTests.cs:line 97 |
||||
* */ |
||||
Assert.DoesNotThrow(RunTest1); |
||||
} |
||||
|
||||
void RunTest1() |
||||
{ |
||||
AXmlParser parser = new AXmlParser(); |
||||
|
||||
try { |
||||
parser.Lock.EnterWriteLock(); |
||||
|
||||
parser.Parse(initialDocumentText, null); // full reparse
|
||||
|
||||
IList<DocumentChangeEventArgs> changes = new List<DocumentChangeEventArgs>(); |
||||
|
||||
changes.Add(new DocumentChangeEventArgs(offset, original, replacement)); |
||||
|
||||
parser.Parse(finalDocumentText, changes); |
||||
} finally { |
||||
parser.Lock.ExitWriteLock(); |
||||
} |
||||
} |
||||
} |
||||
} |
Binary file not shown.
@ -1,15 +0,0 @@
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?> |
||||
<configuration> |
||||
<configSections> |
||||
<sectionGroup name="NUnit"> |
||||
<section name="TestRunner" |
||||
type="System.Configuration.NameValueSectionHandler" /> |
||||
</sectionGroup> |
||||
</configSections> |
||||
<NUnit> |
||||
<TestRunner> |
||||
<!-- Valid values are STA,MTA. Others ignored. --> |
||||
<add key="ApartmentState" value="STA" /> |
||||
</TestRunner> |
||||
</NUnit> |
||||
</configuration> |
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<packages> |
||||
<package id="NUnit" version="2.6.3" targetFramework="net45" /> |
||||
</packages> |
@ -1,140 +0,0 @@
@@ -1,140 +0,0 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||
|
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
/// <summary>
|
||||
/// <para>A checkpoint that allows tracking changes to a TextDocument.</para>
|
||||
/// <para>
|
||||
/// Use <see cref="TextDocument.CreateSnapshot(out ChangeTrackingCheckpoint)"/> to create a checkpoint.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The <see cref="ChangeTrackingCheckpoint"/> class allows tracking document changes, even from background threads.</para>
|
||||
/// <para>Once you have two checkpoints, you can call <see cref="ChangeTrackingCheckpoint.GetChangesTo"/> to retrieve the complete list
|
||||
/// of document changes that happened between those versions of the document.</para>
|
||||
/// </remarks>
|
||||
public sealed class ChangeTrackingCheckpoint |
||||
{ |
||||
// Object that is unique per document.
|
||||
// Used to determine if two checkpoints belong to the same document.
|
||||
// We don't use a reference to the document itself to allow the GC to reclaim the document memory
|
||||
// even if there are still references to checkpoints.
|
||||
readonly object documentIdentifier; |
||||
|
||||
// 'value' is the change from the previous checkpoint to this checkpoint
|
||||
// TODO: store the change in the older checkpoint instead - if only a reference to the
|
||||
// newest document version exists, the GC should be able to collect all DocumentChangeEventArgs.
|
||||
readonly DocumentChangeEventArgs value; |
||||
readonly int id; |
||||
ChangeTrackingCheckpoint next; |
||||
|
||||
internal ChangeTrackingCheckpoint(object documentIdentifier) |
||||
{ |
||||
this.documentIdentifier = documentIdentifier; |
||||
} |
||||
|
||||
internal ChangeTrackingCheckpoint(object documentIdentifier, DocumentChangeEventArgs value, int id) |
||||
{ |
||||
this.documentIdentifier = documentIdentifier; |
||||
this.value = value; |
||||
this.id = id; |
||||
} |
||||
|
||||
internal ChangeTrackingCheckpoint Append(DocumentChangeEventArgs change) |
||||
{ |
||||
Debug.Assert(this.next == null); |
||||
this.next = new ChangeTrackingCheckpoint(this.documentIdentifier, change, unchecked( this.id + 1 )); |
||||
return this.next; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a change tracking checkpoint for the specified document.
|
||||
/// This method is thread-safe.
|
||||
/// If you need a ChangeTrackingCheckpoint that's consistent with a snapshot of the document,
|
||||
/// use <see cref="TextDocument.CreateSnapshot(out ChangeTrackingCheckpoint)"/>.
|
||||
/// </summary>
|
||||
public static ChangeTrackingCheckpoint Create(TextDocument document) |
||||
{ |
||||
if (document == null) |
||||
throw new ArgumentNullException("document"); |
||||
return document.CreateChangeTrackingCheckpoint(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether this checkpoint belongs to the same document as the other checkpoint.
|
||||
/// </summary>
|
||||
public bool BelongsToSameDocumentAs(ChangeTrackingCheckpoint other) |
||||
{ |
||||
if (other == null) |
||||
throw new ArgumentNullException("other"); |
||||
return documentIdentifier == other.documentIdentifier; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Compares the age of this checkpoint to the other checkpoint.
|
||||
/// </summary>
|
||||
/// <remarks>This method is thread-safe.</remarks>
|
||||
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
|
||||
/// <returns>-1 if this checkpoint is older than <paramref name="other"/>.
|
||||
/// 0 if <c>this</c>==<paramref name="other"/>.
|
||||
/// 1 if this checkpoint is newer than <paramref name="other"/>.</returns>
|
||||
public int CompareAge(ChangeTrackingCheckpoint other) |
||||
{ |
||||
if (other == null) |
||||
throw new ArgumentNullException("other"); |
||||
if (other.documentIdentifier != this.documentIdentifier) |
||||
throw new ArgumentException("Checkpoints do not belong to the same document."); |
||||
// We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1.
|
||||
// This is guaranteed on x86 because so many checkpoints don't fit into memory.
|
||||
return Math.Sign(unchecked( this.id - other.id )); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the changes from this checkpoint to the other checkpoint.
|
||||
/// If 'other' is older than this checkpoint, reverse changes are calculated.
|
||||
/// </summary>
|
||||
/// <remarks>This method is thread-safe.</remarks>
|
||||
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
|
||||
public IEnumerable<DocumentChangeEventArgs> GetChangesTo(ChangeTrackingCheckpoint other) |
||||
{ |
||||
int result = CompareAge(other); |
||||
if (result < 0) |
||||
return GetForwardChanges(other); |
||||
else if (result > 0) |
||||
return other.GetForwardChanges(this).Reverse().Select(change => change.Invert()); |
||||
else |
||||
return Empty<DocumentChangeEventArgs>.Array; |
||||
} |
||||
|
||||
IEnumerable<DocumentChangeEventArgs> GetForwardChanges(ChangeTrackingCheckpoint other) |
||||
{ |
||||
// Return changes from this(exclusive) to other(inclusive).
|
||||
ChangeTrackingCheckpoint node = this; |
||||
do { |
||||
node = node.next; |
||||
yield return node.value; |
||||
} while (node != other); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Calculates where the offset has moved in the other buffer version.
|
||||
/// </summary>
|
||||
/// <remarks>This method is thread-safe.</remarks>
|
||||
/// <exception cref="ArgumentException">Raised if 'other' belongs to a different document than this checkpoint.</exception>
|
||||
public int MoveOffsetTo(ChangeTrackingCheckpoint other, int oldOffset, AnchorMovementType movement) |
||||
{ |
||||
int offset = oldOffset; |
||||
foreach (DocumentChangeEventArgs e in GetChangesTo(other)) { |
||||
offset = e.GetNewOffset(offset, movement); |
||||
} |
||||
return offset; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
using System.Text; |
||||
#if NREFACTORY
|
||||
using ICSharpCode.NRefactory.Editor; |
||||
#endif
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
/// <summary>
|
||||
/// A TextWriter implementation that directly inserts into a document.
|
||||
/// </summary>
|
||||
public class DocumentTextWriter : TextWriter |
||||
{ |
||||
readonly IDocument document; |
||||
int insertionOffset; |
||||
|
||||
/// <summary>
|
||||
/// Creates a new DocumentTextWriter that inserts into document, starting at insertionOffset.
|
||||
/// </summary>
|
||||
public DocumentTextWriter(IDocument document, int insertionOffset) |
||||
{ |
||||
this.insertionOffset = insertionOffset; |
||||
if (document == null) |
||||
throw new ArgumentNullException("document"); |
||||
this.document = document; |
||||
var line = document.GetLineByOffset(insertionOffset); |
||||
if (line.DelimiterLength == 0) |
||||
line = line.PreviousLine; |
||||
if (line != null) |
||||
this.NewLine = document.GetText(line.EndOffset, line.DelimiterLength); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the current insertion offset.
|
||||
/// </summary>
|
||||
public int InsertionOffset { |
||||
get { return insertionOffset; } |
||||
set { insertionOffset = value; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(char value) |
||||
{ |
||||
document.Insert(insertionOffset, value.ToString()); |
||||
insertionOffset++; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(char[] buffer, int index, int count) |
||||
{ |
||||
document.Insert(insertionOffset, new string(buffer, index, count)); |
||||
insertionOffset += count; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Write(string value) |
||||
{ |
||||
document.Insert(insertionOffset, value); |
||||
insertionOffset += value.Length; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public override Encoding Encoding { |
||||
get { return Encoding.UTF8; } |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,343 @@
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
#if !NREFACTORY
|
||||
/// <summary>
|
||||
/// A document representing a source code file for refactoring.
|
||||
/// Line and column counting starts at 1.
|
||||
/// Offset counting starts at 0.
|
||||
/// </summary>
|
||||
public interface IDocument : ITextSource, IServiceProvider |
||||
{ |
||||
#if NREFACTORY
|
||||
/// <summary>
|
||||
/// Creates an immutable snapshot of this document.
|
||||
/// </summary>
|
||||
IDocument CreateDocumentSnapshot(); |
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets the text of the whole document..
|
||||
/// </summary>
|
||||
new string Text { get; set; } // hides ITextSource.Text to add the setter
|
||||
|
||||
/// <summary>
|
||||
/// This event is called directly before a change is applied to the document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is invalid to modify the document within this event handler.
|
||||
/// Aborting the change (by throwing an exception) is likely to cause corruption of data structures
|
||||
/// that listen to the Changing and Changed events.
|
||||
/// </remarks>
|
||||
event EventHandler<TextChangeEventArgs> TextChanging; |
||||
|
||||
/// <summary>
|
||||
/// This event is called directly after a change is applied to the document.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is invalid to modify the document within this event handler.
|
||||
/// Aborting the event handler (by throwing an exception) is likely to cause corruption of data structures
|
||||
/// that listen to the Changing and Changed events.
|
||||
/// </remarks>
|
||||
event EventHandler<TextChangeEventArgs> TextChanged; |
||||
|
||||
/// <summary>
|
||||
/// This event is called after a group of changes is completed.
|
||||
/// </summary>
|
||||
/// <seealso cref="EndUndoableAction"/>
|
||||
event EventHandler ChangeCompleted; |
||||
|
||||
/// <summary>
|
||||
/// Gets the number of lines in the document.
|
||||
/// </summary>
|
||||
int LineCount { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the document line with the specified number.
|
||||
/// </summary>
|
||||
/// <param name="lineNumber">The number of the line to retrieve. The first line has number 1.</param>
|
||||
IDocumentLine GetLineByNumber(int lineNumber); |
||||
|
||||
/// <summary>
|
||||
/// Gets the document line that contains the specified offset.
|
||||
/// </summary>
|
||||
IDocumentLine GetLineByOffset(int offset); |
||||
|
||||
/// <summary>
|
||||
/// Gets the offset from a text location.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetLocation"/>
|
||||
int GetOffset(int line, int column); |
||||
|
||||
/// <summary>
|
||||
/// Gets the offset from a text location.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetLocation"/>
|
||||
int GetOffset(TextLocation location); |
||||
|
||||
/// <summary>
|
||||
/// Gets the location from an offset.
|
||||
/// </summary>
|
||||
/// <seealso cref="GetOffset(TextLocation)"/>
|
||||
TextLocation GetLocation(int offset); |
||||
|
||||
/// <summary>
|
||||
/// Inserts text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <remarks>
|
||||
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
|
||||
/// For AnchorMovementType.Default, they will move behind the inserted text.
|
||||
/// The caret will also move behind the inserted text.
|
||||
/// </remarks>
|
||||
void Insert(int offset, string text); |
||||
|
||||
/// <summary>
|
||||
/// Inserts text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <remarks>
|
||||
/// Anchors positioned exactly at the insertion offset will move according to their movement type.
|
||||
/// For AnchorMovementType.Default, they will move behind the inserted text.
|
||||
/// The caret will also move behind the inserted text.
|
||||
/// </remarks>
|
||||
void Insert(int offset, ITextSource text); |
||||
|
||||
/// <summary>
|
||||
/// Inserts text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <param name="defaultAnchorMovementType">
|
||||
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
|
||||
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
|
||||
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
|
||||
/// </param>
|
||||
void Insert(int offset, string text, AnchorMovementType defaultAnchorMovementType); |
||||
|
||||
/// <summary>
|
||||
/// Inserts text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The offset at which the text is inserted.</param>
|
||||
/// <param name="text">The new text.</param>
|
||||
/// <param name="defaultAnchorMovementType">
|
||||
/// Anchors positioned exactly at the insertion offset will move according to the anchor's movement type.
|
||||
/// For AnchorMovementType.Default, they will move according to the movement type specified by this parameter.
|
||||
/// The caret will also move according to the <paramref name="defaultAnchorMovementType"/> parameter.
|
||||
/// </param>
|
||||
void Insert(int offset, ITextSource text, AnchorMovementType defaultAnchorMovementType); |
||||
|
||||
/// <summary>
|
||||
/// Removes text.
|
||||
/// </summary>
|
||||
/// <param name="offset">Starting offset of the text to be removed.</param>
|
||||
/// <param name="length">Length of the text to be removed.</param>
|
||||
void Remove(int offset, int length); |
||||
|
||||
/// <summary>
|
||||
/// Replaces text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The starting offset of the text to be replaced.</param>
|
||||
/// <param name="length">The length of the text to be replaced.</param>
|
||||
/// <param name="newText">The new text.</param>
|
||||
void Replace(int offset, int length, string newText); |
||||
|
||||
/// <summary>
|
||||
/// Replaces text.
|
||||
/// </summary>
|
||||
/// <param name="offset">The starting offset of the text to be replaced.</param>
|
||||
/// <param name="length">The length of the text to be replaced.</param>
|
||||
/// <param name="newText">The new text.</param>
|
||||
void Replace(int offset, int length, ITextSource newText); |
||||
|
||||
/// <summary>
|
||||
/// Make the document combine the following actions into a single
|
||||
/// action for undo purposes.
|
||||
/// </summary>
|
||||
void StartUndoableAction(); |
||||
|
||||
/// <summary>
|
||||
/// Ends the undoable action started with <see cref="StartUndoableAction"/>.
|
||||
/// </summary>
|
||||
void EndUndoableAction(); |
||||
|
||||
/// <summary>
|
||||
/// Creates an undo group. Dispose the returned value to close the undo group.
|
||||
/// </summary>
|
||||
/// <returns>An object that closes the undo group when Dispose() is called.</returns>
|
||||
IDisposable OpenUndoGroup(); |
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ITextAnchor"/> at the specified offset.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="ITextAnchor" select="remarks|example"/>
|
||||
ITextAnchor CreateAnchor(int offset); |
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file the document is stored in.
|
||||
/// Could also be a non-existent dummy file name or null if no name has been set.
|
||||
/// </summary>
|
||||
string FileName { get; } |
||||
|
||||
/// <summary>
|
||||
/// Fired when the file name of the document changes.
|
||||
/// </summary>
|
||||
event EventHandler FileNameChanged; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// A line inside a <see cref="IDocument"/>.
|
||||
/// </summary>
|
||||
public interface IDocumentLine : ISegment |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the length of this line, including the line delimiter.
|
||||
/// </summary>
|
||||
int TotalLength { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the line terminator.
|
||||
/// Returns 1 or 2; or 0 at the end of the document.
|
||||
/// </summary>
|
||||
int DelimiterLength { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the number of this line.
|
||||
/// The first line has the number 1.
|
||||
/// </summary>
|
||||
int LineNumber { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the previous line. Returns null if this is the first line in the document.
|
||||
/// </summary>
|
||||
IDocumentLine PreviousLine { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the next line. Returns null if this is the last line in the document.
|
||||
/// </summary>
|
||||
IDocumentLine NextLine { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets whether the line was deleted.
|
||||
/// </summary>
|
||||
bool IsDeleted { get; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Describes a change of the document text.
|
||||
/// This class is thread-safe.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public class TextChangeEventArgs : EventArgs |
||||
{ |
||||
readonly int offset; |
||||
readonly ITextSource removedText; |
||||
readonly ITextSource insertedText; |
||||
|
||||
/// <summary>
|
||||
/// The offset at which the change occurs.
|
||||
/// </summary>
|
||||
public int Offset { |
||||
get { return offset; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The text that was removed.
|
||||
/// </summary>
|
||||
public ITextSource RemovedText { |
||||
get { return removedText; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The number of characters removed.
|
||||
/// </summary>
|
||||
public int RemovalLength { |
||||
get { return removedText.TextLength; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The text that was inserted.
|
||||
/// </summary>
|
||||
public ITextSource InsertedText { |
||||
get { return insertedText; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The number of characters inserted.
|
||||
/// </summary>
|
||||
public int InsertionLength { |
||||
get { return insertedText.TextLength; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new TextChangeEventArgs object.
|
||||
/// </summary>
|
||||
public TextChangeEventArgs(int offset, string removedText, string insertedText) |
||||
{ |
||||
if (offset < 0) |
||||
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative"); |
||||
this.offset = offset; |
||||
this.removedText = removedText != null ? new StringTextSource(removedText) : StringTextSource.Empty; |
||||
this.insertedText = insertedText != null ? new StringTextSource(insertedText) : StringTextSource.Empty; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new TextChangeEventArgs object.
|
||||
/// </summary>
|
||||
public TextChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText) |
||||
{ |
||||
if (offset < 0) |
||||
throw new ArgumentOutOfRangeException("offset", offset, "offset must not be negative"); |
||||
this.offset = offset; |
||||
this.removedText = removedText ?? StringTextSource.Empty; |
||||
this.insertedText = insertedText ?? StringTextSource.Empty; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the new offset where the specified offset moves after this document change.
|
||||
/// </summary>
|
||||
public virtual int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default) |
||||
{ |
||||
if (offset >= this.Offset && offset <= this.Offset + this.RemovalLength) { |
||||
if (movementType == AnchorMovementType.BeforeInsertion) |
||||
return this.Offset; |
||||
else |
||||
return this.Offset + this.InsertionLength; |
||||
} else if (offset > this.Offset) { |
||||
return offset + this.InsertionLength - this.RemovalLength; |
||||
} else { |
||||
return offset; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates TextChangeEventArgs for the reverse change.
|
||||
/// </summary>
|
||||
public virtual TextChangeEventArgs Invert() |
||||
{ |
||||
return new TextChangeEventArgs(offset, insertedText, removedText); |
||||
} |
||||
} |
||||
#endif
|
||||
} |
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
#if !NREFACTORY
|
||||
/// <summary>
|
||||
/// The TextAnchor class references an offset (a position between two characters).
|
||||
/// It automatically updates the offset when text is inserted/removed in front of the anchor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Use the <see cref="ITextAnchor.Offset"/> property to get the offset from a text anchor.
|
||||
/// Use the <see cref="IDocument.CreateAnchor"/> method to create an anchor from an offset.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The document will automatically update all text anchors; and because it uses weak references to do so,
|
||||
/// the garbage collector can simply collect the anchor object when you don't need it anymore.
|
||||
/// </para>
|
||||
/// <para>Moreover, the document is able to efficiently update a large number of anchors without having to look
|
||||
/// at each anchor object individually. Updating the offsets of all anchors usually only takes time logarithmic
|
||||
/// to the number of anchors. Retrieving the <see cref="ITextAnchor.Offset"/> property also runs in O(lg N).</para>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Usage:
|
||||
/// <code>TextAnchor anchor = document.CreateAnchor(offset);
|
||||
/// ChangeMyDocument();
|
||||
/// int newOffset = anchor.Offset;
|
||||
/// </code>
|
||||
/// </example>
|
||||
public interface ITextAnchor |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the text location of this anchor.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||
TextLocation Location { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the offset of the text anchor.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||
int Offset { get; } |
||||
|
||||
/// <summary>
|
||||
/// Controls how the anchor moves.
|
||||
/// </summary>
|
||||
/// <remarks>Anchor movement is ambiguous if text is inserted exactly at the anchor's location.
|
||||
/// Does the anchor stay before the inserted text, or does it move after it?
|
||||
/// The property <see cref="MovementType"/> will be used to determine which of these two options the anchor will choose.
|
||||
/// The default value is <see cref="AnchorMovementType.Default"/>.</remarks>
|
||||
AnchorMovementType MovementType { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Specifies whether the anchor survives deletion of the text containing it.
|
||||
/// </para><para>
|
||||
/// <c>false</c>: The anchor is deleted when the a selection that includes the anchor is deleted.
|
||||
/// <c>true</c>: The anchor is not deleted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <remarks><inheritdoc cref="IsDeleted" /></remarks>
|
||||
bool SurviveDeletion { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets whether the anchor was deleted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>When a piece of text containing an anchor is removed, then that anchor will be deleted.
|
||||
/// First, the <see cref="IsDeleted"/> property is set to true on all deleted anchors,
|
||||
/// then the <see cref="Deleted"/> events are raised.
|
||||
/// You cannot retrieve the offset from an anchor that has been deleted.</para>
|
||||
/// <para>This deletion behavior might be useful when using anchors for building a bookmark feature,
|
||||
/// but in other cases you want to still be able to use the anchor. For those cases, set <c><see cref="SurviveDeletion"/> = true</c>.</para>
|
||||
/// </remarks>
|
||||
bool IsDeleted { get; } |
||||
|
||||
/// <summary>
|
||||
/// Occurs after the anchor was deleted.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <inheritdoc cref="IsDeleted" />
|
||||
/// <para>Due to the 'weak reference' nature of text anchors, you will receive
|
||||
/// the Deleted event only while your code holds a reference to the TextAnchor object.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
event EventHandler Deleted; |
||||
|
||||
/// <summary>
|
||||
/// Gets the line number of the anchor.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||
int Line { get; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the column number of this anchor.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when trying to get the Offset from a deleted anchor.</exception>
|
||||
int Column { get; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Defines how a text anchor moves.
|
||||
/// </summary>
|
||||
public enum AnchorMovementType |
||||
{ |
||||
/// <summary>
|
||||
/// When text is inserted at the anchor position, the type of the insertion
|
||||
/// determines where the caret moves to. For normal insertions, the anchor will move
|
||||
/// after the inserted text.
|
||||
/// </summary>
|
||||
Default, |
||||
/// <summary>
|
||||
/// Behaves like a start marker - when text is inserted at the anchor position, the anchor will stay
|
||||
/// before the inserted text.
|
||||
/// </summary>
|
||||
BeforeInsertion, |
||||
/// <summary>
|
||||
/// Behave like an end marker - when text is insered at the anchor position, the anchor will move
|
||||
/// after the inserted text.
|
||||
/// </summary>
|
||||
AfterInsertion |
||||
} |
||||
#endif
|
||||
} |
@ -0,0 +1,169 @@
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
#if NREFACTORY
|
||||
using ICSharpCode.NRefactory.Editor; |
||||
#endif
|
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
/// <summary>
|
||||
/// Implements the ITextSource interface using a rope.
|
||||
/// </summary>
|
||||
[Serializable] |
||||
public sealed class RopeTextSource : ITextSource |
||||
{ |
||||
readonly Rope<char> rope; |
||||
readonly ITextSourceVersion version; |
||||
|
||||
/// <summary>
|
||||
/// Creates a new RopeTextSource.
|
||||
/// </summary>
|
||||
public RopeTextSource(Rope<char> rope) |
||||
{ |
||||
if (rope == null) |
||||
throw new ArgumentNullException("rope"); |
||||
this.rope = rope.Clone(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a new RopeTextSource.
|
||||
/// </summary>
|
||||
public RopeTextSource(Rope<char> rope, ITextSourceVersion version) |
||||
{ |
||||
if (rope == null) |
||||
throw new ArgumentNullException("rope"); |
||||
this.rope = rope.Clone(); |
||||
this.version = version; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Returns a clone of the rope used for this text source.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// RopeTextSource only publishes a copy of the contained rope to ensure that the underlying rope cannot be modified.
|
||||
/// </remarks>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification="Not a property because it creates a clone")] |
||||
public Rope<char> GetRope() |
||||
{ |
||||
return rope.Clone(); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string Text { |
||||
get { return rope.ToString(); } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int TextLength { |
||||
get { return rope.Length; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public char GetCharAt(int offset) |
||||
{ |
||||
return rope[offset]; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetText(int offset, int length) |
||||
{ |
||||
return rope.ToString(offset, length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public string GetText(ISegment segment) |
||||
{ |
||||
return rope.ToString(segment.Offset, segment.Length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public TextReader CreateReader() |
||||
{ |
||||
return new RopeTextReader(rope); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public TextReader CreateReader(int offset, int length) |
||||
{ |
||||
return new RopeTextReader(rope.GetRange(offset, length)); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSource CreateSnapshot() |
||||
{ |
||||
return this; |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSource CreateSnapshot(int offset, int length) |
||||
{ |
||||
return new RopeTextSource(rope.GetRange(offset, length)); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(char c, int startIndex, int count) |
||||
{ |
||||
return rope.IndexOf(c, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOfAny(char[] anyOf, int startIndex, int count) |
||||
{ |
||||
return rope.IndexOfAny(anyOf, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastIndexOf(char c, int startIndex, int count) |
||||
{ |
||||
return rope.LastIndexOf(c, startIndex, count); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public ITextSourceVersion Version { |
||||
get { return version; } |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int IndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||
{ |
||||
return rope.IndexOf(searchText, startIndex, count, comparisonType); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastIndexOf(string searchText, int startIndex, int count, StringComparison comparisonType) |
||||
{ |
||||
return rope.LastIndexOf(searchText, startIndex, count, comparisonType); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteTextTo(TextWriter writer) |
||||
{ |
||||
rope.WriteTo(writer, 0, rope.Length); |
||||
} |
||||
|
||||
/// <inheritdoc/>
|
||||
public void WriteTextTo(TextWriter writer, int offset, int length) |
||||
{ |
||||
rope.WriteTo(writer, offset, length); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Document |
||||
{ |
||||
#if !NREFACTORY
|
||||
/// <summary>
|
||||
/// Provides ITextSourceVersion instances.
|
||||
/// </summary>
|
||||
public class TextSourceVersionProvider |
||||
{ |
||||
Version currentVersion; |
||||
|
||||
/// <summary>
|
||||
/// Creates a new TextSourceVersionProvider instance.
|
||||
/// </summary>
|
||||
public TextSourceVersionProvider() |
||||
{ |
||||
this.currentVersion = new Version(this); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the current version.
|
||||
/// </summary>
|
||||
public ITextSourceVersion CurrentVersion { |
||||
get { return currentVersion; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Replaces the current version with a new version.
|
||||
/// </summary>
|
||||
/// <param name="change">Change from current version to new version</param>
|
||||
public void AppendChange(TextChangeEventArgs change) |
||||
{ |
||||
if (change == null) |
||||
throw new ArgumentNullException("change"); |
||||
currentVersion.change = change; |
||||
currentVersion.next = new Version(currentVersion); |
||||
currentVersion = currentVersion.next; |
||||
} |
||||
|
||||
[DebuggerDisplay("Version #{id}")] |
||||
sealed class Version : ITextSourceVersion |
||||
{ |
||||
// Reference back to the provider.
|
||||
// Used to determine if two checkpoints belong to the same document.
|
||||
readonly TextSourceVersionProvider provider; |
||||
// ID used for CompareAge()
|
||||
readonly int id; |
||||
|
||||
// the change from this version to the next version
|
||||
internal TextChangeEventArgs change; |
||||
internal Version next; |
||||
|
||||
internal Version(TextSourceVersionProvider provider) |
||||
{ |
||||
this.provider = provider; |
||||
} |
||||
|
||||
internal Version(Version prev) |
||||
{ |
||||
this.provider = prev.provider; |
||||
this.id = unchecked( prev.id + 1 ); |
||||
} |
||||
|
||||
public bool BelongsToSameDocumentAs(ITextSourceVersion other) |
||||
{ |
||||
Version o = other as Version; |
||||
return o != null && provider == o.provider; |
||||
} |
||||
|
||||
public int CompareAge(ITextSourceVersion other) |
||||
{ |
||||
if (other == null) |
||||
throw new ArgumentNullException("other"); |
||||
Version o = other as Version; |
||||
if (o == null || provider != o.provider) |
||||
throw new ArgumentException("Versions do not belong to the same document."); |
||||
// We will allow overflows, but assume that the maximum distance between checkpoints is 2^31-1.
|
||||
// This is guaranteed on x86 because so many checkpoints don't fit into memory.
|
||||
return Math.Sign(unchecked( this.id - o.id )); |
||||
} |
||||
|
||||
public IEnumerable<TextChangeEventArgs> GetChangesTo(ITextSourceVersion other) |
||||
{ |
||||
int result = CompareAge(other); |
||||
Version o = (Version)other; |
||||
if (result < 0) |
||||
return GetForwardChanges(o); |
||||
else if (result > 0) |
||||
return o.GetForwardChanges(this).Reverse().Select(change => change.Invert()); |
||||
else |
||||
return Empty<TextChangeEventArgs>.Array; |
||||
} |
||||
|
||||
IEnumerable<TextChangeEventArgs> GetForwardChanges(Version other) |
||||
{ |
||||
// Return changes from this(inclusive) to other(exclusive).
|
||||
for (Version node = this; node != other; node = node.next) { |
||||
yield return node.change; |
||||
} |
||||
} |
||||
|
||||
public int MoveOffsetTo(ITextSourceVersion other, int oldOffset, AnchorMovementType movement) |
||||
{ |
||||
int offset = oldOffset; |
||||
foreach (var e in GetChangesTo(other)) { |
||||
offset = e.GetNewOffset(offset, movement); |
||||
} |
||||
return offset; |
||||
} |
||||
} |
||||
} |
||||
#endif
|
||||
} |
@ -0,0 +1,221 @@
@@ -0,0 +1,221 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Linq; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
||||
using System.Security; |
||||
using System.Windows; |
||||
using System.Windows.Input; |
||||
using System.Windows.Interop; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.TextFormatting; |
||||
|
||||
using ICSharpCode.AvalonEdit; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Rendering; |
||||
using ICSharpCode.AvalonEdit.Utils; |
||||
using Draw = System.Drawing; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Editing |
||||
{ |
||||
/// <summary>
|
||||
/// Native API required for IME support.
|
||||
/// </summary>
|
||||
static class ImeNativeWrapper |
||||
{ |
||||
[StructLayout(LayoutKind.Sequential)] |
||||
struct CompositionForm |
||||
{ |
||||
public int dwStyle; |
||||
public POINT ptCurrentPos; |
||||
public RECT rcArea; |
||||
} |
||||
|
||||
[StructLayout(LayoutKind.Sequential)] |
||||
struct POINT |
||||
{ |
||||
public int x; |
||||
public int y; |
||||
} |
||||
|
||||
[StructLayout(LayoutKind.Sequential)] |
||||
struct RECT |
||||
{ |
||||
public int left; |
||||
public int top; |
||||
public int right; |
||||
public int bottom; |
||||
} |
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] |
||||
struct LOGFONT |
||||
{ |
||||
public int lfHeight; |
||||
public int lfWidth; |
||||
public int lfEscapement; |
||||
public int lfOrientation; |
||||
public int lfWeight; |
||||
public byte lfItalic; |
||||
public byte lfUnderline; |
||||
public byte lfStrikeOut; |
||||
public byte lfCharSet; |
||||
public byte lfOutPrecision; |
||||
public byte lfClipPrecision; |
||||
public byte lfQuality; |
||||
public byte lfPitchAndFamily; |
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)] public string lfFaceName; |
||||
} |
||||
|
||||
const int CPS_CANCEL = 0x4; |
||||
const int NI_COMPOSITIONSTR = 0x15; |
||||
const int GCS_COMPSTR = 0x0008; |
||||
|
||||
public const int WM_IME_COMPOSITION = 0x10F; |
||||
public const int WM_IME_SETCONTEXT = 0x281; |
||||
public const int WM_INPUTLANGCHANGE = 0x51; |
||||
|
||||
[DllImport("imm32.dll")] |
||||
public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC); |
||||
[DllImport("imm32.dll")] |
||||
internal static extern IntPtr ImmGetContext(IntPtr hWnd); |
||||
[DllImport("imm32.dll")] |
||||
internal static extern IntPtr ImmGetDefaultIMEWnd(IntPtr hWnd); |
||||
[DllImport("imm32.dll")] |
||||
[return: MarshalAs(UnmanagedType.Bool)] |
||||
internal static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); |
||||
[DllImport("imm32.dll")] |
||||
[return: MarshalAs(UnmanagedType.Bool)] |
||||
static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue = 0); |
||||
[DllImport("imm32.dll")] |
||||
[return: MarshalAs(UnmanagedType.Bool)] |
||||
static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref CompositionForm form); |
||||
[DllImport("imm32.dll", CharSet = CharSet.Unicode)] |
||||
[return: MarshalAs(UnmanagedType.Bool)] |
||||
static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT font); |
||||
[DllImport("imm32.dll")] |
||||
[return: MarshalAs(UnmanagedType.Bool)] |
||||
static extern bool ImmGetCompositionFont(IntPtr hIMC, out LOGFONT font); |
||||
|
||||
[DllImport("msctf.dll")] |
||||
static extern int TF_CreateThreadMgr(out ITfThreadMgr threadMgr); |
||||
|
||||
[ThreadStatic] static bool textFrameworkThreadMgrInitialized; |
||||
[ThreadStatic] static ITfThreadMgr textFrameworkThreadMgr; |
||||
|
||||
public static ITfThreadMgr GetTextFrameworkThreadManager() |
||||
{ |
||||
if (!textFrameworkThreadMgrInitialized) { |
||||
textFrameworkThreadMgrInitialized = true; |
||||
TF_CreateThreadMgr(out textFrameworkThreadMgr); |
||||
} |
||||
return textFrameworkThreadMgr; |
||||
} |
||||
|
||||
public static bool NotifyIme(IntPtr hIMC) |
||||
{ |
||||
return ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL); |
||||
} |
||||
|
||||
public static bool SetCompositionWindow(HwndSource source, IntPtr hIMC, TextArea textArea) |
||||
{ |
||||
if (textArea == null) |
||||
throw new ArgumentNullException("textArea"); |
||||
Rect textViewBounds = textArea.TextView.GetBounds(source); |
||||
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source); |
||||
CompositionForm form = new CompositionForm(); |
||||
form.dwStyle = 0x0020; |
||||
form.ptCurrentPos.x = (int)Math.Max(characterBounds.Left, textViewBounds.Left); |
||||
form.ptCurrentPos.y = (int)Math.Max(characterBounds.Top, textViewBounds.Top); |
||||
form.rcArea.left = (int)textViewBounds.Left; |
||||
form.rcArea.top = (int)textViewBounds.Top; |
||||
form.rcArea.right = (int)textViewBounds.Right; |
||||
form.rcArea.bottom = (int)textViewBounds.Bottom; |
||||
return ImmSetCompositionWindow(hIMC, ref form); |
||||
} |
||||
|
||||
public static bool SetCompositionFont(HwndSource source, IntPtr hIMC, TextArea textArea) |
||||
{ |
||||
if (textArea == null) |
||||
throw new ArgumentNullException("textArea"); |
||||
LOGFONT lf = new LOGFONT(); |
||||
Rect characterBounds = textArea.TextView.GetCharacterBounds(textArea.Caret.Position, source); |
||||
lf.lfFaceName = textArea.FontFamily.Source; |
||||
lf.lfHeight = (int)characterBounds.Height; |
||||
return ImmSetCompositionFont(hIMC, ref lf); |
||||
} |
||||
|
||||
static Rect GetBounds(this TextView textView, HwndSource source) |
||||
{ |
||||
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
|
||||
// in those cases. It should be refreshed immediately.
|
||||
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView)) |
||||
return EMPTY_RECT; |
||||
Rect displayRect = new Rect(0, 0, textView.ActualWidth, textView.ActualHeight); |
||||
return textView |
||||
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
|
||||
.TransformToDevice(source.RootVisual); // rect on HWND
|
||||
} |
||||
|
||||
static readonly Rect EMPTY_RECT = new Rect(0, 0, 0, 0); |
||||
|
||||
static Rect GetCharacterBounds(this TextView textView, TextViewPosition pos, HwndSource source) |
||||
{ |
||||
VisualLine vl = textView.GetVisualLine(pos.Line); |
||||
if (vl == null) |
||||
return EMPTY_RECT; |
||||
// this may happen during layout changes in AvalonDock, so we just return an empty rectangle
|
||||
// in those cases. It should be refreshed immediately.
|
||||
if (source.RootVisual == null || !source.RootVisual.IsAncestorOf(textView)) |
||||
return EMPTY_RECT; |
||||
TextLine line = vl.GetTextLine(pos.VisualColumn, pos.IsAtEndOfLine); |
||||
Rect displayRect; |
||||
// calculate the display rect for the current character
|
||||
if (pos.VisualColumn < vl.VisualLengthWithEndOfLineMarker) { |
||||
displayRect = line.GetTextBounds(pos.VisualColumn, 1).First().Rectangle; |
||||
displayRect.Offset(0, vl.GetTextLineVisualYPosition(line, VisualYPosition.LineTop)); |
||||
} else { |
||||
// if we are in virtual space, we just use one wide-space as character width
|
||||
displayRect = new Rect(vl.GetVisualPosition(pos.VisualColumn, VisualYPosition.TextTop), |
||||
new Size(textView.WideSpaceWidth, textView.DefaultLineHeight)); |
||||
} |
||||
// adjust to current scrolling
|
||||
displayRect.Offset(-textView.ScrollOffset); |
||||
return textView |
||||
.TransformToAncestor(source.RootVisual).TransformBounds(displayRect) // rect on root visual
|
||||
.TransformToDevice(source.RootVisual); // rect on HWND
|
||||
} |
||||
} |
||||
|
||||
[ComImport, Guid("aa80e801-2021-11d2-93e0-0060b067b86e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] |
||||
interface ITfThreadMgr |
||||
{ |
||||
void Activate(out int clientId); |
||||
void Deactivate(); |
||||
void CreateDocumentMgr(out IntPtr docMgr); |
||||
void EnumDocumentMgrs(out IntPtr enumDocMgrs); |
||||
void GetFocus(out IntPtr docMgr); |
||||
void SetFocus(IntPtr docMgr); |
||||
void AssociateFocus(IntPtr hwnd, IntPtr newDocMgr, out IntPtr prevDocMgr); |
||||
void IsThreadFocus([MarshalAs(UnmanagedType.Bool)] out bool isFocus); |
||||
void GetFunctionProvider(ref Guid classId, out IntPtr funcProvider); |
||||
void EnumFunctionProviders(out IntPtr enumProviders); |
||||
void GetGlobalCompartment(out IntPtr compartmentMgr); |
||||
} |
||||
} |
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.ComponentModel; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Runtime.CompilerServices; |
||||
using System.Runtime.InteropServices; |
||||
using System.Security; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Input; |
||||
using System.Windows.Interop; |
||||
using System.Windows.Media; |
||||
using System.Windows.Media.TextFormatting; |
||||
using ICSharpCode.AvalonEdit; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Rendering; |
||||
|
||||
namespace ICSharpCode.AvalonEdit.Editing |
||||
{ |
||||
class ImeSupport |
||||
{ |
||||
readonly TextArea textArea; |
||||
IntPtr currentContext; |
||||
IntPtr previousContext; |
||||
IntPtr defaultImeWnd; |
||||
HwndSource hwndSource; |
||||
EventHandler requerySuggestedHandler; // we need to keep the event handler instance alive because CommandManager.RequerySuggested uses weak references
|
||||
bool isReadOnly; |
||||
|
||||
public ImeSupport(TextArea textArea) |
||||
{ |
||||
if (textArea == null) |
||||
throw new ArgumentNullException("textArea"); |
||||
this.textArea = textArea; |
||||
InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport); |
||||
// We listen to CommandManager.RequerySuggested for both caret offset changes and changes to the set of read-only sections.
|
||||
// This is because there's no dedicated event for read-only section changes; but RequerySuggested needs to be raised anyways
|
||||
// to invalidate the Paste command.
|
||||
requerySuggestedHandler = OnRequerySuggested; |
||||
CommandManager.RequerySuggested += requerySuggestedHandler; |
||||
textArea.OptionChanged += TextAreaOptionChanged; |
||||
} |
||||
|
||||
void OnRequerySuggested(object sender, EventArgs e) |
||||
{ |
||||
UpdateImeEnabled(); |
||||
} |
||||
|
||||
void TextAreaOptionChanged(object sender, PropertyChangedEventArgs e) |
||||
{ |
||||
if (e.PropertyName == "EnableImeSupport") { |
||||
InputMethod.SetIsInputMethodSuspended(this.textArea, textArea.Options.EnableImeSupport); |
||||
UpdateImeEnabled(); |
||||
} |
||||
} |
||||
|
||||
public void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) |
||||
{ |
||||
UpdateImeEnabled(); |
||||
} |
||||
|
||||
public void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) |
||||
{ |
||||
if (e.OldFocus == textArea && currentContext != IntPtr.Zero) |
||||
ImeNativeWrapper.NotifyIme(currentContext); |
||||
ClearContext(); |
||||
} |
||||
|
||||
void UpdateImeEnabled() |
||||
{ |
||||
if (textArea.Options.EnableImeSupport && textArea.IsKeyboardFocused) { |
||||
bool newReadOnly = !textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset); |
||||
if (hwndSource == null || isReadOnly != newReadOnly) { |
||||
ClearContext(); // clear existing context (on read-only change)
|
||||
isReadOnly = newReadOnly; |
||||
CreateContext(); |
||||
} |
||||
} else { |
||||
ClearContext(); |
||||
} |
||||
} |
||||
|
||||
void ClearContext() |
||||
{ |
||||
if (hwndSource != null) { |
||||
ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, previousContext); |
||||
ImeNativeWrapper.ImmReleaseContext(defaultImeWnd, currentContext); |
||||
currentContext = IntPtr.Zero; |
||||
defaultImeWnd = IntPtr.Zero; |
||||
hwndSource.RemoveHook(WndProc); |
||||
hwndSource = null; |
||||
} |
||||
} |
||||
|
||||
void CreateContext() |
||||
{ |
||||
hwndSource = (HwndSource)PresentationSource.FromVisual(this.textArea); |
||||
if (hwndSource != null) { |
||||
if (isReadOnly) { |
||||
defaultImeWnd = IntPtr.Zero; |
||||
currentContext = IntPtr.Zero; |
||||
} else { |
||||
defaultImeWnd = ImeNativeWrapper.ImmGetDefaultIMEWnd(IntPtr.Zero); |
||||
currentContext = ImeNativeWrapper.ImmGetContext(defaultImeWnd); |
||||
} |
||||
previousContext = ImeNativeWrapper.ImmAssociateContext(hwndSource.Handle, currentContext); |
||||
hwndSource.AddHook(WndProc); |
||||
// UpdateCompositionWindow() will be called by the caret becoming visible
|
||||
|
||||
var threadMgr = ImeNativeWrapper.GetTextFrameworkThreadManager(); |
||||
if (threadMgr != null) { |
||||
// Even though the docu says passing null is invalid, this seems to help
|
||||
// activating the IME on the default input context that is shared with WPF
|
||||
threadMgr.SetFocus(IntPtr.Zero); |
||||
} |
||||
} |
||||
} |
||||
|
||||
IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) |
||||
{ |
||||
switch (msg) { |
||||
case ImeNativeWrapper.WM_INPUTLANGCHANGE: |
||||
// Don't mark the message as handled; other windows
|
||||
// might want to handle it as well.
|
||||
|
||||
// If we have a context, recreate it
|
||||
if (hwndSource != null) { |
||||
ClearContext(); |
||||
CreateContext(); |
||||
} |
||||
break; |
||||
case ImeNativeWrapper.WM_IME_COMPOSITION: |
||||
UpdateCompositionWindow(); |
||||
break; |
||||
} |
||||
return IntPtr.Zero; |
||||
} |
||||
|
||||
public void UpdateCompositionWindow() |
||||
{ |
||||
if (currentContext != IntPtr.Zero) { |
||||
ImeNativeWrapper.SetCompositionFont(hwndSource, currentContext, textArea); |
||||
ImeNativeWrapper.SetCompositionWindow(hwndSource, currentContext, textArea); |
||||
} |
||||
} |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue