first commit

This commit is contained in:
2026-01-07 11:33:05 +08:00
commit fc54ffd43b
215 changed files with 31856 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note: This project is derived from Peter Project
// (hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System.Collections.Generic;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public class CSharpFoldingStrategy : IFoldingStrategy
{
#region Methods
/// <summary>
/// Generates the foldings for our document.
/// </summary>
/// <param name="document">The current document.</param>
/// <param name="fileName">The filename of the document.</param>
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
/// <returns>A list of FoldMarkers.</returns>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
var list = new List<FoldMarker>();
var startLines = new Stack<int>();
// Create foldmarkers for the whole document, enumerate through every line.
for (int i = 0; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = document.TextLength;
char c;
for (offs = seg.Offset; offs < end && ((c = document.GetCharAt(offs)) == ' ' || c == '\t'); offs++)
{
}
if (offs == end)
break;
int spaceCount = offs - seg.Offset;
// now offs points to the first non-whitespace char on the line
if (document.GetCharAt(offs) == '#')
{
string text = document.GetText(offs, seg.Length - spaceCount);
if (text.StartsWith("#region"))
startLines.Push(i);
if (text.StartsWith("#endregion") && startLines.Count > 0)
{
// Add a new FoldMarker to the list.
int start = startLines.Pop();
list.Add(new FoldMarker(document, start,
document.GetLineSegment(start).Length,
i, spaceCount + "#endregion".Length, FoldType.Region, "{...}"));
}
}
// { }
if (document.GetCharAt(offs) == '{')
{
int offsetOfClosingBracket = document.FormattingStrategy.SearchBracketForward(document, offs + 1, '{', '}');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "{...}", false));
}
}
if (document.GetCharAt(offs) == '/')
{
string text = document.GetText(offs, seg.Length - spaceCount);
if (text.StartsWith("/// <summary>"))
startLines.Push(i);
if ((text.StartsWith("/// <param") || text.StartsWith("/// <returns>") || text.StartsWith("/// </summary>"))
&& startLines.Count > 0)
{
// Add a new FoldMarker to the list.
int start = startLines.Pop();
list.Add(new FoldMarker(document, start,
document.GetLineSegment(start).Length,
i, spaceCount + "/// </summary>".Length, FoldType.TypeBody, "/// <summary>..."));
}
}
}
return list;
}
#endregion Methods
}
}

View File

@@ -0,0 +1,174 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.TextEditor.Document
{
public enum FoldType {
Unspecified,
MemberBody,
Region,
TypeBody
}
public class FoldMarker : AbstractSegment, IComparable
{
bool isFolded = false;
string foldText = "...";
FoldType foldType = FoldType.Unspecified;
IDocument document = null;
int startLine = -1, startColumn, endLine = -1, endColumn;
static void GetPointForOffset(IDocument document, int offset, out int line, out int column)
{
if (offset > document.TextLength) {
line = document.TotalNumberOfLines + 1;
column = 1;
} else if (offset < 0) {
line = -1;
column = -1;
} else {
line = document.GetLineNumberForOffset(offset);
column = offset - document.GetLineSegment(line).Offset;
}
}
public FoldType FoldType {
get { return foldType; }
set { foldType = value; }
}
public int StartLine {
get {
if (startLine < 0) {
GetPointForOffset(document, offset, out startLine, out startColumn);
}
return startLine;
}
}
public int StartColumn {
get {
if (startLine < 0) {
GetPointForOffset(document, offset, out startLine, out startColumn);
}
return startColumn;
}
}
public int EndLine {
get {
if (endLine < 0) {
GetPointForOffset(document, offset + length, out endLine, out endColumn);
}
return endLine;
}
}
public int EndColumn {
get {
if (endLine < 0) {
GetPointForOffset(document, offset + length, out endLine, out endColumn);
}
return endColumn;
}
}
public override int Offset {
get { return base.Offset; }
set {
base.Offset = value;
startLine = -1; endLine = -1;
}
}
public override int Length {
get { return base.Length; }
set {
base.Length = value;
endLine = -1;
}
}
public bool IsFolded {
get {
return isFolded;
}
set {
isFolded = value;
}
}
public string FoldText {
get {
return foldText;
}
}
public string InnerText {
get {
return document.GetText(offset, length);
}
}
public FoldMarker(IDocument document, int offset, int length, string foldText, bool isFolded)
{
this.document = document;
this.offset = offset;
this.length = length;
this.foldText = foldText;
this.isFolded = isFolded;
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn) : this(document, startLine, startColumn, endLine, endColumn, FoldType.Unspecified)
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType) : this(document, startLine, startColumn, endLine, endColumn, foldType, "...")
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText) : this(document, startLine, startColumn, endLine, endColumn, foldType, foldText, false)
{
}
public FoldMarker(IDocument document, int startLine, int startColumn, int endLine, int endColumn, FoldType foldType, string foldText, bool isFolded)
{
this.document = document;
startLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(startLine, 0));
ISegment startLineSegment = document.GetLineSegment(startLine);
endLine = Math.Min(document.TotalNumberOfLines - 1, Math.Max(endLine, 0));
ISegment endLineSegment = document.GetLineSegment(endLine);
// Prevent the region from completely disappearing
if (string.IsNullOrEmpty(foldText)) {
foldText = "...";
}
this.FoldType = foldType;
this.foldText = foldText;
this.offset = startLineSegment.Offset + Math.Min(startColumn, startLineSegment.Length);
this.length = (endLineSegment.Offset + Math.Min(endColumn, endLineSegment.Length)) - this.offset;
this.isFolded = isFolded;
}
public int CompareTo(object o)
{
if (!(o is FoldMarker)) {
throw new ArgumentException();
}
FoldMarker f = (FoldMarker)o;
if (offset != f.offset) {
return offset.CompareTo(f.offset);
}
return length.CompareTo(f.length);
}
}
}

View File

@@ -0,0 +1,362 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public class FoldingManager
{
List<FoldMarker> foldMarker = new List<FoldMarker>();
List<FoldMarker> foldMarkerByEnd = new List<FoldMarker>();
IDocument document;
public IList<FoldMarker> FoldMarker => foldMarker.AsReadOnly();
public IFoldingStrategy? FoldingStrategy { get; set; }
internal FoldingManager(IDocument document, LineManager lineTracker)
{
this.document = document;
document.DocumentChanged += new DocumentEventHandler(DocumentChanged);
// lineTracker.LineCountChanged += new LineManagerEventHandler(LineManagerLineCountChanged);
// lineTracker.LineLengthChanged += new LineLengthEventHandler(LineManagerLineLengthChanged);
// foldMarker.Add(new FoldMarker(0, 5, 3, 5));
//
// foldMarker.Add(new FoldMarker(5, 5, 10, 3));
// foldMarker.Add(new FoldMarker(6, 0, 8, 2));
//
// FoldMarker fm1 = new FoldMarker(10, 4, 10, 7);
// FoldMarker fm2 = new FoldMarker(10, 10, 10, 14);
//
// fm1.IsFolded = true;
// fm2.IsFolded = true;
//
// foldMarker.Add(fm1);
// foldMarker.Add(fm2);
// foldMarker.Sort();
}
void DocumentChanged(object sender, DocumentEventArgs e)
{
int oldCount = foldMarker.Count;
document.UpdateSegmentListOnDocumentChange(foldMarker, e);
if (oldCount != foldMarker.Count)
{
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
}
}
public List<FoldMarker> GetFoldingsFromPosition(int line, int column)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
for (int i = 0; i < foldMarker.Count; ++i)
{
FoldMarker fm = foldMarker[i];
if ((fm.StartLine == line && column > fm.StartColumn && !(fm.EndLine == line && column >= fm.EndColumn)) ||
(fm.EndLine == line && column < fm.EndColumn && !(fm.StartLine == line && column <= fm.StartColumn)) ||
(line > fm.StartLine && line < fm.EndLine))
{
foldings.Add(fm);
}
}
}
return foldings;
}
class StartComparer : IComparer<FoldMarker>
{
public readonly static StartComparer Instance = new StartComparer();
public int Compare(FoldMarker x, FoldMarker y)
{
if (x.StartLine < y.StartLine)
return -1;
else if (x.StartLine == y.StartLine)
return x.StartColumn.CompareTo(y.StartColumn);
else
return 1;
}
}
class EndComparer : IComparer<FoldMarker>
{
public readonly static EndComparer Instance = new EndComparer();
public int Compare(FoldMarker x, FoldMarker y)
{
if (x.EndLine < y.EndLine)
return -1;
else if (x.EndLine == y.EndLine)
return x.EndColumn.CompareTo(y.EndColumn);
else
return 1;
}
}
List<FoldMarker> GetFoldingsByStartAfterColumn(int lineNumber, int column, bool forceFolded)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
int index = foldMarker.BinarySearch(
new FoldMarker(document, lineNumber, column, lineNumber, column),
StartComparer.Instance);
if (index < 0) index = ~index;
for (; index < foldMarker.Count; index++)
{
FoldMarker fm = foldMarker[index];
if (fm.StartLine > lineNumber)
break;
if (fm.StartColumn <= column)
continue;
if (!forceFolded || fm.IsFolded)
foldings.Add(fm);
}
}
return foldings;
}
public List<FoldMarker> GetFoldingsWithStart(int lineNumber)
{
return GetFoldingsByStartAfterColumn(lineNumber, -1, false);
}
public List<FoldMarker> GetFoldedFoldingsWithStart(int lineNumber)
{
return GetFoldingsByStartAfterColumn(lineNumber, -1, true);
}
public List<FoldMarker> GetFoldedFoldingsWithStartAfterColumn(int lineNumber, int column)
{
return GetFoldingsByStartAfterColumn(lineNumber, column, true);
}
List<FoldMarker> GetFoldingsByEndAfterColumn(int lineNumber, int column, bool forceFolded)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
int index = foldMarkerByEnd.BinarySearch(
new FoldMarker(document, lineNumber, column, lineNumber, column),
EndComparer.Instance);
if (index < 0) index = ~index;
for (; index < foldMarkerByEnd.Count; index++)
{
FoldMarker fm = foldMarkerByEnd[index];
if (fm.EndLine > lineNumber)
break;
if (fm.EndColumn <= column)
continue;
if (!forceFolded || fm.IsFolded)
foldings.Add(fm);
}
}
return foldings;
}
public List<FoldMarker> GetFoldingsWithEnd(int lineNumber)
{
return GetFoldingsByEndAfterColumn(lineNumber, -1, false);
}
public List<FoldMarker> GetFoldedFoldingsWithEnd(int lineNumber)
{
return GetFoldingsByEndAfterColumn(lineNumber, -1, true);
}
public bool IsFoldStart(int lineNumber)
{
return GetFoldingsWithStart(lineNumber).Count > 0;
}
public bool IsFoldEnd(int lineNumber)
{
return GetFoldingsWithEnd(lineNumber).Count > 0;
}
public List<FoldMarker> GetFoldingsContainsLineNumber(int lineNumber)
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
foreach (FoldMarker fm in foldMarker)
{
if (fm.StartLine < lineNumber && lineNumber < fm.EndLine)
{
foldings.Add(fm);
}
}
}
return foldings;
}
public bool IsBetweenFolding(int lineNumber)
{
return GetFoldingsContainsLineNumber(lineNumber).Count > 0;
}
public bool IsLineVisible(int lineNumber)
{
foreach (FoldMarker fm in GetFoldingsContainsLineNumber(lineNumber))
{
if (fm.IsFolded)
return false;
}
return true;
}
public List<FoldMarker> GetTopLevelFoldedFoldings()
{
List<FoldMarker> foldings = new List<FoldMarker>();
if (foldMarker != null)
{
Point end = new Point(0, 0);
foreach (FoldMarker fm in foldMarker)
{
if (fm.IsFolded && (fm.StartLine > end.Y || fm.StartLine == end.Y && fm.StartColumn >= end.X))
{
foldings.Add(fm);
end = new Point(fm.EndColumn, fm.EndLine);
}
}
}
return foldings;
}
public void UpdateFoldings(string fileName, object parseInfo)
{
UpdateFoldings(FoldingStrategy?.GenerateFoldMarkers(document, fileName, parseInfo));
}
public void UpdateFoldings(List<FoldMarker> newFoldings)
{
int oldFoldingsCount = foldMarker.Count;
lock (this)
{
if (newFoldings != null && newFoldings.Count != 0)
{
newFoldings.Sort();
if (foldMarker.Count == newFoldings.Count)
{
for (int i = 0; i < foldMarker.Count; ++i)
{
newFoldings[i].IsFolded = foldMarker[i].IsFolded;
}
foldMarker = newFoldings;
}
else
{
for (int i = 0, j = 0; i < foldMarker.Count && j < newFoldings.Count;)
{
int n = newFoldings[j].CompareTo(foldMarker[i]);
if (n > 0)
{
++i;
}
else
{
if (n == 0)
{
newFoldings[j].IsFolded = foldMarker[i].IsFolded;
}
++j;
}
}
}
}
if (newFoldings != null)
{
foldMarker = newFoldings;
foldMarkerByEnd = new List<FoldMarker>(newFoldings);
foldMarkerByEnd.Sort(EndComparer.Instance);
}
else
{
foldMarker.Clear();
foldMarkerByEnd.Clear();
}
}
if (oldFoldingsCount != foldMarker.Count)
{
document.RequestUpdate(new TextAreaUpdate(TextAreaUpdateType.WholeTextArea));
document.CommitUpdate();
}
}
public string SerializeToString()
{
StringBuilder sb = new StringBuilder();
foreach (FoldMarker marker in this.foldMarker)
{
sb.Append(marker.Offset); sb.Append("\n");
sb.Append(marker.Length); sb.Append("\n");
sb.Append(marker.FoldText); sb.Append("\n");
sb.Append(marker.IsFolded); sb.Append("\n");
}
return sb.ToString();
}
public void DeserializeFromString(string str)
{
try
{
string[] lines = str.Split('\n');
for (int i = 0; i < lines.Length && lines[i].Length > 0; i += 4)
{
int offset = int.Parse(lines[i]);
int length = int.Parse(lines[i + 1]);
string text = lines[i + 2];
bool isFolded = bool.Parse(lines[i + 3]);
bool found = false;
foreach (FoldMarker marker in foldMarker)
{
if (marker.Offset == offset && marker.Length == length)
{
marker.IsFolded = isFolded;
found = true;
break;
}
}
if (!found)
{
foldMarker.Add(new FoldMarker(document, offset, length, text, isFolded));
}
}
if (lines.Length > 0)
{
NotifyFoldingsChanged(EventArgs.Empty);
}
}
catch (Exception)
{
// Empty catch
}
}
public void NotifyFoldingsChanged(EventArgs e)
{
if (FoldingsChanged != null)
{
FoldingsChanged(this, e);
}
}
public event EventHandler FoldingsChanged;
}
}

View File

@@ -0,0 +1,24 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// This interface is used for the folding capabilities
/// of the textarea.
/// </summary>
public interface IFoldingStrategy
{
/// <remarks>
/// Calculates the fold level of a specific line.
/// </remarks>
List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation);
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public interface IFoldingStrategyEx : IFoldingStrategy
{
List<string> GetFoldingErrors();
}
}

View File

@@ -0,0 +1,47 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
namespace ICSharpCode.TextEditor.Document
{
/// <summary>
/// A simple folding strategy which calculates the folding level
/// using the indent level of the line.
/// </summary>
public class IndentFoldingStrategy : IFoldingStrategy
{
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
List<FoldMarker> l = new List<FoldMarker>();
Stack<int> offsetStack = new Stack<int>();
Stack<string> textStack = new Stack<string>();
//int level = 0;
//foreach (LineSegment segment in document.LineSegmentCollection) {
//
//}
return l;
}
int GetLevel(IDocument document, int offset)
{
int level = 0;
int spaces = 0;
for (int i = offset; i < document.TextLength; ++i) {
char c = document.GetCharAt(i);
if (c == '\t' || (c == ' ' && ++spaces == 4)) {
spaces = 0;
++level;
} else {
break;
}
}
return level;
}
}
}

View File

@@ -0,0 +1,129 @@
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This file is part of CodingEditor.
// Note: This project is derived from Peter Project
// (hosted on sourceforge and codeplex)
//
// Copyright (c) 2008-2009, CE Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
public class JSONFoldingStrategy : IFoldingStrategy
{
#region Methods
/// <summary>
/// Generates the foldings for our document.
/// </summary>
/// <param name="document">The current document.</param>
/// <param name="fileName">The filename of the document.</param>
/// <param name="parseInformation">Extra parse information, not used in this sample.</param>
/// <returns>A list of FoldMarkers.</returns>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
var list = new List<FoldMarker>();
var startLines = new Stack<int>();
// Create foldmarkers for the whole document, enumerate through every line.
for (int i = 0; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = seg.Length + seg.Offset;
char c;
for (
offs = seg.Offset;
offs < end;
offs++)
{
c = document.GetCharAt(offs);
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
{
if (c == '{')
{
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '{', '}');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "{...}", false));
}
}
if (c == '[')
{
int offsetOfClosingBracket = SearchBracketForward(document, i, offs, '[', ']');
if (offsetOfClosingBracket > 0)
{
int length = offsetOfClosingBracket - offs + 1;
list.Add(new FoldMarker(document, offs, length, "[...]", false));
}
}
}
}
}
return list;
}
private int SearchBracketForward(IDocument document, int currLine, int currOffset, char openBracket, char closingBracket)
{
// Create foldmarkers for the whole document, enumerate through every line.
int brackets = 1, spaceCount = 0;
for (int i = currLine; i < document.TotalNumberOfLines; i++)
{
var seg = document.GetLineSegment(i);
int offs, end = seg.Length + seg.Offset;
char c;
for (
offs = i == currLine ? currOffset + 1 : seg.Offset;
offs < end;
offs++)
{
c = document.GetCharAt(offs);
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') spaceCount++;
if (seg.Words.Any(w => w.IsDelimiter && w.Offset == offs - seg.Offset && w.Word == c.ToString()))
{
if (c == openBracket)
{
++brackets;
}
else if (c == closingBracket)
{
--brackets;
if (brackets == 0)
{
if (offs - spaceCount - 1 == currOffset)
{
return -1;
}
return offs;
}
}
}
}
}
return -1;
}
#endregion Methods
}
}

View File

@@ -0,0 +1,310 @@
// Copied from http://codingeditor.googlecode.com/svn/trunk/libs/ICSharpCode.TextEditor/Project/Src/Document/FoldingStrategy/
#region Header
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Matthew Ward" email="mrward@users.sourceforge.net"/>
// <version>$Revision: 1971 $</version>
// </file>
#endregion Header
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using ICSharpCode.TextEditor.Document;
namespace ICSharpCode.TextEditor.Src.Document.FoldingStrategy
{
/// <summary>
/// Holds information about the start of a fold in an xml string.
/// </summary>
class XmlFoldStart
{
#region Fields
readonly int _col;
string _foldText = string.Empty;
readonly int _line;
readonly string _name = string.Empty;
readonly string _prefix = string.Empty;
#endregion Fields
#region Constructors
public XmlFoldStart(string prefix, string name, int line, int col)
{
_line = line;
_col = col;
_prefix = prefix;
_name = name;
}
#endregion Constructors
#region Properties
/// <summary>
/// The column where the fold should start. Columns start from 0.
/// </summary>
public int Column
{
get
{
return _col;
}
}
/// <summary>
/// The text to be displayed when the item is folded.
/// </summary>
public string FoldText
{
get
{
return _foldText;
}
set
{
_foldText = value;
}
}
/// <summary>
/// The line where the fold should start. Lines start from 0.
/// </summary>
public int Line
{
get
{
return _line;
}
}
/// <summary>
/// The name of the xml item with its prefix if it has one.
/// </summary>
public string Name
{
get
{
return _prefix.Length > 0 ? string.Concat(_prefix, ":", _name) : _name;
}
}
#endregion Properties
}
/// <summary>
/// Determines folds for an xml string in the editor.
/// </summary>
public class XmlFoldingStrategy : IFoldingStrategyEx
{
#region Fields
/// <summary>
/// Flag indicating whether attributes should be displayed on folded elements.
/// </summary>
public bool ShowAttributesWhenFolded = false;
private List<string> _foldingErrors = new List<string>();
#endregion Fields
#region Methods
public List<string> GetFoldingErrors()
{
return _foldingErrors;
}
/// <summary>
/// Adds folds to the text editor around each start-end element pair.
/// </summary>
/// <remarks>
/// <para>If the xml is not well formed then no folds are created.</para>
/// <para>Note that the xml text reader lines and positions start
/// from 1 and the SharpDevelop text editor line information starts from 0.</para>
/// </remarks>
public List<FoldMarker> GenerateFoldMarkers(IDocument document, string fileName, object parseInformation)
{
_foldingErrors = new List<string>();
//showAttributesWhenFolded = XmlEditorAddInOptions.ShowAttributesWhenFolded;
var foldMarkers = new List<FoldMarker>();
var stack = new Stack();
try
{
string xml = document.TextContent;
var reader = new XmlTextReader(new StringReader(xml));
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (!reader.IsEmptyElement)
{
XmlFoldStart newFoldStart = CreateElementFoldStart(reader);
stack.Push(newFoldStart);
}
break;
case XmlNodeType.EndElement:
var foldStart = (XmlFoldStart)stack.Pop();
CreateElementFold(document, foldMarkers, reader, foldStart);
break;
case XmlNodeType.Comment:
CreateCommentFold(document, foldMarkers, reader);
break;
}
}
}
catch (Exception ex)
{
_foldingErrors.Add(ex.Message);
// If the xml is not well formed keep the foldings that already exist in the document.
return new List<FoldMarker>(document.FoldingManager.FoldMarker);
}
return foldMarkers;
}
/// <summary>
/// Xml encode the attribute string since the string returned from
/// the XmlTextReader is the plain unencoded string and .NET
/// does not provide us with an xml encode method.
/// </summary>
static string XmlEncodeAttributeValue(string attributeValue, char quoteChar)
{
var encodedValue = new StringBuilder(attributeValue);
encodedValue.Replace("&", "&amp;");
encodedValue.Replace("<", "&lt;");
encodedValue.Replace(">", "&gt;");
if (quoteChar == '"')
{
encodedValue.Replace("\"", "&quot;");
}
else
{
encodedValue.Replace("'", "&apos;");
}
return encodedValue.ToString();
}
/// <summary>
/// Creates a comment fold if the comment spans more than one line.
/// </summary>
/// <remarks>The text displayed when the comment is folded is the first
/// line of the comment.</remarks>
void CreateCommentFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader)
{
if (reader.Value != null)
{
string comment = reader.Value.Replace("\r\n", "\n");
string[] lines = comment.Split('\n');
if (lines.Length > 1)
{
// Take off 5 chars to get the actual comment start (takes
// into account the <!-- chars.
int startCol = reader.LinePosition - 5;
int startLine = reader.LineNumber - 1;
// Add 3 to the end col value to take into account the '-->'
int endCol = lines[lines.Length - 1].Length + startCol + 3;
int endLine = startLine + lines.Length - 1;
string foldText = string.Concat("<!--", lines[0], "-->");
var foldMarker = new FoldMarker(document, startLine, startCol, endLine, endCol, FoldType.TypeBody, foldText);
foldMarkers.Add(foldMarker);
}
}
}
/// <summary>
/// Create an element fold if the start and end tag are on
/// different lines.
/// </summary>
void CreateElementFold(IDocument document, List<FoldMarker> foldMarkers, XmlTextReader reader, XmlFoldStart foldStart)
{
int endLine = reader.LineNumber - 1;
if (endLine > foldStart.Line)
{
int endCol = reader.LinePosition + foldStart.Name.Length;
var foldMarker = new FoldMarker(document, foldStart.Line, foldStart.Column, endLine, endCol, FoldType.TypeBody, foldStart.FoldText);
foldMarkers.Add(foldMarker);
}
}
/// <summary>
/// Creates an XmlFoldStart for the start tag of an element.
/// </summary>
XmlFoldStart CreateElementFoldStart(XmlTextReader reader)
{
// Take off 2 from the line position returned
// from the xml since it points to the start
// of the element name and not the beginning
// tag.
var newFoldStart = new XmlFoldStart(reader.Prefix, reader.LocalName, reader.LineNumber - 1, reader.LinePosition - 2);
if (ShowAttributesWhenFolded && reader.HasAttributes)
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, " ", GetAttributeFoldText(reader), ">");
}
else
{
newFoldStart.FoldText = string.Concat("<", newFoldStart.Name, ">");
}
return newFoldStart;
}
/// <summary>
/// Gets the element's attributes as a string on one line that will
/// be displayed when the element is folded.
/// </summary>
/// <remarks>
/// Currently this puts all attributes from an element on the same
/// line of the start tag. It does not cater for elements where attributes
/// are not on the same line as the start tag.
/// </remarks>
string GetAttributeFoldText(XmlTextReader reader)
{
var text = new StringBuilder();
for (int i = 0; i < reader.AttributeCount; ++i)
{
reader.MoveToAttribute(i);
text.Append(reader.Name);
text.Append("=");
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
text.Append(XmlEncodeAttributeValue(reader.Value, reader.QuoteChar));
text.Append(reader.QuoteChar.ToString(CultureInfo.InvariantCulture));
// Append a space if this is not the
// last attribute.
if (i < reader.AttributeCount - 1)
{
text.Append(" ");
}
}
return text.ToString();
}
#endregion Methods
}
}