Слияние кода завершено, страница обновится автоматически
using System;
using System.Collections.Generic;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Highlighting;
using System.IO;
using System.Xml;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Folding;
using System.Windows.Input;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.Search;
using System.Windows;
using System.Text;
using System.Windows.Data;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Document;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{
/// <summary>
/// 创建者:杨震宇
///
/// 说明:Markdown编辑器类。
/// </summary>
public class MarkDownEditorBase : TextEditor
{
/// <summary>
/// 自定义高亮定义,完整定义。
/// 但一千多行的帮助文档就明显延迟。
/// </summary>
private static IHighlightingDefinition advanceMarkdownHighlighting;
/// <summary>
/// 仅仅只提供基本的六级标题的高亮显示。
/// </summary>
private static IHighlightingDefinition onlyHeadersMarkdownHightlighting;
/// <summary>
/// [静态构造方法]载入高亮定义文件。
/// </summary>
static MarkDownEditorBase()
{
// 载入代码高亮的定义文件
//using (Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.CustomHighlighting.xshd"))
Stream s = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.Markdown.SyntaxHightlightRulesets.xshd");
if (s != null)
{
using (XmlReader reader = new XmlTextReader(s))
{
advanceMarkdownHighlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
Stream s2 = typeof(MainWindow).Assembly.GetManifestResourceStream("LunarSF.SHomeWorkshop.LunarMarkdownEditor.MarkdownHeader.SyntaxHightlightRulesets.xshd");
if (s2 != null)
{
using (XmlReader reader = new XmlTextReader(s2))
{
onlyHeadersMarkdownHightlighting = ICSharpCode.AvalonEdit.Highlighting.Xshd.
HighlightingLoader.Load(reader, HighlightingManager.Instance);
}
}
}
/// <summary>
/// 高亮显示方案。
/// </summary>
public enum HighlightingType
{
None, //禁用高亮
OnlyHeaders, //只对六级标题高亮显示
Advance, //全部显示
}
private MarkdownEditor masterEditor;
/// <summary>
/// [只读]父封装。
/// </summary>
public MarkdownEditor MasterEditor { get { return masterEditor; } }
/// <summary>
/// [构造方法]编辑器内核。
/// </summary>
public MarkDownEditorBase(MarkdownEditor masterEditor)
{
this.masterEditor = masterEditor;
//这个不能写死,因为会导致“字体选择”功能无效化。
//this.FontFamily = new FontFamily("SimSun");
this.SnapsToDevicePixels = true;//避免折叠线模糊
this.Background = Brushes.WhiteSmoke;
this.WordWrap = true;
this.ShowLineNumbers = true;
this.Options.ShowColumnRuler = true;
this.Options.ShowSpaces = false;
//AvalonEdit本来使用间隔号代替空格,但间隔号是全角字符,要占据两个半角的宽度;
//我把它改为使用加重号,这样就只需要占据一个半角宽度了。
this.Options.ShowTabs = false;
this.Options.AutoAlignTabs = false;//将Tab显示为“--+”,并禁止Tab自动伸缩尺寸。
//this.Options.WordWrapIndentation = Globals.MainWindow.mainTabControl.FontSize * 2;
//直接演示Markdown文档本身时,这个效果在同段换行时非常糟糕,
//容易出现对不齐的现象——尤其使用的字体不是宋体时更易出问题。
//TODO: 暂时去除它。
this.Options.ShowBoxForControlCharacters = true;
this.Options.ShowEndOfLine = false;
this.Options.AllowScrollBelowDocument = true;
this.Options.HighlightCurrentLine = true;
this.PreviewTextInput += MarkdownEditorBase_PreviewTextInput;
this.TextChanged += MarkDownEditorBase_TextChanged;
this.TextArea.SelectionChanged += TextArea_SelectionChanged;
#region 代码高亮处理
RefreshHighlightingSetting();
#endregion
this.TextArea.IndentationStrategy = new ICSharpCode.AvalonEdit.Indentation.DefaultIndentationStrategy();
#region 代码折叠处理Folding
foldingStrategy = new CustomFoldingStrategy(this);
foldingManager = CustomFoldingManager.Install(this.TextArea);
foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
foldingUpdateTimer.Interval = TimeSpan.FromSeconds(0.05);//50毫秒。
foldingUpdateTimer.Tick += foldingUpdateTimer_Tick;
foldingUpdateTimer.Start();
#endregion
#region 自动完成
this.TextArea.TextEntered += TextArea_TextEntered;
this.TextArea.TextEntering += TextArea_TextEntering;
#endregion
#region 搜索框
if (searchPanel == null)
{
//searchPanel = new SearchPanel() { FontSize = Globals.MainWindow.FontSize, };
//searchPanel.Attach(this.TextArea);
searchPanel = SearchPanel.Install(this.TextArea);
}
#endregion
this.TextChanged += MarkdownEditorBase_TextChanged;
//this.Document.UndoStack.MarkAsOriginalFile();
//加上这句后,无法撤销已保存的文件。
this.GotFocus += MarkDownEditorBase_GotFocus;
this.LostFocus += MarkDownEditorBase_LostFocus;
this.PreviewKeyDown += MarkDownEditorBase_PreviewKeyDown;
//处理拖动
this.AllowDrop = true;
//处理字号缩放
this.PreviewMouseWheel += MarkDownEditorBase_PreviewMouseWheel;
this.PreviewMouseLeftButtonUp += MarkDownEditorBase_PreviewMouseLeftButtonUp;
this.PreviewMouseDown += MarkDownEditorBase_PreviewMouseDown;
//添加快捷工具条
popupToolBar = new LunarMarkdownEditor.EditorPopupToolBar(this) { IsOpen = false, };
this.MouseMove += MarkDownEditorBase_MouseMove;
}
private void MarkDownEditorBase_MouseMove(object sender, MouseEventArgs e)
{
if (this.popupToolBar.IsOpen == false || this.popupToolBar.Entered == false) return;
var topLeft = this.popupToolBar.VPanel.TranslatePoint(new Point(0, 0), this);
var bottomRight = new Point(topLeft.X + this.popupToolBar.MainBorder.ActualWidth, topLeft.Y + this.popupToolBar.MainBorder.ActualHeight);
//var topRight = new Point(bottomRight.X, topLeft.Y);
//var bottomLeft = new Point(topLeft.X, bottomRight.Y);
var rect1 = new Rect(topLeft, bottomRight);
var rect2 = new Rect(new Point(topLeft.X - 25, topLeft.Y - 25), new Point(bottomRight.X + 25, bottomRight.Y + 25));
var rect3 = new Rect(new Point(topLeft.X - 50, topLeft.Y - 50), new Point(bottomRight.X + 50, bottomRight.Y + 50));
var rect4 = new Rect(new Point(topLeft.X - 75, topLeft.Y - 75), new Point(bottomRight.X + 75, bottomRight.Y + 75));
var mpt = e.GetPosition(this);
if (rect1.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 1;
}
else if (rect2.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.75;
}
else if (rect3.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.50;
}
else if (rect4.Contains(mpt))
{
this.popupToolBar.MainBorder.Opacity = 0.25;
}
else
{
this.popupToolBar.IsOpen = false;
this.popupToolBar.MainBorder.Opacity = 1;
this.popupToolBar.Entered = false;
}
//this.popupToolBar.TbInfo.Text = topLeft.ToString() + "|" + bottomRight.ToString() + "|" + mpt.ToString();
}
private void MarkDownEditorBase_LostFocus(object sender, RoutedEventArgs e)
{
this.PopupToolBar.IsOpen = false;
}
private void MarkDownEditorBase_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
this.PopupToolBar.IsOpen = false;
}
private void MarkDownEditorBase_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (masterEditor == null || masterEditor.IsCompareAreaEditor) return;
if (PopupToolBar.IsOpen == true)
{
PopupToolBar.IsOpen = false;
}
if (this.SelectionLength <= 0 || Globals.MainWindow.IsPopupContextToolbarEnabled == false) return;
PopupToolBar.IsOpen = true;
}
private EditorPopupToolBar popupToolBar;
/// <summary>
/// 上下文快捷工具栏。
/// </summary>
internal EditorPopupToolBar PopupToolBar
{
get
{
return popupToolBar;
}
}
private void TextArea_SelectionChanged(object sender, EventArgs e)
{
if (Globals.MainWindow != null)
{
Globals.MainWindow.RefreshTextInfos();
}
}
private void MarkDownEditorBase_TextChanged(object sender, EventArgs e)
{
if (Globals.MainWindow != null)
{
Globals.MainWindow.RefreshTextInfos();
}
}
private void RefreshHighlightingSetting()
{
//this.SyntaxHighlighting.MainRuleSet.Rules.Clear();
//上面这行会导致错误
switch (highlightingSetting)
{
case HighlightingType.None:
{
this.SyntaxHighlighting = null;
break;
}
case HighlightingType.OnlyHeaders:
{
HighlightingManager.Instance.RegisterHighlighting("Markdown Header Highlighting", new string[] { ".md" }, onlyHeadersMarkdownHightlighting);
this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
break;
}
default:
{
HighlightingManager.Instance.RegisterHighlighting("Markdown Highlighting", new string[] { ".md" }, advanceMarkdownHighlighting);
this.SyntaxHighlighting = HighlightingManager.Instance.GetDefinitionByExtension(".md");
break;
}
}
//TODO: 载入用户自定义高亮方案。
//this.SyntaxHighlighting.MainRuleSet.Rules.Add(new HighlightingRule() { })
}
/// <summary>
/// 按住 Ctrl 键 + 鼠标滚轮 缩放字号。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MarkDownEditorBase_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if ((Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) <= 0 && (Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) <= 0) return;
if (e.Delta > 0)
{
Globals.MainWindow.FontSizeUp();
}
else if (e.Delta < 0)
{
Globals.MainWindow.FontSizeDown();
}
e.Handled = true;
}
/// <summary>
/// 是否支持试题编辑。需要开启“IsAutoCompletionEnabled”。
/// </summary>
public bool IsExamEnabled { get; set; }
private bool isAutoCompletionEnabled = false;
/// <summary>
/// 是否支持自动完成。
/// </summary>
public bool IsAutoCompletionEnabled
{
get
{
return isAutoCompletionEnabled;
}
set
{
isAutoCompletionEnabled = value;
if (value == false)
{
if (this.completionWindow != null &&
this.completionWindow.Visibility == Visibility.Visible)
{
this.completionWindow.Close();
this.completionWindow = null;
}
}
}
}
/// <summary>
/// 是否显示空格。
/// </summary>
public bool IsShowSpaces
{
get { return this.Options.ShowSpaces; }
set { this.Options.ShowSpaces = value; }
}
/// <summary>
/// 是否显示段落标记。
/// </summary>
public bool IsShowEndOfLine
{
get { return this.Options.ShowEndOfLine; }
set { this.Options.ShowEndOfLine = value; }
}
/// <summary>
/// 是否显示Tab符。
/// </summary>
public bool IsShowTabs
{
get { return this.Options.ShowTabs; }
set { this.Options.ShowTabs = value; }
}
/// <summary>
/// 是否支持英译中词典的自动提示功能。需要开启“IsAutoCompletionEnabled”。
/// </summary>
public bool IsEnToChineseDictEnabled { get; set; } = false;
private MarkDownEditorBase.HighlightingType highlightingSetting = MarkDownEditorBase.HighlightingType.Advance;
/// <summary>
/// 采用何种高亮显示方案。
/// </summary>
public MarkDownEditorBase.HighlightingType HighlightingSetting
{
get { return highlightingSetting; }
set
{
highlightingSetting = value;
RefreshHighlightingSetting();
}
}
/// <summary>
/// [覆盖方法]只有用这个办法才能使Alt+Up/Down生效。
/// </summary>
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyboardDevice.Modifiers == ModifierKeys.Alt
&& (e.KeyboardDevice.IsKeyDown(Key.RightAlt) || e.KeyboardDevice.IsKeyDown(Key.LeftAlt)))
{
if ((e.Key == Key.Up || e.SystemKey == Key.Up))
{
SwapWithPreviewLine();
e.Handled = true;
}
else if ((e.Key == Key.Down || e.SystemKey == Key.Down))
{
SwapWithNextLine();
e.Handled = true;
}
}
base.OnKeyDown(e);
}
/// <summary>
/// 处理常用快捷键。
/// </summary>
void MarkDownEditorBase_PreviewKeyDown(object sender, KeyEventArgs e)
{
this.PopupToolBar.IsOpen = false;
bool isCtrl = false ||
(Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) > 0;
bool isShift = false ||
(Keyboard.GetKeyStates(Key.RightShift) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftShift) & KeyStates.Down) > 0;
bool isAlt = false ||
(Keyboard.GetKeyStates(Key.RightAlt) & KeyStates.Down) > 0 ||
(Keyboard.GetKeyStates(Key.LeftAlt) & KeyStates.Down) > 0;
try
{
switch (e.Key)
{
case Key.OemSemicolon://分号
{
int startOffset = this.SelectionStart;
int endOffset = this.SelectionStart + this.SelectionLength;
var startLine = this.Document.GetLineByOffset(startOffset);
var endLine = this.Document.GetLineByOffset(endOffset);
if (isCtrl)
{
if (isShift)
{
//取消这些行的备注(如果行首存在分号,取消之)
for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
{
var line = this.Document.GetLineByNumber(i);
var text = this.Document.GetText(line.Offset, 1);
if (text == ";" || text == ";")
{
this.Document.Remove(line.Offset, 1);
}
}
}
else
{
//将这些行变成备注(在头部添加分号)
for (int i = endLine.LineNumber; i >= startLine.LineNumber; i--)
{
var line = this.Document.GetLineByNumber(i);
this.Document.Insert(line.Offset, ";");
}
}
e.Handled = true;
}
break;
}
case Key.OemOpenBrackets://左花括号
{
if (isCtrl && !isAlt)
{
e.Handled = true;
SwitchTaskListItemOrDateTimeLineState(false, isShift);
}
break;
}
case Key.OemCloseBrackets://右花括号
{
if (isCtrl && !isAlt)
{
e.Handled = true;
SwitchTaskListItemOrDateTimeLineState(true, isShift);
}
break;
}
//另有用处:case Key.OemMinus: //Markdown语法:前后各一个“*”包围的文本加粗。例如:这是一段*斜体*的文本。
case Key.I://为什么在这里处理?因为avalonEdit已经绑死Alt+Up,使用Alt+Up不起任何作用。
{
if (isCtrl)//移动到上一行的上方。
{
if (isShift)
{
if (!isAlt) SwapWithPreviewLine();
}
else
{
//斜体
SwitchItalic();//Markdown语法:前后各一个“_”包围的文本加粗。例如:这是一段_斜体_的文本。
}
e.Handled = true;
}
break;
}
case Key.K://为什么用这个?因为avalonEdit已经绑死Alt+Up,使用Alt+Up不起任何作用。
{
if (isCtrl && isShift && !isAlt)//移动到下一行的下方。
{
SwapWithNextLine();
e.Handled = true;
}
break;
}
//另有用处:case Key.Oem8: //Markdown语法:前后两组“**”包围的文本加粗。例如:这是一段**加粗**的文本。
case Key.B: //Markdown语法:前后两组“__”包围的文本加粗。例如:这是一段__加粗__的文本。
{
if (isCtrl)
{
//加粗
SwitchBold();
}
break;
}
case Key.Back://BackSpace,退格键
{
if (isShift && !isCtrl && !isAlt)
{
//删除到行首
var textArea = this.TextArea;
if (textArea != null && textArea.IsKeyboardFocused)
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
this.Select(fstSelectedLine.Offset, textArea.Selection.Length + (this.SelectionStart - fstSelectedLine.Offset));
textArea.PerformTextInput("");
if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
{
FormatTextTable();
}
e.Handled = true;
}
}
break;
}
case Key.Enter://回车键
{
if (isCtrl)
{
if (!isShift)
{
//在当前行下方插入一个新行,并移动光标。
AppendANewLine();
JumpToNextCell();
e.Handled = true;
}
}
else
{
if (isShift)
{
//在当前行上方插入一个新行,并移动光标。
InsertANewLine();
JumpToNextCell();
e.Handled = true;
}
else
{
//AvalonEdit有个默认行为:若一行文本以空格开头,无论在第一个非空格字符前按下回车,还是在这些空格前按下回车,
//新行开头都不会保留空格!!!
if (IsCompleting == false)
{
#region 处理树型列表
var curLine = CurrentDocumentLine();
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
string header, tail; int level;
if (curLine != null && CustomMarkdownSupport.IsImageLinkLine(curLineText) == false &&
IsTreeListLine(curLineText, out header, out tail, out level))
{
e.Handled = true;
InsertNewLineAndFormat(isShift);
break;
}
#endregion
var textArea = this.TextArea;
if (textArea != null && textArea.IsKeyboardFocused)
{
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
//回车换行时保留的前导字串:
//^[\t ☆○◎◇□△§※★●◆■▲>〉》]{ 1,}[ \t]*
//处理用户自定义的前导字符。
//顺序是:先A类,再B类,最后内置前导字符。基本原则:用户定义优先。
var leaderTextRegs = Globals.MainWindow.LeaderTextRegsA;
if (string.IsNullOrEmpty(leaderTextRegs) == false)
{
leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
}
leaderTextRegs = Globals.MainWindow.LeaderTextRegsB;
if (string.IsNullOrWhiteSpace(leaderTextRegs) == false)
{
leaderTextRegs = ConvertToRegexTexts(leaderTextRegs);
if (ReplaceLeaderText(leaderTextRegs, true))
{
e.Handled = true; return;
}
}
leaderTextRegs = @"^[\t ☆○◎◇□△§※★●◆■▲>〉》]{1,}[ \t]*";
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
leaderTextRegs = @"^[" + Globals.MainWindow.ReplaceLeaderChar + "] {0,}";
//默认情况下,以替换式自动添加的前导字串总是“$+空格串”样式的,这是为了便于连续输入“$+空格串”的新行。
//但是用户可以指定另一字符。
if (ReplaceLeaderText(leaderTextRegs, false))
{
e.Handled = true; return;
}
}
}
}
//这个方法会造成无法自动滚动的问题。
}
break;
}
case Key.Tab://Tab键
{
if (!isCtrl && !isAlt)
{
if (IsCompleting == false)
{
if (JumpToNextExamElement(isShift))
{
e.Handled = true;
break;
}
}
if (isShift)
{
if (JumpOverMarks(ref e, isShift)) return;
//2017年1月11日,调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
#region 处理树型列表的层级调整
if (MoveTreeListTableLevel(e, isShift)) return;
#endregion
#region 将光标移动到上一单元格
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (line == null) break;
var text = this.Document.GetText(line.Offset, line.Length);
if (text.Contains("|") == false && text.Contains("|") == false) break;
if (this.SelectionStart == line.Offset && this.SelectionLength == 0)
{
if (this.SelectionStart - 2 >= 0)
{
this.Select(this.SelectionStart - 2, 0);//2是\r\n的长度。
}
e.Handled = true;
break;
}
//先在text中查找前一个分割线
int preIndex = -1;
for (int i = this.SelectionStart - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\n' || c == '\r')
{
preIndex = i;
break;
}
}
//再找后一个分割线位置
int nextIndex = -1;
for (int i = this.SelectionStart; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex = i;
break;
}
}
if (nextIndex >= 0)
{
if (preIndex >= 0 && nextIndex > preIndex)
{
if (Globals.MainWindow.SelectCellFirst &&
(this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
{
this.Select(preIndex + 1, nextIndex - preIndex - 1);
e.Handled = true;
}
else
{
//如果已选中当前单元格所有文本,继续找上一个单元格并选定。
var preIndex2 = -1;
for (int i = preIndex - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
preIndex2 = i;
break;
}
}
if (preIndex2 < 0 && preIndex > 0)
{
this.Select(preIndex - 1, 0);
e.Handled = true;
}
if (preIndex2 < preIndex)
{
this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
e.Handled = true;
}
}
}
else
{
this.Select(0, 0);//整个文档开头
e.Handled = true;
}
}
else
{
//如果就在整个文档的最后一个位置,此时肯定找不到nextIndex
//如果已选中当前单元格所有文本,继续找上一个单元格并选定。
var preIndex2 = -1;
for (int i = preIndex - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
preIndex2 = i;
break;
}
}
if (preIndex2 < 0 && preIndex > 0)
{
this.Select(preIndex - 1, 0);
e.Handled = true;
}
if (preIndex2 < preIndex)
{
this.Select(preIndex2 + 1, preIndex - preIndex2 - 1);
e.Handled = true;
}
}
#endregion
}
else
{
if (IsCompleting == false)
{
if (JumpToNextCell())
{
e.Handled = true;
}
else
{
//跳过反引号,用在:按Ctrl+`输入一对反引号并将插入点置于两个反引号之间,输入一些文本后直接跳过右反引号继续输入其它文本。
//这比按两个组合方向按键跳过去或者按方向键跳过去更方便。
//*号对与_对情形类似。
if (JumpOverMarks(ref e, isShift)) return;
#region 旧版的跳出反引号、星号对的代码。
//var index = this.SelectionStart + this.SelectionLength;
//if (index < this.Document.TextLength)
//{
// var nextChar = this.Document.Text[index];
// if (nextChar == '`')
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// else if (nextChar == '*')
// {
// if (index < this.Document.TextLength - 1)
// {
// var nextNextChar = this.Document.Text[index + 1];
// if (nextNextChar == '*')
// {
// this.Select(index + 2, 0);
// e.Handled = true;
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else if (nextChar == '_')
// {
// if (index < this.Document.TextLength - 1)
// {
// var nextNextChar = this.Document.Text[index + 1];
// if (nextNextChar == '_')
// {
// this.Select(index + 2, 0);
// e.Handled = true;
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
// else
// {
// this.Select(index + 1, 0);
// e.Handled = true;
// }
// }
//}
#endregion
//2017年1月11日,调整到这里是为了让树型文字表中的括号对啥的也支持使用Tab键来越过去。
#region 处理树型列表的层级调整
if (MoveTreeListTableLevel(e, isShift)) return;
#endregion
}
}
}
}
break;
}
case Key.Z:
{
if (isAlt)
{
if (!isShift && isCtrl)
{
this.FoldSelectedBlock();
}
}
else
{
if (!isShift && isCtrl)
{
if (this.CanUndo) this.Undo();
e.Handled = true;
}
}
break;
}
case Key.Y:
{
if (!isAlt && !isShift && isCtrl)
{
if (this.CanRedo) this.Redo();
e.Handled = true;
}
break;
}
case Key.OemPeriod://句点键
{
if (isShift && isCtrl && !isAlt)
{
var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.BeginChange();
for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
{
var line = this.Document.GetLineByNumber(lineNumber);
var lineText = this.Document.GetText(line);
lineText = "> " + lineText;
this.Document.Replace(line.Offset, line.Length, lineText);
}
this.EndChange();
}
break;
}
case Key.OemComma://逗号健
{
if (isShift && isCtrl && !isAlt)
{
var fstSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var lastSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
Regex reg = new Regex(@"^[ ]{0,3}[>〉》][ ]{0,}");
this.BeginChange();
for (int lineNumber = fstSelLine.LineNumber; lineNumber <= lastSelLine.LineNumber; lineNumber++)
{
var line = this.Document.GetLineByNumber(lineNumber);
var lineText = this.Document.GetText(line);
var match = reg.Match(lineText);
if (match.Success)
{
lineText = lineText.Substring(match.Length);
this.Document.Replace(line.Offset, line.Length, lineText);
}
}
this.EndChange();
}
break;
}
}
}
catch (Exception ex)
{
LMessageBox.Show("发生意外错误,无法执行操作。建议保存文件并重启程序。\r\n" +
ex.Message + "\r\n" + ex.StackTrace, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
/// <summary>
/// 保存的正则表达式是多行的,每行一个规则,这个方法是用来将这些规则合并为一个规则字串的。
/// 合并后的样式类似:(规则一)|(规则二)|(规则三)
/// </summary>
/// <param name="leaderTextRegs"></param>
/// <returns></returns>
private static string ConvertToRegexTexts(string leaderTextRegs)
{
var spans = leaderTextRegs.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
if (spans.Length > 0)
{
StringBuilder sb = new StringBuilder();
sb.Append("(");
foreach (var span in spans)
{
sb.Append(span);
sb.Append(")|(");
}
leaderTextRegs = sb.ToString();
if (leaderTextRegs.EndsWith("|("))
leaderTextRegs = leaderTextRegs.Substring(0, leaderTextRegs.Length - 2);
}
return leaderTextRegs;
}
/// <summary>
/// 按Enter时在新行头部替换添加“前导字符串”。
/// </summary>
/// <param name="regexText">当前行头部前导字符的正则表达式规则。</param>
/// <param name="replaceMode">是否替换为“$+空格串”的样式。为false时原文输出。</param>
/// <returns></returns>
private bool ReplaceLeaderText(string regexText, bool replaceMode)
{
if (string.IsNullOrEmpty(regexText)) return false;
var textArea = this.TextArea;
var fstSelectedLine = this.Document.GetLineByOffset(this.SelectionStart);
var lineText = textArea.Document.GetText(fstSelectedLine.Offset, fstSelectedLine.Length);
try
{
var regex = new Regex(regexText);
var match = regex.Match(lineText);
if (match.Success == false) return false;
if (match.Value == lineText)//如果只添加了标记,却没有其它文本内容,直接取消标记。
{
textArea.TextEditor.BeginChange();
textArea.TextEditor.Select(fstSelectedLine.Offset, this.SelectionStart - fstSelectedLine.Offset + this.SelectionLength);
textArea.TextEditor.SelectedText = "\r\n";
textArea.TextEditor.Select(fstSelectedLine.Offset + 2, 0);
textArea.TextEditor.EndChange();
textArea.BringIntoView();
}
else
{
if (replaceMode)
{
//将匹配的文本转换为半角字符组成的等字符宽度字串
//一个全角字符被视为两个半角空格的字符宽度
//这里的“字符宽度”只考虑全、半角,不考虑字体带来的宽度变化
string spaceText;
var length = GetTextWidth(match.Value);
if (length > 1)
{
spaceText = Globals.MainWindow.ReplaceLeaderChar + new string(' ', length - 1);
}
else { spaceText = Globals.MainWindow.ReplaceLeaderChar; }
textArea.PerformTextInput($"\r\n{spaceText}");
}
else
{
//当前行符合规则的前导字串原文添加到新行头部。
textArea.PerformTextInput($"\r\n{match.Value}");
}
textArea.BringIntoView();
}
return true;
}
catch (Exception ex)
{
LMessageBox.Show("要保留的前导字符串正则表达式有错误,请重写正则表达式。\r\n\r\n" + ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
}
/// <summary>
/// 改变树型文字表行的层级。
/// </summary>
/// <param name="isShift">为真时(按Shift键+Tab键)层级减小一级(向左);
/// 为假时(只按Tab键)层级增加一级(向右)。</param>
private bool MoveTreeListTableLevel(KeyEventArgs e, bool isShift)
{
var curLine = CurrentDocumentLine();
string header, tail; int level;
if (curLine != null && IsTreeListLine(this.Document.GetText(curLine.Offset, curLine.Length), out header, out tail, out level))
{
e.Handled = true;
if (isShift) MoveTreeListLineLevelLeft(curLine, header, tail, level);
else MoveTreeListLineLevelRight(curLine, header, tail, level);
return true;
}
return false;
}
#region 处理树型列表
/// <summary>
/// 是否树型列表文本行。
/// </summary>
/// <param name="line">要判断的文本行。</param>
/// <param name="header">用于返回此文本行头部的格式字符串。</param>
/// <param name="tail">用于返回此文本行尾部的正文字符串。</param>
/// <param name="level">用于返回此文本行的层级,从1开始。当此层级为1时,文本行形如:!XXX。</param>
public static bool IsTreeListLine(string line, out string header, out string tail, out int level)
{
if (string.IsNullOrEmpty(line))
{
header = "";
tail = "";
level = 0;//0表示不是树型列表文本行。
return false;
}
Regex rgx = new Regex(@"^[!!][│ ]{0,}[├└]{0,}(?!([ \t]*\{))");
var match = rgx.Match(line);
if (match.Success)
{
header = match.Value;
tail = line.Substring(match.Length);
level = header.Length;
return true;
}
else
{
header = tail = "";
level = 0;//0表示不是树型列表文本行。
return false;
}
}
private DocumentLine CurrentDocumentLine()
{
return this.Document.GetLineByOffset(this.SelectionStart);
}
private string CurrentLine()
{
int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);
if (preReturnIndex < 0) preReturnIndex = -2;//后面会加2(\r\n's Length)
if (nextReturnIndex < 0) nextReturnIndex = this.Text.Length;
var length = nextReturnIndex - preReturnIndex - 2;
if (length < 0) length = 0;
return this.Text.Substring(preReturnIndex + 2, length);
}
private DocumentLine PreviewDocumentLine()
{
var curLine = CurrentDocumentLine();
if (curLine == null || curLine.Offset <= 0) return null;
return this.Document.GetLineByOffset(curLine.Offset - 2);//-\r\n's Length
}
private string PreviewLine()
{
int preReturnIndex = this.Text.LastIndexOf("\r\n", this.SelectionStart);
if (preReturnIndex <= 0) return "";
int prepreReturnIndex = this.Text.LastIndexOf("\r\n", preReturnIndex - 1);
if (prepreReturnIndex < 0) return this.Text.Substring(0, preReturnIndex);
return this.Text.Substring(prepreReturnIndex + 2, preReturnIndex - prepreReturnIndex - 2);
}
private DocumentLine NextDocumentLine()
{
var curLine = CurrentDocumentLine();
//当插入点在最后一个字符时,TextLength==lastLine.EndOffset
if (curLine == null || curLine.EndOffset >= this.Document.TextLength) return null;
return this.Document.GetLineByOffset(curLine.EndOffset + 2);//+\r\n's Length
}
private string NextLine()
{
int nextReturnIndex = this.Text.IndexOf("\r\n", this.SelectionStart);
if (nextReturnIndex < 0) return "";
int nextnextReturnIndex = this.Text.IndexOf("\r\n", nextReturnIndex + 2);
if (nextnextReturnIndex < 0) return this.Text.Substring(nextReturnIndex + 2);
return this.Text.Substring(nextReturnIndex + 2, nextnextReturnIndex - nextReturnIndex - 2);
}
/// <summary>
/// 根据当前行的上一行决定可以移动的层级,并进行整体格式化。
/// 左移可连续多次移动,不需要考虑上一行的层级,但需要考虑上一行的格式化。
/// 左移前跟随的所有下级均须同步左移。
/// </summary>
private void MoveTreeListLineLevelLeft(DocumentLine curLine, string header, string tail, int level)
{
if (level <= 1) return;//1级标题不能再左移了。
//左移时,所有级别高于当前行的、跟随在当前行之后的都顺序左移一级。
//基本思路:本行左移时,先自动生成本行的基准头部。
// 由于本行左移时,可能对前面所有行产生影响,所以要对前面的行重新计算格式字符;
// 本行左移时,可能对后面所有行产生影响,所以还需要对后面的行重新计算格式字符。
// →所以不如寻找头、尾,然后依次重新写格式字符。
//层级标准:只有一个!号的算1级,[!│ ├]这四个字符(其中一个是全角空格)每有一个加1级。
List<TreeListTextLine> previewLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
previewLines.Add(new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
preLine = preLine.PreviousLine;
}
else break;
}
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
for (int i = previewLines.Count - 1; i >= 0; i--)
{
allLines.Add(previewLines[i]);
}
List<TreeListTextLine> moveLines = new List<TreeListTextLine>();
moveLines.Add(new TreeListTextLine()
{
Line = curLine,
//HeaderTextArray = header.ToCharArray(),
NewText = "",
OldText = this.Document.GetText(curLine.Offset, curLine.Length),
TailText = tail,
OldLevel = level,
NewLevel = level - 1, //当前行层级降一级
});
allLines.Add(moveLines[0]);
var nextLine = curLine.NextLine;
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel > level)
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel - 1, //层级高于当前行的也降一级
};
moveLines.Add(nextTreeListTextLine);
allLines.Add(nextTreeListTextLine); //无论层级都应加入此列表
}
else break;
}
else break;
nextLine = nextLine.NextLine;
}
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel, //当碰到一个层级与当前行移位前层级相同的行后,之后所有行都不再降级。
};
allLines.Add(nextTreeListTextLine);
}
else break;
nextLine = nextLine.NextLine;
}
//重新格式化。
FormatAllTreeLines(allLines, true);
}
/// <summary>
/// 根据当前行的上一行决定可以移动的层级,并进行整体格式化。
/// 右移最多移动到上一行的下一级。
/// 右移前跟随的所有下级同步右移一级。
/// </summary>
private void MoveTreeListLineLevelRight(DocumentLine curLine, string header, string tail, int level)
{
if (curLine == null) return;
//右移时最多移动到上一行的下一级。
//如果上一行不是树型列表项,禁止;如果上一行是树型列表项,最多移动到上一行的更高1级。
var preLine = curLine.PreviousLine;
if (preLine == null) return;
var pText = this.Document.GetText(preLine.Offset, preLine.Length);
string pHeader, pTail; int pLevel;
if (IsTreeListLine(pText, out pHeader, out pTail, out pLevel))
{
if (level > pLevel) return;
}
else return;
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
List<TreeListTextLine> previewLines = new List<TreeListTextLine>();
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
previewLines.Add(new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
preLine = preLine.PreviousLine;
}
else break;
}
for (int i = previewLines.Count - 1; i >= 0; i--)
{
allLines.Add(previewLines[i]);
}
List<TreeListTextLine> moveLines = new List<TreeListTextLine>();
moveLines.Add(new TreeListTextLine()
{
Line = curLine,
//HeaderTextArray = header.ToCharArray(),
NewText = "",
OldText = this.Document.GetText(curLine.Offset, curLine.Length),
TailText = tail,
OldLevel = level,
NewLevel = level + 1, //当前行层级降一级
});
allLines.Add(moveLines[0]);
var nextLine = curLine.NextLine;
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel > level)
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel + 1, //层级高于当前行的也升一级
};
moveLines.Add(nextTreeListTextLine);
allLines.Add(nextTreeListTextLine); //无论层级都应加入此列表
}
else break;
}
else break;
nextLine = nextLine.NextLine;
}
while (nextLine != null)
{
string oldNextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(oldNextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = oldNextLineText,
TailText = nextTail,
OldLevel = nextLevel,
NewLevel = nextLevel, //当碰到一个层级与当前行移位前层级相同的行后,之后所有行都不再升级。
};
allLines.Add(nextTreeListTextLine);
}
else break;
nextLine = nextLine.NextLine;
}
//重新格式化。
FormatAllTreeLines(allLines);
}
private void FormatAllTreeLines(List<TreeListTextLine> allLines, bool? selectLeft = false)
{
if (allLines == null || allLines.Count <= 0) return;
foreach (var tLine in allLines)
{
tLine.HeaderTextArray = BuildTreeLineBaseHeader(tLine.NewLevel).ToCharArray();
}
int basePreLevel = 0;
for (int i = 0; i < allLines.Count; i++)
{
var tLine = allLines[i];
if (tLine.OldLevel > basePreLevel + 1)
{
tLine.NewLevel = basePreLevel + 1;
}
basePreLevel = tLine.NewLevel;
}
for (int i = 0; i < allLines.Count; i++)
{
var tLine = allLines[i];
//格式化冒号开头的注释
if (tLine.TailText.StartsWith(":"))
{
tLine.TailText = ":" + tLine.TailText.Substring(1);
}
var newHeaderText = BuildTreeLineBaseHeader(tLine.NewLevel);
tLine.NewText = newHeaderText + tLine.TailText;
tLine.HeaderTextArray = newHeaderText.ToCharArray();
//根据情况刷新竖线。
if (tLine.NewLevel <= 1) continue;
for (int j = i - 1; j >= 0; j--)
{
var aPreLine = allLines[j];
if (aPreLine.NewLevel < tLine.NewLevel) break;
if (aPreLine.NewLevel == tLine.NewLevel)
{
aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '├';
break;
}
if (aPreLine.NewLevel > tLine.NewLevel)
{
aPreLine.HeaderTextArray[tLine.NewLevel - 1] = '│';
}
}
}
StringBuilder sb = new StringBuilder();
foreach (var line in allLines)
{
sb.Append(new string(line.HeaderTextArray) + line.TailText);
sb.Append("\r\n");
}
this.BeginChange();
var oldSelectionStart = this.SelectionStart;
var oldSelectionLength = this.SelectionLength;
var newText = sb.ToString();
if (newText.EndsWith("\r\n"))
{
newText = newText.Substring(0, newText.Length - 2);
}
this.Document.Replace(allLines[0].Line.Offset, allLines[allLines.Count - 1].Line.EndOffset - allLines[0].Line.Offset, newText);
if (selectLeft.HasValue)
{
if (selectLeft.Value)
{
if (oldSelectionStart > 0)
{
this.Select(oldSelectionStart - 1, oldSelectionLength);
}
else { this.Select(0, 0); }
}
else
{
if (oldSelectionLength < this.Document.TextLength - 1)
{
this.Select(oldSelectionStart + 1, oldSelectionLength);
}
}
}
this.EndChange();
}
private string BuildTreeLineBaseHeader(int level)
{
if (level <= 0) return "";
if (level == 1) return "!";
if (level == 2) return "!└";
return $"!{new string(' ', level - 2)}└";
}
private void InsertNewLineAndFormat(bool insertAtPreview = false)
{
var curLine = CurrentDocumentLine();
var preLine = PreviewDocumentLine();
var nextLine = NextDocumentLine();
var curLineText = curLine == null ? "" : this.Document.GetText(curLine.Offset, curLine.Length);
var preLineText = preLine == null ? "" : this.Document.GetText(preLine.Offset, preLine.Length);
var nextLineText = nextLine == null ? "" : this.Document.GetText(nextLine.Offset, nextLine.Length);
//MessageBox.Show($"P行:{preLineText}\r\nC行:{curLineText}\r\nN行:{nextLineText}");
string headerText, tailText;
int level;
if (IsTreeListLine(curLineText, out headerText, out tailText, out level))
{
if (this.SelectionStart == curLine.Offset && this.SelectionLength == 0)
{
this.BeginChange();
this.Document.Insert(curLine.Offset, "\r\n");
this.EndChange();
this.Select(curLine.Offset + 2, 0);
return;
}
this.BeginChange();
int startReplaceOffset = this.SelectionStart;
int tailStartOffset = curLine.Offset + headerText.Length;
int replaceLength = curLine.EndOffset - this.SelectionStart;
if (startReplaceOffset < tailStartOffset)
{
startReplaceOffset = tailStartOffset;
replaceLength = curLine.EndOffset - startReplaceOffset;
}
var curTail = this.Document.GetText(startReplaceOffset, replaceLength);
this.Select(this.SelectionStart, 0);
var mark = "└";
string nextHeaderText, nextTailText; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeaderText, out nextTailText, out nextLevel) && nextLevel == level)
{
mark = "├";
}
if (NextBrotherLineExist(curLine, level))
{
mark = "├";
}
else
{
mark = "└";
}
if (level == 1) mark = "!";
if (curLine.TotalLength == curLine.Length)//没有定界符(\r\n)最后一行
{
this.Document.Insert(curLine.EndOffset, "\r\n" + headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
}
else
{
this.Document.Insert(curLine.EndOffset + 2, headerText.Substring(0, headerText.Length - 1) + $"{mark}{curTail}\r\n");
}
this.Select(curLine.EndOffset + 2 + headerText.Length, 0);
if (replaceLength > 0)//这个替换必须放到后面进行
{
this.Document.Replace(startReplaceOffset, replaceLength, "");
}
FormatPreviewBrotherLines(this.Document.GetLineByNumber(curLine.LineNumber + 1), level);
this.EndChange();
}
}
private bool NextBrotherLineExist(DocumentLine curLine, int level)
{
if (level <= 0) return false;
if (curLine == null || curLine.LineNumber >= this.Document.LineCount) return false;
var nextLine = this.Document.GetLineByNumber(curLine.LineNumber + 1);
while (nextLine != null)
{
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(this.Document.GetText(nextLine.Offset, nextLine.Length), out nextHeader, out nextTail, out nextLevel))
{
if (nextLevel < level) return false;//截断
if (nextLevel == level) return true;
else
{
if (nextLine.LineNumber == this.Document.LineCount) return false;
nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
continue;
}
}
else
{
if (nextLine.LineNumber == this.Document.LineCount) return false;
nextLine = this.Document.GetLineByNumber(nextLine.LineNumber + 1);
continue;
}
}
return false;
}
private void FormatPreviewBrotherLines(DocumentLine curLine, int level)
{
if (level == 1) return;//level==1时,只有惊叹号开头,其它不论。
if (curLine == null || curLine.LineNumber <= 1) return;
DocumentLine preLine = null;
preLine = this.Document.GetLineByNumber(curLine.LineNumber - 1);
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
string headerText, tailText; int preLevel;
if (IsTreeListLine(preLineText, out headerText, out tailText, out preLevel) == false) return;
if (preLevel != level) break;
if (preLineText[level - 1] != '├')
{
this.Document.Replace(preLine.Offset + preLevel - 1, 1, "├");
}
if (preLine.LineNumber <= 1) break;
preLine = this.Document.GetLineByNumber(preLine.LineNumber - 1);
}
}
#endregion
/// <summary>
/// 跳转到下一个输入区域。这些输入区域包括:成对的小括弧之间,成对的大括弧之间,成对的中括弧之间等等。
/// </summary>
/// <param name="e"></param>
private bool JumpOverMarks(ref KeyEventArgs e, bool isShift)
{
if (SelectionLength != 0) return false;
//只有当插入点正好在某些特殊符号(及符号组合)前面时才起作用,用来跳过这些个特殊符号组合。
try
{
var regexText = "(</?[a-zA-Z]{1,}( [^><]*)?>)|(\\*\\*)|(\\[\\=)|(\\=\\])|(__)|([_\\*“”‘’'\"`<>《》〈〉\\(\\)\\[\\]\\{\\}()〔〕〈〉「」『』〖〗【】[]{}"'°])";
Regex regex = GetRegEx(regexText, isShift);
int start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? SelectionStart : SelectionStart + SelectionLength;
Match match = regex.Match(Text, start);
if (!match.Success) // start again from beginning or end
{
if (regex.Options.HasFlag(RegexOptions.RightToLeft))
match = regex.Match(Text, Text.Length);
else
match = regex.Match(Text, 0);
}
if (match.Success)
{
if (isShift)
{
if (match.Index + match.Length != SelectionStart) return false;
var destSel = match.Index;
if (destSel >= 0)
{
Select(destSel, 0);
TextLocation loc = Document.GetLocation(match.Index);
ScrollTo(loc.Line, loc.Column);
e.Handled = true;
return true;
}
}
else
{
if (match.Index != SelectionStart + SelectionLength) return false;
var destSel = match.Index + match.Length;
if (destSel <= Document.TextLength)
{
Select(destSel, 0);
TextLocation loc = Document.GetLocation(match.Index);
ScrollTo(loc.Line, loc.Column);
e.Handled = true;
return true;
}
}
return false;
}
return false;
}
catch (Exception ex)
{
LMessageBox.Show(ex.Message, Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return false;
}
}
/// <summary>
/// 自动完成窗口目前是否可见,是否正在进行提示。
/// </summary>
private bool IsCompleting
{
get
{
if (this.CompletionWindow != null && this.CompletionWindow.Visibility == Visibility.Visible)
return true;
else return false;
}
}
/// <summary>
/// 切换目标文本的任务列表状态标记。
/// </summary>
/// <param name="backOrder">决定正序还是逆序。</param>
/// <param name="isShift">决定是作用于本行还是作用于本行所在的任务列表项。false时仅作用于本行文本。</param>
public void SwitchTaskListItemOrDateTimeLineState(bool backOrder, bool isShift)
{
int startOffset = this.SelectionStart;
int selectionLength = this.SelectionLength;
int endOffset = this.SelectionStart + this.SelectionLength;
var startLine = this.Document.GetLineByOffset(startOffset);
var endLine = this.Document.GetLineByOffset(endOffset);
var startLineText = this.Document.GetText(startLine.Offset, startLine.Length);
if (startLineText.StartsWith(" ") || startLineText.StartsWith("\t"))
{
LMessageBox.Show("插入点所在的这行文本属于一个代码块,不能执行此命令!", Globals.AppName,
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (isShift)
{
if (CustomMarkdownSupport.IsTaskLine(startLineText) == false && startLine.LineNumber > 1)
{
//如果当前行不是任务列表,则向文件头部倒找。
DocumentLine destLine = null;
destLine = this.Document.GetLineByNumber(startLine.LineNumber - 1);
while (destLine != null)
{
var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
if (CustomMarkdownSupport.IsTaskLine(destLineText))
{
break;
}
destLine = this.Document.GetLineByNumber(destLine.LineNumber - 1);
}
if (destLine != null)
{
this.Select(destLine.Offset, destLine.Length);
SwitchTaskListItemOrDateTimeLineState(backOrder, false);
this.Select(startLine.Offset, selectionLength);
return;
}
}//如果本行就是任务列表,则直接改动本行即可,继续。
}
CustomMarkdownSupport.TaskListItemState state = GetTaskListItemState(startLineText);
//只改第一行,避免无谓的错误,也可降低开发难度。
//CustomMarkdownSupport.TaskListItemState destState;
string newHeader;
string oldHeader;
if (backOrder)
{
switch (state)
{
case CustomMarkdownSupport.TaskListItemState.Finished:
{
oldHeader = "[+]";
newHeader = "[%] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Precessing:
{
oldHeader = "[%]";
//destState = CustomMarkdownSupport.TaskListItemState.Finished;
newHeader = "[-] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.UnStart:
{
oldHeader = "[-]";
//destState = CustomMarkdownSupport.TaskListItemState.Precessing;
newHeader = "[#] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Aborted:
{
oldHeader = "[#]";
//destState = CustomMarkdownSupport.NotTaskListItem
newHeader = "[+] ";
break;
}
default:
{
oldHeader = "";
//destState = CustomMarkdownSupport.TaskListItemState.UnStart;
newHeader = "[#] ";
break;
}
}
}
else
{
switch (state)
{
case CustomMarkdownSupport.TaskListItemState.Finished:
{
oldHeader = "[+]";
//destState = CustomMarkdownSupport.TodoListItemState.Abort;
newHeader = "[#] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Precessing:
{
oldHeader = "[%]";
//destState = CustomMarkdownSupport.TodoListItemState.Finished;
newHeader = "[+] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.UnStart:
{
oldHeader = "[-]";
//destState = CustomMarkdownSupport.TodoListItemState.Precessing;
newHeader = "[%] ";
break;
}
case CustomMarkdownSupport.TaskListItemState.Aborted:
{
oldHeader = "[#]";
//destState = CustomMarkdownSupport.NotTodoListItem
newHeader = "[-] ";
break;
}
default:
{
oldHeader = "";
//destState = CustomMarkdownSupport.TodoListItemState.UnStart;
newHeader = "[-] ";
break;
}
}
}
if (CustomMarkdownSupport.IsDateLine(startLineText))
{
if (oldHeader == "")
{
var rightIndex = startLineText.IndexOf("]");
if (rightIndex >= 0)
{
startLineText = startLineText.Substring(0, rightIndex + 1) + newHeader + " " + startLineText.Substring(rightIndex + 1).TrimStart(new char[] { ' ' });
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = startLineText;
var indexTmp = startLineText.IndexOf("]");
if (indexTmp >= 0)
{
this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
}
else
{
this.Select(startLine.Offset, 0);
}
return;
}
}
else
{
var indexOfOldHeader = startLineText.IndexOf(oldHeader);
if (indexOfOldHeader >= 0)
{
startLineText = startLineText.Substring(0, indexOfOldHeader) + newHeader + startLineText.Substring(indexOfOldHeader + oldHeader.Length).TrimStart(new char[] { ' ' });
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = startLineText;
var indexTmp = startLineText.IndexOf("]");
if (indexTmp >= 0)
{
this.Select(startLine.Offset + indexTmp + newHeader.Length + 1, 0);
}
else
{
this.Select(startLine.Offset, 0);
}
return;
}
}
}
int index = startLineText.Length;
for (int i = 0; i < startLineText.Length - 1; i++)
{
char c = startLineText[i];
if (c != ' ' && c != ' ' && c != '\t' && c != '[' && c != ']' && c != '+' && c != '-' && c != '%' && c != '#')
{
index = i;
break;
}
}
var tail = startLineText.Substring(index);
this.Select(startLine.Offset, startLine.Length);
this.SelectedText = newHeader + tail;
this.Select(startLine.Offset + newHeader.Length, 0);
}
private CustomMarkdownSupport.TaskListItemState GetTaskListItemState(string lineText)
{
if (string.IsNullOrWhiteSpace(lineText)) return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;
var tmp = lineText.Replace(" ", "").Replace("\t", "").Replace(" ", "");
if (tmp.StartsWith("[-]")) return CustomMarkdownSupport.TaskListItemState.UnStart;
if (tmp.StartsWith("[%]")) return CustomMarkdownSupport.TaskListItemState.Precessing;
if (tmp.StartsWith("[+]")) return CustomMarkdownSupport.TaskListItemState.Finished;
if (tmp.StartsWith("[#]")) return CustomMarkdownSupport.TaskListItemState.Aborted;
if (CustomMarkdownSupport.IsDateLine(lineText))
{
if (tmp.IndexOf("[-]") >= 0) return CustomMarkdownSupport.TaskListItemState.UnStart;
if (tmp.IndexOf("[%]") >= 0) return CustomMarkdownSupport.TaskListItemState.Precessing;
if (tmp.IndexOf("[+]") >= 0) return CustomMarkdownSupport.TaskListItemState.Finished;
if (tmp.IndexOf("[#]") >= 0) return CustomMarkdownSupport.TaskListItemState.Aborted;
}
return CustomMarkdownSupport.TaskListItemState.NotTaskListItem;
}
/// <summary>
/// 在当前选定文本两侧加上倾斜标记文本。
/// </summary>
public void SwitchItalic()
{
if (this.SelectionLength == 0)
{
//this.Document.Insert(this.SelectionStart, "_");
this.SelectedText = "__";
this.Select(this.SelectionStart + 1, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if ((this.SelectedText.StartsWith("_") && this.SelectedText.EndsWith("_")) ||
(this.SelectedText.StartsWith("*") && this.SelectedText.EndsWith("*")))
{
this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
}
else
{
this.SelectedText = "*" + this.SelectedText + "*";
}
}
}
/// <summary>
/// 在当前选定文本两侧加上或取消代码片段标记文本(``,即一对反引号)。
/// </summary>
public void SwitchCodeSnippet()
{
if (this.SelectionLength == 0)
{
this.SelectedText = "``";
this.Select(this.SelectionStart + 1, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if (this.SelectedText.StartsWith("`") && this.SelectedText.EndsWith("`"))
{
this.SelectedText = this.SelectedText.Substring(1, this.SelectedText.Length - 2);
}
else
{
this.SelectedText = "`" + this.SelectedText + "`";
}
}
}
/// <summary>
/// 在当前选定文本两侧加上加粗标记文本。
/// </summary>
public void SwitchBold()
{
//原本使用****语法,但易造成冲突,在引用块中导致高亮显示不正常
if (this.SelectionLength == 0)
{
//this.Document.Insert(this.SelectionStart, "_");
this.SelectedText = "____";
this.Select(this.SelectionStart + 2, 0);
}
else
{
var lineEnd = this.Document.GetLineByOffset(this.SelectionStart).EndOffset;
if (this.SelectionStart + this.SelectionLength > lineEnd) return;//不支持跨行
if ((this.SelectedText.StartsWith("__") && this.SelectedText.EndsWith("__")) ||
(this.SelectedText.StartsWith("**") && this.SelectedText.EndsWith("**")))
{
this.SelectedText = this.SelectedText.Substring(2, this.SelectedText.Length - 4);
}
else
{
this.SelectedText = "__" + this.SelectedText + "__";
}
}
}
/// <summary>
/// 折叠插入点所在的可折叠区域。
/// </summary>
public void FoldSelectedBlock()
{
ICSharpCode.AvalonEdit.Folding.FoldingSection folding = null;
foreach (var i in this.FoldingManager.AllFoldings)
{
if (i.StartOffset <= this.SelectionStart &&
i.EndOffset >= this.SelectionStart + this.SelectionLength)
{
if (folding == null)
{
folding = i;
continue;
}
else
{
if (folding.StartOffset < i.StartOffset)
{
folding = i;
continue;
}
}
}
}
if (folding != null)
{
folding.IsFolded = !folding.IsFolded;
}
}
/// <summary>
/// 跳转到下一个单元格。
/// </summary>
public bool JumpToNextCell()
{
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (line == null) return false;
var text = this.Document.GetText(line.Offset, line.Length);
if (text.Contains("|") == false && text.Contains("|") == false) return false;
if (this.SelectionStart == line.EndOffset && this.SelectionLength == 0)
{
if (this.SelectionStart + 2 < this.Text.Length)
{
this.Select(this.SelectionStart + 2, 0);//2是\r\n的长度。
}
return true;
}
//先在text中查找前一个分割线
int preIndex = -1;
for (int i = this.SelectionStart + this.SelectionLength - 1; i >= 0; i--)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\n' || c == '\r')
{
preIndex = i;
break;
}
}
//再找后一个分割线位置
int nextIndex = -1;
for (int i = this.SelectionStart + this.SelectionLength; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex = i;
break;
}
}
if (nextIndex > preIndex)
{
//如果在首行
if (line.LineNumber == 1 && preIndex < 0)
{
if (this.SelectionStart != 0 || this.SelectionLength != nextIndex)
{
this.Select(0, nextIndex);
}
else
{
//如果已选中当前单元格所有文本,继续找下一个单元格并选定。
var nextIndex2 = -1;
for (int i = nextIndex + 1; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex2 = i;
break;
}
}
if (nextIndex2 < 0)
{
this.Select(nextIndex + 1, 0);
}
else if (nextIndex2 > nextIndex)
{
this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
}
}
return true;
}
if (Globals.MainWindow.SelectCellFirst &&
(this.SelectionStart != preIndex + 1 || this.SelectionLength != nextIndex - preIndex - 1))
{
this.Select(preIndex + 1, nextIndex - preIndex - 1);
}
else
{
//如果已选中当前单元格所有文本,继续找下一个单元格并选定。
var nextIndex2 = -1;
for (int i = nextIndex + 1; i < this.Text.Length; i++)
{
char c = this.Text[i];
if (c == '|' || c == '|' || c == '\r' || c == '\n')
{
nextIndex2 = i;
break;
}
}
if (nextIndex2 < 0)
{
this.Select(nextIndex + 1, 0);
}
else if (nextIndex2 > nextIndex)
{
this.Select(nextIndex + 1, nextIndex2 - nextIndex - 1);
}
}
return true;
}
return false;
}
/// <summary>
/// 跳转到下一个试题元素。
/// </summary>
/// <param name="isShift">Shift 键是否处于被按下状态。</param>
private bool JumpToNextExamElement(bool isShift)
{
if (Globals.MainWindow.IsExamEnabled == false) return false;
string textToFind = ">>[^>]";
Regex regex = GetRegEx(textToFind, isShift);
int start;
if (isShift)
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
var lineText = this.Document.GetText(line.Offset, line.Length);
//找一下,前一个【】在哪里
//if (lineText.StartsWith(" 试题>>"))
//{
var startIndex = this.SelectionStart + this.SelectionLength - line.Offset - 1;
if (startIndex >= 0)
{
var rightIndex = lineText.LastIndexOf('】', startIndex);
if (rightIndex >= 0)
{
var leftIndex = lineText.LastIndexOf('【', rightIndex);
if (rightIndex > leftIndex)
{
this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
this.ScrollTo(loc.Line, loc.Column);
return true;
}
}
}
//}
if (lineText.Contains(">>"))
{
start = line.Offset;//跳过这行的开头向文件头部逆向查找,否则只能停留在本行(这样没用处)
}
else
{
start = regex.Options.HasFlag(RegexOptions.RightToLeft) ? this.SelectionStart : this.SelectionStart + this.SelectionLength;
}
}
else return false;
}
else
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
//正向时,不需要越过标记,所以这里不需要判断本行有无“>>”。
//找一下,下一个【】在哪里
var lineText = this.Document.GetText(line.Offset, line.Length);
var startIndex = this.SelectionStart - line.Offset;
if (startIndex >= 0)
{
var leftIndex = lineText.IndexOf('【', startIndex);
if (leftIndex >= 0)
{
var rightIndex = lineText.IndexOf('】', leftIndex);
if (rightIndex > leftIndex)
{
this.Select(line.Offset + leftIndex + 1, rightIndex - leftIndex - 1);
TextLocation loc = this.Document.GetLocation(line.Offset + leftIndex + 1);
this.ScrollTo(loc.Line, loc.Column);
return true;
}
}
}
start = line.EndOffset;
}
else return false;
}
Match match = regex.Match(this.Text, start);
if (!match.Success) //重头开始或结束
{
if (regex.Options.HasFlag(RegexOptions.RightToLeft))
match = regex.Match(this.Text, this.Text.Length);
else
match = regex.Match(this.Text, 0);
}
if (match.Success)
{
var destLine = this.Document.GetLineByOffset(match.Index + 2);
int length;
var destLineText = this.Document.GetText(destLine.Offset, destLine.Length);
var firstIndex = match.Index - destLine.Offset + 2;
var lastIndex = destLineText.LastIndexOf("<<");
if (lastIndex >= firstIndex)
{
length = lastIndex - firstIndex;
}
else
{
length = destLine.EndOffset - match.Index - 2;
}
this.Select(match.Index + 2, length);
TextLocation loc = this.Document.GetLocation(match.Index);
this.ScrollTo(loc.Line, loc.Column);
}
return match.Success;
}
/// <summary>
/// 根据选项构建正则表达式。
/// </summary>
/// <param name="textToFind">要查找的文本</param>
/// <param name="leftToRight">正序还是逆序。</param>
private Regex GetRegEx(string textToFind, bool leftToRight)
{
RegexOptions options = RegexOptions.None;
if (leftToRight)
{
options |= RegexOptions.RightToLeft;
}
options |= RegexOptions.IgnoreCase;
string pattern = textToFind; // Regex.Escape(textToFind);
return new Regex(pattern, options);
}
/// <summary>
/// 插入新行。
/// </summary>
private void InsertANewLine()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var text = this.Document.GetText(curLine.Offset, curLine.Length);
if (text.Contains("|") == false && text.Contains("|") == false)
{
this.Document.Insert(curLine.Offset, "\r\n");
this.Select(curLine.Offset, 0);
}
else
{
//如果本行是表格行,仿制一行
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c == '|')
{
sb.Append("|");
}
else if (c == '|')
{
sb.Append("|");
}
else if ((int)c <= 255)
{
sb.Append(" ");
}
else
{
sb.Append(" ");
}
}
this.Document.Insert(curLine.Offset, sb.ToString() + "\r\n");
this.Select(curLine.Offset, 0);
}
}
/// <summary>
/// 附加新行。
/// </summary>
private void AppendANewLine()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var text = this.Document.GetText(curLine.Offset, curLine.Length);
if (text.Contains("|") == false && text.Contains("|") == false)
{
this.Document.Insert(curLine.EndOffset, "\r\n");
this.Select(curLine.EndOffset + 2, 0);
}
else
{
//如果本行是表格行,仿制一行
StringBuilder sb = new StringBuilder();
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c == '|')
{
sb.Append("|");
}
else if (c == '|')
{
sb.Append("|");
}
else if ((int)c <= 255)
{
sb.Append(" ");
}
else
{
sb.Append(" ");
}
}
this.Document.Insert(curLine.EndOffset, "\r\n" + sb.ToString());
this.Select(curLine.EndOffset + 2, 0);
}
}
/// <summary>
/// 与后一行交换位置。
/// </summary>
internal void SwapWithNextLine()
{
var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var nextLine = curEndLine.NextLine;
if (nextLine != null)
{
this.BeginChange();
var lineSplitter = "\n";
if (this.Document.Text.Contains("\r\n"))
{
lineSplitter = "\r\n";
}
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
this.Document.Remove(nextLine.Offset - lineSplitter.Length, nextLine.Length + lineSplitter.Length);//先删除下一行
var curStartLineOffset = curStartLine.Offset;
this.Document.Insert(curStartLineOffset, nextLineText + lineSplitter);
this.EndChange();
}
}
/// <summary>
/// 与前一行交换位置。
/// </summary>
internal void SwapWithPreviewLine()
{
var curStartLine = this.Document.GetLineByOffset(this.SelectionStart);
var curEndLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var preLine = curStartLine.PreviousLine;
if (preLine != null)
{
this.BeginChange();
var preStartLineOffset = preLine.Offset;
var startOffset = this.SelectionStart - curStartLine.Offset;
var selLength = this.SelectionLength;
var lineSplitter = "\n";
if (this.Document.Text.Contains("\r\n"))
{
lineSplitter = "\r\n";
}
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
this.Document.Insert(curEndLine.EndOffset, lineSplitter);
this.Document.Insert(curEndLine.EndOffset + lineSplitter.Length, preLineText);
this.Document.Remove(preLine.Offset, preLine.Length + lineSplitter.Length);
this.EndChange();
this.Select(preStartLineOffset + startOffset, selLength);
}
}
/// <summary>
/// 寻找前一个标题。
/// </summary>
public void FindPreviewHeader()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
if (curLine == null) return;
int i = curLine.LineNumber - 1;//当前行不算
DocumentLine preHeaderLine = null;
while (i > 0)
{
var preLine = this.Document.GetLineByNumber(i);
if (preLine == null) break;
var text = this.Document.GetText(preLine.Offset, preLine.Length);
if (text == null) break;
if (text.Length == 0)
{
i--;
continue;
}
else
{
Regex reg = new Regex(@"^[ ##]{1,}");
if (reg.Match(text).Success)
{
preHeaderLine = preLine;
break;
}
else
{
i--;
continue;
}
}
}
if (preHeaderLine != null)
{
this.Select(preHeaderLine.Offset, 0);
this.ScrollTo(preHeaderLine.LineNumber, 0);
}
else
{
this.Select(0, 0);
this.ScrollTo(0, 0);
}
}
/// <summary>
/// 寻找后一个标题。
/// </summary>
public void FindNextHeader()
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (curLine == null) return;
int i = curLine.LineNumber + 1;//当前行不算。
DocumentLine nextHeaderLine = null;
while (i < this.Document.LineCount)
{
var nextLine = this.Document.GetLineByNumber(i);
if (nextLine == null) break;
var text = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (text == null) break;
if (text.Length == 0)
{
i++;
continue;
}
else
{
Regex reg = new Regex(@"^[ ##]{1,}");
if (reg.Match(text).Success)
{
nextHeaderLine = nextLine;
break;
}
else
{
i++;
continue;
}
}
}
if (nextHeaderLine != null)
{
this.Select(nextHeaderLine.Offset, 0);
this.ScrollTo(nextHeaderLine.LineNumber, 0);
}
else
{
this.Select(this.Document.Lines[this.Document.Lines.Count - 1].EndOffset, 0);
this.ScrollToLine(this.Document.Lines.Count - 1);
}
}
/// <summary>
/// 使当前文本行在“普通文本”与“无序列表项”之间切换。
/// </summary>
/// <param name="convertToListItem">作为无序列表项。</param>
public void SwitchListMark(bool convertToListItem)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.BeginChange();
for (int i = endLine.LineNumber - 1; i >= startLine.LineNumber - 1; i--)
{
var line = this.Document.Lines[i];
var lineText = this.Document.GetText(line.Offset, line.Length);
var lineContent = lineText.Trim(new char[] { ' ', ' ', '\t' });
if (string.IsNullOrWhiteSpace(lineContent)) continue;
if (convertToListItem == false)
{
//取消行首的无序列表标记。
Regex reg = new Regex(@"^[ ]{0,3}[\+\--\*][ \t]{0,}");
var match = reg.Match(lineText);
if (match != null && match.Success)
{
this.Document.Remove(line.Offset, match.Length);
}
}
else
{
//在行首添加星号,作为无序列表
this.Document.Insert(line.Offset, "+ ");
}
}
this.EndChange();
}
/// <summary>
/// 在选定文本行前加“引用块标记文本(> )”
/// </summary>
internal void AddBlockQuoterMarksToSelectedLines()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
var startReplaceOffset = startLine.Offset;
var endReplaceOffset = endLine.EndOffset;
StringBuilder sb = new StringBuilder();
for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
{
var line = this.Document.GetLineByNumber(i);
var lineText = this.Document.GetText(line.Offset, line.Length);
if (string.IsNullOrWhiteSpace(lineText))
{
sb.Append("\r\n");
continue;
}
sb.Append("> ");
sb.Append(lineText);
sb.Append("\r\n");
}
this.Document.Replace(startReplaceOffset, endReplaceOffset - startReplaceOffset + 1, sb.ToString());
}
/// <summary>
/// 使当前文本行在六级标题之间切换。
/// </summary>
/// <param name="newLevel">标题层级。</param>
internal void SwitchTitleLevel(int newLevel)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++)
{
var line = this.Document.GetLineByNumber(i);
var lineText = this.Document.GetText(line.Offset, line.Length);
if (string.IsNullOrWhiteSpace(lineText))
{
var header = GetTitleHeader(newLevel);
this.Document.Replace(line.Offset, 0, GetTitleHeader(newLevel));
this.Select(line.Offset + header.Length, 0);
return;
}
var oldSelOffset = this.SelectionStart;
var oldSelLength = this.SelectionLength;
Regex reg1 = new Regex(@"^[ ]{4,}");
var match1 = reg1.Match(lineText);
if (match1 != null && match1.Success) return;//不考虑代码块
Regex reg = new Regex(@"^[ ##]{1,}");
var match = reg.Match(lineText);
if (match != null && match.Success)
{
var level = GetTitleLevel(match.Value);
if (level == newLevel)
{
this.Document.Replace(line.Offset, match.Length, "");
this.Select(oldSelOffset - match.Length, oldSelLength);
}
else
{
var newHeader = GetTitleHeader(newLevel);
this.Document.Replace(line.Offset, match.Length, newHeader);
var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
this.Select(newSelOffset, oldSelLength);
}
}
else
{
var newHeader = GetTitleHeader(newLevel);
this.Document.Insert(line.Offset, GetTitleHeader(newLevel));
var newSelOffset = oldSelOffset + newHeader.Length - match.Length;
this.Select(newSelOffset, oldSelLength);
}
}
}
/// <summary>
/// 取文本行的标题级别。
/// </summary>
/// <param name="lineText">文本行。</param>
private int GetTitleLevel(string lineText)
{
if (string.IsNullOrWhiteSpace(lineText)) return 0;
int level = 0;
foreach (char c in lineText)
{
switch (c)
{
case ' ':
case ' ': continue;
case '#':
case '#':
{
level++;
continue;
}
default: continue;
}
}
return level;
}
/// <summary>
/// 根据层级生成标题的头文本(#字符串)。。
/// </summary>
/// <param name="level">标题层级。</param>
private string GetTitleHeader(int level)
{
switch (level)
{
case 1: return "#";
case 2: return "##";
case 3: return "###";
case 4: return "####";
case 5: return "#####";
case 6: return "######";
default: return "";
}
}
/// <summary>
/// 编辑器获得焦点时自动打开输入法。
/// </summary>
void MarkDownEditorBase_GotFocus(object sender, RoutedEventArgs e)
{
Globals.SwitchInputMethod(true);
}
/// <summary>
/// 查找面板是否处于开启状态。
/// * AvalonEdit的查找面板有 Bug,经常无法输入中文。
/// 所以实际上没有用它,此属性已无用处。
/// </summary>
public bool IsSearchPanelOpened
{
get
{
if (searchPanel == null) return false;
if (searchPanel.IsClosed) return false;
return true;
}
}
/// <summary>
/// [已废弃]AvalonEdit 实现的查找面板,出现在编辑区右上角。有Bug,常无法输入中文。
/// </summary>
private SearchPanel searchPanel;
/// <summary>
/// [已废弃]关闭 AvalonEdit 自己实现的搜索面板。
/// </summary>
public void CloseSearchPanel()
{
searchPanel.Close();
}
/// <summary>
/// [已废弃]查找文本。
/// </summary>
public void Find()
{
searchPanel.SearchPattern = this.TextArea.Selection.GetText();
if (searchPanel.IsClosed) searchPanel.Open();
searchPanel.Reactivate();
}
/// <summary>
/// [已废弃]逆向查找。
/// </summary>
public void FindPreview()
{
searchPanel.FindPrevious();
}
/// <summary>
/// [已废弃]正向查找。
/// </summary>
public void FindNext()
{
searchPanel.FindNext();
}
/// <summary>
/// 文档内容变化时更改 IsModified 属性的值。
/// </summary>
void MarkdownEditorBase_TextChanged(object sender, EventArgs e)
{
this.IsModified = true;//勉强解决。
}
/// <summary>
/// [已废弃]自动将输入的某些方括号转换为统一格式。
/// </summary>
void MarkdownEditorBase_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
//这个转换会造成一些意外的错误——例如添加一个图片链接时需要的不是【】而是[]。
//switch (e.Text)
//{
// case "[":
// case "[":
// case "〖":
// {
// this.Document.Insert(this.CaretOffset, "【");
// e.Handled = true;
// return;
// }
// case "]":
// case "]":
// case "〗":
// {
// this.Document.Insert(this.CaretOffset, "】");
// e.Handled = true;
// return;
// }
//}
}
#region 自动完成
/// <summary>
/// 判断此文本是否与试题相关。
/// </summary>
/// <param name="lineText">要检查的文本行。</param>
public static bool IsStartWithTestPaperKeyWord(string lineText)
{
if (string.IsNullOrEmpty(lineText)) return false;
if (lineText.StartsWith(" 试题>>")) return true;
if (lineText.StartsWith(" 答案>>")) return true;
if (lineText.StartsWith(" 错项>>")) return true;
if (lineText.StartsWith(" 解析>>")) return true;
if (lineText.StartsWith(" 试题>>")) return true;
if (lineText.StartsWith("<<材料>>")) return true;
if (lineText.StartsWith("<<出处>>")) return true;
if (lineText.StartsWith("<<问题>>")) return true;
if (lineText.StartsWith("<<<信息>")) return true;
if (lineText.StartsWith(" 类型>>")) return true;
if (lineText.StartsWith(" 标题>>")) return true;
if (lineText.StartsWith(" 日期>>")) return true;
if (lineText.StartsWith(" 辑录>>")) return true;
if (lineText.StartsWith(" 作者>>")) return true;
if (lineText.StartsWith(" 电邮>>")) return true;
if (lineText.StartsWith(" 备注>>")) return true;
if (lineText.StartsWith(" 辑录>>")) return true;
if (lineText.StartsWith("<信息>>>")) return true;
return false;
}
private CompletionWindow completionWindow;
/// <summary>
/// 自动完成窗口。
/// </summary>
public CompletionWindow CompletionWindow
{
get
{
return completionWindow;
}
}
/// <summary>
/// 关闭自动完成窗口。
/// </summary>
public void InvalidateCompletionWindow()
{
if (this.completionWindow == null) return;
this.completionWindow.Close();
this.completionWindow = null;
}
/// <summary>
/// 用于在切换工作区时、工作区管理器发生变化时重新载入关于各 Markdown 文件的自动完成列表。
/// </summary>
/// <param name="workspaceTreeView">应传入工作区管理器</param>
public void LoadLinkAutoCompletionItems(TreeView workspaceTreeView, bool isEnableAutoComplete)
{
if (this.CompletionWindow == null) return;
IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
for (int i = dataList.Count - 1; i >= 0; i--)
{
var item = dataList[i] as CustomCompletionData;
if (item == null) continue;
if (item.Type == CompleteItemType.MarkdownFileLink ||
item.Type == CompleteItemType.MetaMarkdownFileLink ||
item.Type == CompleteItemType.ImageResourceLink) dataList.RemoveAt(i);
}//先清除
if (workspaceTreeView == null || workspaceTreeView.Items.Count <= 0 || isEnableAutoComplete == false) return;
var wtvi = workspaceTreeView.Items[0] as WorkspaceTreeViewItem;
if (wtvi != null)
{
AddLinksAutoCompleteItem(wtvi, ref dataList);
}
}
/// <summary>
/// 计算两个文档间的相对位置,即要能从当前文档找到目标链接文档。
/// </summary>
/// <param name="thisDocPath">要插入链接的文档的完全路径。</param>
/// <param name="linkDestDocPath">要被链接的文档的完全路径。</param>
/// <param name="withHtmlSuffix">true,返回 .html 后缀;false 返回 .md 后缀。</param>
/// <returns></returns>
public static string GetRelativePath(string thisDocPath, string linkDestDocPath, bool withHtmlSuffix)
{
try
{
var thisDocInfo = new FileInfo(thisDocPath);
var linkDestDocInfo = new FileInfo(linkDestDocPath);
//用 ~ 代替 _ 风险太高了!!
//if (linkDestDocInfo.Name.StartsWith("_"))
//{
// linkDestDocPath = linkDestDocInfo.Directory.FullName + "\\~" + linkDestDocInfo.Name.Substring(1);
//}
//基本思路:
//当前文档路径: C:\abc\def\ghi\jkl\lmn.md
//要链接到的文件路径: C:\abc\def\mnl\opq\xyz.md
//办法是先找到两者开始分叉的层级,然后算出当前文档得向上移动几层。
var parentInfo = thisDocInfo.Directory;
var root = parentInfo.Root;
var prefix = "";
while (parentInfo != null)
{
if (false == linkDestDocPath.StartsWith(parentInfo.FullName, StringComparison.CurrentCultureIgnoreCase))
{
prefix += "../";
}
else
{
//prefix += "../";//这行很奇怪,之前控制台应用程序调试时没问题,但转移到这里却会嫌多。
var length = parentInfo.FullName.Length;
if (parentInfo.FullName.EndsWith("\\") == false && parentInfo.FullName.EndsWith("/") == false)
length++;
var tail = linkDestDocPath.Substring(length); //加的1是指 \
var result = (prefix + tail).Replace("\\", "/");
if (withHtmlSuffix && result.EndsWith(".md", StringComparison.CurrentCultureIgnoreCase))
{
result = result.Substring(0, result.Length - 3) + ".html";
}
return result;
}
parentInfo = parentInfo.Parent;
}
return null;
}
catch
{
return null;
}
}
/// <summary>
/// 这里的 Link 包括普通 Markdwon 文件、目录元文件、图像资源文件。
/// </summary>
/// <param name="item"></param>
/// <param name="dataList"></param>
private void AddLinksAutoCompleteItem(WorkspaceTreeViewItem item, ref IList<ICompletionData> dataList)
{
if (item == null || dataList == null) return;
var editor = this.MasterEditor;
if (editor == null) return;
var thisFullPath = editor.FullFilePath;
if (string.IsNullOrWhiteSpace(thisFullPath)) return;
var title = item.Title;
if (item.IsMetaDirectoryPath)
{
//添加目录元文件对应的Html文件的路径。
var metaFilePath = item.MetaHtmlFilePath;
if (metaFilePath == null) return;
var relativePath = GetRelativePath(thisFullPath, metaFilePath, true);
dataList.Add(new CustomCompletionData(title, $"链接到:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.MetaMarkdownFileLink,
Image = linkIcon,
});
}
else if (item.IsMarkdownFilePath)
{
//添加Markdown文件对应的Html文件的路径。
var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
dataList.Add(new CustomCompletionData(title, $"链接到:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.MarkdownFileLink,
Image = linkIcon,
});
}
else if (item.IsImageFileExist)
{
//添加Markdown文件对应的Html文件的路径。
var relativePath = GetRelativePath(thisFullPath, item.FullPath, true);
dataList.Add(new CustomCompletionData(title, $"链接到图像:{title}",
$"[{title}]({relativePath} \"{title}\")")
{
Type = CompleteItemType.ImageResourceLink,
Image = linkIcon,
});
}
if (item.Items.Count > 0)
{
foreach (var si in item.Items)
{
var subItem = si as WorkspaceTreeViewItem;
if (subItem == null) continue;
AddLinksAutoCompleteItem(subItem, ref dataList);
}
}
}
/// <summary>
/// 向自动完成窗口添加可用的条目。
/// </summary>
/// <param name="entry">自动完成提示条目。</param>
public void AddEntryToCompletionWindow(EnToChEntry entry)
{
if (this.CompletionWindow == null) return;
IList<ICompletionData> dataList = CompletionWindow.CompletionList.CompletionData;
dataList.Add(new CustomCompletionData(entry.Chinese,
$"{entry.Chinese}({entry.Alpha},{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}({entry.Alpha},{entry.English})"));
dataList.Add(new CustomCompletionData(entry.English,
$"{entry.Chinese}({entry.Alpha},{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}({entry.Alpha},{entry.English})"));
}
/// <summary>
/// 在向活动编辑器中输入文本时,根据情况弹出自动提示。
/// </summary>
void TextArea_TextEntering(object sender, TextCompositionEventArgs e)
{
if (IsAutoCompletionEnabled == false) return;
if ("\r\n `~!@#$%^&*()_+-=[]\\{}|;\':\",.<>/?~·!@#¥%……&*(){}|【】、:";',./<>?".Contains(e.Text)) return;//仅只换行,不需要提示
var line = this.Document.GetLineByOffset(this.CaretOffset);
if (line == null) return;
if (completionWindow == null)
{
completionWindow = new CompletionWindow(this.TextArea)
{
MinHeight = 34,
MinWidth = 100,
};
}
IList<ICompletionData> data = CompletionWindow.CompletionList.CompletionData;
//清除自动完成列表并重新添加其中的项目。
data.Clear();
if (IsExamEnabled)//不需要这个,强制换行即可。 && this.CaretOffset - line.Offset <= 3)
{
#region 添加试题需要的提示项
var st = new CustomCompletionData("试题>>", "每个试题都应以此开头。", "\r\n 试题>>") { Image = examIcon, };
var da = new CustomCompletionData("答案>>", "每个答案都应以此为开头。", "\r\n 答案>>") { Image = examIcon, };//缩进不采用Tab是因为易乱。
var cx = new CustomCompletionData("错项>>", "每个备选的错项都应以此为开头。", "\r\n 错项>>") { Image = examIcon, };
var jx = new CustomCompletionData("解析>>", "每个解析条目都应以此开头。", "\r\n 解析>>") { Image = examIcon, };
var cl = new CustomCompletionData("<<材料>>", "主观题每则材料均应以此开头。", "\r\n<<材料>>") { Image = examIcon, };
var cc = new CustomCompletionData("<<出处>>", "主观题每则材料的出处。", "\r\n<<出处>>") { Image = examIcon, };
var wt = new CustomCompletionData("<<问题>>", "问题集中可包含多个问题。", "\r\n<<问题>>") { Image = examIcon, };
var wb = new CustomCompletionData("==〓==", "最后一道试题的结尾添加此符号才能折叠,其它试题不需要添加。", "〓〓〓〓〓〓") { Image = examIcon, };//这样设计是保证全角等于号与半角等于号都有用。
var xx = new CustomCompletionData("信息><信息", "每个文档开头应以指明文档相关信息,如章节、创建者、创建时间等等。",
"<<<信息>\r\n" +
" 类型>><<类型\r\n" +
" 标题>>请输入文档标题<<标题\r\n" +
" 日期>><<日期\r\n" +
" 辑录>><<辑录\r\n" +
" 电邮>><<电邮\r\n" +
" 备注>><<备注\r\n" +
"<信息>>>\r\n\r\n")
{ Image = documentInfoIcon, };
//st.Completed += st_Completed;
//da.Completed += Item_Completed;
//cx.Completed += Item_Completed;
//jx.Completed += Item_Completed;
//cl.Completed += Item_Completed;
//cc.Completed += Item_Completed;
//wt.Completed += Item_Completed;
//wb.Completed += Item_Completed;
xx.Completed += xx_Completed;
data.Add(st);
data.Add(da);
data.Add(cx);
data.Add(jx);
data.Add(cl);
data.Add(cc);
data.Add(wt);
//data.Add(new MyCompletionData("【方括弧对】", "试题中应包含一个或多个填空项目,这些填空项用一对方括弧包围。","【】"));//效果不好。
data.Add(wb);
data.Add(xx);
#endregion
}
#region 添加水平线相关的几个项
var spx1 = new CustomCompletionData("水平线1", "水平线 01", "\r\n<hr class=\"hr1\"/>\r\n") { Image = horizontalLineIcon, };
var spx2 = new CustomCompletionData("水平线2", "水平线 02", "\r\n<hr class=\"hr2\"/>\r\n") { Image = horizontalLineIcon, };
var spx3 = new CustomCompletionData("水平线3", "水平线 03", "\r\n<hr class=\"hr3\"/>\r\n") { Image = horizontalLineIcon, };
var spx4 = new CustomCompletionData("水平线4", "水平线 04", "\r\n<hr class=\"hr4\"/>\r\n") { Image = horizontalLineIcon, };
var spx5 = new CustomCompletionData("水平线5", "水平线 05", "\r\n<hr class=\"hr5\"/>\r\n") { Image = horizontalLineIcon, };
data.Add(spx1);
data.Add(spx2);
data.Add(spx3);
data.Add(spx4);
data.Add(spx5);
#endregion
#region 添加指定Markdown元素
var anchor = new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n") { Image = anchorIcon, };
anchor.Completed += Anchor_Completed;
data.Add(anchor);
var region = new CustomCompletionData("折叠区", "自定义折叠区域", "\r\n\r\nregion { 请输入折叠区标题\r\n\r\n\r\n\r\n}region \r\n\r\n") { Image = collapseAreaIcon, };
region.Completed += Region_Completed;
data.Add(region);
var link = new CustomCompletionData("链接符", "适用于链接工作区以外的地址", "[请输入链接名称]()") { Image = emptyLinkIcon, };
link.Completed += Link_Completed;
data.Add(link);
#endregion
#region 添加英译中词典
if (IsEnToChineseDictEnabled)
{
//new CustomCompletionData("锚", "锚", "\r\n[](@请输入锚的标识符)\r\n");
foreach (var entry in Globals.MainWindow.Dictionary)
{
data.Add(new CustomCompletionData(entry.Chinese,
$"{entry.Chinese}({entry.Alpha},{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}({entry.Alpha},{entry.English})")
{ Image = dictionEntryIcon, });
data.Add(new CustomCompletionData(entry.English,
$"{entry.Chinese}({entry.Alpha},{entry.English})" + (entry.Comment == string.Empty ? null : entry.Comment),
$"{entry.Chinese}({entry.Alpha},{entry.English})")
{ Image = dictionEntryIcon, });
}
}
#endregion
#region 添加对工作区中各文件的链接
LoadLinkAutoCompletionItems(Globals.MainWindow.tvWorkDirectory, Globals.MainWindow.IsAutoCompletionEnabled);
#endregion
CompletionWindow.Show();
CompletionWindow.Closed += delegate
{
completionWindow = null;
};
}
private static ImageSource documentInfoIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-info.png"));
private static ImageSource examIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/options.png"));
private static ImageSource horizontalLineIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/insert-horizontal-rule.png"));
private static ImageSource anchorIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link.png"));
private static ImageSource collapseAreaIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/collapseArea.png"));
private static ImageSource emptyLinkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/link-edit.png"));
private static ImageSource dictionEntryIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/book-key.png"));
private static ImageSource linkIcon = new BitmapImage(new Uri("pack://application:,,,/LunarMarkdownEditor;component/Images/AutoCompleteIcons/document-link.png"));
/// <summary>
/// 自动完成链接文本标记。
/// </summary>
private void Link_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 10, 7);
}
/// <summary>
/// 自动完成“region”文本标记。
/// </summary>
private void Region_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 28, 8);
}
/// <summary>
/// 自动完成锚文本标记。
/// </summary>
private void Anchor_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 11, 8);
}
/// <summary>
/// 自动完成“信息”文本标记。
/// </summary>
void xx_Completed(object sender, EventArgs e)
{
this.Select(this.SelectionStart - 67, 7);
}
/// <summary>
/// 自动完成(上屏)。
/// </summary>
void TextArea_TextEntered(object sender, TextCompositionEventArgs e)
{
if (IsAutoCompletionEnabled == false) return;
if (CompletionWindow == null) return;
if (e.Text.Length > 0)
{
if (CompletionWindow.CompletionList.SelectedItem == null)
{
CompletionWindow.Close();
completionWindow = null;
return;
}
if (!char.IsLetterOrDigit(e.Text[0]))
{
if (e.Text[0] != '=' && e.Text[0] != '=')//不屏蔽等号会导致一闪而过,虽然方便,但会难以理解。
{
// Whenever a non-letter is typed while the completion window is open,
// insert the currently selected element.
CompletionWindow.CompletionList.RequestInsertion(e);
}
}
}
// Do not set e.Handled=true.
// We still want to insert the character that was typed.
}
#endregion
#region 代码折叠
void foldingUpdateTimer_Tick(object sender, EventArgs e)
{
if (foldingStrategy != null)
{
foldingStrategy.UpdateFoldings(FoldingManager, this.Document);
}
}
DispatcherTimer foldingUpdateTimer = new DispatcherTimer();
/// <summary>
/// 提供这个属性是为了便于在选项卡“不在最前”时停止折叠计算,减少消耗。
/// </summary>
public DispatcherTimer FoldingUpdateTimer { get { return this.foldingUpdateTimer; } }
public CustomFoldingManager FoldingManager
{
get { return foldingManager; }
}
CustomFoldingManager foldingManager;
AbstractFoldingStrategy foldingStrategy;
public AbstractFoldingStrategy FoldingStrategy
{
get { return foldingStrategy; }
}
#endregion
// <summary>
// 以选择区起始位置为准,选择所在的整行文本。
// </summary>
internal void SelectLine()
{
if (this.SelectionStart >= 0 && this.SelectionStart <= this.Document.TextLength)
{
var line = this.Document.GetLineByOffset(this.SelectionStart);
if (line != null)
{
this.Select(line.Offset, line.EndOffset - line.Offset);
}
}
}
#region 交换上下相邻行,有更好的方案,此方案因只支持单行,被废弃
//internal void SwapToPreviewLine()
//{
// if (this.SelectionLength > 0) return;
// var line = this.Document.GetLineByOffset(this.SelectionStart);
// if (line.LineNumber <= 1) return;
// int caretOffset = this.SelectionStart - line.Offset;
// var previewLine = this.Document.GetLineByNumber(line.LineNumber - 1);
// string previewText = this.Document.GetText(previewLine.Offset, previewLine.Length);
// string thisText = this.Document.GetText(line.Offset, line.Length);
// this.Document.Remove(previewLine.Offset, previewLine.Length);
// this.Document.Remove(line.Offset, line.Length);
// int previewOffset = this.Document.GetLineByNumber(previewLine.LineNumber).Offset;
// this.Document.Insert(previewOffset, thisText);
// int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
// this.Document.Insert(thisOffset, previewText);
// this.Select(previewOffset + caretOffset, 0);
//}
//internal void SwapToNextLine()
//{
// if (this.SelectionLength > 0) return;
// var line = this.Document.GetLineByOffset(this.SelectionStart);
// if (line.LineNumber >= this.Document.LineCount) return;
// int caretOffset = this.SelectionStart - line.Offset;
// var nextLine = this.Document.GetLineByNumber(line.LineNumber + 1);
// string nextText = this.Document.GetText(nextLine.Offset, nextLine.Length);
// string thisText = this.Document.GetText(line.Offset, line.Length);
// this.Document.Remove(nextLine.Offset, nextLine.Length);
// this.Document.Remove(line.Offset, line.Length);
// int thisOffset = this.Document.GetLineByNumber(line.LineNumber).Offset;
// this.Document.Insert(thisOffset, nextText);
// int nextOffset = this.Document.GetLineByNumber(nextLine.LineNumber).Offset;
// this.Document.Insert(nextOffset, thisText);
// this.Select(nextOffset + caretOffset, 0);
//}
#endregion
/// <summary>
/// 用一对反引号包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithAntiQuotes()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
var oldSelLength = this.SelectionLength;
this.SelectedText = "`" + this.SelectedText + "`";
this.Select(this.SelectionStart + 1, oldSelLength);
}
else
{
this.SelectedText = "`" + this.SelectedText + "`";
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 用一对引号包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithQuotes(bool isShift)
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
var oldSelLength = this.SelectionLength;
if (isShift)
{
this.SelectedText = "“" + this.SelectedText + "”";
}
else
{
this.SelectedText = "‘" + this.SelectedText + "’";
}
this.Select(this.SelectionStart + 1, oldSelLength);
}
else
{
if (isShift)
{
this.SelectedText = "“" + this.SelectedText + "”";
}
else
{
this.SelectedText = "‘" + this.SelectedText + "’";
}
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 用方括号对【】包围选定的文本片段(不能跨行)。
/// </summary>
internal void WrapTextWithSquareQuotes()
{
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startLine.LineNumber != endLine.LineNumber) return;
if (this.SelectionLength > 0)
{
this.SelectedText = "【" + this.SelectedText + "】";
}
else
{
this.SelectedText = "【" + this.SelectedText + "】";
this.Select(this.SelectionStart + 1, 0);
}
}
/// <summary>
/// 尝试根据试题文本判断其中有几个填空项,然后根据这些填空项自动插入一些“答案>>”“错项>>”“解析>>”之类的标签。
/// </summary>
public void InsertChoiceQuestionTags()
{
//选择区域所在的行都选中
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);
//要求选中题干文本(全部)
var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace(" ", "");
if (text.StartsWith("试题>>") == false)
{
LMessageBox.Show(" 请选中要生成标签的试题文本,这些文本中应至少有一个用方括弧对标记出来的填充项。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
int fillblankCount = 0;
StringBuilder errorMsg = new StringBuilder();
if (QuestionValidateManager.ValidatePairOfBrackets(text, ref fillblankCount, true, errorMsg) == false)
{
LMessageBox.Show(" 选中的文本格式存在问题,无法自动生成标签集。错误信息如下:\r\n" + errorMsg.ToString(),
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
char[] splitterLeft = new char[1];
splitterLeft[0] = '【';
char[] splitterRight = new char[1];
splitterRight[0] = '】';
string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);
List<string> answers = new List<string>();
foreach (string s in arrayLeft)
{
if (s.Contains("】") == false) continue;
int index = s.IndexOf('】');
if (index < 0) continue;
string answer = s.Substring(0, index);
answers.Add(answer);
}
StringBuilder tagsText = new StringBuilder();
int fstAnswerLength = -1;
tagsText.Append("\r\n\t\t\t\r\n");
foreach (var s in answers)
{
if (fstAnswerLength < 0)
{
fstAnswerLength = s.Length;
}
tagsText.Append(" 答案>>" + s + "\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
//默认三个错项。
tagsText.Append(" 错项>>\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
tagsText.Append(" 错项>>\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
tagsText.Append(" 错项>>\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
}
tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行,顶格,方便立即开始编辑新试题
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。
int oldEndOffset = line.EndOffset;
this.Document.Insert(line.EndOffset, tagsText.ToString());
this.Select(oldEndOffset + fstAnswerLength + 34, 0);
}
/// <summary>
/// 添加判断题标记。
/// </summary>
/// <param name="judgeValue">判断题答案标记。</param>
internal void InsertJudgeQuestionTags(string judgeValue = "正确")
{
//选择区域所在的行都选中
var startLine = this.Document.GetLineByOffset(this.SelectionStart);
var endLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
this.Select(startLine.Offset, endLine.EndOffset - startLine.Offset);
//要求选中题干文本(全部)
var text = this.SelectedText.Replace("\r\n", "").Replace("\t", "").Replace(" ", "").Replace(" ", "");
if (text.StartsWith("试题>>") == false)
{
LMessageBox.Show(" 请选中要生成标签的试题文本,这些文本中不应包括方括弧【和】,因为方括弧对包括的文本会被当成填充项。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (text.Contains("【") || text.Contains("】"))
{
LMessageBox.Show(" 选中的文本格式存在问题,无法自动生成判断题标签集。错误信息如下:\r\n" +
"判断题题干中不应包括方括号【和】,被方括号包围的文本会被当成填空项来处理。",
Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
//在末尾加上【正确】或【错误】。
text += "【" + judgeValue + "】";
char[] splitterLeft = new char[1];
splitterLeft[0] = '【';
char[] splitterRight = new char[1];
splitterRight[0] = '】';
string[] arrayLeft = text.Split(splitterLeft, StringSplitOptions.RemoveEmptyEntries);
List<string> answers = new List<string>();
foreach (string s in arrayLeft)
{
if (s.Contains("】") == false) continue;
int index = s.IndexOf('】');
if (index < 0) continue;
string answer = s.Substring(0, index);
answers.Add(answer);
}
StringBuilder tagsText = new StringBuilder();
int fstAnswerLength = -1;
tagsText.Append("【" + judgeValue + "】\r\n\t\t\t\r\n");
foreach (var s in answers)
{
if (fstAnswerLength < 0)
{
fstAnswerLength = s.Length;
}
tagsText.Append(" 答案>>" + judgeValue + "\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
//默认三个错项。
tagsText.Append(" 错项>>" + (judgeValue == "正确" ? "错误" : "正确") + "\r\n");
tagsText.Append(" 解析>>\r\n\t\t\t\r\n");
}
tagsText.Append("〓〓〓〓〓〓\r\n\r\n");//空一行,顶格,方便立即开始编辑新试题
var line = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength - 1);//这个长度肯定大于0。
int oldEndOffset = line.EndOffset;
this.Document.Insert(line.EndOffset, tagsText.ToString());
this.Select(oldEndOffset + fstAnswerLength + 25, 0);
}
/// <summary>
/// 格式化 Markdown 文本。
/// </summary>
public void Format()
{
var selStart = this.SelectionStart;
var selLength = this.SelectionLength;
this.Document.Text = CustomMarkdownSupport.FormatMarkdownText(this.Text);
//如果开启了演讲者模式,格式化后自动折叠特殊折叠块。
if (Globals.MainWindow.PerspectiveMode == Perspective.EditingAndPresentation)
{
this.FoldingManager.FoldSpecialFoldings = true;
this.FoldingStrategy.UpdateFoldings(this.FoldingManager, this.Document);
this.FoldingManager.FoldSpecialFoldings = false;
}
if (selStart < this.Document.TextLength)
{
if (selStart + selLength < this.Document.TextLength)
{
this.Select(selStart, selLength);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
else
{
this.Select(selStart, 0);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
}
else
{
this.Select(this.Document.TextLength, 0);
this.ScrollToLine(this.Document.GetLineByOffset(this.SelectionStart).LineNumber);
}
}
public void FormatTextTable()
{
var curLine = CurrentDocumentLine();
var curLineText = this.Document.GetText(curLine.Offset, curLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(curLineText))
{
//如果当前行是树型列表行,则应格式化当前行前、后可能存在的树型列表行。
List<TreeListTextLine> allLines = new List<TreeListTextLine>();
//向前找
List<TreeListTextLine> preLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
preLines.Insert(0, new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
}
preLine = preLine.PreviousLine;
}
allLines.AddRange(preLines);
string curHeader, curTail; int curLevel;
if (IsTreeListLine(curLineText, out curHeader, out curTail, out curLevel))
{
allLines.Add(new TreeListTextLine()
{
Line = curLine,
OldText = curLineText,
TailText = curTail,
OldLevel = curLevel,
NewLevel = curLevel,
NewText = "",
});
}
//向后找
List<TreeListTextLine> nextLines = new List<TreeListTextLine>();
var nextLine = curLine.NextLine;
var preLevelOfNextLines = curLevel;
while (nextLine != null)
{
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = nextLineText,
TailText = nextTail,
OldLevel = nextLevel,
};
if (nextTreeListTextLine.OldLevel > preLevelOfNextLines + 1)
{
nextTreeListTextLine.NewLevel = preLevelOfNextLines + 1;
}
else nextTreeListTextLine.NewLevel = nextTreeListTextLine.OldLevel;//不变
nextLines.Add(nextTreeListTextLine);
nextLine = nextLine.NextLine;
}
}
allLines.AddRange(nextLines);
var oldSelStart = this.SelectionStart;
FormatAllTreeLines(allLines, null);
if (oldSelStart < this.Document.TextLength)
{
this.Select(oldSelStart, 0);
}
return;
}
else
{
if (curLineText.Contains("|") || curLineText.Contains("|"))
{
//这种情况下格式化为二维文字表。
Format2DTextTable();
}
else
{
//如果当前行是按Shift+Backspace取消的,则要更新当前行上、下可能存在的树型文字表
//这种情况要分别格式化成两个树型文字表。
//向前找
List<TreeListTextLine> preLines = new List<TreeListTextLine>();
var preLine = curLine.PreviousLine;
while (preLine != null)
{
var preLineText = this.Document.GetText(preLine.Offset, preLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(preLineText) == false) break;
string preHeader, preTail; int preLevel;
if (IsTreeListLine(preLineText, out preHeader, out preTail, out preLevel))
{
preLines.Insert(0, new TreeListTextLine()
{
Line = preLine,
OldText = preLineText,
NewText = "",
//HeaderTextArray = preHeader.ToCharArray(),
TailText = preTail,
OldLevel = preLevel,
NewLevel = preLevel, //之前的行层级不变
});
}
preLine = preLine.PreviousLine;
}
FormatAllTreeLines(preLines, null);
//向后找
List<TreeListTextLine> nextLines = new List<TreeListTextLine>();
var nextLine = curLine.NextLine;
while (nextLine != null)
{
var nextLineText = this.Document.GetText(nextLine.Offset, nextLine.Length);
if (CustomMarkdownSupport.IsTreeListTextLine(nextLineText) == false) break;
string nextHeader, nextTail; int nextLevel;
if (IsTreeListLine(nextLineText, out nextHeader, out nextTail, out nextLevel))
{
var nextTreeListTextLine = new TreeListTextLine()
{
Line = nextLine,
//HeaderTextArray = nextHeader.ToCharArray(),
NewText = "",
OldText = nextLineText,
TailText = nextTail,
OldLevel = nextLevel - 1,
};
nextLines.Add(nextTreeListTextLine);
}
nextLine = nextLine.NextLine;
}
FormatAllTreeLines(nextLines, null);
}
}
}
/// <summary>
/// 格式化二维文字表。
/// </summary>
/// <param name="insertColumnIndex">可以指定文字表的列数。</param>
private void Format2DTextTable(int? insertColumnIndex = null)
{
var curLine = this.Document.GetLineByOffset(this.SelectionStart);
var curLineNumber = curLine.LineNumber;
int selStartOffset = this.SelectionStart - curLine.Offset;
this.Document.UndoStack.StartUndoGroup();
int startOffset;
int endOffset;
string commentText;
var srcTableText = FindSourceTableText(out startOffset, out endOffset, out commentText);
if (string.IsNullOrEmpty(srcTableText)) return;
var lines = new List<TableLine>();
var srcLines = srcTableText.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string s in srcLines)
{
if (IsTableCaptionSplittor(s)) continue;//忽略“表格格式化”功能自动添加的标题与标题分割行。
if (s.StartsWith(";") || s.StartsWith(";")) continue;//忽略备注
if (s.EndsWith("|^") || s.EndsWith("|^"))
{
lines.Add(new TableLine() { LineText = s.Substring(0, s.Length - 1), Type = TableLineType.MergeLine, });
}
else
{
lines.Add(new TableLine() { LineText = s, Type = TableLineType.Normal, });
}
}
if (lines.Count <= 0) return;
var cellSplittor = new char[] { '|' };
var maxColumn = 0;
var lineArrays = new List<TableCellArray>();
string[] columnDefinitionArray = null;
var columnDefinitionLineIndex = -1;
for (int i = 0; i < lines.Count; i++)
{
var text = lines[i].LineText;
text = text.Replace("|", "|").Trim();
if (text.StartsWith("|", StringComparison.CurrentCulture)) text = text.Substring(1);
if (text.EndsWith("|", StringComparison.CurrentCulture)) text = text.Substring(0, text.Length - 1);
var cells = text.Split(cellSplittor, StringSplitOptions.None);
for (int j = 0; j < cells.Length; j++)
{
cells[j] = cells[j].Trim();
}
if (CustomMarkdownSupport.IsColumnAlignmentDefinitionLine("|" + text + "|"))
{
columnDefinitionArray = cells;
columnDefinitionLineIndex = i;
}
else
{
lineArrays.Add(new TableCellArray() { Cells = cells, Type = lines[i].Type, });
}
lines[i].LineText = text;
maxColumn = Math.Max(cells.Length, maxColumn);
}
var columnDefinitions = new List<TableColumnDefinition>();
for (int i = 0; i < maxColumn; i++)
{
columnDefinitions.Add(new TableColumnDefinition());
}
//if (columnDefinitions.Count <= 0 || lineArrays.Count <= 0)
//{
// //LMessageBox.Show(" 插入附近似乎没有可以格式化的表格!", Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
// //return;
// for (int i = 0; i < maxColumn; i++)
// {
// columnDefinitions.Add(new TableColumnDefinition());
// }
//}
#region 取各列对齐方式定义
bool hascolumndefinitions = false;
if (columnDefinitionArray != null && columnDefinitionArray.Length > 0)
{
hascolumndefinitions = true;
for (int i = 0; i < maxColumn; i++)
{
if (i < columnDefinitionArray.Length)
{
columnDefinitionArray[i] = columnDefinitionArray[i].Replace(":", ":").Trim();
if (columnDefinitionArray[i].Contains("^"))
{
columnDefinitions[i].StartNumber = 0;
}
if (columnDefinitionArray[i].StartsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].StartsWith("^:", StringComparison.CurrentCulture))
{
columnDefinitions[i].Align =
(columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture)) ?
ColumnAlignment.CENTER : ColumnAlignment.LEFT;
}
else if (columnDefinitionArray[i].EndsWith(":", StringComparison.CurrentCulture) ||
columnDefinitionArray[i].EndsWith(":^", StringComparison.CurrentCulture))
{
columnDefinitions[i].Align = ColumnAlignment.RIGHT;
}
else
{
columnDefinitions[i].Align = ColumnAlignment.LEFT;
}
}
else
{
columnDefinitions[i].Align = ColumnAlignment.LEFT;
}
}
}
#endregion
#region 取各列最大字符宽度
for (int i = 0; i < lineArrays.Count; i++)
{
var cells = lineArrays[i];
for (int j = 0; j < maxColumn; j++)
{
if (j == 0 && i == 0 && cells.Cells.Length == 1 && hascolumndefinitions)
{
//第一列需要考虑标题的长度,不能把表格标题的长度作为第一列所需要的最大宽度
continue;
}
if (j < cells.Cells.Length)
{
columnDefinitions[j].MaxTextWidth = Math.Max(GetTextWidth(cells.Cells[j]), columnDefinitions[j].MaxTextWidth);
}
}
for (int j = 0; j < columnDefinitions.Count; j++)
{
if (columnDefinitions[j].MaxTextWidth % 2 == 1)
{
columnDefinitions[j].MaxTextWidth += 1;
}
}
}
#endregion
var sb = new StringBuilder();
if (columnDefinitionLineIndex >= 0)
{
//标题(可能不存在)
var headerStartIndex = 0;
if (lineArrays[0].Cells.Length == 1 && columnDefinitions.Count > 0)
{
headerStartIndex = 1;//表头行从索引值1开始算。
//如果第一行只有一个成员,算表的标题。
sb.Append("|");
sb.Append(BuildTableCaptionText(lineArrays[0].Cells[0], columnDefinitions, insertColumnIndex));
sb.Append("|\r\n|");
sb.Append(BuildTableCaptionSplittorLine(columnDefinitions, insertColumnIndex));
sb.Append("|\r\n");
}
//表头(可能不止一行),在
for (int lineIndex = headerStartIndex; lineIndex < columnDefinitionLineIndex; lineIndex++)
{
//表格标题、表头均居中对齐
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, ColumnAlignment.CENTER));
if (cells.Cells[i].Trim() == "序号" || cells.Cells[i].Trim().ToLower() == "no.")
{
columnDefinitions[i].StartNumber = 0;
}
}
else
{
sb.Append(BuildCellText(null, maxTextLength, ColumnAlignment.CENTER));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
sb.Append("\r\n");
}
//列定义行
if (columnDefinitionLineIndex >= 0)
{
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextWidth = columnDefinitions[i].MaxTextWidth;
sb.Append(GetColumnDefinitionText(maxTextWidth, columnDefinitions[i].Align, columnDefinitions[i].StartNumber == 0));
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("|---|");
}
else
{
sb.Append("|");
}
}
sb.Append("\r\n");
}
//表体(可能不存在)
for (int lineIndex = columnDefinitionLineIndex; lineIndex < lineArrays.Count; lineIndex++)
{
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
if (columnDefinitions[i].StartNumber.HasValue)
{
columnDefinitions[i].StartNumber += 1;
sb.Append(BuildCellText(columnDefinitions[i].StartNumber.Value.ToString("D2"), maxTextLength, columnDefinitions[i].Align));
}
else
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
}
}
else
{
sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
{
sb.Append("^");
}
sb.Append("\r\n");
}
if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
}
else
{
//LMessageBox.Show(" 在附近文本中没有找到【表示表格中各列对齐方式】的那一行文本,无法格式化!\r\n" +
//" 该文本一般会类似下面这行:\r\n\r\n" +
//" |:------|:------:|------:|\r\n\r\n" +
//" 这行文本分别表示对应的列:【左对齐】、【居中对齐】、【右对齐】。",
// Globals.AppName, MessageBoxButton.OK, MessageBoxImage.Information);
//return;
//如果没有“列对齐定义”这一行,就将所有列都视为表体列好了。
for (int lineIndex = 0; lineIndex < lineArrays.Count; lineIndex++)
{
var cells = lineArrays[lineIndex];
sb.Append("|");
for (int i = 0; i < maxColumn; i++)
{
int maxTextLength = columnDefinitions[i].MaxTextWidth;
if (i < cells.Cells.Length)
{
sb.Append(BuildCellText(cells.Cells[i], maxTextLength, columnDefinitions[i].Align));
}
else
{
sb.Append(BuildCellText(null, maxTextLength, columnDefinitions[i].Align));
}
if (insertColumnIndex != null && insertColumnIndex.HasValue && insertColumnIndex.Value == i)
{
sb.Append("| |");
}
else
{
sb.Append("|");
}
}
if (lineArrays[lineIndex].Type == TableLineType.MergeLine)
{
sb.Append("^");
}
sb.Append("\r\n");
}
if (string.IsNullOrEmpty(commentText) == false) sb.Append("\r\n");
}
var result = sb.ToString();
if (srcTableText.EndsWith("\r\n") == false && result.EndsWith("\r\n"))
{
result = result.Substring(0, result.Length - 2);
}
this.Document.Replace(startOffset, endOffset - startOffset, result + (commentText ?? ""));
this.Document.UndoStack.EndUndoGroup();
if (curLineNumber <= this.Document.LineCount)
{
var cl = this.Document.GetLineByNumber(curLineNumber);
if (cl != null)
{
var selStart = cl.Offset + Math.Min(selStartOffset, cl.Length);
this.Select(selStart, 0);
}
}
}
/// <summary>
/// 生成文字表标题分割行文本。
/// </summary>
/// <param name="columnDefinitions">列定义集。</param>
/// <param name="insertColumnIndex">列数。</param>
private string BuildTableCaptionSplittorLine(List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
{
if (columnDefinitions == null || columnDefinitions.Count <= 0) return string.Empty;
var length = 0;
foreach (var cd in columnDefinitions)
{
length += cd.MaxTextWidth;
length += 2;
}
if (length > 2) length -= 2;
if (insertColumnIndex.HasValue)
{
length += 5;////不需要考虑索引值到底是多少,只需要知道需要插入一列即可。一个空列是“ |”宽度为5。
}
var sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
sb.Append("=");
}
return sb.ToString();
}
/// <summary>
/// 生成二维文字表的标题文本行。
/// </summary>
/// <param name="source">源文本。</param>
/// <param name="columnDefinitions">列定义集合。</param>
/// <param name="insertColumnIndex">列数。</param>
private string BuildTableCaptionText(string source, List<TableColumnDefinition> columnDefinitions, int? insertColumnIndex)
{
if (columnDefinitions == null || columnDefinitions.Count <= 0) return source;
var srcWidth = GetTextWidth(source);
var sumTextWidth = 0;
foreach (var cd in columnDefinitions)
{
sumTextWidth += cd.MaxTextWidth;
sumTextWidth += 2;
}
if (sumTextWidth > 2) sumTextWidth -= 2;//末尾一个“|”字符宽度不算。
if (insertColumnIndex.HasValue)
{
sumTextWidth += 5;//不需要考虑索引值到底是多少,只需要知道需要插入一列即可。一个空列是“ |”宽度为5。
}
//表格标题居中对齐
var sb = new StringBuilder();
var offset = sumTextWidth - srcWidth;
for (int i = 1; i <= offset / 2; i++)
{
sb.Append(" ");
}
string result;
if ((int)(offset / 2) * 2 + 1 == offset)
{
result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
}
else
{
result = sb.ToString() + (source ?? "") + sb.ToString();
}
return result;
}
/// <summary>
/// 取出列定义文本。
/// </summary>
/// <param name="maxLength">最大长度。</param>
/// <param name="align">列对齐方式。</param>
/// <param name="withSortMark">是否带“填充序号”标志符(^)。</param>
/// <returns></returns>
private string GetColumnDefinitionText(int maxLength, ColumnAlignment align, bool withSortMark)
{
if (withSortMark)
{
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 2); i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + "^";
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 2); i++)
{
sb.Append("-");
}
return sb.ToString() + "^:";
}
case ColumnAlignment.CENTER:
default:
{
var sb = new StringBuilder();
for (int i = 0; i < maxLength - 3; i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + "^:";
}
}
}
else
{
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 1); i++)
{
sb.Append("-");
}
return ":" + sb.ToString();
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 0; i < (maxLength - 1); i++)
{
sb.Append("-");
}
return sb.ToString() + ":";
}
case ColumnAlignment.CENTER:
default:
{
var sb = new StringBuilder();
for (int i = 0; i < maxLength - 2; i++)
{
sb.Append("-");
}
return ":" + sb.ToString() + ":";
}
}
}
}
/// <summary>
/// 取文本宽度。
/// </summary>
/// <param name="text">要取宽度的文本</param>
/// <param name="trimStartAndEnd">是否去除首尾空格。</param>
/// <returns></returns>
public static int GetTextWidth(string text, bool trimStartAndEnd = false)
{
if (string.IsNullOrEmpty(text)) return 0;
if (CustomMarkdownSupport.IsColumnAlignmentDefinitionLine(text)) return 0;
if (trimStartAndEnd) text = text.Trim();
int width = 0;
foreach (char c in text)
{
width += Encoding.Default.GetByteCount(new char[] { c });//判断全、半角不需要再手工做了。
}
return width;
}
/// <summary>
/// 用半角空格填充,使单元格中文本能够对齐。
/// </summary>
/// <param name="source">要填空空格的源文本。</param>
/// <param name="maxTextWidth">最大宽度。</param>
/// <param name="align">对齐方式</param>
private string BuildCellText(string source, int maxTextWidth, ColumnAlignment align)
{
var srcWidth = GetTextWidth(source);
string result;
switch (align)
{
case ColumnAlignment.LEFT:
{
var sb = new StringBuilder();
for (int i = 1; i <= maxTextWidth - srcWidth; i++)
{
sb.Append(" ");
}
result = (source ?? "") + sb.ToString();
break;
}
case ColumnAlignment.RIGHT:
{
var sb = new StringBuilder();
for (int i = 1; i <= maxTextWidth - srcWidth; i++)
{
sb.Append(" ");
}
result = sb.ToString() + (source ?? "");
break;
}
//case ColumnDefinition.CENTER:
default:
{
var sb = new StringBuilder();
var offset = maxTextWidth - srcWidth;
for (int i = 1; i <= offset / 2; i++)
{
sb.Append(" ");
}
if ((int)(offset / 2) * 2 + 1 == offset)
{
result = sb.ToString() + (source ?? "") + sb.ToString() + " ";
break;
}
else
{
result = sb.ToString() + (source ?? "") + sb.ToString();
break;
}
}
}
return result;
}
/// <summary>
/// 根据指定文本偏移位置,取附近属于二维文字表的所有文本行。
/// </summary>
/// <param name="startOffset">文本偏移位置(头索引)。</param>
/// <param name="endOffset">文本偏移位置(尾索引)</param>
/// <param name="commentText">注释文本。</param>
/// <returns></returns>
private string FindSourceTableText(out int startOffset, out int endOffset, out string commentText)
{
var startSelLine = this.Document.GetLineByOffset(this.SelectionStart);
var endSelLine = this.Document.GetLineByOffset(this.SelectionStart + this.SelectionLength);
if (startSelLine == null || endSelLine == null)
{
startOffset = endOffset = -1;
commentText = null;
return null;
}
if (startSelLine.LineNumber == endSelLine.LineNumber)
{
var text = this.Document.GetText(startSelLine.Offset, startSelLine.Length);
if (string.IsNullOrEmpty(text) || (text.Contains("|") == false && text.Contains("|") == false))
{
startOffset = endOffset = -1;
commentText = null;
return null;
}
}
//基本思路:向上、向下找到表格的首、尾两行。
var commentString = new StringBuilder();
int i = startSelLine.LineNumber;//LineNumber要比索引大1。索引仍然是从0开始计算的。
while (i >= 1)
{
var line = this.Document.GetLineByNumber(i);
if (line == null) break;
var previewLineText = this.Document.GetText(line.Offset, line.Length);
if (previewLineText.StartsWith(";") || previewLineText.StartsWith(";"))
{
//注释不算
commentString.Insert(0, previewLineText + "\r\n");
i--;
continue;
}
if (previewLineText.Contains("|") == false && previewLineText.Contains("|") == false) break;
i--;
}
int fstLineNumber = i + 1;
i = endSelLine.LineNumber;
while (i <= this.Document.LineCount)
{
var line = this.Document.GetLineByNumber(i);
if (line == null) break;
var nextLineText = this.Document.GetText(line.Offset, line.Length);
if (nextLineText.StartsWith(";") || nextLineText.StartsWith(";"))
{
//注释不算
commentString.Append(nextLineText + "\r\n");
i++;
continue;
}
if (nextLineText.Contains("|") == false && nextLineText.Contains("|") == false) break;
i++;
}
int lastLineNumber = i - 1;
startOffset = this.Document.GetLineByNumber(fstLineNumber).Offset;
endOffset = this.Document.GetLineByNumber(lastLineNumber).EndOffset;
commentText = commentString.ToString();
var srcText = this.Document.GetText(startOffset, endOffset - startOffset);
return srcText;
}
/// <summary>
/// 判断此行文本是否只包含“|”、“=”等。这样的行是“表格格式化功能”自动添加的分割表标题与表头行的装饰行。
/// </summary>
/// <param name="lineText">文本行</param>
private bool IsTableCaptionSplittor(string lineText)
{
if (string.IsNullOrEmpty(lineText)) return false;
foreach (char c in lineText)
{
if (c == '=' || c == '|' || c == '|' || c == '=' ||
c == ' ' || //下面这三个是考虑到在<code>块中的文字表也需要格式化。
c == ' ' ||
c == '\t')
continue;
return false;
}
return true;
}
/// <summary>
/// 给二维文字表插入列。
/// </summary>
internal void InsertTableColumn()
{
var selEndOffset = this.SelectionStart + this.SelectionLength;
var curLine = this.Document.GetLineByOffset(selEndOffset);
var curLineLeftText = this.Document.GetText(curLine.Offset, selEndOffset - curLine.Offset);
int pipesCount = 0;
foreach (char c in curLineLeftText)
{
if (c == '|' || c == '|')
{
pipesCount++;
}
}
int? insertColumnIndex = null;
if (curLineLeftText.StartsWith("|") || curLineLeftText.StartsWith("|"))
{
insertColumnIndex = pipesCount;
}
else
{
insertColumnIndex = pipesCount + 1;
}
Format2DTextTable(insertColumnIndex.Value - 1);
JumpToNextCell();
if (Globals.MainWindow.miSelectCellFirst.IsChecked)
{
JumpToNextCell();//再调用一次,选中新插入的列在当前行的单元格。
}
}
}
public class TableCellArray
{
public string[] Cells { get; set; }
public TableLineType Type { get; set; }
}
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарий ( 0 )